Come si limita il numero di risultati di una query SQL?

2

Questa risposta altamente pubblicizzata afferma che come protezione contro l'iniezione SQL, si dovrebbe

always use bound parameters and limit how many results get returned

(sottolineatura mia)

Ovviamente, il consiglio di limitare il numero di risultati non può essere applicato alla cieca. Se la tua applicazione richiede che tutti i risultati funzionino correttamente in un contesto specifico, è probabile che l'inserimento di un limite rigido sui risultati possa causare errori. (Questa è una situazione che sto trattando attualmente. Sto descrivendo tutti i bug di un'applicazione perché ciò è stato fatto in silenzio da alcune funzioni ampiamente utilizzate.) Non penso che la risposta suggerisca che l'applicazione dovrebbe essere progettata per risultati della pagina, poiché il paging significa solo che un utente malintenzionato deve effettuare più recuperi per ottenere i risultati desiderati.

La domanda che chiede quale sia l'iniezione SQL non ne parla come difesa.

Di cosa sta parlando questa risposta? La limitazione dei risultati riduce il rischio associato all'iniezione SQL? In quali contesti può essere usato se è così?

(Piccola precisazione preventiva: sono, naturalmente, a bordo con 1000% di query parametrizzate su ogni singolo input dell'utente.)

    
posta jpmc26 20.10.2017 - 02:04
fonte

1 risposta

2

limit how many results get returned

Sembra un po 'sciocco. È possibile sfruttare un'iniezione SQL anche se la query non restituisce alcun risultato, o anche se i risultati non vengono visualizzati affatto.

Nel caso in cui ti chiedi: se riesci a iniettare qualcosa che può innescare un errore del database dipendente dai dati, o un errore dell'applicazione, o restituire zero o un risultato, puoi interrogare il database per i test booleani, come:

email REGEXP "^[a-k]"? yes
email REGEXP "^[a-g]"? yes
email REGEXP "^[a-d]"? no
email REGEXP "^[e-g]"? yes, no need to ask!

continua a restringerlo ... Poi con un po 'di pazienza, una sceneggiatura, dicotomia e regexps, puoi scaricare qualsiasi riga in qualsiasi tabella, incluso information_schema che dovrebbe essere il tuo primo obiettivo btw. Qualche tempo fa qualcuno si è vantato su un forum francese sul fatto che il suo sito fosse al sicuro con una tale vulnerabilità, due ore dopo ho avuto tutte le password, che sono state archiviate in chiaro all'interno di una vecchia copia di backup di un tavolo da qualche parte all'interno di uno schema di cui si era dimenticato. ..

Inoltre, ha detto che la vittima volontaria pensava che le citazioni magiche fossero sicure. Quindi non potevo iniettare alcun 'ma ... CHAR () può costruire qualsiasi stringa bene senza alcun' ...

È anche possibile fare in modo che il server esegua una query molto lunga e intensa (una sorta di attacco DOS) che non restituisce alcun risultato, quindi neanche il LIMIT può essere d'aiuto in questo caso. Per LULZ extra puoi fare in modo che la query blocchi ogni riga con un SELEZIONA PER AGGIORNAMENTO e poi ti blocchi fino a quando la pagina va in timeout con un enorme UNION. Questo DOS è l'intero sito web.

Anche se è possibile iniettare un "-" o un ";" per commentare il resto della query, quindi il LIMIT è, naturalmente, commentato. Classico senza tempo, set id="OR 1; -" nel forum "cancella il tuo messaggio" thingie e ... tada!

DELETE FROM posts WHERE id=1234 OR 1 -- LIMIT 1

IMO il modo migliore per evitare l'SQL injection è utilizzare un framework o una libreria DB che renda più conveniente e più veloce utilizzare il metodo sicuro invece del metodo non sicuro. Quindi, non vi è alcuna tentazione di tagliare gli angoli ...

Alcune implementazioni di parametri associati falliscono questo test, perché sono scomode da usare e / o mal progettate. Considera il seguente esempio PHP / PDO:

$stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $firstname, $lastname, $email);
$stmt->execute();

Questo utilizza tre righe di codice anziché uno. Ancora peggio, i tipi di variabile sono nascosti da qualche parte all'interno della chiamata bind_param, sono posizionali e all'interno di una stringa che verrà duplicata su tutto il codice. Inoltre se hai 20 colonne, quale è l'int quando la stringa di tipo è "sssssssssssssisss"? OK, sposta il cursore, conta i caratteri, uno, due, tre ... Anche il tipo è dato da un carattere, quindi quando si usa postgres e la colonna è di tipo "array of ints" o "polygon" non lo farà funziona così dovrai tornare a un metodo non sicuro, magari quotare manualmente ... aaargh ...

$req = $bdd->prepare('INSERT INTO decisions(decision, numero, publique, date, ip) VALUES(:decision, :numero, :publique, NOW(), :ip)');
$req->execute(array(
    'decision' => $want,
    'numero' => $nombremagique,
    'publique' => $publique,
    'ip' => $ip
    ));

Questo è un passo avanti, dato che i parametri ora sono nominati anziché posizionali, che è molto più facile da usare. Ma avete ancora preparato / eseguito, e (a meno che non si utilizzino le false dichiarazioni preparate di PDO che credo funzionano solo su MySQL) si ottengono due roundtrips di database, il che significa che il metodo sicuro è più lento di quello non sicuro. Anche questo è un fallimento, perché il metodo sicuro dovrebbe essere il migliore, il più facile da usare e anche il più veloce se vogliamo evitare la tentazione di tagliare gli angoli ...

Un'opzione molto migliore in questo caso sarebbe quella di utilizzare un ORM. Crea un oggetto e fai object- > insert (). Se l'ORM è buono, controllerà i tipi e gestirà le query in modo sicuro.

Per le query non elaborate, la mia interfaccia preferita è DBAPI di Python, userò psycopg2 come esempio:

conn.execute("SELECT * FROM ... WHERE firstname=%s AND lastname=%s", (val1,val2))

Ci vuole solo una linea, un roundtrip del database, è veloce, gli argomenti sono automaticamente sfuggiti e convertiti in base al loro tipo, anche i tipi di utenti possono avere codificatori / decodificatori personalizzati aggiunti, gestisce unicode, in pratica fa tutto.

data = {'firstname': 'Bob', 'lastname': 'Smith'}
conn.execute("SELECT * FROM ... WHERE firstname=%(firstname)s AND lastname=%(lastname)s", data)

È possibile utilizzare anche argomenti con nome. Questo di solito è più conveniente, dato che gli argomenti solitamente provengono da qualcosa come una libreria di moduli, che li consegnerebbe impacchettati in un array dict / associativo.

Quindi, per rispondere alla tua domanda, sarei diffidente nei confronti di indizi casuali come "aggiungi un limite"; un approccio sistematico è molto meglio, come ho detto l'interfaccia utilizzata per interrogare il DB dovrebbe rendere la scelta sicura la scelta più ovvia, più veloce, più facile, più conveniente, ecc.

    
risposta data 20.10.2017 - 12:38
fonte

Leggi altre domande sui tag