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


172

Comment les instructions préparées nous aident-elles à empêcher les attaques par injection SQL ?

Wikipedia dit:

Les instructions préparées résistent à l'injection SQL, car les valeurs de paramètre, qui sont transmises ultérieurement à l'aide d'un protocole différent, n'ont pas besoin d'être correctement échappées. Si le modèle d'instruction d'origine 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 simple et quelques exemples?

Réponses:


290

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 réside dans 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, les données peuvent interférer avec le code du 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 qu'elles deviennent une partie du programme, de sorte que les données peuvent modifier le programme, et en fonction des données transmises, nous aurons soit une sortie régulière, soit une table userssupprimée.

Alors qu'en cas d'instructions préparées, nous ne modifions pas notre programme, il reste intact.
C'est le point.

Nous envoyons d'abord un programme au serveur

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

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

Notez qu'exactement la même requête est envoyée au serveur, sans aucune donnée! Et puis nous envoyons les données avec la deuxième requête, essentiellement séparée de la requête elle-même:

$db->execute($data);

il ne peut donc pas modifier notre programme et faire du mal.
Assez simple - n'est-ce pas?

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

Les instructions préparées ne peuvent protéger que les littéraux de données , mais ne peuvent être utilisées avec aucune autre partie de requête.
Donc, une fois que nous devons ajouter, disons, un identifiant dynamique - un nom de champ, par exemple - les instructions préparées ne peuvent pas nous aider. J'ai expliqué la question récemment , donc je ne me répéterai pas.


2
"Par exemple, par défaut, PDO n'utilise pas d'instructions préparées" - ce n'est pas tout à fait vrai, car PDO émule des instructions préparées uniquement pour les pilotes qui ne prennent pas en charge cette fonctionnalité.
pinepain

3
@ zaq178miami: "PDO émule les instructions préparées uniquement pour les pilotes qui ne prennent pas en charge la fonctionnalité" - n'est pas exactement vrai. MySQL prend en charge les instructions préparées depuis un certain temps maintenant. Le pilote PDO a également. Mais pourtant, les requêtes MySQL étaient toujours préparées par PDO par défaut, la dernière fois que j'ai vérifié.
cHao

9
Qu'est-ce qui est différent entre $spoiled_data = "1; DROP TABLE users;"-> $query = "SELECT * FROM users where id=$spoiled_data";, par rapport à: $db->prepare("SELECT * FROM users where id=?");-> $data = "1; DROP TABLE users;"-> $db->execute($data);. Ne feront-ils pas la même chose?
Juha Untinen

14
@Juha Untinen Les données peuvent être n'importe quoi. Il n'analysera pas les données. Ce n'est pas la commande DATA. Ainsi, même si les $ data contiennent des commandes sql, elles ne seront pas exécutées. De plus, si l'id est un nombre, le contenu de la chaîne générera un rapport ou une valeur zéro.
Soley

21

Voici SQL pour mettre en place 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 l'entrée utilisateur. Le but de la requête était d'afficher des informations sur Bob. Soit un salaire, soit une prime, en fonction de l'entrée de l'utilisateur. Mais l'utilisateur malveillant manipule l'entrée corrompant la requête en plaçant l'équivalent d'un 'or true' à la clause where pour que tout soit renvoyé, y compris les informations sur Aaron qui étaient censées être caché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 avec une 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 créer 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 de mise en cache sur le serveur (l'instruction change, donc moins susceptible d'obtenir un hit de cache d'instruction SQL alors que l'exemple de liaison exécute toujours la même instruction).

Voici un exemple de Binding 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écuter avec la même entrée que l'exemple précédent montre que le code malveillant ne fonctionne pas car il n'y a pas de paymentType correspondant à 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=?

L'utilisation d'une instruction préparée à partir du programme se connectant à la base de données a-t-elle le même effet que l'utilisation d'une instruction préparée faisant partie de la base de données? Par exemple, Postgres a sa propre instruction préparée et son utilisation empêcherait-elle l'injection SQL? postgresql.org/docs/9.2/static/sql-prepare.html
Celeritas

@Celeritas Je n'ai pas de réponse définitive pour Postgresql. En regardant la documentation, il semble que l'effet est le même. PREPAREcrée une instruction nommée fixe qui est déjà analysée (c'est-à-dire que l'instruction ne changera plus quelle que soit l'entrée) tandis que EXECUTEexécutera l'instruction nommée liant les paramètres. Comme il PREPAREn'y a que la durée de la session, il semble vraiment qu'il soit destiné à des raisons de performances, et non à empêcher l'injection via des scripts psql. Pour l'accès psql, pourrait donner des autorisations aux procédures stockées et lier les paramètres dans les procs.
Glenn

@Celeritas J'ai essayé le code ci-dessus en utilisant PostgreSQL 11.1 sur x86_64 et l'exemple SQLi ci-dessus a fonctionné.
Krishna Pandey

15

Fondamentalement, avec des instructions préparées, les données provenant d'un pirate informatique potentiel sont traitées comme des données - et il n'y a aucun moyen qu'elles puissent être mélangées avec votre application SQL et / ou interprétées comme SQL (ce qui peut se produire lorsque les données transmises sont placées directement dans votre SQL de l'application).

