In che modo le istruzioni preparate possono proteggere dagli attacchi SQL injection?


172

In che modo istruzioni preparate ci aiutano a prevenire gli attacchi di iniezione SQL ?

Wikipedia dice:

Le istruzioni preparate sono resilienti contro l'iniezione SQL, poiché i valori dei parametri, che vengono trasmessi in seguito utilizzando un protocollo diverso, non devono essere sottoposti a escape correttamente. Se il modello di istruzione originale non deriva da input esterni, l'iniezione SQL non può avvenire.

Non riesco a vedere molto bene il motivo. Quale sarebbe una semplice spiegazione in inglese semplice e alcuni esempi?

Risposte:


290

L'idea è molto semplice: la query e i dati vengono inviati al server di database separatamente .
È tutto.

La radice del problema di iniezione SQL è nella miscelazione del codice e dei dati.

In effetti, la nostra query SQL è un programma legittimo . E stiamo creando un tale programma in modo dinamico, aggiungendo alcuni dati al volo. Pertanto, i dati possono interferire con il codice del programma e persino modificarlo, come mostra ogni esempio di iniezione SQL (tutti gli esempi in PHP / Mysql):

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

produrrà una query regolare

SELECT * FROM users where id=1

mentre questo codice

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

produrrà una sequenza dannosa

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

Funziona perché stiamo aggiungendo i dati direttamente al corpo del programma e diventa una parte del programma, quindi i dati possono alterare il programma e, a seconda dei dati passati, avremo un output regolare o una tabella userseliminata.

Mentre in caso di dichiarazioni preparate non alteriamo il nostro programma, rimane intatto
Questo è il punto.

In primo luogo stiamo inviando un programma al server

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

dove i dati sono sostituiti da una variabile chiamata parametro o segnaposto.

Si noti che esattamente la stessa query viene inviata al server, senza dati in essa contenuti! E poi stiamo inviando i dati con la seconda richiesta, essenzialmente separati dalla query stessa:

$db->execute($data);

quindi non può alterare il nostro programma e fare del male.
Abbastanza semplice, no?

L'unica cosa che devo aggiungere è sempre stata omessa in ogni manuale:

Le istruzioni preparate possono proteggere solo i letterali di dati , ma non possono essere utilizzate con qualsiasi altra parte della query.
Quindi, una volta che dobbiamo aggiungere, per esempio, un identificatore dinamico - un nome di campo, ad esempio - le istruzioni preparate non possono aiutarci. Di recente ho spiegato la questione , quindi non mi ripeterò.


2
"ad esempio, per impostazione predefinita PDO non utilizza istruzioni preparate" - non è esattamente vero, poiché PDO emula istruzioni preparate solo per i driver che non supportano tale funzionalità.
pinpain,

3
@ zaq178miami: "DOP emula le istruzioni preparate solo per i driver che non supportano la funzione" - non è esattamente vero. MySQL supporta dichiarazioni preparate da un po 'di tempo ormai. Anche il driver DOP ha. Tuttavia, le query MySQL erano ancora preparate dal PDO per impostazione predefinita, l'ultima volta che ho controllato.
cao

9
Ciò che è diverso tra $spoiled_data = "1; DROP TABLE users;"-> $query = "SELECT * FROM users where id=$spoiled_data";, rispetto a: $db->prepare("SELECT * FROM users where id=?");-> $data = "1; DROP TABLE users;"-> $db->execute($data);. Non faranno la stessa cosa?
Juha Untinen,

14
@Juha Untinen I dati possono essere qualsiasi cosa. Non analizzerà i dati. Questo è DATA non il comando. Quindi, anche se $ data contiene comandi sql, non verrà eseguito. Inoltre, se l'id è un numero, il contenuto della stringa genererà un rapporto o un valore zero.
Soley,

21

Ecco SQL per l'impostazione di un esempio:

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 è vulnerabile all'iniezione SQL. La query viene incollata dinamicamente insieme all'input dell'utente. L'intento della query era di mostrare informazioni su Bob. Salario o bonus, in base all'input dell'utente. Ma l'utente malintenzionato manipola l'input che corrompe la query confrontando l'equivalente di una clausola "o true" con la clausola where in modo che tutto venga restituito, comprese le informazioni su Aaron che avrebbero dovuto essere nascoste.

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"));
        }
    }
}

Eseguendo questo, il primo caso è con un utilizzo normale e il secondo con l'iniezione dannosa:

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

