Files
doc-fr/security/database.xml
Mehdi Achour 31eb3dc129 pas de comme par exemple
git-svn-id: https://svn.php.net/repository/phpdoc/fr/trunk@160562 c90b9560-bf6c-de11-be94-00142212c4b1
2004-06-04 19:43:32 +00:00

458 lines
20 KiB
XML

<?xml version="1.0" encoding="iso-8859-1"?>
<!-- $Revision: 1.7 $ -->
<!-- EN-Revision: 1.4 Maintainer: dams Status: ready -->
<sect1 id="security.database">
<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 (Structured Query Language). 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, &php; 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.
</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>
<sect2 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 pourront pas affecter toute l'application.
</simpara>
<simpara>
Il est recommandé de ne pas implémenter toute la logique fonctionnelle
dans l'application web (c'est-à-dire dans vos scripts), mais d'en
reporter une partie dans la base en utilisant les triggers, vues et
règles. Si le système évolue, les nouvelles versions vous feront réécrire
toute la logique et donc tous vos scripts. De plus, l'utilisation de
trigger permet de gérer de manière transparente des données, et
fournit des indications pour déboguer votre application.
</simpara>
</sect2>
<sect2 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>
<!--simpara>
If your database server native SSL support, consider to use <link
linkend="ref.openssl">OpenSSL functions</link> in communication between
PHP and database via SSL.
</simpara-->
</sect2>
<sect2 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, mais SSL/SSH ne protègent 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 peut 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;.
&php; peut vous aider dans cette tâche grâce aux nombreuses extensions
dont il dispose, comme par exemple
<link linkend="ref.mcrypt">Mcrypt</link> et
<link linkend="ref.mhash">Mhash</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 losrqu'elles seront relues. Voyez la suite
pour des exemples d'utilisation de ce chiffrement.
</simpara>
<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é au
MD5. Voyez les fonctions <function>crypt</function> et <function>md5</function>.
</simpara>
<example>
<title>Utiliser un mot de passe et MD5</title>
<programlisting role="php">
<![CDATA[
// Stockage du mot de passe hashé
$query = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
addslashes($username), md5($password));
$result = pg_exec($connection, $query);
// interroger le serveur pour comparer le mot de passe soumis
$query = sprintf("SELECT 1 FROM users WHERE name='%s' AND pwd='%s';",
addslashes($username), md5($password));
$result = pg_exec($connection, $query);
if (pg_numrows($result) > 0) {
echo "Bienvenue, $username!";
}
else {
echo "Identification échouée pour $username.";
}
]]>
</programlisting>
</example>
</sect2>
<sect2 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 par
exemple les identifications et authentifications, 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 contruire 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[
$offset = argv[0]; // Attention, aucune validation!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
// avec PostgreSQL
$result = pg_exec($conn, $query);
// avec MySQL
$result = mysql_query($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'URL. Le script s'attend à ce que la variable
<varname>$offset</varname> soit alors un nombre décimal. Cependant,
il est possible de modifier l'URL en ajoutant une nouvelle valeur,
au format URL, comme ceci :
<example>
<title>Exemple d'injection SQL</title>
<programlisting>
<![CDATA[
// cas de PostgreSQL
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'crack', usesysid, 't','t','crack'
from pg_shadow where usename='postgres';
--
// cas de MySQL
0;
UPDATE user SET Password=PASSWORD('crack') WHERE user='root';
FLUSH PRIVILEGES;
]]>
</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 techinque répandue que de forcer l'analyseur SQL d'ignorer le
reste de la requête, en utilisant les symboles <literal>--</literal> pour
mettre en commentaire.
</para>
</note>
<para>
Un moyen possible 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[
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'
ORDER BY $order LIMIT $limit, $offset;";
$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[
'
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>, l'injection SQL va arriver.
</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 connaitre 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 nommination 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[
$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', admin='yes', trusted=100 "</literal> (avec l'espace final) pour
obtenir des droits supplémentaires. La requête sera alors devenue :
<example>
<title>Une requête et son injection</title>
<programlisting role="php">
<![CDATA[
// $uid == ' or uid like'%admin%'; --
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --";
// $pwd == "hehehe', admin='yes', trusted=100 "
$query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 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[
$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[
$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>
<sect3 id="security.database.avoiding">
<title>Techniques de contournement</title>
<simpara>
Vous pouvez prétendre que le pirate doit d'abord obtenir des informations
sur le schéma de la base de données, dans la plupart des cas d'injections.
C'est vrai, mais vous ne saurez jamais comment ni quand ces informations
auront filtré, et si cela arrive, votre base de données sera en grand
danger. Si vous utilisez une base de données Open Srouce, ou une
base qui est du domaine public, ou encore un schéma qui appartient
à un système de gestion de contenu ou d'un forum, le pirate peut facilement
se procurer une copie du code que vous utilisez. Cela peut être un
risque potentiel si la base a été mal conçue.
</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>
Vérifiez que les données ont bien le type attendu. &php; 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 régulière Perl</link>.
</simpara>
</listitem>
<listitem>
<para>
Si l'application attend une entrée numérique, vérifiez vos données
avec la fonction <function>is_numeric</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>
Mettez entre guillemet toutes les valeurs non numériques qui sont
passées à la base de données, et protégez-vous des guillemets dans
les valeurs avec la fonction <function>addslashes</function> ou
<function>addcslashes</function>. Voyez
<link linkend="security.database.storage">le premier exemple</link>.
Comme les exemples l'on montré, les guillemets statiques peuvent
être facilement contournés.
</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>
A coté 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. Evidemment,
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>
</sect3>
</sect2>
</sect1>
<!-- 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:"../../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
-->