Domanda

Se dovessi selezionare una riga da una tabella, fondamentalmente ho due opzioni, o in questo modo

int key = some_number_derived_from_a_dropdown_or_whatever
SqlCommand cmd = new SqlCommand("select * from table where primary_key = " + key.ToString());

o usa un parametro

SqlCommand cmd = new SqlCommand("select * from table where primary_key = @pk");
SqlParameter param  = new SqlParameter();
param.ParameterName = "@pk";
param.Value         = some_number_derived_from_a_dropdown_or_whatever;
cmd.Parameters.Add(param);

Ora, so che il primo metodo è disapprovato a causa di possibili attacchi di iniezione sql, ma in questo caso il parametro è un numero intero e quindi non dovrebbe davvero essere possibile iniettare codice dannoso.

La mia domanda è questa: usi l'opzione 1 nel codice di produzione perché consideri l'uso sicuro a causa della facilità d'uso e del controllo sul parametro inserito (come sopra, o se il parametro viene creato nel codice)? O usi sempre i parametri, non importa cosa? I parametri sono sicuri al 100%?

È stato utile?

Soluzione

Salterò l'argomento SQL Injection, che è fin troppo noto e mi concentrerò solo sull'aspetto SQL dei parametri rispetto ai non parametri.

Quando si invia un batch SQL al server, qualsiasi batch, deve essere analizzato per essere compreso. Come qualsiasi altro compilatore, il compilatore SQL deve produrre un AST dal testo e quindi operare su l'albero della sintassi. Alla fine l'ottimizzatore trasforma l'albero di sintassi in un albero di esecuzione e alla fine produce un piano di esecuzione che viene effettivamente eseguito. Negli anni bui del 1995 circa faceva la differenza se il batch era una query ad hoc o una procedura memorizzata, ma oggi non ne fa assolutamente nessuno, sono tutti uguali.

Ora dove i parametri fanno la differenza è che un client che invia una query come seleziona * dalla tabella in cui primary_key = @pk invierà esattamente lo stesso testo SQL ogni volta , indipendentemente dal valore interessato. Ciò che accade è che il processo intero che ho descritto sopra è in corto circuito. SQL cercherà in memoria un piano di esecuzione per il testo grezzo, non analizzato ricevuto (basato su un digest hash dell'input) e, se trovato, eseguirà quel piano. Ciò significa nessuna analisi, nessuna ottimizzazione, niente, il batch va direttamente in esecuzione . Sui sistemi OLTP che eseguono centinaia e migliaia di piccole richieste ogni secondo, questo percorso rapido fa un'enorme differenza di prestazioni.

Se invii la query nel formato seleziona * dalla tabella dove primary_key = 1 , SQL dovrà almeno analizzarla per capire cosa c'è dentro il testo, poiché il testo è probabilmente un nuovo uno, diverso da qualsiasi batch precedente che ha visto (anche un singolo carattere come 1 vs. 2 rende l'intero batch diverso). Opererà quindi sull'albero della sintassi risultante e tenterà un processo chiamato Parametrizzazione semplice . Se la query può essere auto-paremeterizzata, SQL probabilmente troverà un piano di esecuzione memorizzato nella cache da altre query eseguite in precedenza con altri valori pk e riutilizzerà quel piano, quindi almeno la query non deve essere ottimizzata e salti il fase di generazione di un vero piano di esecuzione. Ma non hai mai raggiunto il cortocircuito completo, il percorso più breve possibile che hai ottenuto con una vera query con parametri client.

Puoi esaminare SQL Server, oggetto SQL Statistics contatore delle prestazioni del tuo server. Il contatore Tentativi di parametri automatici / sec mostrerà più volte al secondo che SQL deve tradurre una query ricevuta senza parametri in una parametrizzazione automatica. Ogni tentativo può essere evitato se si parametrizza correttamente la query nel client. Se hai anche un numero elevato di Auto-Params / sec non funzionanti, è ancora peggio, significa che le query stanno andando all'intero ciclo di ottimizzazione e generazione del piano di esecuzione.