En effet, les instructions préparées «préparent» d'abord la requête SQL pour trouver un plan de requête efficace et envoient les valeurs réelles qui proviennent vraisemblablement d'un formulaire plus tard - à ce moment-là, la requête est effectivement exécutée.

Plus d'informations ici:

Instructions préparées et injection SQL


6

J'ai lu les réponses et j'ai toujours ressenti le besoin de souligner le point clé qui éclaire l'essence des déclarations préparées. Considérez deux façons d'interroger sa base de données où l'entrée de l'utilisateur est impliquée:

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 utilisateur malveillante peut conduire à SQLStringêtre égale à"SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

En raison de l'utilisateur malveillant, SQLStringcontient 2 déclarations, où la deuxième ( "DROP TABLE CUSTOMERS") causera des dommages.

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. Ainsi, le "DROP TABLE CUSTOMERS"ne serait jamais exécuté dans le cas ci-dessus.

En un mot, avec des instructions préparées, le code malveillant introduit via l'entrée utilisateur ne sera pas exécuté!


Vraiment? La réponse acceptée ne dit pas exactement cela?
Votre bon sens

@Your Common Sense La réponse acceptée est remplie de nombreuses informations précieuses, mais cela m'a fait me demander ce que les détails de mise en œuvre de la séparation des données et de la requête impliquent. Tandis que se concentrer sur le fait que les données injectées de manière malveillante (s'il y en avait une) ne seraient jamais exécutées frappe le clou sur la tête.
N.Vegeta

Et quels "détails de mise en œuvre" sont fournis dans votre réponse qui n'y figurent pas?
Votre bon sens

si vous essayez de voir d'où je viens, vous vous rendrez compte que mon point est le suivant: Le bref désir de voir les détails de l'implémentation découlait d'un besoin de comprendre la raison explicite pour laquelle l'entrée utilisateur malveillante ne causera aucun nuire. Pas tellement besoin de voir les détails de la mise en œuvre. C'est pourquoi, sachant que les détails de l'implémentation étaient tels qu'à aucun moment le SQL entré de manière malveillante ne sera exécuté, le message renvoyé à la maison. Votre réponse répond à la question, comment (comme demandé)?, Mais j'imagine que d'autres personnes (comme moi) seraient satisfaites d'une réponse succincte à pourquoi?
N.Vegeta

Considérez ceci comme un enrichissement qui élucide le nœud, et non comme une critique implicite (recenlty réalisé qui était l'auteur de la réponse acceptée).
N.Vegeta

5

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

Vous liez ultérieurement vos données à la requête de telle sorte que le SGBD utilise ces données 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; ce sont 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êche cela.


4

Dans SQL Server , l'utilisation d'une instruction préparée est définitivement à l'épreuve des injections 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 par injection SQL.

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

Maintenant, si la valeur de la variable inoutusername est quelque chose comme un '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 --, donc il n'est jamais exécuté et contourné 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 effet, vous ne pouvez pas envoyer un autre paramètre, évitant ainsi l'injection SQL ...


3

La phrase clé est need not be correctly escaped. Cela signifie que vous ne devez pas vous inquiéter des gens qui essaient de lancer des tirets, des apostrophes, des citations, etc.

Tout est géré pour vous.


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

Supposons que vous ayez cela dans un servlet que vous avez raison. Si une personne malveillante passe une mauvaise valeur pour «filtre», vous risquez de pirater votre base de données.


0

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

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

Cause fondamentale n ° 2 - Nature humaine, les gens sont rusés et certains rusés sont malveillants et tous font 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 faites une erreur sur une requête structurée, cela ne rend pas votre système 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 mettant 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 l'erreur humaine de créer des failles de sécurité critiques. En ce qui concerne les humains faisant des erreurs, l'injection SQL ne peut pas se produire lorsque des requêtes de structure sont utilisées. Il existe des moyens d'empêcher l'injection SQL qui n'impliquent pas de requêtes structurées, mais une 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 les injections SQL. Vous pouvez faire toutes les erreurs du monde, presque, avec des requêtes structurées, comme n'importe quelle autre programmation, mais aucune de ce que vous pouvez faire ne peut être transformée en un ssstem repris par injection SQL. C'est pourquoi les gens aiment dire que c'est la bonne façon d'empêcher l'injection SQL.

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.