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, et extract permet de créer ou modifier n’importe quelle variable.
  • sans argument, par défaut extract() utilisera le drapeau EXTR_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 :

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é :

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.

Diff

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.