Perché non solo fare in modo che le query non parametrizzate restituiscano un errore?


22

L'iniezione di SQL è un problema di sicurezza molto grave, in gran parte perché è così facile sbagliare: il modo ovvio e intuitivo per creare una query che incorpora l'input dell'utente ti rende vulnerabile e il modo giusto per mitigarlo richiede di conoscere i parametri query e iniezione SQL prima.

Mi sembra che il modo ovvio per risolvere questo sarebbe chiudere l'opzione ovvia (ma sbagliata): correggere il motore di database in modo che qualsiasi query ricevuta che utilizza valori hardcoded nella sua clausola WHERE invece di parametri restituisca un bel descrittivo messaggio di errore che indica di utilizzare invece i parametri. Ciò dovrebbe ovviamente avere un'opzione di opt-out in modo che roba come le query ad hoc dagli strumenti di amministrazione continueranno a funzionare facilmente, ma dovrebbe essere abilitata per impostazione predefinita.

Avere questo chiuderebbe l'iniezione di SQL a freddo, quasi da un giorno all'altro, ma per quanto ne so, nessun RDBMS lo fa davvero. C'è qualche buona ragione perché no?


22
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'avrebbe valori sia codificati sia parametrizzati in una singola query - prova a catturarlo! Penso che ci siano casi d'uso validi per tali query miste.
amon,

6
Che ne dici di selezionare i record di oggiSELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
Jaydee,

10
@MasonWheeler scusa, volevo dire "prova a permetterlo". Si noti che è perfettamente parametrizzato e non soffre di iniezione SQL. Tuttavia, il driver del database non è in grado di dire se il valore letterale "bad"è veramente letterale o è il risultato della concatenazione di stringhe. Le due soluzioni che vedo sono eliminare SQL e altri DSL incorporati nelle stringhe (sì, per favore) o promuovere linguaggi in cui la concatenazione delle stringhe è più fastidiosa rispetto all'utilizzo di query con parametri (umm, no).
amon,

4
e in che modo il RDBMS rileverà se farlo? Durante la notte renderebbe impossibile accedere all'RDBMS usando un prompt SQL interattivo ... Non saresti più in grado di inserire comandi DDL o DML usando nessuno strumento.
jwenting

8
In un certo senso puoi farlo: non costruire affatto query SQL in fase di runtime, invece usa un ORM o qualche altro livello di astrazione che eviti la necessità di costruire query SQL. ORM non ha le funzionalità di cui hai bisogno? Quindi SQL è un linguaggio destinato alle persone che vogliono scrivere SQL, motivo per cui nel complesso consente loro di scrivere SQL. Il problema fondamentale è che generare dinamicamente il codice è più difficile di quanto sembri, ma le persone vogliono comunque farlo e saranno insoddisfatte dei prodotti che non li permettono.
Steve Jessop,

Risposte:


45

Ci sono troppi casi in cui l'uso di un valore letterale è l'approccio giusto.

Dal punto di vista delle prestazioni, ci sono volte in cui desideri letterali nelle tue query. Immagina di avere un bug tracker in cui una volta che diventa abbastanza grande da preoccuparsi delle prestazioni, mi aspetto che il 70% dei bug nel sistema sarà "chiuso", il 20% sarà "aperto", il 5% sarà "attivo" e 5 % sarà in qualche altro stato. Potrei ragionevolmente voler avere la query che restituisce tutti i bug attivi

SELECT *
  FROM bug
 WHERE status = 'active'

anziché passare la statusvariabile as come bind. Voglio un piano di query diverso a seconda del valore trasmesso per status- Vorrei fare una scansione della tabella per restituire i bug chiusi e una scansione dell'indice sulstatuscolonna per restituire i prestiti attivi. Ora, database diversi e versioni diverse hanno approcci diversi per (più o meno correttamente) consentire alla stessa query di utilizzare un piano di query diverso a seconda del valore della variabile bind. Ma ciò tende a introdurre una discreta quantità di complessità che deve essere gestita per bilanciare la decisione se preoccuparsi di ri-analizzare una query o se riutilizzare un piano esistente per un nuovo valore variabile di bind. Per uno sviluppatore, può avere senso affrontare questa complessità. Oppure può avere senso forzare un percorso diverso quando ho più informazioni su come saranno i miei dati rispetto all'ottimizzatore.

Dal punto di vista della complessità del codice, ci sono anche molte volte in cui ha perfettamente senso avere letterali nelle istruzioni SQL. Ad esempio, se hai una zip_codecolonna che ha un codice postale di 5 caratteri e talvolta ha altre 4 cifre, ha perfettamente senso fare qualcosa come

SELECT substr( zip_code, 1, 5 ) zip,
       substr( zip_code, 7, 4 ) plus_four

anziché passare in 4 parametri separati per i valori numerici. Queste non sono cose che cambieranno mai, quindi renderle variabili di legame serve solo a rendere il codice potenzialmente più difficile da leggere e creare il potenziale che qualcuno legherà i parametri nell'ordine sbagliato e finirà con un bug.


12

L'iniezione SQL si verifica quando viene creata una query concatenando il testo da un'origine non attendibile e non convalidata con altre parti di una query. Mentre una cosa del genere si verificherebbe più spesso con i letterali di stringa, non sarebbe l'unico modo in cui potrebbe accadere. Una query per valori numerici potrebbe richiedere una stringa immessa dall'utente (che dovrebbe contenere solo cifre) e concatenarsi con altro materiale per formare una query senza le virgolette normalmente associate ai valori letterali delle stringhe; il codice che si fida eccessivamente della convalida lato client potrebbe avere elementi come i nomi dei campi provenienti da una stringa di query HTML. Non è possibile che il codice guardando una stringa di query SQL possa vedere come è stata assemblata.

