Come funziona l'iniezione SQL dal fumetto XKCD "Bobby Tables"?


1094

Solo guardando:

Striscia XKCD (Fonte: https://xkcd.com/327/ )

Cosa fa questo SQL:

Robert'); DROP TABLE STUDENTS; --

Conosco entrambi 'e --sono per commenti, ma la parola non DROPviene commentata anche perché fa parte della stessa riga?


16
Se ascolti Stack Overflow Podcast # 31 (27 novembre 2008), ne discutono effettivamente.
EBGreen

93
In MySQL, 'non è per i commenti . Anche se lo fosse, non c'è spazio prima che possa terminare solo la stringa che lo precede.
Razze di leggerezza in orbita

45
Per quanto riguarda XKCD, se c'è qualche domanda su alcuni dei fumetti puoi sempre andare a Spiegare XKCD e far capire la tua risposta. C'è anche un wiki XKCD , che è molto utile per alcuni fumetti difficili come il geohashing XKCD
Anatoli,

13
Credo che questo link debba essere registrato qui: bobby-tables.com
Arioch 'The

2
beta.companieshouse.gov.uk/company/10542519 è la registrazione per una società di consulenza denominata; DROP TABLE "COMPANIES"; - LTD
Alex Dupuy,

Risposte:


1117

Rilascia il tavolo degli studenti.

Il codice originale nel programma della scuola probabilmente assomiglia a qualcosa

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

Questo è il modo ingenuo di aggiungere input di testo a una query ed è molto negativo , come vedrai.

Dopo che i valori del primo nome, della casella di testo FNMName.Text (che è Robert'); DROP TABLE STUDENTS; --) e della casella di testo LName.Text (che chiamiamo così Derper) sono concatenati con il resto della query, il risultato ora è in realtà due query separate dal terminatore istruzione (punto e virgola). La seconda query è stato iniettato nel primo. Quando il codice esegue questa query sul database, sarà simile al seguente

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

che, in un inglese semplice, si traduce approssimativamente nelle due query:

Aggiungi un nuovo record alla tabella Studenti con un valore Nome di "Robert"

e

Elimina la tabella Studenti

Tutto ciò che supera la seconda query è contrassegnato come un commento : --', 'Derper')

Il 'nome dello studente non è un commento, è il delimitatore della stringa di chiusura . Poiché il nome dello studente è una stringa, è necessario sintatticamente per completare la query ipotetica. Gli attacchi di iniezione funzionano solo quando la query SQL che iniettano genera un SQL valido .

Modificato di nuovo secondo l' astuto commento di dan04


3
Mmm, il WHERE con parentesi attorno agli argomenti è piuttosto insolito, ma almeno evita un errore di sintassi ... :-)
PhiLho,

60
@PhiLho: se la frase originale fosse un INSERT, allora la parentesi avrebbe più senso. Spiegherebbe anche perché la connessione al database non è in modalità di sola lettura.
dan04

3
Come spiega @ dan04, la parentesi ha più senso con un INSERT. Pensando al SELECTcontrario , il non funzionerebbe comunque poiché l'inserto dei tavolini Bobby nel tavolo avrebbe già lasciato cadere il tavolo.
ypercubeᵀᴹ

10
In realtà, in questo esempio la prima query ("aggiungi un nuovo record ...") fallirà perché si Studentsaspetta più di una sola colonna (l'istruzione originale / corretta ha fornito due colonne). Detto questo, la presenza della seconda colonna è utile per mostrare perché è necessario commentare; e poiché non si può cambiare il nome di Bobby, è probabilmente meglio lasciare così com'è con poco più di questa osservazione come nota a piè di pagina.
Eggyal

7
Il cognome di Bobby - o almeno quello di sua madre, è Roberts , come spiega XKCD . Tuttavia, non sono sicuro che la correzione possa migliorare la chiarezza della risposta.
WBT,

611

Diciamo che il nome è stato usato in una variabile, $Name .

Quindi eseguire questa query :

INSERT INTO Students VALUES ( '$Name' )

Il codice inserisce erroneamente tutto ciò che l'utente ha fornito come variabile.

Volevi che l' SQL fosse:

INSERISCI IN VALORI DEGLI STUDENTI (' Robert Tables')

Ma un utente intelligente può fornire quello che vuole:

INSERISCI IN VALORI DEGLI STUDENTI (' Robert'); DROP TABLE Students; --')

Quello che ottieni è:

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

L' --unico commento al resto della riga.


87
Questo è molto meglio del voto più alto, perché spiega la parentesi di chiusura.
Tim Büthe,

1
A proposito, non c'è modo che il direttore della scuola nei fumetti sia a conoscenza o l'XSS poiché il tavolo degli studenti viene eliminato, non può sapere chi lo ha fatto.
xryl669,

@ xryl669 I registri sono molto utili in situazioni come questa ... A volte tutte le query vengono registrate e altre volte le informazioni registrate possono aiutarti a dedurre il colpevole.
Inemanja

165

Come tutti gli altri hanno già sottolineato, la ');frase originale viene chiusa e quindi segue una seconda frase. La maggior parte dei framework, inclusi linguaggi come PHP, hanno ormai impostazioni di sicurezza predefinite che non consentono più istruzioni in una stringa SQL. In PHP, ad esempio, puoi eseguire più istruzioni in una stringa SQL solo usandomysqli_multi_query funzione.

Tuttavia, è possibile manipolare un'istruzione SQL esistente tramite iniezione SQL senza dover aggiungere una seconda istruzione. Supponiamo che tu abbia un sistema di accesso che controlla un nome utente e una password con questa semplice selezione:

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

Se si fornisce petercome nome utente e secretcome password, la stringa SQL risultante sarà simile a questa:

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

È tutto ok. Ora immagina di fornire questa stringa come password:

' OR '1'='1

Quindi la stringa SQL risultante sarebbe questa:

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

Ciò ti consentirebbe di accedere a qualsiasi account senza conoscere la password. Quindi non è necessario essere in grado di utilizzare due istruzioni per utilizzare l'iniezione SQL, sebbene si possano fare cose più distruttive se si è in grado di fornire più istruzioni.


71

No, 'non è un commento in SQL, ma un delimitatore.

La mamma supponeva che il programmatore del database avesse fatto una richiesta simile a:

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

(ad esempio) per aggiungere il nuovo studente, in cui il $xxxcontenuto della variabile è stato estratto direttamente da un modulo HTML, senza controllare il formato né sfuggire a caratteri speciali.

Quindi se $firstNamecontiene Robert'); DROP TABLE students; --il programma di database eseguirà la seguente richiesta direttamente sul DB:

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

vale a dire. terminerà in anticipo l'istruzione insert, eseguirà qualunque codice dannoso il cracker voglia, quindi commenterà qualunque resto di codice ci possa essere.

Sono troppo lento, vedo già 8 risposte prima delle mie nella banda arancione ... :-) Un argomento popolare, a quanto pare.


39

TL; DR

- L'applicazione accetta input, in questo caso 'Nancy', senza tentare di - disinfettare l'input, ad esempio sfuggendo a caratteri speciali 
school => INSERT INTO student VALUES ( 'Nancy' ); INSERISCI 0 1
   
  

- L'inserimento di SQL si verifica quando viene manipolato l'input in un comando di database per - fare in modo che il server di database esegua arbitrariamente SQL 
school => INSERT INTO student VALUES ( 'Robert' ); Studenti DROP TABLE ; - '); INSERISCI 0 1 TABELLA GOCCE
      
  
 

- I registri degli studenti ora sono andati - avrebbe potuto essere anche peggio! 
scuola => SELEZIONA * DA studenti ; 
ERRORE    :   relazione "studenti" fa non esiste
LINEA 1 : SELEZIONA * DA    studenti ; ^
                      

Questo elimina (elimina) la tabella degli studenti.

( Tutti gli esempi di codice in questa risposta sono stati eseguiti su un server di database PostgreSQL 9.1.2. )

Per chiarire cosa sta succedendo, proviamo con una semplice tabella contenente solo il campo del nome e aggiungiamo una singola riga:

school => CREATE TABLE studenti ( nome TEXT PRIMARY KEY ); 
AVVISO : CREATE TABLE / PRIMARY KEY sarà creare implicita indice "students_pkey" per tavolo "studenti" CREATE TABLE 
scuola => INSERT INTO studenti VALORI ( 'John' ); INSERISCI 0 1             
    
  

Supponiamo che l'applicazione utilizzi il seguente SQL per inserire i dati nella tabella:

INSERISCI AI VALORI degli studenti ( 'foobar' );  

Sostituisci foobarcon il nome effettivo dello studente. Una normale operazione di inserimento sarebbe simile a questa:

- Input: Nancy 
school => INSERT INTO student VALUES ( 'Nancy' ); INSERISCI 0   
   1

Quando eseguiamo una query sulla tabella, otteniamo questo:

scuola => SELEZIONA * DA    studenti ;
 nome
-------
 John
 Nancy
( 2 file ) 

Cosa succede quando inseriamo il nome di Little Bobby Tables nel tavolo?

- Input: Robert '); Studenti DROP TABLE; - 
scuola => INSERIRE IN VALORI studenti ( 'Robert' ); Studenti DROP TABLE ; - '); INSERISCI 0 1 TABELLA GOCCE      
  
 

L'iniezione SQL qui è il risultato del nome dello studente che termina la dichiarazione e include un separato DROP TABLE comando ; i due trattini alla fine dell'input hanno lo scopo di commentare qualsiasi codice rimanente che altrimenti causerebbe un errore. L'ultima riga dell'output conferma che il server del database ha eliminato la tabella.

È importante notare che durante l' INSERToperazione l'applicazione non controlla l'input di caratteri speciali e quindi consente l'inserimento di input arbitrari nel comando SQL. Ciò significa che un utente malintenzionato può inserire, in un campo normalmente destinato all'input dell'utente, simboli speciali come virgolette insieme a codice SQL arbitrario per causare l'esecuzione da parte del sistema di database, quindi iniezione SQL  .

Il risultato?

scuola => SELEZIONA * DA studenti ; 
ERRORE :   relazione "studenti" fa non    esiste
LINEA 1 : SELEZIONA * DA studenti ; ^   
                      

SQL injection è l'equivalente del database di una vulnerabilità di esecuzione di codice arbitrario remoto in un sistema operativo o in un'applicazione. Il potenziale impatto di un attacco con iniezione SQL riuscito non può essere sottovalutato: a seconda del sistema di database e della configurazione dell'applicazione, può essere utilizzato da un utente malintenzionato per causare la perdita di dati (come in questo caso), ottenere l'accesso non autorizzato ai dati o persino eseguire codice arbitrario sul computer host stesso.

Come notato dal fumetto XKCD, un modo per proteggersi dagli attacchi di iniezione SQL è quello di disinfettare gli input del database, ad esempio sfuggendo a caratteri speciali, in modo che non possano modificare il comando SQL sottostante e quindi non possano causare l'esecuzione di codice SQL arbitrario. Se si utilizzano query con parametri, ad esempio utilizzandoSqlParameter in ADO.NET, l'input verrà almeno sanificato automaticamente per evitare l'iniezione di SQL.

Tuttavia, la disinfezione degli input a livello di applicazione potrebbe non arrestare tecniche di iniezione SQL più avanzate. Ad esempio, ci sono modi per aggirare la mysql_real_escape_stringfunzione PHP . Per una maggiore protezione, molti sistemi di database supportano istruzioni preparate . Se implementati correttamente nel back-end, le istruzioni preparate possono rendere impossibile l'iniezione SQL trattando gli input di dati come semanticamente separati dal resto del comando.


30

Supponiamo che tu abbia scritto ingenuamente un metodo di creazione di studenti come questo:

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

E qualcuno inserisce il nome Robert'); DROP TABLE STUDENTS; --

Ciò che viene eseguito sul database è questa query:

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

Il punto e virgola termina il comando di inserimento e ne avvia un altro; il - commenta il resto della riga. Il comando DROP TABLE viene eseguito ...

Questo è il motivo per cui i parametri di bind sono una buona cosa.


26

Una singola citazione è l'inizio e la fine di una stringa. Un punto e virgola è la fine di un'istruzione. Quindi se stessero facendo una selezione come questa:

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

L'SQL diventerebbe:

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

Su alcuni sistemi, selectverrebbero eseguiti prima seguiti dropdall'istruzione! Il messaggio è: NON INCORPORARE VALORI NEL TUO SQL. Usa invece i parametri!


18

Le ');estremità della query, non si avvia un commento. Quindi rilascia la tabella degli studenti e commenta il resto della query che avrebbe dovuto essere eseguita.


17

Lo scrittore del database probabilmente ha fatto un

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

Se student_name è quello indicato, esegue la selezione con il nome "Robert" e quindi elimina la tabella. La parte "-" cambia il resto della query data in un commento.


Era il mio primo pensiero, ma si ottiene un errore di sintassi con la parentesi di chiusura finale, no?
PhiLho,

3
Ecco perché c'è un - alla fine, che indica che il testo rimanente è un commento e dovrebbe essere ignorato.

17

In questo caso, "non è un carattere di commento. È usato per delimitare i letterali di stringa. L'artista di fumetti punta sull'idea che la scuola in questione abbia un sql dinamico da qualche parte che assomigli a questo:

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

Quindi ora il carattere termina letteralmente la stringa prima che il programmatore se lo aspettasse. Combinato con; carattere per terminare l'affermazione, un attaccante ora può aggiungere qualunque sql voglia. Il - commento alla fine è quello di assicurarsi che qualsiasi sql rimanente nell'istruzione originale non impedisca la compilazione della query sul server.

FWIW, penso anche che il fumetto in questione abbia un dettaglio importante sbagliato: se stai pensando di disinfettare gli input del tuo database, come suggerisce il fumetto, stai ancora facendo qualcosa di sbagliato. Invece, dovresti pensare in termini di mettere in quarantena gli input del tuo database e il modo corretto per farlo è tramite query parametrizzate.


16

Il 'carattere in SQL viene utilizzato per le costanti di stringa. In questo caso viene utilizzato per terminare la costante di stringa e non per un commento.


7

Funziona così: supponiamo che l'amministratore stia cercando i record degli studenti

Robert'); DROP TABLE STUDENTS; --

Poiché l'account amministratore ha privilegi elevati, è possibile eliminare la tabella da questo account.

Il codice per recuperare il nome utente dalla richiesta è

Ora la query sarebbe simile a questa (per cercare nella tabella degli studenti)

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

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

La query risultante diventa

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

Poiché l'input dell'utente non è igienizzato, la query sopra è stata manipolata in 2 parti

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

DROP TABLE STUDENTS; --

Il doppio trattino (-) commenterà solo la parte rimanente della query.

Ciò è pericoloso in quanto può annullare l'autenticazione della password, se presente

Il primo eseguirà la ricerca normale.

Il secondo abbandonerà lo studente della tabella se l'account ha privilegi sufficienti (in genere l'account dell'amministratore della scuola eseguirà tale query e avrà i privilegi di cui sopra).


SELECT* FROM sutdents ...- hai dimenticato una "s". Questo è ciò che stai lasciando cadere. DROP TABLE STUDENTS;
DevWL,

4

Non è necessario inserire i dati del modulo per eseguire l'iniezione SQL.

Nessuno lo ha sottolineato prima, quindi potrei avvisare alcuni di voi.

Principalmente proveremo a patchare l'input dei moduli. Ma questo non è l'unico posto dove puoi essere attaccato con l'iniezione SQL. Puoi eseguire un attacco molto semplice con l'URL che invia i dati tramite la richiesta GET; Considera l'esempio incolto:

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

Il tuo URL sarebbe http://yoursite.com/show?id=1

Ora qualcuno potrebbe provare qualcosa del genere

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

Prova a sostituire table_name con il nome reale della tabella. Se avesse capito bene il nome del tuo tavolo, avrebbero svuotato il tuo tavolo! (È molto facile forzare brutalmente questo URL con un semplice script)

La tua query sarebbe simile a questa ...

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

Esempio di codice vulnerabile PHP tramite 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!!! :( *************/
...

Soluzione: utilizzare i metodi DOP () e bindParam () DOP:

<?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! :) *************/
...
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.