Énoncé #
Remember Bruh 1,2 ? This is bruh 3 :D
login with admin:admin and you will get the flag :*Author: abdoghazy
Aperçu #
Le challenge fournit son code source et une page de connexion très simple :

L’énoncé donne même les identifiants à utiliser : admin:admin. Pourtant, leur saisie depuis l’interface publique ne suffit pas. Il faut donc comprendre le trajet de la requête avant de chercher à contourner ce refus.
Résolution #
De la surface à la requête #
Le formulaire public est traité par index.php, tandis que l’authentification réelle se déroule dans une application Flask interne. Les identifiants annoncés étant refusés depuis l’extérieur, il faut examiner comment une même requête traverse ces deux couches.
HTTP Parameter Pollution #
Le point d’entrée accepte un corps application/x-www-form-urlencoded. Ce format autorise plusieurs paramètres portant le même nom. Si deux composants n’en retiennent pas la même occurrence, une requête peut prendre un sens différent à chaque étape : c’est une HTTP Parameter Pollution (HPP).
Il faut donc comparer la manière dont PHP et Flask lisent username, puis vérifier si la requête originale est conservée entre les deux applications.
Deux interprétations de la même requête #
Côté Python, une authentification réussie renvoie le 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"La requête n’atteint toutefois pas directement cette route. Dans l’application PHP exposée, Check_Admin() détecte la chaîne admin :
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;
}
}Une connexion classique avec les identifiants annoncés est donc bloquée avant que Flask puisse les vérifier. Le code qui relie les deux applications révèle toutefois que PHP contrôle la valeur issue de $_POST, puis transmet sans le reconstruire le corps original lu depuis php://input à http://127.0.0.1:5000/login.
Afficher le code source
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>";
}
}Pour des paramètres username répétés, $_POST['username'] contient la dernière occurrence. Flask reçoit malgré tout les deux occurrences dans le corps brut relayé, et request.form.get('username') renvoie la première. L’ordre permet donc de présenter deux identités différentes aux deux contrôles :
- la première,
admin, est utilisée par Flask pour l’authentification ; - la seconde, différente de
admin, est utilisée par PHP et passeCheck_Admin().
Exploitation #
Il reste à intercepter la requête de connexion et à y placer deux paramètres username, dans cet ordre : d’abord admin, puis une valeur qui ne contient pas admin. Le mot de passe reste celui donné par l’énoncé.

PHP valide ainsi la dernière valeur et relaie le corps intact. Flask lit la première, authentifie le compte administrateur et renvoie le flag. La vulnérabilité ne vient donc pas d’un filtre contourné caractère par caractère, mais de deux parseurs qui ne choisissent pas la même valeur dans une requête ambiguë.
Flag: 0xL4ugh{M1cr0_Serv!C3_My_Bruuh}