it-swarm.it

Le istruzioni preparate sono sicure al 100% contro l'iniezione SQL?

Le istruzioni preparate in realtà 100% sicure contro l'iniezione SQL, presupponendo che tutti i parametri forniti dall'utente vengano passati come parametri associati alla query?

Ogni volta che vedo persone che usano il vecchio mysql_ funzioni su StackOverflow (che è, purtroppo, troppo frequentemente) In genere dico alle persone che le dichiarazioni preparate sono le misure di sicurezza dell'iniezione SQL di Chuck Norris (o Jon Skeet).

Tuttavia, in realtà non ho mai visto alcuna documentazione che dichiari categoricamente "questo è sicuro al 100%". La mia comprensione è che separano il linguaggio di query e i parametri fino alla porta principale del server, che li tratta come entità separate.

Sono corretto in questo presupposto?

80
Polynomial

Garanzia del 100% sicura dall'iniezione SQL? Non lo otterrò (da me).

In linea di principio, il tuo database (o libreria nella tua lingua che interagisce con il db) potrebbe implementare istruzioni preparate con parametri associati in un modo non sicuro suscettibile a una sorta di attacco avanzato, ad esempio sfruttando overflow del buffer o avendo caratteri nulli in user- fornito stringhe, ecc. (Si potrebbe sostenere che questi tipi di attacchi non dovrebbero essere chiamati iniezione SQL in quanto sono fondamentalmente diversi; ma questa è solo una semantica).

Non ho mai sentito parlare di nessuno di questi attacchi a dichiarazioni preparate su database reali sul campo e suggerisco vivamente di utilizzare parametri associati per prevenire l'iniezione di SQL. Senza parametri associati o misure igieniche di input, è banale eseguire l'iniezione SQL. Con solo l'input di servizi igienico-sanitari, è abbastanza spesso possibile trovare un'oscura scappatoia attorno all'igiene.

Con i parametri associati, il piano di esecuzione della query SQL viene individuato in anticipo senza fare affidamento sull'input dell'utente, il che dovrebbe rendere impossibile l'iniezione SQL (poiché eventuali citazioni, simboli di commento, ecc. Inseriti vengono inseriti solo all'interno dell'istruzione SQL già compilata).

L'unico argomento contro l'utilizzo di istruzioni preparate è che si desidera che il database ottimizzi i piani di esecuzione in base alla query effettiva. La maggior parte dei database quando viene data la query completa sono abbastanza intelligenti da fare un piano di esecuzione ottimale; ad esempio, se la query restituisce una grande percentuale della tabella, vorrebbe percorrere l'intera tabella per trovare le corrispondenze; mentre se otterrà solo pochi record potresti fare una ricerca basata sull'indice [1] .

