La vulnérabilité à détecter pour ce challenge était une injection de ressources (variables) qui permettait de contourner l’authentification. La résolution de challenge demande une connaissance de certaines fonctions PHP.
Note : Cet article est aussi disponible en anglais 🇬🇧. Le challenge a été annoncé dans ce tweet 🐦.
Explication
Le problème réside principalement dans l’utilisation de la fonction extract()
sur des données non sûres (entrées utilisateur).
extract()
permet d’importer des variables depuis un tableau dans la table des symboles.
Pour rappel, les array() ne sont pas des tableaux dans le sens classique des autres langages, mais sont des map (association d’une clé et d’une valeur) parfois appelés tableaux associatifs (Dictionary en Python, Hash en Ruby, etc.).
extract()
va donc prendre un tableau associatif et créer les variables dont les noms sont les index / clés de ce tableau, et leur affecte la valeur associée.
Utiliser extract()
sur un tableau associatif provenant d’une entrée utilisateur permet donc à celui-ci de créer arbitrairement des variables.
Le code vulnérable était le suivant :
if (isset($_REQUEST['color']['color']))
extract($_REQUEST['color']);
Note : la page extract du manuel PHP émet un avertissement à propos de cette erreur de développement.
Le extract($config);
qui précède est tout à fait bénin comme les données ne sont pas contrôlables par l’utilisateur.
Note : le $_SERVER['PHP_AUTH_USER']}
dans le message welcome
est une mauvaise pratique car c’est une entrée utilisateur qui n’est pas assainie. En revanche, il n’y a pas de risque de sécurité dans ce cas de figure car le message n’est affiché que lorsque l’utilisateur est authentifié et donc qu’il aura été validé que $_SERVER['PHP_AUTH_USER']}
correspond au nom de l’utilisateur (sauf, bien sûr, dans le cas de l’injection de ressource présenté ici).
Analysons extract($_REQUEST['color'])
:
$_REQUEST
est une variable globale qui récupère toutes les entrées de$_GET
,$_POST
et$_COOKIE
- Le fait d’utiliser le nom
color
dans$_REQUEST['color']
pourrait laisser penser qu’il ne permet que de modifier la variable$color
extraite du tableau associatif$config
. Mais il n’en est rien, le nom du paramètre HTTP est arbitraire, etextract
permet de créer ou modifier n’importe quelle variable. - sans argument, par défaut
extract()
utilisera le drapeauEXTR_OVERWRITE
, s’il y a collision lors de l’extraction d’une variable (si elle existe déjà) alors son contenu sera écrasé. Si la variable n’existe pas déjà, alors elle sera créée.
On comprend donc que extract()
+ $_REQUEST
va permettre d’écraser n’importe quelle variable depuis un paramètre HTTP GET
ou POST
.
Analysons maintenant la condition if (isset($_REQUEST['color']['color']))
: c’est un semblant de protection qui vérifie simplement que la clé color
est présente dans le paramètre color
et donc que le paramètre color
soit de type tableau.
Les requêtes suivantes ne seront donc pas autorisées :
- http://127.0.0.2:8080/app.vuln.php?color[nouvelle_variable]=1337 (création d’une nouvelle variable)
- http://127.0.0.2:8080/app.vuln.php?color[messages]=rien (écrasement de
$messages
)
Car il faudra a minima que la clé color
soit présente, c’est-à-dire, par exemple, http://127.0.0.2:8080/app.vuln.php?color[color]=green, qui est le cas qui semble avoir été prévu pour pouvoir changer la couleur de fond du texte.
Cependant, l’application vérifie que la clé color
existe mais pas qu’aucune autre clé n’est présente. Il est tout à fait possible d’ajouter une seconde clé en plus de color
: http://127.0.0.2:8080/app.vuln.php?color[color]=green&color[messages]=rien.
Mis à part casser le comportement de l’application, cela ne semble pas nous amener très loin.
Il faut corréler ce comportement avec l’extrait de code suivant :
if (!empty($credentials)) {
login($credentials['user'], $credentials['password']);
}
En effet, l’authentification n’est faite que si $credentials
n’est pas vide. Pas d’authentification si la configuration est laissée vide. Avec extract
, nous pourrions donc écraser $credentials
avec une valeur assimilable à vide par empty afin de contourner l’authentification. Par exemple, 0
est évalué comme vide.
La charge utile suivante permet donc de contourner l’authentification :
http://127.0.0.2:8080/app.vuln.php?color[color]=green&color[credentials]=0
Note : ne rien mettre (null
) plutôt que 0
fonctionne aussi.
Code corrigé
Voici donc le code corrigé :
Il suffit de réaffecter une valeur à la variable $color
lorsqu’une entrée utilisateur est saisie, il n’y pas de risque d’écraser une autre variable et les XSS ne sont pas possibles grâce à l’encodage URL.
Le code source est disponible sur le dépôt Github Acceis/vulnerable-code-snippets et sur le site web acceis.github.io/avcs-website.
À propos de l’auteur
Article écrit par Alexandre ZANNI alias noraj, Ingénieur en Test d’Intrusion chez ACCEIS.