Énnoncé

Remember Bruh 1,2 ? This is bruh 3 : D
login with admin:admin and you will get the flag :*

Author : abdoghazy

Aperçu

  Pour ce challenge web, le code source nous est fourni :

Nous pouvons voir que les langages utilisé coté serveur sont le PHP et le Python. Nous avons deux applications web, une accessible directement depuis l’extérieur (app php) et une qui ne l’est pas (app python). En effet pour communiquer avec l’app web python, il faut obligatoirement passer par celle en php.

Si nous regardons le script app.py, nous pouvons voir qu’il faut se “connecter” au compte admin pour accéder au flag :

@app.route('/login', methods=['POST'])
def handle_request():
    try:
        username = request.form.get('username')
        password = hashlib.md5(request.form.get('password').encode()).hexdigest()
        # Authenticate user
        user_data = authenticate_user(username, password)

        if user_data:
            return "0xL4ugh{Test_Flag}"  
        else:
            return "Invalid credentials"  
    except:
        return "internal error happened"

Cependant dans le fichier index.php, la fonction Check_Admin() empêche cela :

function Check_Admin($input)
{
    $input=iconv('UTF-8', 'US-ASCII//TRANSLIT', $input);   // Just to Normalize the string to UTF-8
    if(preg_match("/admin/i",$input))
    {
        return true;
    }
    else
    {
        return false;
    }
}

Résolution

En résumé, pour résoudre ce challenge il faut se connecter au compte admin, malgré le bloquage de celui-ci par la fonction php Check_Admin().

HTTP Parameter Pollution

La vulnérabilité à utiliser pour accéder au compte admin est une HPP. En effet le programme index.php va lire le paramètre username de la requête HTTP, pour vérifier si l’utilisateur essai de se connecter au compte admin. Puis si ce n’est pas le cas, les paramètres de la requête seront utilisé pour en faire une sur la route /login de app.py (http://127.0.0.1:5000/login).

function send_to_api($data)
{
    $api_url = 'http://127.0.0.1:5000/login';
    $options = [
        'http' => [
            'method' => 'POST',
            'header' => 'Content-Type: application/x-www-form-urlencoded',
            'content' => $data,
        ],
    ];
    $context = stream_context_create($options);
    $result = file_get_contents($api_url, false, $context);
    
    if ($result !== false) 
    {
        echo "Response from Flask app: $result";
    } 
    else 
    {
        echo "Failed to communicate with Flask app.";
    }
}
if(isset($_POST['login-submit']))
{
	if(!empty($_POST['username'])&&!empty($_POST['password']))
	{
        $username=$_POST['username'];
		$password=md5($_POST['password']);
        if(Check_Admin($username) && $_SERVER['REMOTE_ADDR']!=="127.0.0.1")
        {
            die("Admin Login allowed from localhost only : )");
        }
        else
        {
            send_to_api(file_get_contents("php://input"));
        }   

	}
	else
	{
		echo "<script>alert('Please Fill All Fields')</script>";
	}
}

Cependant index.php va vérifier si le paramètre username correspond au regex /admin/i seulement pour le dernier paramètre username de la requête. Tandis que app.py va vérifier le premier paramètre username pour l’authentification au compte.

Exploitation

Pour exploiter la vulnérabilité il faut donc envoyer deux paramètres username, le premier doit être égal à admin car il sera utilisé par app.py et le deuxième différent de la valeur admin.

Flag : 0xL4ugh{M1cr0_Serv!C3_My_Bruuh}