Tranformez facilement vos scripts PHP pour qu’ils marchent en register_globals = Off...

vendredi 28 avril 2006
par  G a.k.a Gérard Milhaud
popularité : 100%

Transformer vos scripts PHP qui ne fonctionnent qu’en "register_globals = On" afin qu’ils fonctionnent immédiatement en "register_globals = Off" sans autres modifications que l’inclusion d’un fichier d’une seule ligne, ça vous plairait, non ?

register_globals, c’est quoi ???

Sur les serveurs web correctement configurés, PHP fonctionne en "registers_global = Off", c’est-à-dire que les variables des registres Environnement, GET, POST, Cookie, et Built-in (souvent désignées par variables EGPCS) ne sont pas vues comme des variables globales.

C’est bien sûr beaucoup plus sûr, en particulier pour les variables de formulaires qu’il est ainsi *beaucoup* plus dur d’abuser par une URL bien sentie...

Par contre c’est un peu plus loud pour le programmeur qui doit, pour désigner la variable toto récupérée d’un formulaire, utiliser des lourds $_GET[’toto’] ou $_POST[’toto’] au lieu d’un simple $toto en register_globals = On.

Simplifier le code : idée 1

On peut revenir à un comportement de type register_globals = On en commençant son script par autant de lignes du type

if (isset( $_GET['var_i'])) {$toto = $_GET['var_i'];}

qu’on a de variables à récupérer...

Ça marche bien mais

  1. c’est assez vilain
  2. pour que ça marche dans tous les cas, il faut mettre autant de lignes que de variables potentiellement récupérables. C’est-à-dire que si le script comporte de multiples formulaires indépendants qui le rappellent, il faut que toutes les variables à récupérer dans tous les cas d’appel possibles soient listées... OK, c’est pinailler, mais reconnaissez qu’évaluer 37 fois if (isset( $_GET[’var_i’])) $toto = $_GET[’var_i’] ; si seulement 2 variables sont concernés par l’appel courant, ça n’est pas très optimal.

Mais surtout, en fait, il est des cas pour lesquels ça ne marche pas, en particulier si les nom des variables du formulaires sont... dynamiques. En effet, prenons le code simple suivant :

Ici,le nom de la variable à récupérer est fonction du champ de la table MySQL. Sachant que le script attaque potentiellement toutes les bases du serveur MySQL, le nombre de tables et donc de champs rend caduque la précédente méthode d’énumération des variables...

Simplifier le code : idée 2

L’idée est finalement simple : au lieu d’énumérer les variables potentiellement récupérables, partons plutôt du tableau qui les contient, soit par exemple le tableau _GET.

Il suffit, pour toutes les clés X définies dans le tableau _GET à l’appel du script, c’est-à-dire tous les noms des variables qui ont été passées par la méthode GET, de générer la ligne de code $X = $_GET[’X’].

Bien... Maintenant, il faut l’écrire en PHP...

