Guide de Sécurité PHP: Bases de données et SQL
< PrécédentSuivant >Les sessionsTraitement des formulaires
Autorisations d'accès exposées
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:
<?php $host = 'example.org'; $username = 'myuser'; $password = 'mypass'; $db = mysql_connect($host, $username, $password); ?>
Ceci pourrait être l'exemple d'un fichier nommé db.inc, 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.
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 include et require. Cependant, cela peut amener à des situations qui exposent vos autorisations d'accès (access credentials).
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 /usr/local/apache/htdocs, alors le fichier /usr/local/apache/htdocs/inc/db.inc possède un URL du genre http://example.org/inc/db.inc.
Combinez ceci avec le fait que la plupart des serveurs web traitent les fichiers .inc 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.
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 include et require 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.
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 httpd.conf (en supposant un serveur Apache):
<Files ~ "\.inc$">
Order allow,deny
Deny from all
</Files>
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 .php, ainsi que d'utiliser la directive AddType pour traiter les fichiers .inc 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é.
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 /path/to/secret-stuff que seul root peut lire (et pas nobody):
SetEnv DB_USER "myuser" SetEnv DB_PASS "mypass"
Incluez ce fichier dans httpd.conf de cette manière:
Include "/path/to/secret-stuff"
Désormais, vous pouvez utiliser $_SERVER['DB_USER'] et $_SERVER['DB_PASS'] 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 secret-stuff, 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 phpinfo() ou print_r($_SERVER).
Injection de code SQL
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:
<?php
$sql = "INSERT
INTO users (reg_username,
reg_password,
reg_email)
VALUES ('{$_POST['reg_username']}',
'$reg_password',
'{$_POST['reg_email']}')";
?>
Cette requête est bâtie sur $_POST, ce qui devrait paraître immédiatement suspect.
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:
bad_guy', 'mypass', ''), ('good_guy
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 (shiflett@php.net, par exemple), et que l'application génère 1234 comme mot de passe, alors la requête SQL devient ceci:
<?php
$sql = "INSERT
INTO users (reg_username,
reg_password,
reg_email)
VALUES ('bad_guy', 'mypass', ''), ('good_guy',
'1234',
'shiflett@php.net')"; ?>
Plutôt que de procéder comme prévu à la création d'un seul compte utilisateur (good_guy) 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 bad_guy.
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.
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.
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 (ext/mysqli) vous oblige à utiliser une fonction particulière si vous voulez envoyer des requêtes multiples (mysqli_multi_query() au lieu de mysqli_query()). Ne permettre que les requêtes simples est plus sûr, puisque ça limite ce qu'un attaquant pourrait faire.
Il est facile de se protéger contre les injections de SQL:
-
Filtrez vos données.
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.
-
Placez vos données entre apostrophes.
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.
-
Utilisez des séquences d'échappement pour vos données.
Parfois, des données valides peuvent interférer involontairement avec le format même des requêtes SQL. Utilisez mysql_escape_string() ou une fonction d'échappement native à votre base de données particulière. S'il n'en existe spécifiquement aucune, la fonction addslashes() est un bon dernier recours.
< PrécédentSuivant >Les sessionsTraitement des formulaires