Accueil > Développement Web > La Sécurité en PHP - Partie 3

La Sécurité en PHP - Partie 3

Par John Coggeshall, le 09/10/2003

Traduit par Mactov, le 03/12/2003

Bienvenue dans une nouvelle étape de mise en place des fondations de PHP. La dernière fois, je vous ai présenté  les failles de sécurité potentielles liées à l’utilisation des appels-système dans les scripts PHP (et quelques méthodes pour vous protéger). Puisque cet article est le dernier de cette série sur les chausse-trappes et les techniques utiles dans l’écriture d’applications PHP sûres, je ne présenterai pas de nouvelles failles de sécurité potentielles. En revanche, je finirai aujourd’hui cette présentation en introduisant les outils fournis par PHP pour gérer la journalisation (ndt. les fameux journaux de log) des erreurs et leurs rapports, et en résumant les points importants évoqués dans cette série.

Journalisation et Sécurité

Pour que des personnes mal intentionnées puissent tirer profit de vos programmes, elles doivent tout d’abord en connaître les faiblesses. Pour cela, un pirate doit “tester” vos applications web pour rassembler le plus d’informations possible à leur sujet. Un moyen efficace et très répandu consiste à essayer de la faire planter. Par exemple, imaginons qu’un pirate ait saisi des données erronées dans un formulaire d’identification, provoquant le message d’erreur standard PHP suivant :

Notice: Undefined index: content in /usr/local/apache/htdocs/index.php
on line 22

Que peut-on tirer de ce message ? De toute évidence, l’erreur indique qu’il y a un tableau avec la clé content qui est indéfinie dans le fichier index.php, à la ligne 22. Grâce à ce message d’erreur, une personne mal intentionnée possède désormais une piste sur un point faible potentiel à partir duquel il pourra pénétrer et polluer les données de votre application. Peut-être que l’index indéfini est un index des tableaux  $_GET ou $_POST. En fait, vous pouvez même avoir une idée générale de la manière dont est codée un script en prêtant attention aux numéros de lignes où apparissent les erreurs, dans certains cas. De plus, le type d’erreur peut fournir des informations plus précises, comme l’emplacement où le script stocke les fichiers sur le serveur (s’il travaille avec des commandes du système de fichiers), le format des requêtes utilisées par le script (s’il travaille avec des bases de données), et bien plus encore. Et s’il est rarement souhaitable que vos utilisateurs voient des messages d’erreurs détaillés lorsque quelque chose se passe mal, cela peut même devenir un risque de sécurité dans certaines circonstances.

La première chose à faire pour résoudre ce problème est de passer par toutes les étapes obligatoires de débogage pour minimiser le risque d’erreurs à l’exécution de vos scripts. Toute application PHP de qualité devrait s’assurer que toutes ses variables sont bien définies, ou au moins testées avec isset(), s’il s’agit de données globales. Cependant, quelle que soit l’attention que vous aurez portée à votre application, il est irréaliste de croire que vous aurez envisagé tous les cas possibles. Pour cette raison, les applications intégrant la notion de sécurité implémentent aussi des systèmes de gestion et de journalisation des erreurs.

Le mécanisme de journalisation des erreurs de PHP

Le problème de la gestion et de la journalisation des erreurs, au moins lorsqu’il s’agit de sécurité, est de refuser l’accès aux informations système à un pirate, tout en permettant cet accès à un développeur. Un système de journalisation des erreurs bien pensé enregistrera les tentatives de corruption de la sécurité de votre application, vous donnant l’information nécessaire pour renforcer la sécurité de votre application aux endroits où c’est nécessaire.

Implémenter un sytème de journalisation en PHP peut être aussi simple ou aussi compliqué que vous le souhaitez. PHP offre, nativement, plusieurs options aux développeurs pour gérer et enregistrer la trace de leurs erreurs. Par exemple, bien que, par défaut, PHP affiche les erreurs survenant dans le déroulement du script dans le navigateur, il peut aussi être configuré pour enregistrer ces erreurs sans les afficher. Ce comportement est réglé par les directives de configuration log_errors et display_errors du fichier php.ini. Modifiez l’affichage des erreurs au fur et à mesure de vos besoins de développement. L’habitude consiste à afficher les erreurs sans les enregistrer pendant les phases de développement et de débogage d’une application. Le produit livrable fera le contraire : enregistrement mais pas d’affichage des erreurs.

