mirror of
https://github.com/macintoshplus/doc-fr.git
synced 2026-03-25 09:22:07 +01:00
497 lines
20 KiB
XML
497 lines
20 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- $Revision$ -->
|
|
<!-- EN-Revision: fa6c0138655159c9a360fbbf0364ac0f38274abd 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>
|
|
De nombreux développeurs web ne sont pas conscients des possibilités
|
|
de manipulation des requêtes SQL, et supposent que les requêtes SQL
|
|
sont des commandes sûres. Cela signifie qu'une requête SQL est
|
|
capable de contourner les contrôles et vérifications, comme les
|
|
identifications, et parfois, les requêtes SQL ont accès aux commandes
|
|
d'administration.
|
|
</simpara>
|
|
<simpara>
|
|
L'injection SQL directe est une technique où un pirate modifie une requête
|
|
SQL existante pour afficher des données cachées, ou pour écraser des
|
|
valeurs importantes, ou encore exécuter des commandes dangereuses pour la
|
|
base. Cela se fait lorsque l'application prend les données envoyées par
|
|
l'internaute, et l'utilise directement pour construire une requête SQL. Les
|
|
exemples ci-dessous sont basés sur une histoire vraie, malheureusement.
|
|
</simpara>
|
|
<para>
|
|
Avec le manque de vérification des données de l'internaute et la connexion
|
|
au serveur avec des droits de super utilisateur, le pirate peut créer des
|
|
utilisateurs, et créer un autre super utilisateur.
|
|
<example>
|
|
<title>
|
|
Séparation des résultats en pages, et créer des administrateurs
|
|
(PostgreSQL et MySQL)
|
|
</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$offset = $argv[0]; // 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 décimal. 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 va créer un nouveau super utilisateur.
|
|
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 des mots de passe chiffrés 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>
|
|
Si cette requête, exploitant les <literal>'</literal> et
|
|
<literal>--</literal> est affectée à une variable utilisée dans
|
|
<varname>$query</varname>, une injection SQL va se produire.
|
|
</para>
|
|
<para>
|
|
Les commandes <literal>UPDATE</literal> sont aussi sujettes à des
|
|
attaques de votre base de données. Ces requêtes peuvent aussi introduire
|
|
toute une nouvelle requête dans votre commande initiale. Mais en plus,
|
|
le pirate peut jouer sur la commande <literal>SET</literal>. Dans ce cas,
|
|
il doit connaître un peu votre base de données. Cela peut se deviner
|
|
en examinant les noms de variables dans les formulaires, ou simplement,
|
|
en testant les cas les plus classiques. Il n'y a pas beaucoup de
|
|
conventions de noms pour stocker des noms d'utilisateurs et des mots de
|
|
passe.
|
|
<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>
|
|
Mais 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>
|
|
<para>
|
|
C'est un exemple terrible d'acquisition de droits d'administrateur sur un
|
|
serveur de base de données.
|
|
<example>
|
|
<title>Attaque d'un serveur de bases de données (MSSQL Server)</title>
|
|
<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 des exemples ci-dessus sont spécifiques à certains serveurs de
|
|
bases de données. 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 concernant l'injection SQL</alt>
|
|
<imageobject>
|
|
<imagedata fileref="en/security/figures/xkcd-bobby-tables.png" format="PNG"/>
|
|
</imageobject>
|
|
</mediaobject>
|
|
Image de <link xlink:href="&url.xkcd;327">xkcd</link>
|
|
</para>
|
|
|
|
<sect2 xml:id="security.database.avoiding">
|
|
<title>Techniques de contournement</title>
|
|
<simpara>
|
|
Bien qu'il semble évident qu'un pirate doit posséder quelques connaissances
|
|
de l'architecture de la base de données afin de conduire avec succès une
|
|
attaque, il est souvent très simple de les obtenir. Par exemple, si la
|
|
base de données fait partie d'un paquet open source ou disponible publiquement,
|
|
ces informations sont complètement ouvertes et disponibles. Ces informations
|
|
peuvent aussi être divulgués pour des codes sources fermés - y compris si
|
|
ce code est encodé, occulté, ou compilé - aux travers des messages d'erreurs.
|
|
D'autres méthodes consistent à deviner l'utilisateur de table commune ainsi
|
|
que des noms des colonnes. Par exemple, un formulaire d'identification
|
|
qui utilise la table 'users' avec les colonnes de noms
|
|
'id', 'username', et 'password'.
|
|
</simpara>
|
|
<simpara>
|
|
Ces attaques sont généralement basées sur l'exploitation de code qui
|
|
n'est pas écrit de manière sécuritaire. N'ayez aucune confiance dans
|
|
les données qui proviennent de l'utilisateur, même si cela provient d'un
|
|
menu déroulant, d'un champ caché ou d'un cookie. Le premier exemple montre
|
|
comment une requête peut causer un désastre.
|
|
</simpara>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<simpara>
|
|
Ne nous connectez jamais sur une base de données en tant que super
|
|
utilisateur ou propriétaire de la base. Utilisez toujours un utilisateur
|
|
adapté, avec des droits très limités.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Utilisez des requêtes préparées avec des variables liées. Elles sont
|
|
disponibles avec <link linkend="pdo.prepared-statements">PDO</link>,
|
|
<link linkend="mysqli.quickstart.prepared-statements">MySQLi</link>
|
|
ainsi que d'autres bibliotèques.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Vérifiez que les données ont bien le type attendu. <acronym>PHP</acronym> dispose
|
|
d'un éventail de fonction de validation large, depuis les plus
|
|
simples, de la section <link linkend="ref.var">Variables</link> et
|
|
la section <link linkend="ref.ctype">Caractères</link>
|
|
(e.g. <function>is_numeric</function>, <function>ctype_digit</function>
|
|
respectivement) aux fonctions avancées de
|
|
<link linkend="ref.pcre">Expression rationnelle Perl</link>.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Si l'application attend une entrée numérique, vérifiez vos données
|
|
avec la fonction <function>ctype_digit</function>, ou bien modifiez
|
|
automatiquement le type avec la fonction <function>settype</function>,
|
|
ou encore avec <function>sprintf</function>.
|
|
<example>
|
|
<title>Une navigation de fiches plus sécuritaire</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
settype($offset, 'integer');
|
|
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
|
|
|
|
// notez que %d dans la chaîne de format : %s serait inutile
|
|
$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
|
|
$offset);
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Si la couche de base de données ne suppose pas les variables liées,
|
|
alors, mettez entre guillemets toutes les valeurs non numériques qui sont
|
|
passées à la base de données avec la fonction spécifique à la base de
|
|
données d'échappement de caractères (e.g.
|
|
<function>mysql_real_escape_string</function>,
|
|
<function>sqlite_escape_string</function>, etc.).
|
|
Les fonctions génériques comme <function>addslashes</function> sont utiles
|
|
uniquement dans un environnement très spécifique (i.e. MySQL avec un jeu
|
|
de caractères sur un seul octet avec <varname>NO_BACKSLASH_ESCAPES</varname>
|
|
désactivé), aussi, il est préférable de ne pas les utiliser.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
N'affichez jamais d'informations spécifiques à la base, et notamment
|
|
des informations concernant le schéma. Voyez aussi la section
|
|
<link linkend="security.errors">Rapport d'erreur</link> et le chapitre
|
|
<link linkend="ref.errorfunc">Gestion des erreurs</link>.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Vous pouvez avoir des procédures stockées et des curseurs prédéfinis qui
|
|
font que les utilisateurs n'ont pas un accès direct aux tables ou vues,
|
|
mais cette solution a d'autres impacts.
|
|
</simpara>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<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
|
|
-->
|