mirror of
https://github.com/macintoshplus/doc-fr.git
synced 2026-03-26 01:42:09 +01:00
git-svn-id: https://svn.php.net/repository/phpdoc/fr/trunk@160562 c90b9560-bf6c-de11-be94-00142212c4b1
458 lines
20 KiB
XML
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
|
|
-->
|