0
Posted by alex on Sep 1st, 2008

En php, comme dans la plupart des langages, lorsque nous effectuons des fonctions récursives (s'appelant elle même) on arrive vite à un dépassement de la pile. Dans un langage tel que le C ce n'est pas très important, on peut très facilement éviter/prévoire cela. Dans une application en PHP aussi c'est facile d'éviter les fonctions récursives s'appelant elle même des dizaines de milliers de fois.

Cependant, dans le cas d'un hébergement vous ne pouvez évidement pas vérifier tous les scripts de votre serveur. Le problème est que certains membres pourraient s'en servir pour causer un déni de service. En effet lorsque PHP est utilisé comme module apache, en effectuant un dépassement de la pile, il se produit une faute de segmentation et cela tue le processus entier. Si c'est un serveur multithreader alors tout le serveur meurt, si c'est en prefork alors le fils meurt et éventuellement le serveur au complet pourrait mourrir dans le cas d'une attaque répété. Même si vous n'êtes pas hébergeur, avec un script tel que phpmyadmin ou peu importe il est facile d'envoyer des milliers de variables POST qui sont lue récursivement par PHP (magic_quotes, etc) !

Je n'entrerai pas dans les explications de bas niveaux, je vais plutôt vous expliquer comment produire un crash de PHP :)  et éventuellement comment le corriger.

En C vous pouvez essayer un code du genre:

 

#include <stdio.h>
void a(int i){
printf("Stack: %d\
",i);
i++;
a(i);
}

int main( int argc, char **argv ) {
a(0);
}


Lorsque la pile va être pleine, vous allez avoir une faute de segmentation. Le nombre d'appels récursif dépend de votre système (surtout de la grosseur du programme en mémoire), dans mon cas ça tourne aux alentours de 392200 à 392940. Vous pouvez évidement utiliser un autre type qu'un entier, mais vous allez arriver au même résultat.

En php pour produire une faute de segmentation il suffit de faire ceci:

function a($i) {
echo "$i\
";
$i++;
a($i);
}

a(0);

Pour php 5.2.6 sous linux 2.6.25 le dépassement de la pile se produit entre 23354 et 39291 appels.

 

Comment s'en protéger ?

Il n'y a pas des centaines de façons:

  1. Contrôler les scripts du serveur.
  2. Utiliser l'extension Xdebug
  3. Patcher PHP.

La première méthode étant hors de question dans le cas d'un hébergement, la façon la plus stable est sans doute d'utiliser une extension tel que Xdebug qui permets de limiter la récursion. (Voir la documentation, en particulier la directive xdebug.max_nesting_level. Je le mentionne pour les paresseux :) )

Par ailleurs j'ai commencé à travailler sur un patch qui limite le nombre d'appel récursif juste avant qu'une faute de segmentation se produise. Les premiers résultats sont très concluants, mais je n'ai pas encore eu la chance de tester tous les cas de figure possible. Je commence à peine à comprendre l'api Zend sur l'exécution interne de fonction. Il est donc fonctionnel, mais hautement expérimental.

Vous pouvez avoir le patch ICI, mais je ne conseille pas de l'utiliser sur un environnement en production pour le moment, je viens de le commencer et, je me répète, il est hautement expérimental.

Dernière note: Ce script n'a pas pour but de protéger la pile au complet, par conséquent si vous mettez une limite qui fonctionne dans un script de test, dans un vrai script d'autres éléments peuvent très bien prendre la place et causer un overflow si une récursion est appelé en fin de script. Pour éviter ce genre de situation il suffit simplement de mettre une limite de récursion plus basse, 10000 par exemple. (Je doute beaucoup que vous ayez besoin de beaucoup plus que 100 ou 200 récursions :| )

Le patch ajoute une directive au php.ini "e3bsecurity.call_stack_size" par défaut sa valeur est 20000. Une valeur de 0 désactive la protection.

Pourquoi s'en protéger ?

Principalement pour éviter les attaques de déni de service. Et puis c'est jamais très bien un programme qui cause des fautes de segmentation, surtout sur un serveur de production !

Référence: http://www.php-security.org/MOPB/MOPB-02-2007.html

0 comment
Connectez-vous ou postez en tant qu'invité:
Your Name Your Email


Vérification: 0610
Go to Top