L'importante non è se un'istruzione SQL contenga valori letterali di stringa, ma piuttosto se una stringa contenga sequenze di caratteri da fonti non attendibili e la migliore convalida di ciò dovrebbe essere gestita nella libreria che crea query. In genere in C # non è possibile scrivere codice che consenta una stringa letterale ma che non permetta altri tipi di espressione di stringa, ma si potrebbe avere una regola di pratiche di codifica che richiede che le query vengano costruite usando una classe di creazione di query piuttosto che concatenazione di stringhe e chiunque passi una stringa non letterale al generatore di query deve giustificare tale azione.


1
Come approssimazione di "è letterale" puoi verificare se la stringa è internata.
CodesInChaos,

1
@CodesInChaos: True, e un test del genere potrebbe essere abbastanza accurato per questo scopo, a condizione che chiunque avesse un motivo per generare una stringa in fase di runtime usasse un metodo che accettasse una stringa non letterale anziché internare la stringa generata in runtime e usare che (assegnare un nome diverso al metodo della stringa non letterale renderebbe più semplice per i revisori del codice esaminarne tutti gli usi).
supercat

Si noti che mentre non è possibile farlo in C #, alcune altre lingue hanno delle funzionalità che lo rendono possibile (ad esempio il modulo di stringa contaminato di Perl).
Jules,

Più sinteticamente, si tratta di un problema client , non di un server.
Blrfl,

7
SELECT count(ID)
FROM posts
WHERE deleted = false

Se vuoi mettere i risultati di questi nel piè di pagina del tuo forum, devi aggiungere un parametro fittizio solo per dire falso ogni volta. Oppure l'ingenuo programmatore web cerca come disabilitare quell'avviso e poi continua.

Ora puoi dire che aggiungerai un'eccezione per gli enum ma che apre di nuovo il buco (anche se più piccolo). Per non parlare delle persone che devono prima essere istruite per non usarle varchars.

Il vero problema dell'iniezione è la costruzione programmatica della stringa di query. La soluzione è un meccanismo di procedura memorizzata che ne impone l'utilizzo o una whitelist di query consentite.


2
Se la tua soluzione per "è fin troppo facile dimenticare - o non sapere in primo luogo - utilizzare query parametrizzate" è "far ricordare a tutti - e in primo luogo sapere - usare i processi memorizzati", allora tu stai perdendo l'intero punto della domanda.
Mason Wheeler,

5
Ho visto l'iniezione di SQL tramite procedure memorizzate sul mio lavoro. Si scopre che impone le procedure memorizzate per tutto ciò che è MALE. C'è sempre quello 0,5% che sono vere query dinamiche (non è possibile parametrizzare un'intera clausola where, per non parlare di un join di tabella).
Giosuè,

Nell'esempio in questa risposta è possibile sostituire deleted = falsecon NOT deleted, che evita il letterale. Ma il punto è valido in generale.
psmears,

5

TL; DR : dovresti limitare tutti i letterali, non solo quelli nelle WHEREclausole. Per motivi che non lo fanno, consente al database di rimanere disaccoppiato da altri sistemi.

Innanzitutto, la tua premessa è difettosa. Vuoi limitare solo le WHEREclausole, ma non è l'unico posto in cui l'input dell'utente può andare. Per esempio,

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Questo è ugualmente vulnerabile all'iniezione SQL:

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) FROM item; DROP TABLE user_info; SELECT CASE(WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Quindi non puoi semplicemente limitare i letterali nella WHEREclausola. Devi limitare tutti i letterali.

Ora ci rimane la domanda "Perché consentire letteralmente?" Tenetelo a mente: mentre i database relazionali sono usati sotto un'applicazione scritta in un'altra lingua una grande percentuale del tempo, non v'è alcun requisito che è necessario utilizzare il codice di applicazione per utilizzare il database. E qui abbiamo una risposta: hai bisogno di letterali per scrivere codice. L'unica altra alternativa sarebbe richiedere che tutto il codice sia scritto in una lingua indipendente dal database. Quindi averli ti dà la possibilità di scrivere "codice" (SQL) direttamente nel database. Questo è un prezioso disaccoppiamento e sarebbe impossibile senza i letterali. (Prova a scrivere nella tua lingua preferita qualche volta senza letterali. Sono sicuro che puoi immaginare quanto sarebbe difficile.)

Come esempio comune, i letterali vengono spesso utilizzati nella popolazione di tabelle di elenchi di valori / di ricerca:

CREATE TABLE user_roles (role_id INTEGER, role_name VARCHAR(50));
INSERT INTO user_roles (1, 'normal');
INSERT INTO user_roles (2, 'admin');
INSERT INTO user_roles (3, 'banned');

Senza di essi, dovresti scrivere il codice in un altro linguaggio di programmazione solo per popolare questa tabella. La capacità di farlo direttamente in SQL è preziosa .

Ci resta quindi un'altra domanda: perché le librerie client del linguaggio di programmazione non lo fanno? E qui abbiamo una risposta molto semplice: avrebbero implementato nuovamente il parser dell'intero database per ogni versione supportata del database . Perché? Perché non c'è altro modo per garantire che hai trovato ogni letterale. Le espressioni regolari non sono sufficienti. Ad esempio: contiene 4 letterali separati in PostgreSQL:

SELECT $lit1$I'm a literal$lit1$||$lit2$I'm another literal $$ with nested string delimiters$$ $lit2$||'I''m ANOTHER literal'||$$I'm the last literal$$;

Provare a farlo sarebbe un incubo per la manutenzione, soprattutto perché una sintassi valida spesso cambia tra le principali versioni dei database.

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.