EDIT: Rispondere a due critiche (che sono un po 'troppo lunghe per i commenti):

Innanzitutto, come altri hanno notato sì, ogni database relazionale che supporta istruzioni preparate e parametri associati non necessariamente precompila l'istruzione preparata senza guardare il valore dei parametri associati. Molti database lo fanno abitualmente, ma è anche possibile che i database guardino i valori dei parametri associati durante la definizione del piano di esecuzione. Questo non è un problema, in quanto la struttura dell'istruzione preparata con parametri associati separati, consente al database di differenziare in modo chiaro l'istruzione SQL (comprese le parole chiave SQL) dai dati nei parametri associati (dove non sarà presente nulla in un parametro associato interpretato come una parola chiave SQL). Ciò non è possibile quando si creano istruzioni SQL dalla concatenazione di stringhe in cui variabili e parole chiave SQL verrebbero mescolate.

In secondo luogo, come indica altra risposta , l'uso di parametri associati quando si chiama un'istruzione SQL in un punto di un programma impedirà in modo sicuro l'iniezione di SQL quando si effettua quella chiamata di livello superiore. Tuttavia, se si hanno vulnerabilità di iniezione SQL altrove nell'applicazione (ad esempio, nelle funzioni definite dall'utente sono state archiviate ed eseguite nel database che sono state scritte in modo non sicuro per costruire query SQL mediante concatenazione di stringhe).

Ad esempio, se nella tua applicazione hai scritto pseudo-codice come:

sql_stmt = "SELECT create_new_user(?, ?)"
params = (email_str, hashed_pw_str)
db_conn.execute_with_params(sql_stmt, params)

Non è possibile eseguire alcuna iniezione SQL durante l'esecuzione di questa istruzione SQL a livello di applicazione. Tuttavia, se la funzione di database definita dall'utente è stata scritta in modo non sicuro (utilizzando la sintassi PL/pgSQL):

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
DECLARE
   sql_str TEXT;
BEGIN
     sql_str := 'INSERT INTO users VALUES (' || email_str || ', ' || hashed_pw_str || ');'
     EXECUTE sql_str;
END;
$$
LANGUAGE plpgsql;

allora saresti vulnerabile agli attacchi di SQL injection, perché esegue un'istruzione SQL costruita tramite concatenazione di stringhe che mescola l'istruzione SQL con stringhe contenenti i valori delle variabili definite dall'utente.

Detto questo, a meno che tu non stia cercando di non essere sicuro (costruendo istruzioni SQL tramite concatenazione di stringhe), sarebbe più naturale scrivere l'utente definito in modo sicuro come:

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
BEGIN
     INSERT INTO users VALUES (email_str, hashed_pw_str);
END;
$$
LANGUAGE plpgsql;

Inoltre, se hai davvero sentito la necessità di comporre un'istruzione SQL da una stringa in una funzione definita dall'utente, puoi comunque separare le variabili di dati dall'istruzione SQL allo stesso modo dei parametri stored_procedures/bound anche all'interno di una funzione definita dall'utente. Ad esempio in PL/pgSQL :

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
DECLARE
   sql_str TEXT;
BEGIN
     sql_str := 'INSERT INTO users VALUES($1, $2)'
     EXECUTE sql_str USING email_str, hashed_pw_str;
END;
$$
LANGUAGE plpgsql;

Quindi l'uso di istruzioni preparate è sicuro dall'iniezione SQL, purché non si stiano facendo cose non sicure altrove (ovvero costruendo istruzioni SQL mediante concatenazione di stringhe).

54
dr jimbob

Sicuro al 100%? Neanche vicino. I parametri associati (preparati per quanto riguarda le istruzioni o in altro modo) possono prevenire efficacemente, al 100%, una classe della vulnerabilità dell'iniezione SQL (presupponendo che non vi siano bug di database e un'implementazione sana ). In nessun modo impediscono altre classi. Si noti che PostgreSQL (il mio db di scelta) ha un'opzione per associare i parametri alle istruzioni ad hoc che consente di salvare un round trip per quanto riguarda le istruzioni preparate se non sono necessarie alcune funzionalità di queste.

Devi capire che molti database grandi e complessi sono programmi di per sé. La complessità di questi programmi varia un po 'e l'iniezione SQL è qualcosa che deve essere ricercato all'interno delle routine di programmazione. Tali routine includono trigger, funzioni definite dall'utente, stored procedure e simili. Non è sempre ovvio come queste cose interagiscano da un livello di applicazione poiché molti buoni dba forniscono un certo grado di astrazione tra il livello di accesso dell'applicazione e il livello di archiviazione.

Con parametri associati, l'albero delle query viene analizzato, quindi, almeno in PostgreSQL, i dati vengono esaminati per pianificare. Il piano viene eseguito. Con le dichiarazioni preparate, il piano viene salvato in modo da poter rieseguire lo stesso piano con dati diversi più e più volte (questo può essere o meno ciò che si desidera). Ma il punto è che con i parametri associati, un parametro non può iniettare nulla nella struttura di analisi. Quindi questa classe di problemi di iniezione SQL è stata adeguatamente curata.

Ma ora dobbiamo registrare chi scrive cosa su una tabella, quindi aggiungiamo trigger e funzioni definite dall'utente per incapsulare la logica di questi trigger. Questi pongono nuovi problemi. Se hai SQL dinamico in questi, devi preoccuparti dell'iniezione SQL lì. Le tabelle in cui scrivono possono avere dei trigger propri e così via. Allo stesso modo una chiamata di funzione potrebbe invocare un'altra query che potrebbe invocare un'altra chiamata di funzione e così via. Ognuno di questi è pianificato indipendentemente dall'albero principale.

Ciò significa che se eseguo una query con un parametro associato come foo'; drop user postgres; -- quindi non può implicare direttamente l'albero delle query di livello superiore e causare l'aggiunta di un altro comando per eliminare l'utente postgres. Tuttavia, se questa query chiama un'altra funzione direttamente o no, è possibile che da qualche parte lungo la linea, una funzione sia vulnerabile e l'utente postgres verrà eliminato. I parametri associati hanno offerto nessuna protezione alle query secondarie. Tali query secondarie devono assicurarsi di utilizzare anche i parametri associati nella misura del possibile e, in caso contrario, dovranno utilizzare routine di quotazione appropriate.

La tana del coniglio diventa profonda.

A proposito per una domanda su Stack Overflow in cui questo problema è evidente, vedere https://stackoverflow.com/questions/37878426/conditional-where-expression-in-dynamic-query/37878574#37878574

Anche una versione più problematica (a causa della limitazione delle dichiarazioni di utilità) a https://stackoverflow.com/questions/38016764/perform-create-index-in-plpgsql-doesnt-run/38021245#38021245

25
Chris Travers