web-dev-qa-db-fra.com

Comment les instructions préparées peuvent-elles protéger des attaques d'injection SQL?

Comment les instructions préparées nous aident-elles à prévenir les injections SQL ?

Wikipedia dit:

Les instructions préparées sont résilientes à l’injection SQL, car les valeurs de paramètre, qui sont transmises plus tard en utilisant un autre protocole, n'a pas besoin d'être échappé correctement. Si la déclaration originale le modèle n'est pas dérivé d'une entrée externe, l'injection SQL ne peut pas se produire.

Je ne vois pas très bien la raison. Quelle serait une explication simple en anglais facile et quelques exemples?

132
Aan

L'idée est très simple: la requête et les données sont envoyées séparément au serveur de base de données .
C'est tout.

La racine du problème d'injection SQL est le mélange du code et des données.

En fait, notre requête SQL est un programme légitime . Et nous créons un tel programme de manière dynamique, en ajoutant des données à la volée. Ainsi, ces données peuvent interférer avec le code de programme et même le modifier, comme le montre chaque exemple d'injection SQL (tous les exemples en PHP/Mysql):

$expected_data = 1;
$query = "SELECT * FROM users where id=$expected_data";

produira une requête régulière

SELECT * FROM users where id=1

tandis que ce code

$spoiled_data = "1; DROP TABLE users;"
$query        = "SELECT * FROM users where id=$spoiled_data";

produira une séquence malveillante

SELECT * FROM users where id=1; DROP TABLE users;

Cela fonctionne parce que nous ajoutons les données directement au corps du programme et que celles-ci deviennent une partie intégrante du programme, elles risquent donc de modifier le programme. Selon les données transmises, nous aurons soit une sortie normale, soit un tableau. users supprimé.

Bien que en cas de déclarations préparées, nous ne modifions pas notre programme, celui-ci reste intact
C'est le but.

Nous envoyons d'abord un programme au serveur

$db->prepare("SELECT * FROM users where id=?");

où les données sont substituées par une variable appelée paramètre ou espace réservé.

Notez que la même requête est envoyée au serveur, sans aucune donnée! Et ensuite, nous envoyons les données avec la demande second , essentiellement séparée de la requête elle-même:

$db->execute($data);

alors, cela ne peut pas altérer notre programme et faire du mal.
C'est simple, n'est-ce pas?

Toutefois, il convient de noter que vous n'utilisez pas à chaque fois un espace réservé, , il est traité comme un déclaration préparée .

Un espace réservé est une idée générale permettant de remplacer les données réelles par une variable pour le traitement futur (voir printf(), par exemple), tandis qu'une instruction préparée en est un sous-ensemble.

Dans certains cas (notamment PDO dans PHP peut le faire), une instruction préparée peut être émulée, et une requête est en fait composée avec des données et envoyée au serveur en une seule requête. Mais il est important de comprendre que cette approche est également sûre , car chaque bit de données est correctement formaté en fonction de c'est le type et donc rien ne peut se passer.

La seule chose que je dois ajouter est toujours omise dans chaque manuel:

Les instructions préparées ne peuvent protéger que les données , mais ne peut pas défendre le programme lui-même .
Donc, une fois que nous devons ajouter, par exemple, un identifiant dynamique - un nom de champ, par exemple, les instructions préparées ne peuvent pas aider nous. J'ai expliqué le problème récemment , je ne me répète donc pas.

266
Your Common Sense

Voici SQL pour configurer un exemple:

CREATE TABLE employee(name varchar, paymentType varchar, amount bigint);

INSERT INTO employee VALUES('Aaron', 'salary', 100);
INSERT INTO employee VALUES('Aaron', 'bonus', 50);
INSERT INTO employee VALUES('Bob', 'salary', 50);
INSERT INTO employee VALUES('Bob', 'bonus', 0);

La classe Inject est vulnérable à l'injection SQL. La requête est collée dynamiquement avec la saisie de l'utilisateur. Le but de la requête était de montrer des informations sur Bob. Salaire ou bonus, en fonction des entrées de l'utilisateur. Mais l'utilisateur malveillant manipule l'entrée corrompant la requête en se basant sur l'équivalent d'une clause 'ou true' à la clause where afin que tout soit renvoyé, y compris les informations sur Aaron qui étaient supposées être masquées.

