Injection SQL

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.

Exemple #1 Séparation des résultats en pages, et créer des superutilisateurs (PostgreSQL)

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.

<?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);
?>
Un utilisateur normal clique sur les boutons 'suivant' et 'précédent', qui sont alors placés dans la variable $offset, encodée dans l'URL. Le script s'attend à ce que la variable $offset soit alors un nombre. Cependant, il est possible de modifier l'URL en ajoutant une nouvelle valeur, au format URL, comme ceci :
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
    select 'crack', usesysid, 't','t','crack'
    from pg_shadow where usename='postgres';
--
Si cela arrive, le script donnerait un accĂšs super utilisateur Ă  l'attaquant. Il est Ă  noter que la valeur 0; fournit un dĂ©calage valide Ă  la requĂȘte originale et la termine correctement.

Note:

C'est une technique rĂ©pandue que de forcer l'analyseur SQL Ă  ignorer le reste de la requĂȘte, en utilisant les symboles -- pour mettre en commentaires.

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 WHERE, ORDER BY, LIMIT et OFFSET des requĂȘtes SELECT. Si la base de donnĂ©es supporte les commandes UNION, 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Ă©.

Exemple #2 Liste d'articles ... et quelques mots de passe (n'importe quel serveur de bases de données)

<?php
$query
= "SELECT id, name, inserted, size FROM products
WHERE size = '
$size'";
$result = odbc_exec($conn, $query);
?>
La partie statique de la requĂȘte, combinĂ©e avec une autre requĂȘte SELECT, va rĂ©vĂ©ler les mots de passe :
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--

Les instructions UPDATE et INSERT sont également susceptibles de telles attaques.

Exemple #3 Modifier un mot de passe ... et gain de droits ! (n'importe quel serveur de bases de données)

<?php
$query
= "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
Un internaute fourbe peut envoyer une valeur telle que ' or uid like'%admin% dans $uid pour modifier le mot de passe utilisateur, ou simplement, utiliser la variable $pwd avec la valeur hehehe', trusted=100, admin='yes pour obtenir des droits supplĂ©mentaires. La requĂȘte devient alors :
<?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
...;"
;

?>

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 le code de l'application Ă  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'.

Exemple #4 Attaque du systÚme d'exploitation de l'hÎte de base de données (MSSQL Server)

Un exemple effrayant de la maniĂšre dont des commandes de niveau systĂšme d'exploitation peuvent ĂȘtre accessibles sur certains hĂŽtes de base de donnĂ©es.

<?php
$query
= "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
?>
Si le pirate injecte la valeur a%' exec master..xp_cmdshell 'net user test testpass /ADD' -- dans la variable $prod, alors la requĂȘte $query devient :
<?php

$query
= "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD' --%'"
;
$result = mssql_query($query);

?>
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 sa et que le service MSSQLSERVER disposait d'un niveau de droits suffisant, le pirate disposerait dĂ©sormais d'un compte avec accĂšs au serveur.

Note:

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. La base de donnĂ©es sera alors vulnĂ©rable d'une autre maniĂšre.

Un exemple amusant concernant l'injection SQL

Image de » xkcd

Techniques de contournement

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 valeurs littĂ©rales dynamiques dans les clauses WHERE, SET et VALUES 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.

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.

Exemple #5 Éviter les injections SQL en utilisant des requĂȘtes prĂ©parĂ©es PDO

<?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}%"]);
?>

Les instructions préparées sont fournies par PDO, par MySQLi, et par d'autres bibliothÚques de bases de données.

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Ă©. Il ne faut jamais faire 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.

Une stratégie de défense en profondeur implique plusieurs bonnes pratiques de codage :

  • Il ne faut jamais se connecter Ă  la base de donnĂ©es en tant que superutilisateur ou en tant que propriĂ©taire de la base de donnĂ©es. Il convient de toujours utiliser des utilisateurs personnalisĂ©s avec des privilĂšges minimaux.
  • Il faut vĂ©rifier si l'entrĂ©e donnĂ©e a le type de donnĂ©es attendu. PHP a une large gamme de fonctions de validation d'entrĂ©e, des plus simples trouvĂ©es dans Fonctions de variables et dans Fonctions de type de caractĂšres (par exemple is_numeric(), ctype_digit() respectivement) et jusqu'Ă  la prise en charge des Expressions rĂ©guliĂšres compatibles avec Perl.
  • Si l'application s'attend Ă  une entrĂ©e numĂ©rique, il convient de vĂ©rifier les donnĂ©es avec ctype_digit(), de changer silencieusement son type en utilisant settype(), ou d'utiliser sa reprĂ©sentation numĂ©rique avec sprintf().
  • Si la couche de base de donnĂ©es ne prend pas en charge la liaison de variables, alors il faut mettre 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 mysql_real_escape_string(), sqlite_escape_string(), etc.). Les fonctions gĂ©nĂ©riques comme addslashes() ne sont utiles que dans un environnement trĂšs spĂ©cifique (par exemple MySQL dans un ensemble de caractĂšres Ă  octets uniques avec NO_BACKSLASH_ESCAPES dĂ©sactivĂ©), il est donc prĂ©fĂ©rable de les Ă©viter.
  • 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. Se rĂ©fĂ©rer Ă©galement Ă  Rapport d'erreurs et Fonctions de gestion et de journalisation des erreurs.

À cĂŽtĂ© de ces conseils, il est recommandĂ© d'enregistrer les requĂȘtes, soit dans les 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 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 il y a de dĂ©tails, mieux c'est.

add a note

User Contributed Notes 1 note

up
41
Richard dot Corfield at gmail dot com ¶
14 years ago
The best way has got to be parameterised queries. Then it doesn't matter what the user types in the data goes to the database as a value. 

A quick search online shows some possibilities in PHP which is great! Even on this site - http://php.net/manual/en/pdo.prepared-statements.php
which also gives the reasons this is good both for security and performance.