Il suffit de regarder:
(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?
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
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.
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.
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.
- 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; ^
( 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.
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.
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!
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.
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.
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.
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.
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).
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"
<?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!!! :( *************/
...
<?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! :) *************/
...