Vous êtes sûrement habitués à la façon dont PHP affiche les messages d’erreur dans le navigateur, mais où PHP stocke-t-il les logs quand la journalisation est activée ? Une autre directive de configuration, error_log, contrôle le comportement du mécanisme de journalisation de PHP. Cette directive peut être définie par le nom d’un fichier, la chaîne de caractères syslog, ou carrément non-renseignée (ce qui est le cas par défaut). Quand la directive error_log n’est pas renseignée dans php.ini, PHP utilise le système de journalisation du serveur web (celui d’Apache par exemple) pour enregistrer ses messages. Si c’est un nom de fichier, PHP écrit tous ses messages dans ce fichier (si les permissions du système de fichiers le permettent). Si c’est le mot-clé syslog, PHP utilise le système de journalisation du système d’exploitation. Sur les systèmes UNIX, c’est le syslog standard du système, et sur les systèmes Windows NT ou XP, c’est le système d’enregistrement des évènements.

Certes PHP s’occupera seul d’enregistrer les messages d’erreur si vous utilisez le gestionnaire d’erreur interne, mais si vous mettez en place un système de gestion des erreurs personnalisé (j’évoquerai ce point plus loin dans cet article), vous devez enregistrer ces erreurs par vous-même. Pour faire cela, PHP fournit la fonction error_log(),  dont voici la syntaxe:

error_log($message [, $message_type [, $dest [, $extra_info]]]);

Selon la valeur du paramètre optionnel $message_type (la valeur par défaut est 0 (l’entier “zéro”)), les procédures ci-dessous seront appliquées:

  • Si $message_type vaut 0, le message d’erreur $message sera enregistré selon le moyen spécifié par la directive de configuration error_log.
  • Si $message-type vaut 1, le message d’erreur $message sera envoyé par mail à l’adresse spécifiée par le paramètre $dest. Vous pouvez ajouter des éléments additionnels d’entêtes mail dans le paramètre $extra_info.
  • Si $message_type vaut 3, le message d’erreur $message sera écrit dans le fichier spécifié par le paramètre $dest.

Note: Vous avez probablement remarqué qu’il n’y a pas de comportement défini lorsque $message_type vaut 2. C’est un (vieux ?) souvenir de la version 3 de PHP, où le débogage distant était disponible dans la version standard. Il n’est plus disponible dans PHP4.

La fonction error_log() peut être utilisée n’importe où pour enregistrer dans le journal de log, on l’utilise en général dans un système de gestion personnalisé des erreurs. Allez jeter un oeil dans les exemples du manuel PHP.

Le modèle d’erreur de PHP

Comprendre le modèle d’erreur de PHP est presqu’aussi important que d’avoir un mécanisme de journalisation des erreurs adapté. Ce modèle gère les types d’erreurs que PHP enregistre, quand et comment il les enregistre. Pour comprendre le modèle d’erreur, vous devez connaître les types et les significations des erreurs pouvant survenir en PHP. Ces informations se trouvent dans le manuel de PHP ainsi que dans la liste ci-dessous :

  • E_ERROR signifie qu’un problème grave est survenu au sein de PHP ou d’une de ses extensions (comme par exemple, un échec d’allocation mémoire).
  • E_WARNING a généralement lieu pour attirer votre attention sur des problèmes qui peuvent exister dans votre code et qui le feront planter, comme par exemple passer une valeur scalaire à une fonction interne qui attend une valeur complexe comme un tableau.
  • E_NOTICE est la moins grave des erreurs internes de PHP. Cette erreur ne signifie en général pas que votre application ne fonctionne pas correctement. Elle sert généralement à attirer l’attention du développeur sur certaines choses comme une variable utilisée avant d’être initialisée.
  • E_CORE_ERROR est identique à E_ERROR en termes de sévérité, mais elle n’est générée que pendant la phase d’initialisation du moteur.
  • E_CORE_WARNING est le pendant “non-fatal” (ndt. vous pouvez passer outre même si ce n’est pas conseillé)  de E_CORE_ERROR et est comparable à E_WARNING. Elle n’est générée que lors de la phase d’initialisation du moteur.
  • E_COMPILE_ERROR signale une erreur grave lors de la compilation par le moteur de scripts Zend.
  • E_COMPILE_WARNING est un avertissement “non-fatal”, comparable à E_WARNING, généré par le moteur Zend pendant la compilation.
  • E_USER_ERROR est strictement réservé pour être utilisée avec la fonction trigger_error() (voir plus loin). Par défaut, PHP traite cette erreur de la même manière qu’une E_ERROR, en affichant l’erreur et en stoppant l’exécution.
  • E_USER_WARNING est strictement réservé pour être utilisée avec la fonction trigger_error(). Par défaut, PHP traite cette erreur comme une erreur E_WARNING.
  • E_USER_NOTICE est strictement réservé pour être utilisée avec la fonction trigger_error(). A la différence des erreurs E_NOTICE, qui sont ignorées par défaut, PHP affichera les erreurs E_USER_NOTICE à l’utilisateur.