import Java.sql.*;

public class Inject {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=user&password=pwd";
        Connection conn = DriverManager.getConnection(url);

        Statement stmt = conn.createStatement();
        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='" + args[0] + "'";
        System.out.println(sql);
        ResultSet rs = stmt.executeQuery(sql);

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

En exécutant ceci, le premier cas est en utilisation normale et le second avec l'injection malveillante:

c:\temp>Java Inject salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary'
salary 50

c:\temp>Java Inject "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary' OR 'a'!='b'
salary 100
bonus 50
salary 50
bonus 0

Vous ne devez pas construire vos instructions SQL avec une concaténation de chaînes d'entrée utilisateur. Non seulement il est vulnérable à l'injection, mais il a également des implications pour la mise en cache sur le serveur (l'instruction change, donc il est moins probable que le cache d'instruction SQL soit touché alors que l'exemple de liaison exécute toujours la même instruction).

Voici un exemple de liaison pour éviter ce type d’injection:

import Java.sql.*;

public class Bind {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=postgres&password=postgres";
        Connection conn = DriverManager.getConnection(url);

        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?";
        System.out.println(sql);

        PreparedStatement stmt = conn.prepareStatement(sql);
        stmt.setString(1, args[0]);

        ResultSet rs = stmt.executeQuery();

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

L'exécution de cette opération avec la même entrée que dans l'exemple précédent montre que le code malveillant ne fonctionne pas car aucun type de paiement ne correspond à cette chaîne:

c:\temp>Java Bind salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
salary 50

c:\temp>Java Bind "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
20
Glenn

Fondamentalement, avec les instructions préparées, les données provenant d’un pirate informatique potentiel sont traitées comme des données - et il n’ya aucun moyen de les mélanger avec le code SQL de votre application et/ou de les interpréter en tant que code SQL (ce qui peut arriver lorsque les données transmises sont placées directement dans votre fichier application SQL).

Cela est dû au fait que les instructions préparées "préparent" d'abord la requête SQL pour trouver un plan de requête efficace, puis envoient ultérieurement les valeurs qui proviennent vraisemblablement d'un formulaire - à ce moment, la requête est réellement exécutée. 

Plus d'infos ici:

Instructions préparées et injection SQL

13
Jose

Lorsque vous créez et envoyez une instruction préparée au SGBD, elle est stockée en tant que requête SQL à exécuter.

Vous liez ensuite vos données à la requête de sorte que le SGBD les utilise comme paramètres de requête pour l'exécution (paramétrage). Le SGBD n'utilise pas les données que vous liez comme complément à la requête SQL déjà compilée; c'est simplement les données.

Cela signifie qu'il est fondamentalement impossible d'effectuer une injection SQL à l'aide d'instructions préparées. La nature même des instructions préparées et leur relation avec le SGBD empêchent cela.

5
wulfgarpro

Dans SQL Server , l'utilisation d'une instruction préparée est définitivement protégée contre l'injection, car les paramètres d'entrée ne forment pas la requête. Cela signifie que la requête exécutée n'est pas une requête dynamique . Exemple d'instruction vulnérable à l'injection SQL.

string sqlquery = "select * from table where username='" + inputusername +"' and password='" + pass + "'";

Maintenant, si la valeur dans la variable inoutusername est quelque chose comme 'ou 1 = 1 -, cette requête devient maintenant:

select * from table where username='a' or 1=1 -- and password=asda

Et le reste est commenté après --, de sorte qu'il n'est jamais exécuté et ignoré en utilisant l'exemple d'instruction préparé comme ci-dessous.

Sqlcommand command = new sqlcommand("select * from table where username = @userinput and password=@pass");
command.Parameters.Add(new SqlParameter("@userinput", 100));
command.Parameters.Add(new SqlParameter("@pass", 100));
command.prepare();

Donc, en réalité, vous ne pouvez pas envoyer un autre paramètre, évitant ainsi l'injection SQL ...

4
lloydom

La phrase clé est need not be correctly escaped. Cela signifie que vous n'avez pas à vous soucier des gens qui essaient de lancer des tirets, des apostrophes, des citations, etc.

Tout est géré pour vous.

3
Feisty Mango
ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter");

Supposons que vous ayez raison dans un Servlet. Si une personne malveillante a passé une mauvaise valeur pour "filtre", vous risquez de pirater votre base de données.

2
MeBigFatGuy

J'ai lu les réponses et j'ai toujours ressenti le besoin d'insister sur le point clé qui éclaire l'essence des déclarations préparées. Examinez deux manières d'interroger la base de données dans laquelle l'utilisateur est impliqué:

Approche naïve

L'un concatène l'entrée utilisateur avec une chaîne SQL partielle pour générer une instruction SQL. Dans ce cas, l'utilisateur peut intégrer des commandes SQL malveillantes, qui seront ensuite envoyées à la base de données pour exécution.

String SQLString = "SELECT * FROM CUSTOMERS WHERE NAME='"+userInput+"'"

Par exemple, une entrée d'utilisateur malveillant peut conduire à SQLString égal à "SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

En raison de l'utilisateur malveillant, SQLString contient 2 instructions, la deuxième ("DROP TABLE CUSTOMERS") pouvant être dommageable.

Déclarations préparées

Dans ce cas, en raison de la séparation de la requête et des données, l'entrée utilisateur n'est jamais traitée comme une instruction SQL, et n'est donc jamais exécutée. C'est pour cette raison que tout code SQL malveillant injecté ne causerait aucun dommage. Donc, le "DROP TABLE CUSTOMERS" ne sera jamais exécuté dans le cas ci-dessus.

En résumé, avec les instructions préparées, le code malveillant introduit via la saisie de l'utilisateur ne sera pas exécuté!

1
N.Vegeta

Cause fondamentale n ° 1 - Le problème de délimiteur

L'injection de SQL est possible car nous utilisons des guillemets pour délimiter les chaînes et en faire partie, empêchant parfois leur interprétation. Si nous avions des délimiteurs qui ne pourraient pas être utilisés dans des données de chaîne, l'injection SQL ne se serait jamais produite. La résolution du problème de délimiteur élimine le problème d'injection SQL. Les requêtes de structure font cela.

Cause fondamentale n ° 2 - La nature humaine, les gens sont astucieux et Certaines personnes astucieuses sont malicieusesEt tout le monde fait des erreurs

L'autre cause fondamentale de l'injection SQL est la nature humaine. Les gens, y compris les programmeurs, font des erreurs. Lorsque vous commettez une erreur sur une requête structurée, votre système n'est pas vulnérable à l'injection SQL. Si vous n'utilisez pas de requêtes structurées, des erreurs peuvent générer une vulnérabilité d'injection SQL.

Comment les requêtes structurées résolvent les causes profondes de l'injection SQL

Les requêtes structurées résolvent le problème du délimiteur en plaçant des commandes SQL dans une instruction et en plaçant les données dans une instruction de programmation distincte. Les instructions de programmation créent la séparation nécessaire.

Les requêtes structurées aident à empêcher les erreurs humaines de créer des failles de sécurité critiques. En ce qui concerne les erreurs humaines, l'injection SQL n'est pas possible lorsque des requêtes de structure sont utilisées. Il existe des moyens de prévention de l'injection SQL qui n'impliquent pas de requêtes structurées, mais l'erreur humaine normale dans cette approche conduit généralement à au moins une certaine exposition à l'injection SQL. Les requêtes structurées sont protégées contre l'injection SQL. Vous pouvez presque faire toutes les erreurs du monde avec des requêtes structurées, comme toute autre programmation, mais vous ne pouvez en transformer aucune en un système pris en charge par une injection SQL. C'est pourquoi les gens aiment dire que c'est la bonne façon de prévenir l'injection SQL.

Donc, voilà les causes de l'injection SQL et les requêtes structurées par la nature qui les rendent impossibles lorsqu'elles sont utilisées.

0
DanAllen