Non è necessario creare le istruzioni SQL con la concatenazione di stringhe dell'input dell'utente. Non solo è vulnerabile all'iniezione, ma ha anche implicazioni nella cache sul server (l'istruzione cambia, quindi meno probabilità di ottenere un hit della cache dell'istruzione SQL mentre l'esempio di bind esegue sempre la stessa istruzione).

Ecco un esempio di Binding per evitare questo tipo di iniezione:

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'esecuzione con lo stesso input dell'esempio precedente mostra che il codice dannoso non funziona perché non esiste alcun paymentType corrispondente a quella stringa:

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'uso di un'istruzione preparata dal programma che si collega al database ha lo stesso effetto dell'uso di un'istruzione preparata che fa parte del database? Ad esempio Postgres ha la sua istruzione preparata e usandola impedirebbe l'iniezione SQL? postgresql.org/docs/9.2/static/sql-prepare.html
Celeritas,

@Celeritas Non ho una risposta definitiva per Postgresql. Guardando i documenti, sembra che l'effetto sia lo stesso. PREPAREcrea un'istruzione denominata fissa che è già analizzata (ovvero l'istruzione non cambierà più indipendentemente dall'input) mentre EXECUTEeseguirà l'istruzione denominata vincolante i parametri. Dal momento PREPAREche ha solo una durata della sessione, sembra davvero che sia pensato per motivi di prestazioni, non per impedire l'iniezione tramite script psql. Per l'accesso a psql, potrebbe fornire autorizzazioni alle stored procedure e associare i parametri all'interno dei proc.
Glenn,

@Celeritas Ho provato sopra il codice usando PostgreSQL 11.1 su x86_64 e l'esempio SQLi sopra ha funzionato.
Krishna Pandey,

15

Fondamentalmente, con dichiarazioni preparate i dati che arrivano da un potenziale hacker sono trattati come dati - e non c'è modo che possano essere mescolati con l'applicazione SQL e / o interpretati come SQL (cosa che può accadere quando i dati passati vengono inseriti direttamente nel tuo applicazione SQL).

Questo perché le istruzioni preparate "preparano" prima la query SQL per trovare un piano di query efficiente e inviano i valori effettivi che presumibilmente provengono da un modulo in un secondo momento - in quel momento la query viene effettivamente eseguita.

Maggiori informazioni qui:

Dichiarazioni preparate e SQL Injection


6

Ho letto le risposte e ho ancora sentito la necessità di sottolineare il punto chiave che illumina l'essenza delle dichiarazioni preparate. Considera due modi per interrogare il proprio database in cui è coinvolto l'input dell'utente:

Approccio ingenuo

Uno concatena l'input dell'utente con una stringa SQL parziale per generare un'istruzione SQL. In questo caso l'utente può incorporare comandi SQL dannosi, che verranno quindi inviati al database per l'esecuzione.

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

Ad esempio, l'input di utenti malintenzionati può portare a SQLStringessere uguale a"SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

A causa di un utente malintenzionato, SQLStringcontiene 2 istruzioni, in cui la seconda ( "DROP TABLE CUSTOMERS") causerà danni.

Dichiarazioni preparate

In questo caso, a causa della separazione della query e dei dati, l'input dell'utente non viene mai trattato come un'istruzione SQL e quindi non viene mai eseguito . È per questo motivo che qualsiasi codice SQL dannoso iniettato non causerebbe alcun danno. Quindi "DROP TABLE CUSTOMERS"non verrebbe mai eseguito nel caso sopra.

In poche parole, con dichiarazioni preparate il codice dannoso introdotto tramite l'input dell'utente non verrà eseguito!


Veramente? La risposta accettata non dice esattamente questo?
Il tuo buon senso il

@Your Common Sense La risposta accettata è piena di molte informazioni preziose, ma mi ha fatto meravigliare i dettagli dell'implementazione della separazione di dati e query. Considerando che concentrarsi sul punto che i dati maliziosamente iniettati (se presenti) non verrebbero mai eseguiti colpisce l'unghia sulla testa.
N.Vegeta,

E quali "dettagli di implementazione" sono forniti nella risposta che non sono presenti?
Il tuo senso comune