Définir le niveau de rapport des erreurs

La directive de configuration error_reporting fixe quels messages d’erreurs sont enregistrés et affichés dans le navigateur lorsqu’elles ont lieu. Cette directive est un champ de bits, ce qui signifie que les messages d’erreurs peuvent être combinés de toutes les manières souhaitées en utilisant les opérateurs logiques booléens ET, OU, et NON. dans le fichier php.ini, l’esperluette (&) signifie ET, la barre verticale (|) signifie OU, et le symbole “tilde” (~) signifie NON. De cette façon, pour afficher uniquement les erreurs les plus graves (pas d’avertissements ni de notifications), vous pourriez utiliser :

error_reporting = E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR

En plus des des types d’erreurs standards, la directive error_reporting autorise aussi un type d’erreurs spécial, E_ALL, qui représente tous les types d’erreurs possibles, ce qui équivaut à tous les types d’erreurs séparés par des OU. Puisque E_ALL inclut tous les types d’erreurs, le code ci-dessous affichera ou enregistrera toutes les erreurs sauf celles de la famille E_USER :

error_reporting = E_ALL & ~E_USER_ERROR & ~E_USER_WARNING & ~E_USER_NOTICE

Par défaut, la directive de configuration error_reporting affiche ou enregistre toutes les erreurs sauf les E_NOTICE.

Indépendamment de la façon dont PHP est configuré pour gérer les erreurs, il y a plusieurs manières de modifier ce qui a effectivement lieu à l’exécution. la plus simple des méthodes est d’utiliser l’opérateur spécial @ pour passer les erreurs sous silence. Placez cet opérateur devant une instruction et PHP ignorera, sans broncher, une erreur apparaissant dans l’évaluation de cette expression. Par exemple :

<?php
echo @$mavar;
?>

ne provoquera plus d’erreur E_NOTICE, même si $mavar est indéfinie. Au lieu de cela, PHP ignorera cette erreur et n’affichera rien.

Une autre méthode pour modifier le système de gestion des erreurs de PHP est d’utiliser la fonction error_reporting(). Cette fonction modifie directement la valeur intrinsèque de la directive de configuration error_reporting. Elle a la syntaxe suivante:

error_reporting([$error_value])

où le paramètre optionnel $error_value est la nouvelle valeur d’erreur. PHP définit des constantes pour représenter chacun des types d’erreur décrit plus haut, qui peuvent être combinées avec les opérateurs logiques de PHP. L’instruction suivante oblige PHP à réagir uniquement aux erreurs graves, comme dans un des exemples précédents :

<?php
error_reporting(E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR);
?>

Quel que soit le paramètre passé à la fonction error_reporting(), sa valeur de retour sera toujours un entier représentant la valeur précédente de la directive error_reporting. Ceci permet de personnaliser le niveau de rapport d’erreur pour une partie réduite de votre application web et de revenir à l’ancienne valeur facilement :

<?php
$old_error = error_reporting(0); /* Interdit tout rapport d'erreur */
/* votre code ici */
error_reporting($old_error); /* revient à la précédente valeur. */
?>

Les gestionnaires d’erreurs personnalisés

En plus de toutes les services de gestion d’erreurs fournit en standard par PHP, le langage vous permet aussi de définir un gestionnaire d’erreur personnalisé. Ce gestionnaire d’erreur personnalisé vous permet de contrôler précisément comment votre application web réagira suite à une erreur générée en interne par PHP ou déclenchée par la fonction trigger_error(). Pour utiliser la gestion personnalisée des erreurs, définissez une fonction de la forme suivante :

<?php
function mon_gestionnaire($code_erreur, $msg_erreur [, $fichier_erreur [,
$ligne _erreur [, $variables]]]) {
    /* Placez ici vos routines de gestion des erreurs */
}
?>

Comme décrit dans l’extrait ci-dessus, tout gestionnaire d’erreur personnalisé doit accepter les paramètres $code_erreur et $msg_erreur correspondant au code d’erreur (comme E_ERROR) et le message d’erreur associé à cette erreur. Votre gestionnaire peut aussi accepter optionnellement jusqu’à trois paramètres supplémentaires : $fichier_erreur, $ligne_erreur, et $variables. Les deux premiers paramètes représentent le fichier PHP et le numéro de ligne où l’erreur est apparue. Le dernier paramètre, $variables, est un tableau associatif contenant le nom et la valeur de chaque variable disponible au moment de l’erreur.

