web-dev-qa-db-fra.com

Comment fonctionne l'injection SQL de la bande dessinée XKCD "Bobby Tables"?

Il suffit de regarder:

XKCD Strip  (Source: https://xkcd.com/327/ )

Que fait ce SQL:

Robert'); DROP TABLE STUDENTS; --

Je sais que ' et -- sont tous deux destinés aux commentaires, mais le mot DROP n'est-il pas également commenté puisqu'il fait partie de la même ligne?

1061
Blankman

Il supprime la table des étudiants.

Le code original dans le programme de l'école ressemble probablement à quelque chose comme

q = "INSERT INTO Students VALUES ('" + FNMName.Text + "', '" + LName.Text + "')";

C'est le moyen naïf d'ajouter du texte dans une requête. très mauvais, comme vous le verrez.

Après les valeurs du prénom, deuxième zone de texte du nom FNMName.Text ​​(qui est Robert'); DROP TABLE STUDENTS; --) et la dernière zone de texte du nom LName.Text ​​(appelons-le Derper) sont concaténés avec le reste de la requête, le résultat est maintenant en fait deux requêtes séparés par le terminateur de déclaration (point-virgule). La deuxième requête a été injectée dans la première. Lorsque le code exécute cette requête sur la base de données, il ressemble à ceci

INSERT INTO Students VALUES ('Robert'); DROP TABLE Students; --', 'Derper')

qui, en clair, traduit approximativement les deux requêtes:

Ajouter un nouvel enregistrement à la table Étudiants avec la valeur Nom 'Robert'

et

Supprimer la table des étudiants

Tout ce qui est passé la seconde requête est marqué comme un commentaire : --', 'Derper')

Le ' dans le nom de l'étudiant n'est pas un commentaire, c'est le dernier délimiteur de chaîne . Comme le nom de l'étudiant est une chaîne, il est nécessaire de le faire syntaxiquement pour compléter la requête hypothétique. Les attaques par injection ne fonctionnent que lorsque la requête SQL injecte les résultats dans un code SQL valide.

Edité encore selon le commentaire astucieux de dan04

1083
Will

Disons que le nom a été utilisé dans une variable, $Name. Vous exécutez ensuite cette requête:

INSERT INTO Students VALUES ( '$Name' )

Le code place par erreur tout ce que l'utilisateur a fourni comme variable. Vous vouliez que le SQL soit:

INSÉRER DANS LES VALEURS DES ÉTUDIANTS ('Robert Tables`)

Mais un utilisateur intelligent peut fournir ce qu'il veut:

INSÉRER DANS LES VALEURS DES ÉTUDIANTS ('Robert'); DROP TABLE Students; --')

Ce que vous obtenez est:

INSERT INTO Students VALUES ( 'Robert' );  DROP TABLE STUDENTS; --' )

Le -- ne commente que le reste de la ligne.

593
sinoth

Comme tout le monde l’a déjà fait remarquer, le '); ferme la déclaration originale, puis une deuxième déclaration suit. La plupart des frameworks, y compris des langages tels que PHP, ont désormais des paramètres de sécurité par défaut qui n'autorisent pas plusieurs instructions dans une chaîne SQL. En PHP, par exemple, vous ne pouvez exécuter plusieurs instructions dans une seule chaîne SQL à l'aide de la fonction mysqli_multi_query.

Vous pouvez cependant manipuler une instruction SQL existante via une injection SQL sans avoir à en ajouter une seconde. Disons que vous avez un système de connexion qui vérifie un nom d'utilisateur et un mot de passe avec cette simple sélection:

$query="SELECT * FROM users WHERE username='" . $_REQUEST['user'] . "' and (password='".$_REQUEST['pass']."')";
$result=mysql_query($query);

Si vous fournissez peter comme nom d'utilisateur et secret comme mot de passe, la chaîne SQL résultante se présentera comme suit:

SELECT * FROM users WHERE username='peter' and (password='secret')

Tout va bien. Maintenant, imaginez que vous fournissiez cette chaîne en tant que mot de passe:

' OR '1'='1

La chaîne SQL résultante serait alors la suivante:

SELECT * FROM users WHERE username='peter' and (password='' OR '1'='1')

Cela vous permettrait de vous connecter à n’importe quel compte sans connaître le mot de passe. Vous n'avez donc pas besoin de pouvoir utiliser deux instructions pour utiliser l'injection SQL, bien que vous puissiez faire des choses plus destructives si vous êtes en mesure de fournir plusieurs instructions.

156
Johannes Fahrenkrug

Non, ' n'est pas un commentaire en SQL, mais un délimiteur.

Maman a supposé que le programmeur de base de données avait fait une requête ressemblant à:

INSERT INTO 'students' ('first_name', 'last_name') VALUES ('$firstName', '$lastName');

(par exemple) pour ajouter le nouvel élève, où le contenu de la variable $xxx a été extrait directement d'un formulaire HTML, sans vérification du format ni échappement de caractères spéciaux.

Ainsi, si $firstName contient Robert'); DROP TABLE students; --, le programme de base de données exécutera la requête suivante directement sur le DB:

INSERT INTO 'students' ('first_name', 'last_name') VALUES ('Robert'); DROP TABLE students; --', 'XKCD');

c'est à dire. il mettra fin à l’instruction insert plus tôt, exécutera le code malveillant souhaité par le pirate, puis commentera le reste du code qui pourrait subsister.

Mmm, je suis trop lent, je vois déjà 8 réponses avant la mienne dans le groupe orange ... :-) Un sujet populaire, semble-t-il.

70
PhiLho

TL; DR

 - L'application accepte les entrées, dans ce cas-ci 'Nancy', sans tenter de 
 - nettoie l'entrée, par exemple en échappant des caractères spéciaux 
 School => INSERT INTO student VALUES ('Nancy'); 
 INSERT 0 1 
 
 - Une injection SQL a lieu lorsque l'entrée dans une commande de base de données est manipulée pour 
 - car le serveur de base de données exécuter une instruction SQL 
 arbitraire => INSÉRER DANS LES VALEURS des étudiants ('Robert'); DROP TABLE étudiants; - '); 
 INSÉRER 0 1 
 TABLEAU DE DÉPOSE 
 
 - Les dossiers des élèves sont maintenant révolus - cela aurait pu être encore pire! 
 school => SELECT * FROM étudiants; 
 ERREUR: la relation "étudiants" n’existe pas 
 LIGNE 1: SELECT * FROM étudiants; 
 ^ 

Cela supprime (supprime) la table des étudiants.

( Tous les exemples de code de cette réponse ont été exécutés sur un serveur de base de données PostgreSQL 9.1.2. )

Pour clarifier ce qui se passe, essayons ceci avec une simple table contenant uniquement le champ de nom et ajoutons une seule ligne:

 school => CREATE TABLE élèves (nom TEXT PRIMARY KEY); 
 AVIS: CREATE TABLE/PRIMARY KEY créera un index implicite "students_pkey" pour le tableau "étudiants" 
 CREATE TABLE 
 école => INSÉRER DANS LES VALEURS des élèves ('John'); 
 INSÉRER 0 1 

Supposons que l'application utilise le code SQL suivant pour insérer des données dans la table:

 INSERER DANS LES VALEURS des étudiants ('foobar'); 

Remplacez foobar par le nom réel de l’élève. Une opération d'insertion normale ressemblerait à ceci:

 - Entrée: Nancy 
 École => INSÉRER DANS LES VALEURS des élèves ('Nancy'); 
 INSÉRER 0 1 

Lorsque nous interrogeons la table, nous obtenons ceci:

 school => SELECT * FROM élèves; 
 nom 
 ------- 
 John 
 Nancy 
 (2 Lignes)

Que se passe-t-il lorsque nous insérons le nom de Little Bobby Tables dans la table?

 - Entrée: Robert '); DROP TABLE étudiants; - 
 school => INSERT INTO VALORS DES ÉLÈVES ('Robert'); DROP TABLE étudiants; - '); 
 INSÉRER 0 1 
 TABLEAU DE DÉPOSE 

L'injection SQL ici est le résultat du nom de l'étudiant mettant fin à l'instruction et incluant une commande séparée DROP TABLE; les deux tirets à la fin de l'entrée sont destinés à mettre en commentaire tout code restant qui, autrement, causerait une erreur. La dernière ligne de la sortie confirme que le serveur de base de données a supprimé la table.

Il est important de noter que, lors de l'opération INSERT, l'application ne vérifie aucun caractère spécial dans l'entrée et autorise donc une entrée arbitraire dans la commande SQL. Cela signifie qu'un utilisateur malveillant peut insérer, dans un champ normalement destiné à cette saisie, des symboles spéciaux tels que des guillemets ainsi que du code SQL arbitraire pour que le système de base de données l'exécute, donc SQL injection .

Le résultat?

 école => SELECT * FROM étudiants; 
 ERREUR: la relation "étudiants" n'existe pas 
 LIGNE 1: SELECT * FROM étudiants; 
 ^ 

L'injection SQL correspond à la base de données d'une vulnérabilité exécution de code arbitraire à distance dans un système d'exploitation ou une application. L'impact potentiel d'une attaque par injection SQL réussie ne peut pas être sous-estimé. En fonction du système de base de données et de la configuration de l'application, il peut être utilisé par un attaquant pour causer la perte de données (comme dans ce cas), obtenir un accès non autorisé à des données, voire exécuter code arbitraire sur la machine hôte elle-même.

Comme indiqué par le comic XKCD, un moyen de se protéger contre les attaques par injection SQL consiste à nettoyer les entrées de base de données, par exemple en échappant des caractères spéciaux, de sorte qu'elles ne puissent pas modifier la commande SQL sous-jacente et ne puissent donc pas exécuter de code SQL arbitraire. Si vous utilisez des requêtes paramétrées, par exemple SqlParameter dans ADO.NET, l'entrée sera au minimum automatiquement vérifiée pour se prémunir contre l'injection SQL.

Toutefois, la désinfection des entrées au niveau de l'application peut ne pas arrêter les techniques d'injection SQL plus avancées. Par exemple, il existe des moyens de contourner la fonction mysql_real_escape_string PHP . Pour une protection accrue, de nombreux systèmes de base de données prennent en charge les instructions préparées . Si elles sont correctement implémentées dans le back-end, les instructions préparées peuvent rendre l'injection SQL impossible en traitant les entrées de données comme étant sémantiquement séparées du reste de la commande.

37
bwDraco

Dites que vous avez naïvement écrit une méthode de création d’étudiants comme celle-ci:

void createStudent(String name) {
    database.execute("INSERT INTO students (name) VALUES ('" + name + "')");
}

Et quelqu'un entre le nom Robert'); DROP TABLE STUDENTS; --

Ce qui est exécuté sur la base de données est cette requête:

INSERT INTO students (name) VALUES ('Robert'); DROP TABLE STUDENTS --')

Le point-virgule termine la commande d'insertion et en lance une autre; the - commente le reste de la ligne. La commande DROP TABLE est exécutée ...

C'est pourquoi les paramètres de liaison sont une bonne chose.

28
Dan Vinton

Un seul guillemet est le début et la fin d'une chaîne. Un point-virgule est la fin d'une déclaration. Donc, s'ils faisaient un choix comme celui-ci:

Select *
From Students
Where (Name = '<NameGetsInsertedHere>')

Le SQL deviendrait:

Select *
From Students
Where (Name = 'Robert'); DROP TABLE STUDENTS; --')
--             ^-------------------------------^

Sur certains systèmes, la select serait exécutée en premier, suivie de la déclaration drop! Le message est le suivant: NE PAS INCORPORER DE VALEUR DANS VOTRE SQL. Utilisez plutôt des paramètres!

25
CodeAndCats

Le '); termine la requête, il ne commence pas de commentaire. Ensuite, il supprime la table des étudiants et commente le reste de la requête qui devait être exécutée.

17
Jorn

L'auteur de la base de données a probablement fait une

sql = "SELECT * FROM STUDENTS WHERE (STUDENT_NAME = '" + student_name + "') AND other stuff";
execute(sql);

Si nom_étudiant est celui indiqué, la sélection portant le nom "Robert" est effectuée, puis la table est supprimée. La partie "-" transforme le reste de la requête donnée en un commentaire.

16
Paul Tomblin

Dans ce cas, 'n'est pas un caractère de commentaire. Il est utilisé pour délimiter les littéraux de chaîne. Le dessinateur de bandes dessinées mise sur l’idée que l’école en question dispose d’un sql dynamique qui ressemble à ceci:

$sql = "INSERT INTO `Students` (FirstName, LastName) VALUES ('" . $fname . "', '" . $lname . "')";

Alors maintenant, le caractère 'termine le littéral chaîne avant que le programmeur ne l'attende. Combiné avec le; caractère pour mettre fin à la déclaration, un attaquant peut désormais ajouter le code SQL de son choix. Le commentaire à la fin est de s’assurer que tout SQL restant dans l’instruction originale n’empêche pas la compilation de la requête sur le serveur.

FWIW, je pense aussi que le comique en question a un détail important qui ne va pas: si vous songez à désinfection les entrées de votre base de données, comme le suggère le comique, vous faites toujours une erreur. Au lieu de cela, vous devriez penser en termes de mise en quarantaine vos entrées de base de données, et la manière correcte de le faire consiste à utiliser des requêtes paramétrées.

16
Joel Coehoorn

Le caractère ' en SQL est utilisé pour les constantes de chaîne. Dans ce cas, il est utilisé pour mettre fin à la constante de chaîne et non pour un commentaire.

15
Rockcoder

Voici comment cela fonctionne: supposons que l’administrateur recherche des enregistrements d’étudiants

Robert'); DROP TABLE STUDENTS; --

Étant donné que le compte administrateur a des privilèges élevés, il est possible de supprimer la table de ce compte.

Le code permettant de récupérer le nom d'utilisateur à partir de la demande est

Maintenant, la requête serait quelque chose comme ça (pour chercher dans la table des étudiants)

String query="Select * from student where username='"+student_name+"'";

statement.executeQuery(query); //Rest of the code follows

La requête résultante devient

Select * from student where username='Robert'); DROP TABLE STUDENTS; --