se provi a vedere da dove vengo, capirai che il mio punto è il seguente: il breve desiderio di vedere i dettagli dell'implementazione derivava dalla necessità di comprendere il motivo esplicito per cui l'input dell'utente malintenzionato non causerà alcun danno. Non c'è molto bisogno di vedere i dettagli dell'implementazione. Questo è il motivo per cui rendersi conto che i dettagli dell'implementazione erano tali che, in nessun caso verrà eseguito un codice SQL inserito in modo dannoso, ha inviato a casa il messaggio. La tua risposta risponde alla domanda, come (come richiesto)? Ma immagino che altre persone (come me) sarebbero soddisfatte di una risposta concisa al perché?
N.Vegeta,

Considera questo un arricchimento che chiarisce il nocciolo, e non come una critica implicita (si rese conto di chi fosse l'autore della risposta accettata).
N.Vegeta,

5

Quando si crea e si invia un'istruzione preparata al DBMS, viene archiviata come query SQL per l'esecuzione.

Successivamente associ i tuoi dati alla query in modo tale che il DBMS li utilizzi come parametri della query per l'esecuzione (parametrizzazione). Il DBMS non utilizza i dati associati a una query SQL già compilata; sono semplicemente i dati.

Ciò significa che è fondamentalmente impossibile eseguire l'iniezione SQL usando istruzioni preparate. La natura stessa delle istruzioni preparate e la loro relazione con il DBMS lo impediscono.


4

In SQL Server , l'utilizzo di un'istruzione preparata è sicuramente a prova di iniezione perché i parametri di input non formano la query. Significa che la query eseguita non è una query dinamica. Esempio di un'istruzione vulnerabile di iniezione SQL.

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

Ora se il valore nella variabile inoutusername è qualcosa come un 'o 1 = 1 -, questa query ora diventa:

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

E il resto viene commentato dopo --, quindi non viene mai eseguito e ignorato come usando l'esempio dell'istruzione preparata come di seguito.

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();

Quindi in effetti non è possibile inviare un altro parametro, evitando così l'iniezione di SQL ...


3

La frase chiave è need not be correctly escaped. Ciò significa che non devi preoccuparti delle persone che cercano di lanciare trattini, apostrofi, citazioni, ecc ...

È tutto gestito per te.


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

Supponiamo che tu l'abbia in un Servlet giusto. Se una persona malevola ha superato un valore negativo per "filtro", è possibile hackerare il database.


0

Causa principale n. 1 - Il problema del delimitatore

L'iniezione di SQL è possibile perché utilizziamo le virgolette per delimitare le stringhe e anche per essere parti di stringhe, rendendo talvolta impossibile interpretarle. Se avessimo delimitatori che non potevano essere usati nei dati delle stringhe, l'iniezione sql non sarebbe mai avvenuta. Risolvere il problema del delimitatore elimina il problema di iniezione sql. Le query sulla struttura lo fanno.

Root Cause # 2 - La natura umana, le persone sono furbe e alcune persone furbe sono dannose e tutte le persone fanno errori

L'altra causa principale dell'iniezione di sql è la natura umana. Le persone, compresi i programmatori, commettono errori. Quando si commette un errore su una query strutturata, questo non rende il sistema vulnerabile all'iniezione sql. Se non si utilizzano query strutturate, gli errori possono generare la vulnerabilità dell'iniezione sql.

In che modo le query strutturate risolvono le cause alla radice dell'iniezione SQL

Le query strutturate risolvono il problema del delimitatore inserendo i comandi sql in un'istruzione e inserendo i dati in un'istruzione di programmazione separata. Le istruzioni di programmazione creano la separazione necessaria.

Le query strutturate aiutano a prevenire errori umani che creano falle di sicurezza critiche. Per quanto riguarda gli umani che commettono errori, l'iniezione sql non può avvenire quando vengono utilizzate le query sulla struttura. Esistono modi per prevenire l'iniezione sql che non comportano query strutturate, ma il normale errore umano in questi approcci di solito porta ad almeno un'esposizione all'iniezione sql. Le query strutturate sono a prova di errore dall'iniezione sql. Puoi fare quasi tutti gli errori del mondo con query strutturate, come qualsiasi altra programmazione, ma nessuna di quelle che puoi fare può essere trasformata in un ssstem preso in carico da sql injection. Ecco perché alla gente piace dire che questo è il modo giusto per prevenire l'iniezione sql.

Quindi, ecco qua, le cause dell'iniezione sql e le query strutturate sulla natura che le rendono impossibili quando vengono utilizzate.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.