Perché non solo le query non parametrizzate restituiscono un errore?

22

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

Mi sembra che il modo ovvio per risolvere questo problema sarebbe quello di chiudere l'opzione ovvia (ma sbagliata): aggiustare il motore del database in modo che qualsiasi query ricevuta che utilizza valori hardcoded nella sua clausola WHERE invece che parametri restituisca un bel messaggio di errore descrittivo che indica di utilizzare invece i parametri. Ciò ovviamente richiederebbe un'opzione di opt-out in modo che le cose come le query ad-hoc dagli strumenti di amministrazione continuino a essere eseguite facilmente, ma dovrebbero essere abilitate per impostazione predefinita.

Avendo questo si potrebbe chiudere l'iniezione SQL a freddo, quasi da un giorno all'altro, ma per quanto ne so, nessun RDBMS lo fa in realtà. C'è qualche buona ragione per cui no?

    
posta Mason Wheeler 11.08.2015 - 17:42
fonte

4 risposte

45

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

Dal punto di vista delle prestazioni, ci sono momenti in cui vuoi letterali nelle tue query. Immagina di avere un bug tracker dove una volta diventato 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 volere che la query che restituisce tutti i bug attivi sia

SELECT *
  FROM bug
 WHERE status = 'active'

anziché passare status come variabile di bind. Voglio un piano di query diverso in base al valore passato per status - Vorrei eseguire una scansione della tabella per restituire i bug chiusi e una scansione dell'indice sulla colonna status per restituire i prestiti attivi. Ora, diversi database e versioni differenti hanno approcci diversi per (più o meno con successo) consentono alla stessa query di utilizzare un piano di query diverso a seconda del valore della variabile di binding. Ma questo tende a introdurre una quantità decente di complessità che deve essere gestita per bilanciare la decisione di se preoccuparsi di ri-analizzare una query o se riutilizzare un piano esistente per un nuovo valore di variabile di bind. Per uno sviluppatore, può avere senso affrontare questa complessità. Oppure può essere opportuno forzare un percorso diverso quando ho più informazioni su come saranno i miei dati rispetto all'ottimizzatore.

Da un punto di vista della complessità del codice, ci sono anche un sacco di volte che ha perfettamente senso avere letterali nelle istruzioni SQL. Ad esempio, se hai una colonna zip_code con un codice postale di 5 caratteri e a volte ha 4 cifre aggiuntive, è perfettamente logico fare qualcosa di simile

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 farle legare le variabili serve solo a rendere il codice potenzialmente più difficile da leggere e a creare il potenziale che qualcuno legherà i parametri nell'ordine sbagliato e finirà con un bug.

    
risposta data 11.08.2015 - 18:23
fonte
12

L'iniezione SQL si verifica quando una query viene creata concatenando il testo da un'origine non sicura e non convalidata con altre parti di una query. Mentre una cosa del genere si verifica più spesso con stringhe letterali, non sarebbe l'unico modo in cui potrebbe verificarsi. Una query per valori numerici può richiedere una stringa immessa dall'utente (cioè supposto per contenere solo cifre) e concatenare con altro materiale per formare una query senza i segni di virgola normalmente associati a stringhe letterali; il codice che si fida eccessivamente della convalida sul lato client potrebbe avere cose come i nomi dei campi provengono da una stringa di query HTML. Non c'è modo in cui il codice che esamina una stringa di query SQL può vedere come è stato assemblato.

Ciò che è importante non è se un'istruzione SQL contiene stringhe letterali, ma piuttosto se una stringa contiene qualsiasi sequenza di caratteri da fonti non attendibili , e la convalida per quella sarebbe meglio gestita nella libreria che costruisce le query . In C # non è generalmente possibile scrivere codice che consenta una stringa letterale ma non consentirà altri tipi di espressioni di stringa, ma si potrebbe avere una regola di pratiche di codifica che richiede che le query vengano create utilizzando 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.

    
risposta data 11.08.2015 - 21:48
fonte
7
SELECT count(ID)
FROM posts
WHERE deleted = false

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

Ora puoi dire che aggiungerebbe un'eccezione per l'enumerazione, ma che apre di nuovo il buco (anche se più piccolo). Per non parlare delle persone che prima devono essere educate a non usare varchars per quelle.

Il vero problema dell'iniezione è la costruzione di una stringa di query a livello di programmazione. La soluzione per questo è un meccanismo di stored procedure e l'applicazione del suo uso o una whitelist di query consentite.

    
risposta data 11.08.2015 - 17:56
fonte
4

TL; DR : dovresti limitare tutti letterali, non solo quelli in WHERE clausole. Per ragioni che non lo fanno, consente al database di rimanere disaccoppiato da altri sistemi.

In primo luogo, la tua premessa è imperfetta. Vuoi limitare solo le clausole WHERE , ma non è l'unico posto in cui l'input dell'utente può andare. Ad 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 clausola WHERE . Devi limitare tutti letterali.

Ora ci rimane la domanda: "Perché consentire letteralmente i letterali?" Tenere presente questo: mentre i database relazionali vengono utilizzati sotto un'applicazione scritta in un'altra lingua per una grande percentuale del tempo, non esiste un requisito che sia necessario utilizzare il codice dell'applicazione per utilizzare il database. E qui abbiamo una risposta: hai bisogno di letterali per scrivere codice. L'unica altra alternativa sarebbe quella di 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 valido disaccoppiamento e sarebbe impossibile senza i letterali. (Prova a scrivere nella tua lingua preferita prima o poi letteralmente. Sono sicuro che puoi immaginare quanto sarebbe difficile.)

Come esempio comune, i letterali vengono spesso utilizzati nella popolazione delle tabelle list-of-value / look-up:

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 codice in un altro linguaggio di programmazione solo per popolare questa tabella. La possibilità di farlo direttamente in SQL è preziosa .

Ci rimane una domanda in più: perché non programmare le librerie client del linguaggio in quel momento? E qui abbiamo una risposta molto semplice: avrebbero re-implementare l'intero parser del database per ogni versione supportata del database. Perché? Perché non c'è altro modo per garantire di aver trovato ogni letterale. Le espressioni regolari non sono abbastanza. Ad esempio: questo 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$$;

Cercare di farlo sarebbe un incubo di manutenzione, soprattutto perché la sintassi valida spesso cambia tra le versioni principali dei database.

    
risposta data 12.08.2015 - 16:14
fonte

Leggi altre domande sui tag