Eh bien en fait c’est facile. Voici le fichier Recup__GET_vars.php à inclure [1] (au début de vos scripts PHP écrits pour register_globals = On afin qu’ils fonctionnent immédiatement en register_globals = Off du point de vue des variables GET. [2]

Court, non ?!!

Explications

Pour commencer on va récupérer les paires (clé, valeur) du tableau _GET, c’est-à-dire les paires ("nom de variable", $_GET[’nom_de_variable’]).
La fonction each($_GET) va beaucoup nous aider. Extrait de la doc PHP :

D’après cette définition, list($key, $val) = each($_GET) met dans
$key l’élément de clé "0" du tableau renvoyé par each, c’est-à-dire le le nom de la variable. De la même manière
$val est instanciée par l’élément de clé "1" du tableau renvoyé par each, donc la valeur de la variable ayant pour nom $key.

Reste le problème de désigner la variable elle-même (soit $V) dans notre code alors que nous ne disposons que d’une variable ($key) de type chaine contenant le nom de cette variable (soit "V").
L’idéal serait de pouvoir écrire $$key et que PHP l’interprète comme $V...

Ça tombe bien, PHP permet ça : c’est fini.


[1La commande require(Recup__GET_vars.php) inclura et exécutera le code de Recup__GET_vars.php. Toutes les variables définies dans Recup__GET_vars.php seront disponibles pour le script incluant. L’absence du fichier en argument de require produit une erreur fatale, tandis qu’on a seulement un warning avec la fonction voisine include.

[2Il suffit bien sûr de remplacer GET par un autre registre (POST, etc.) pour créer le fichier monoligne à inclure permettant de gérer les variables de ce registre.


Commentaires  (fermé)

Logo de guino
lundi 23 octobre 2006 à 16h14 - par  guino

Je n’aime pas trop ça : pour la relecture du code, même si ça peut sembler lourd, il est très pratique de voir en début de script, la liste des variables attendues en GET, POST, REQUEST, etc , pour ne pas se mélanger les pinceaux entre variables globales provenant d’include, variables crées à la volées, variables d’environnement...

Il y a certe des moyens de présenter les choses correctements, mais quand on se retrouve à plusieurs sur un même projet, ou encore pire à repasser une couche de peinture par dessus, les gens étant ce qu’ils sont, on arrive rapidement à du code illisible, avec des variables globales en minuscule, des var de GET qui apparaissent comme par magie, etc etc...

Logo de G a.k.a Gérard Milhaud
mardi 9 mai 2006 à 14h14 - par  G a.k.a Gérard Milhaud

Raphi dit :
« Cette fonction est très pratique mais il faut garder à l’esprit qu’elle
revient au niveau de sécurité d’une configuration avec les variables
globales activées (ce qui semble également être le cas du script de maître
G si je ne m’abuse). »

Et bien non, les deux méthodes, la mienne comme celle de Raphi ne reviennent pas à un comportement en register_globals à On.

En effet, le danger de register_globals à On est qu’une variable peut être passée malicieusement via l’URL et que PHP ne fera pas de différence entre les variables internes utilisées dans le script et les variables issues de données envoyées par l’utilisateur (par l’URL, ou par formulaire, etc.). Si vos variables internes ne sont pas initialisées (PHP ne le requiert pas), alors un utilisateur malicieux pourra les renseigner en forgeant habilement son URL : Aïe !

Avec register_globals à Off, PHP fait la difference entre variables internes et variables issues de données envoyées par l’utilisateur : donc, même avec des variables non initialisées, on est protégé de l’attaque simpliste par URL forgée.

Donc, la méthode décrite dans cette article, avec register_globals = Off, ne revient pas du tout à register_globals = On. De même que celle de Raphi. En effet, on est protégé des attaques basiques par URL forgées grâce à register_globals = Off, et on se contente uniquement de simplifier le code en instanciant explicitement toutes les variables $toto mises en jeu avec leurs valeurs $_REG[’toto’] (ou REG peut être POST, GET, SESSION, etc.). On utilise ensuite librement dans le script $toto au lieu de $_REG[’toto’].

Il faut cependant pour être tout à fait complet, préciser que l’utilisation des méthodes décrites ici, pour renseigner les variables EGPCS exactement comme si on fonctionnait en register_globals = On, doit être faite de façon coordonnée avec l’ordre de priorité des variables EGPCS tel qu’il est défini dans le paramètre variables_order du fichier de configuration de php (voir http://fr3.php.net/manual/fr/ini.co...). La valeur par défaut est EGPCS, soit Environnement > GET > POST > Cookie > Session, c’est-à-dire qu’en cas de register_globals à On, si le script référence une variable $toto, et qu’à la fois un cookie ET une variable de formulaire POST ont le nom toto, alors $toto prend la valeur de la variable POST... Donc avec un variable_order à EGPCS, il faudrait utiliser le code suivant :

<?php
while (list($key, $val) = each($_ENV)) {$$key=$val;}
while (list($key, $val) = each($_GET)) {$$key=$val;}
while (list($key, $val) = each($_POST)) {$$key=$val;}
while (list($key, $val) = each($_COOKIE)) {$$key=$val;}
while (list($key, $val) = each($_SESSION)) {$$key=$val;}
?>

Mais bien sûr, le mieux est encore de ne pas donner le même nom aux variables issues de $_REG différents...

On pourra voir d’autres méthodes de simplification de code similaires à celle exposée ici à : http://www.zend.com/zend/art/art-sw... .

Pour finir, il faut bien avoir en tête qu’aucune configuration de PHP ne rendra vos scripts totalement sûrs. Le bon "comportement" pour le programmeur PHP, vis-à-vis des variables, reste et restera toujours bien sûr :
- de TOUJOURS initialiser ses variables internes afin que le programme soit sûr aussi bien en register_globals ON ou Off
- de TOUJOURS vérifier au maximum les données issues de l’utilisateur, car register_globals ne protège pas de tout. Il suffit à un utilisateur malin de créer une page contenant le même formulaire que le vôtre, et dont l’action appelle le même script visé "super_prog.php", et d’y injecter ses propres valeurs : les valeurs du tableau $_POST (ou $_GET selon la méthode du formulaire) seront perverties à l’exécution "super_prog.php"...

Pour ces aspects, on pourra regarder avec intérêt : http://www.php.documentation.givah....

Logo de Raphi
mercredi 3 mai 2006 à 14h33 - par  Raphi

Il existe également une solution de facilité mais qui pose les mêmes soucis de sécurité qu’avec le register_global à 1. Cette solution consiste à utiliser la fonction extract() de PHP.
En mettant en début de script :

extract($_POST,EXTR_OVERWRITE) ;

on récupèrera toutes les variables du tableau $_POST qui seront directement diponibles sous leurs noms de référence. Cette fonction crée automatiquement une variable pour chaque clé du tableau associatif $_POST (ou autre). Par exemple, si on a $_POST[’nom’] et $_POST[’prenom’] , la fonction extract() va créer les variables $nom et $prenom contenant bien entendu leurs valeurs de départ.

Cette fonction est très pratique mais il faut garder à l’esprit qu’elle revient au niveau de sécurité d’une configuration avec les variables globales activées (ce qui semble également être le cas du script de maître G si je ne m’abuse).
Le second argument permet de fixer le comportement en cas de collision de variables. On utilise principalement EXTR_OVERWRITE et EXTR_SKIP pour respectivement écraser ou ne pas écraser des variables existantes.