mirror of
https://github.com/macintoshplus/doc-fr.git
synced 2026-03-24 08:52:09 +01:00
492 lines
20 KiB
XML
492 lines
20 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- $Revision$ -->
|
|
<!-- EN-Revision: 7204e2dbb9b484c8b67bb5ad4a93fa1369c5b317 Maintainer: yannick Status: ready -->
|
|
<!-- Reviewed: no -->
|
|
|
|
<chapter xml:id="security.database" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
<title>Sécurité des bases de données</title>
|
|
<simpara>
|
|
De nos jours, les bases de données sont des composants incontournables
|
|
des serveurs web et des applications en ligne, qui fournissent du contenu
|
|
dynamique. Des données secrètes ou critiques peuvent être stockées dans les
|
|
bases de données : il est donc important de les protéger efficacement.
|
|
</simpara>
|
|
<simpara>
|
|
Pour lire ou stocker des informations, vous devez vous connecter au serveur
|
|
de bases de données, envoyer une requête valide, lire le résultat et
|
|
refermer la connexion. De nos jours, le langage le plus courant pour ce
|
|
type de communication est le langage SQL
|
|
(<literal>Structured Query Language</literal>). Voyez
|
|
comment un pirate peut
|
|
<link linkend="security.database.sql-injection">s'introduire dans une
|
|
requête SQL</link>.
|
|
</simpara>
|
|
<simpara>
|
|
Comme vous pouvez le réaliser, <acronym>PHP</acronym> ne peut pas protéger
|
|
vos bases de données pour vous. La section suivante vous introduira aux
|
|
notions de base pour protéger vos bases de données, lors de la programmation
|
|
de vos scripts <acronym>PHP</acronym>.
|
|
</simpara>
|
|
<simpara>
|
|
Gardez bien cette règle simple en tête : la défense se fait par couches.
|
|
Plus vous ajouterez de tests pour protéger votre base, plus faible sera la
|
|
probabilité de réussite d'un pirate. Ajoutez à cela un bon schéma de base
|
|
de données, et vous aurez une application réussie.
|
|
</simpara>
|
|
|
|
<sect1 xml:id="security.database.design">
|
|
<title>Schéma de base de données</title>
|
|
<simpara>
|
|
La première étape est de créer une base de données, à moins que vous ne
|
|
souhaitiez utiliser une base de données déjà créée. Lorsque la base
|
|
de données est créée, un utilisateur propriétaire en est responsable.
|
|
Généralement, seul le propriétaire et le super utilisateur peuvent
|
|
intervenir avec les tables de cette base, et il faut que ce dernier
|
|
donne des droits à tous les intervenants qui auront à travailler sur cette
|
|
base.
|
|
</simpara>
|
|
<simpara>
|
|
Les applications ne doivent jamais se connecter au serveur de
|
|
bases de données sous le nom du propriétaire ou de l'administrateur,
|
|
car ces utilisateurs ont des droits très importants, et pourront
|
|
exécuter n'importe quelle requête, comme la
|
|
modification de tables, l'effacement de lignes ou même encore,
|
|
la destruction de la base.
|
|
</simpara>
|
|
<simpara>
|
|
Vous pouvez créer différents utilisateurs de bases de données pour
|
|
chaque aspect de votre application, avec des droits limités aux
|
|
seules actions planifiées. Il faut alors éviter que le même utilisateur
|
|
dispose des droits de plusieurs cas d'utilisation. Cela permet que
|
|
si des intrus obtiennent l'accès à la base avec l'un de ces jeux
|
|
de droits, ils ne puissent pas affecter toute l'application.
|
|
</simpara>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="security.database.connection">
|
|
<title>Connexions au serveur de base de données</title>
|
|
<simpara>
|
|
Il est recommandé d'établir des connexions au serveur avec le
|
|
protocole SSL, pour chiffrer les échanges clients/serveur, afin
|
|
d'améliorer la sécurité. Vous pouvez aussi utiliser un client
|
|
SSH pour chiffrer la connexion entre les clients et le serveur
|
|
de bases de données. Si l'une de ces deux protections est
|
|
mise en place, il sera difficile de surveiller votre trafic et
|
|
de comprendre les informations échangées.
|
|
</simpara>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="security.database.storage">
|
|
<title>Modèle de stockage avec chiffrement</title>
|
|
<simpara>
|
|
Les protocoles SSL/SSH protègent les données qui circulent entre
|
|
le serveur et le client : SSL/SSH ne protège pas les données
|
|
une fois dans la base. SSL est un protocole en ligne.
|
|
</simpara>
|
|
<simpara>
|
|
Une fois que le pirate a obtenu l'accès direct à votre base de données
|
|
(en contournant le serveur web), les données sensibles, stockées dans votre
|
|
base sont accessibles directement, à moins que les données de la base
|
|
ne soient protégées par la base. Chiffrer les données est une bonne
|
|
solution pour réduire cette menace, mais très peu de bases de données
|
|
offrent ce type de chiffrement.
|
|
</simpara>
|
|
<simpara>
|
|
Le moyen le plus simple pour contourner ce problème est de créer votre
|
|
propre logiciel de chiffrement, et de l'utiliser dans vos scripts PHP.
|
|
<acronym>PHP</acronym> peut vous aider dans cette tâche grâce aux nombreuses extensions
|
|
dont il dispose, comme
|
|
<link linkend="book.openssl">OpenSSL</link> et
|
|
<link linkend="book.sodium">Sodium</link>, qui connaissent un large éventail
|
|
de méthodes de chiffrement. Le script PHP va chiffrer les données qui
|
|
seront stockées, et les déchiffrer lorsqu'elles seront relues. Voyez la
|
|
suite pour des exemples d'utilisation de ce chiffrement.
|
|
</simpara>
|
|
|
|
<sect2 xml:id="security.database.storage.hashing">
|
|
<title>Hachage</title>
|
|
<simpara>
|
|
Dans le cas de données vraiment sensibles, si la représentation originale
|
|
n'est pas nécessaire (pour affichage, ou comparaison), utiliser un
|
|
hash est une bonne solution. L'exemple classique est le stockage de
|
|
mots de passe dans les bases de données, après les avoir passés au
|
|
en hachage de cryptage.
|
|
</simpara>
|
|
<simpara>
|
|
Les fonctions <link linkend="ref.password">password</link>
|
|
fournissent une bonne façon de hacher les données sensibles et fonctionnent avec ces haches.
|
|
</simpara>
|
|
<simpara>
|
|
La fonction <function>password_hash</function> est utilisée pour hacher une chaîne donnée
|
|
en utilisant l'algorithme le plus fort actuellement disponible et la fonction
|
|
<function>password_verify</function> vérifie si le mot de passe fourni correspond au
|
|
hash stocké en base de données.
|
|
</simpara>
|
|
<example>
|
|
<title>Hacher un champ mot de passe</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
// stockage du hash du mot de passe
|
|
$query = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
|
|
pg_escape_string($username),
|
|
password_hash($password, PASSWORD_DEFAULT));
|
|
$result = pg_query($connection, $query);
|
|
|
|
// on vérifie si l'utilisateur a soumis le bon mot de passe
|
|
$query = sprintf("SELECT pwd FROM users WHERE name='%s';",
|
|
pg_escape_string($username));
|
|
$row = pg_fetch_assoc(pg_query($connection, $query));
|
|
|
|
if ($row && password_verify($password, $row['pwd'])) {
|
|
echo 'Bonjour, ' . htmlspecialchars($username) . '!';
|
|
} else {
|
|
echo 'L\'authentification a échoué pour ' . htmlspecialchars($username) . '.';
|
|
}
|
|
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</sect2>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="security.database.sql-injection">
|
|
<title>Injection SQL</title>
|
|
<simpara>
|
|
L'injection SQL est une technique où un attaquant exploite les failles
|
|
dans le code d'application responsable de la création de requêtes SQL dynamiques.
|
|
L'attaquant peut accéder à des sections privilégiées de l'application,
|
|
récupérer toutes les informations de la base de données, altérer des données existantes,
|
|
voire exécuter des commandes dangereuses au niveau système sur l'hôte de la base de données.
|
|
La vulnérabilité se produit lorsque les développeurs concatènent ou
|
|
interpolent une entrée arbitraire dans leurs déclarations SQL.
|
|
</simpara>
|
|
<para>
|
|
<example>
|
|
<title>
|
|
Séparation des résultats en pages, et créer des administrateurs
|
|
(PostgreSQL et MySQL)
|
|
</title>
|
|
<simpara>
|
|
Dans l'exemple suivant, l'entrée de l'utilisateur est directement interpolée dans la
|
|
requête SQL, permettant à l'attaquant d'obtenir un compte superutilisateur dans la base de données.
|
|
</simpara>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$offset = $_GET['offset']; // Attention, aucune validation!
|
|
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
|
|
$result = pg_query($conn, $query);
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
Un utilisateur normal clique sur les boutons 'suivant' et 'précédent',
|
|
qui sont alors placés dans la variable <varname>$offset</varname>,
|
|
encodée dans l'<acronym>URL</acronym>. Le script s'attend à ce que la variable
|
|
<varname>$offset</varname> soit alors un nombre. Cependant,
|
|
il est possible de modifier l'<acronym>URL</acronym> en ajoutant une nouvelle valeur,
|
|
au format <acronym>URL</acronym>, comme ceci :
|
|
<example>
|
|
<title>Exemple d'injection SQL</title>
|
|
<programlisting>
|
|
<![CDATA[
|
|
0;
|
|
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
|
|
select 'crack', usesysid, 't','t','crack'
|
|
from pg_shadow where usename='postgres';
|
|
--
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
Si cela arrive, le script donnerait un accès super utilisateur à l'attaquant.
|
|
Notez que la valeur <literal>0;</literal> sert à terminer la requête
|
|
originale et la terminer correctement.
|
|
</para>
|
|
<note>
|
|
<para>
|
|
C'est une technique répandue que de forcer l'analyseur SQL à ignorer le
|
|
reste de la requête, en utilisant les symboles <literal>--</literal> pour
|
|
mettre en commentaires.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
Un moyen disponible pour accéder aux mots de passe est de contourner
|
|
la recherche de page. Ce que le pirate doit faire, c'est simplement
|
|
voir si une variable du formulaire est utilisée dans la requête, et
|
|
si elle est mal gérée. Ces variables peuvent avoir été configurées
|
|
dans une page précédente pour être utilisées dans les clauses
|
|
<literal>WHERE, ORDER BY, LIMIT</literal> et <literal>OFFSET</literal> des
|
|
requêtes <literal>SELECT</literal>. Si votre base de données supporte
|
|
les commandes <literal>UNION</literal>, le pirate peut essayer d'ajouter
|
|
une requête entière pour lister les mots de passe dans n'importe quelle
|
|
table. Utiliser la technique de stocker uniquement des hachages sécurisés
|
|
des mots de passe au lieu des mots de passe eux-mêmes est fortement
|
|
recommandé.
|
|
<example>
|
|
<title>
|
|
Liste d'articles ... et ajout de mot de passe
|
|
</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$query = "SELECT id, name, inserted, size FROM products
|
|
WHERE size = '$size'";
|
|
$result = odbc_exec($conn, $query);
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
La partie statique de la requête, combinée avec une autre
|
|
requête <literal>SELECT</literal>, va révéler les mots de passe :
|
|
<example>
|
|
<title>Révélation des mots de passe</title>
|
|
<programlisting>
|
|
<![CDATA[
|
|
<?php
|
|
'
|
|
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
|
|
--
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Les instructions <literal>UPDATE</literal> et <literal>INSERT</literal> sont également
|
|
susceptibles à de telles attaques
|
|
<example>
|
|
<title>Modifier un mot de passe ... et gain de droits!</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$query= "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
Un internaute fourbe peut envoyer une valeur telle que
|
|
<literal>' or uid like'%admin%</literal> dans <varname>$uid</varname>
|
|
pour modifier le mot de passe utilisateur, ou simplement, utiliser la
|
|
variable <varname>$pwd</varname> avec la valeur
|
|
<literal>hehehe', trusted=100, admin='yes</literal>
|
|
pour obtenir des droits supplémentaires. La requête devient alors :
|
|
<example>
|
|
<title>Une requête et son injection</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
// $uid == ' or uid like '%admin%
|
|
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";
|
|
|
|
// $pwd: hehehe', trusted=100, admin='yes
|
|
$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE
|
|
...;";
|
|
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<simpara>
|
|
Bien qu'il reste évident qu'un attaquant doit posséder au moins
|
|
certaines connaissances sur l'architecture de la base de données pour mener une attaque réussie,
|
|
obtenir ces informations est souvent très simple. Par exemple,
|
|
le code peut faire partie d'un logiciel open-source et être disponible publiquement.
|
|
Ces informations peuvent également être divulguées par un code source fermé -
|
|
même s'il est codé, obscurci ou compilé - et même par votre propre code à travers l'affichage de messages d'erreur.
|
|
D'autres méthodes comprennent l'utilisation de noms de table et de colonne typiques.
|
|
Par exemple, un formulaire de connexion qui utilise une table 'users' avec des noms de colonnes
|
|
'id', 'username' et 'password'.
|
|
</simpara>
|
|
<para>
|
|
<example>
|
|
<title>Attaque d'un serveur de bases de données (MSSQL Server)</title>
|
|
<simpara>
|
|
Un exemple effrayant de la manière dont des commandes de niveau systèmes d'exploitation
|
|
peuvent être accessibles sur certains hôtes de base de données.
|
|
</simpara>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
|
|
$result = mssql_query($query);
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
Si le pirate injecte la valeur
|
|
<literal>a%' exec master..xp_cmdshell 'net user test testpass /ADD' --</literal>
|
|
dans la variable <varname>$prod</varname>, alors la requête
|
|
<varname>$query</varname> devient :
|
|
<example>
|
|
<title>Attaque d'un serveur de base de données (MSSQL Server) - 2</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$query = "SELECT * FROM products
|
|
WHERE id LIKE '%a%'
|
|
exec master..xp_cmdshell 'net user test testpass /ADD' --%'";
|
|
$result = mssql_query($query);
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
MSSQL Server exécute les requêtes SQL en lot, y compris la commande
|
|
d'ajout d'un nouvel utilisateur à la base de données locale. Si cette
|
|
application fonctionnait en tant que <literal>sa</literal> et que le
|
|
service MSSQLSERVER disposait de niveau de droits suffisant, le pirate
|
|
dispose désormais d'un compte avec accès au serveur.
|
|
</para>
|
|
<note>
|
|
<para>
|
|
Certains exemples ci-dessus sont spécifiques à certains serveurs de
|
|
bases de données, mais cela n'empêche pas des attaques similaires d'être
|
|
possibles sur d'autres produits. Votre base de données sera alors
|
|
vulnérable d'une autre manière.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
<mediaobject>
|
|
<alt>Un exemple amusant concernant l'injection SQL</alt>
|
|
<imageobject>
|
|
<imagedata fileref="en/security/figures/xkcd-bobby-tables.png" format="PNG"/>
|
|
</imageobject>
|
|
<caption>
|
|
<simpara>
|
|
Image de <link xlink:href="&url.xkcd;327">xkcd</link>
|
|
</simpara>
|
|
</caption>
|
|
</mediaobject>
|
|
</para>
|
|
|
|
<sect2 xml:id="security.database.avoiding">
|
|
<title>Techniques de contournement</title>
|
|
<para>
|
|
La méthode recommandée pour éviter les injections SQL est de lier toutes les données
|
|
via des requêtes préparées. L'utilisation de requêtes paramétrées n'est pas suffisante
|
|
pour éviter complètement les injections SQL, mais c'est le moyen le plus facile et le plus sûr
|
|
de fournir une entrée aux instructions SQL. Toutes les littérales de données dynamiques dans les clauses
|
|
<literal>WHERE</literal>, <literal>SET</literal> et <literal>VALUES</literal> doivent être remplacées
|
|
par des espaces réservés. Les données réelles seront liées pendant
|
|
l'exécution et envoyées séparément de la commande SQL.
|
|
</para>
|
|
<para>
|
|
La liaison de paramètres ne peut être utilisée que pour les données. Les autres parties dynamiques de la requête SQL doivent être filtrées contre une liste connue de valeurs autorisées.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Éviter les injections SQL en utilisant des requêtes préparées PDO</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
// La partie SQL dynamique est validée par rapport aux valeurs attendues
|
|
$sortingOrder = $_GET['sortingOrder'] === 'DESC' ? 'DESC' : 'ASC';
|
|
$productId = $_GET['productId'];
|
|
// Le SQL est préparé avec un espace réservé
|
|
$stmt = $pdo->prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}");
|
|
// La valeur est fournie avec des caractères génériques LIKE
|
|
$stmt->execute(["%{$productId}%"]);
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<simpara>
|
|
Les instructions préparées sont fournies
|
|
<link linkend="pdo.prepared-statements">par PDO</link>,
|
|
<link linkend="mysqli.quickstart.prepared-statements">par MySQLi</link>,
|
|
et par d'autres bibliothèques de bases de données.
|
|
</simpara>
|
|
|
|
<simpara>
|
|
Les attaques par injection SQL sont principalement basées sur l'exploitation du code qui n'a pas été écrit
|
|
en tenant compte de la sécurité. Ne faites jamais confiance à une entrée, en particulier
|
|
côté client, même si elle provient d'une boîte de sélection,
|
|
d'un champ de saisie masqué ou d'un cookie. Le premier exemple montre qu'une telle requête simple peut causer des désastres.
|
|
</simpara>
|
|
|
|
<para>
|
|
Une stratégie de défense en profondeur implique plusieurs bonnes pratiques de codage :
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
Ne vous connectez jamais à la base de données en tant que superutilisateur ou en tant que propriétaire de la base de données.
|
|
Utilisez toujours des utilisateurs personnalisés avec des privilèges minimaux.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Vérifiez si l'entrée donnée a le type de données attendu. <acronym>PHP</acronym> a
|
|
une large gamme de fonctions de validation d'entrée, des plus simples
|
|
trouvées dans <link linkend="ref.var">Fonctions de variables</link> et
|
|
dans <link linkend="ref.ctype">Fonctions de type de caractères</link>
|
|
(par exemple <function>is_numeric</function>, <function>ctype_digit</function>
|
|
respectivement) et jusqu'à la
|
|
prise en charge des <link linkend="ref.pcre">Expressions régulières compatibles avec Perl</link>.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Si l'application s'attend à une entrée numérique, envisagez de vérifier les données
|
|
avec <function>ctype_digit</function>, de changer silencieusement son type
|
|
en utilisant <function>settype</function>, ou d'utiliser sa représentation numérique
|
|
avec <function>sprintf</function>.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Si la couche de base de données ne prend pas en charge la liaison de variables, alors
|
|
mettez chaque valeur fournie par l'utilisateur non numérique entre guillemets avec la fonction d'échappement de chaîne spécifique à la base de données (par exemple
|
|
<function>mysql_real_escape_string</function>,
|
|
<function>sqlite_escape_string</function>, etc.).
|
|
Les fonctions génériques comme <function>addslashes</function> ne sont utiles que dans un environnement très spécifique (par exemple MySQL dans un ensemble de caractères à octets uniques avec <varname>NO_BACKSLASH_ESCAPES</varname> désactivé), il est donc
|
|
préférable de les éviter.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Il est particulièrement important de ne pas divulguer d'informations spécifiques à la base de données,
|
|
en particulier sur le schéma, que ce soit de manière légale ou illégale.
|
|
Consultez également <link linkend="security.errors">Signalement d'erreurs</link> et
|
|
<link linkend="ref.errorfunc">Fonctions de gestion et de journalisation des erreurs</link>.
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
|
|
<simpara>
|
|
À côté de ces conseils, il est recommandé d'enregistrer vos requêtes, soit
|
|
dans vos scripts, soit dans la base elle-même, si elle le supporte.
|
|
Évidemment, cet enregistrement ne sera pas capable d'empêcher une attaque,
|
|
mais vous permettra de retrouver la requête qui a fauté. L'historique
|
|
n'est pas très utile par lui-même mais au niveau des informations qu'il
|
|
contient. Plus vous avez de détails, mieux c'est.
|
|
</simpara>
|
|
</sect2>
|
|
</sect1>
|
|
</chapter>
|
|
|
|
<!-- Keep this comment at the end of the file
|
|
Local variables:
|
|
mode: sgml
|
|
sgml-omittag:t
|
|
sgml-shorttag:t
|
|
sgml-minimize-attributes:nil
|
|
sgml-always-quote-attributes:t
|
|
sgml-indent-step:1
|
|
sgml-indent-data:t
|
|
indent-tabs-mode:nil
|
|
sgml-parent-document:nil
|
|
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
|
|
sgml-exposed-tags:nil
|
|
sgml-local-catalogs:nil
|
|
sgml-local-ecat-files:nil
|
|
End:
|
|
vim600: syn=xml fen fdm=syntax fdl=2 si
|
|
vim: et tw=78 syn=sgml
|
|
vi: ts=1 sw=1
|
|
-->
|