Altri suggerimenti

Usa sempre l'opzione 2).

Il primo è NON sicuro per quanto riguarda attacchi SQL Injection .

Il secondo non solo è molto più sicuro, ma funzionerà meglio perché Query Optimizer ha maggiori possibilità di creare un buon piano di query perché la query ha un aspetto lo stesso per tutto il tempo, aspettati ovviamente i parametri.

Perché dovresti evitare l'opzione 1

Anche se sembra che tu stia ottenendo questo valore dall'elemento select , qualcuno potrebbe falsificare una richiesta HTTP e pubblicare ciò che desidera all'interno di determinati campi. Il tuo codice nell'opzione 1 dovrebbe almeno sostituire alcuni caratteri / combinazioni pericolosi (es. Virgolette singole, parentesi quadre, ecc.).

Perché sei incoraggiato a utilizzare l'opzione 2

Il motore di query SQL è in grado di memorizzare nella cache il piano di esecuzione e gestire le statistiche per le varie query che vengono generate. Quindi avere query unificate (la migliore sarebbe ovviamente avere una stored procedure separata) accelera l'esecuzione a lungo termine.

Non ho ancora sentito parlare di alcun esempio in cui i parametri possano essere dirottati per l'iniezione SQL. Fino a quando non lo vedrò dimostrato diversamente, li considererei al sicuro.

L'SQL dinamico non dovrebbe mai essere considerato sicuro. Anche con " trusted " input, è una cattiva abitudine entrare. Si noti che ciò include SQL dinamico nelle procedure memorizzate; Anche qui l'iniezione SQL può avvenire.

Poiché hai il controllo che il valore è un numero intero, sono praticamente equivalenti. In genere non utilizzo il primo modulo e, in genere, non autorizzo neanche il secondo, poiché di solito non consento l'accesso alla tabella e richiedo l'utilizzo di stored procedure. E sebbene sia possibile eseguire l'esecuzione di SP senza utilizzare la raccolta parametri, consiglio comunque di utilizzare i parametri SP AND :

-- Not vulnerable to injection as long as you trust int and int.ToString()
int key = some_number_derived_from_a_dropdown_or_whatever ;
SqlCommand cmd = new SqlCommand("EXEC sp_to_retrieve_row " + key.ToString()); 

-- Vulnerable to injection all of a sudden
string key = some_number_derived_from_a_dropdown_or_whatever ;
SqlCommand cmd = new SqlCommand("EXEC sp_to_retrieve_row " + key.ToString()); 

Nota che sebbene l'opzione 1 sia sicura nel tuo caso , cosa succede quando qualcuno vede e usa quella tecnica con una variabile non intera - ora stai addestrando le persone a usare una tecnica che potrebbe essere aperto all'iniezione.

Nota che puoi rafforzare in modo proattivo il tuo database per evitare che l'iniezione sia efficace anche quando il codice dell'applicazione è difettoso. Per account / ruoli utilizzati dalle applicazioni:

  • Consentire l'accesso alle tabelle solo se assolutamente necessario
  • Consentire l'accesso alle viste solo se assolutamente necessario
  • Non consentire istruzioni DDL
  • Consenti esecuzione a SP in base al ruolo
  • Qualsiasi SQL dinamico in SP deve essere rivisto come assolutamente necessario

Personalmente, userei sempre l'opzione 2 per abitudine. Detto questo:

Dato che stai forzando la conversione dal valore a discesa in un int, ciò fornirà una certa protezione contro gli attacchi di iniezione sql. Se qualcuno tenta di iniettare informazioni aggiuntive nelle informazioni pubblicate, C # genererà un'eccezione quando tenta di convertire il codice dannoso in un valore intero contrastando il tentativo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top