Puisque la saisie de l'utilisateur n'est pas vérifiée, la requête ci-dessus a été manipulée en 2 parties

Select * from student where username='Robert'); 

DROP TABLE STUDENTS; --

Le double tiret (-) mettra en commentaire la partie restante de la requête.

Ceci est dangereux car il peut annuler l'authentification par mot de passe, le cas échéant

Le premier fera la recherche normale.

Le second lâchera la table si le compte dispose de privilèges suffisants (en règle générale, le compte administrateur de l'école exécutera cette requête et disposera des privilèges décrits ci-dessus).

6
vivek

Il n'est pas nécessaire de saisir des données de formulaire pour effectuer une injection SQL.

Personne n’en avait déjà parlé, alors je pourrais alerter certains d’entre vous.

Nous essaierons surtout de corriger les entrées de formulaires. Mais ce n'est pas le seul endroit où vous pouvez être attaqué avec l'injection SQL. Vous pouvez effectuer une attaque très simple avec une URL qui envoie des données via une requête GET; Prenons l'exemple suivant:

<a href="/show?id=1">show something</a>

Votre URL aurait l'air http://votresite.com/show?id=1

Maintenant, quelqu'un pourrait essayer quelque chose comme ça

http://yoursite.com/show?id=1;TRUNCATE table_name

Essayez de remplacer nom_table par le nom réel de la table. S'il donne le nom de votre table, ils videraient votre table! (Il est très facile de forcer brutalement cette URL avec un script simple)

Votre requête ressemblerait à quelque chose comme ça ...

"SELECT * FROM page WHERE id = 4;TRUNCATE page"

Exemple de PHP code vulnérable utilisant PDO:

<?php
...
$id = $_GET['id'];

$pdo = new PDO($database_dsn, $database_user, $database_pass);
$query = "SELECT * FROM page WHERE id = {$id}";
$stmt = $pdo->query($query);
$data = $stmt->fetch(); 
/************* You have lost your data!!! :( *************/
...

Solution - utilisez les méthodes PDO prepare () et bindParam ():

<?php
...
$id = $_GET['id'];

$query = 'SELECT * FROM page WHERE id = :idVal';
$stmt = $pdo->prepare($query);
$stmt->bindParam('idVal', $id, PDO::PARAM_INT);
$stmt->execute();
$data = $stmt->fetch();
/************* Your data is safe! :) *************/
...
1
DevWL