Dès que vous avez créé une fonction correcte, vous devez l’enregistrer dans PHP comme étant le gestionnaire d’erreurs actif. Utilisez la fonction set_error_handler(), avec la syntaxe suivante :

set_error_handler($nom_de_la_fonction)

$nom_de_la_fonction est une chaîne contenant le nom de la fonction que vous avez défini (dans notre exemple, mon_gestionnaire). Quand cette fonction s’exécute, elle renvoit la valeur de l’ancien gestionnaire d’erreurs, qui peut être sauvegardée pour rétablir l’ancien gestionaire au besoin. Quand vous utilisez un gestionnaire d’erreur personnalisé, il y a plusieurs facteurs à prendre en compte :

  • Le gestionnaire d’erreurs personnalisé ne peut traiter les erreurs fatales (qui font planter l’application). Le gestionnaire d’erreurs ne sera appelé que pour les erreurs E_WARNING, E_NOTICE, et celles du type E_USER. Toutes les autres erreurs seront traitées par le gestionnaire d’erreur interne comme s’il n’y avait pas de gestionnaire personnalisé.
  • Lorsque vous utilisez un gestionnaire d’erreurs personnalisé, PHP l’appellera quand n’importe quelle erreur, d’un type cité ci-dessus, apparaît, indépendamment du réglage de la directive de configuration error_reporting. Il revient au gestionnaire d’erreurs d’utiliser la fonction error_reporting() et d’agir en conséquence.
  • A moins que le gestionnaire d’erreurs personnalisé n’interrompe l’exécution du script avec une instruction die() ou exit(), PHP continuera à exécuter le script, sans se soucier de l’erreur. Le gestionnaire personnalisé doit interrompre lui-même l’exécution si cela est nécessaire.

La dernière fonction pour aujourd’hui est trigger_error(). Cette fonction est utilisée pour déclencher des erreurs personnalisées. Elle a la syntaxe suivante :

trigger_error($msg [, $code_erreur])

$msg est une chaîne contenant un message d’erreur décrivant l’erreur, et le paramètre optionnel $code_erreur est une erreur de la famille E_USER. PHP utilisera automatiquement une erreur E_USER_NOTICE si aucun code erreur n’est fourni. Cette fonction est prévue pour être utilisée conjoitement à un gestionnaire d’erreur personnalisé, et s’il n’y en a pas, PHP réagira à l’erreur en utilisant le gestionnaire interne.

Un dernier mot sur la sécurité

Avant d’achever cette série sur la sécurité en PHP, je voudrais prendre du recul sur cette discussion et récapituler les principaux sujets abordés dans les derniers articles. Quand vous écrivez une application web en PHP (ou n’importe quelle application avec n’importe quel langage d’ailleurs), la chose la plus essentielle que vous pouvez faire pour augmenter la sécurité de votre application est d’avoir toujours à l’esprit les risques potentiels que présente votre application. Utilisez-vous les appels-système ? Que faites-vous pour éviter qu’ils ne soient détourner de leur but ? Comment votre application va-t-elle réagir à une entrée utilisateur invalide ? Quelles précautions prennez-vous pour filtrer les valeurs entrées par les utilisateurs ? Vous devriez vous poser toutes ces questions tout en développant.

Au bout du compte, n’importe quel texte (celui-ci y compris) ne peut faire plus que de vous faire apprendre. Dès que vous aurez assimiler les concepts élémentaires, comme la journalisation et la validation des valeurs entrées par l’utilisateur, ce sera à vous de les mettre en oeuvre dans votre application. L’application et l’attention au moindre détail sont les meilleurs outils à la disposition des développeurs pour s’assurer de la sécurité de leurs applications. Bien que les pirates utilisent des tactiques “classiques” pour faire agir vos programmes de façon imprévue, du fait même de la nature de leurs agressions, ils essaieront toujours des choses auxquelles vous n’aurez pas pensé.

Dans mon prochain article, je passe à toute autre chose que la sécurité. J’évoquerai des outils pour vous aider à manipuler et travailler les données. Jusque là, scriptez bien !

Textes originaux en anglais sur O’Reilly : PHP Security, Part 3 par John Coggeshall

mactov Développement Web , ,

  1. Pas encore de commentaire
  1. Pas encore de trackbacks
S'abonner aux commentaires de cet article