Suddiviso in due parti, questo articolo analizza le cause di errore più comuni relativamente alla sicurezza applicativa e gli accorgimenti da adottare per limitare al minimo la vulnerabilità delle applicazioni ASP.NET. Se la prima parte dell'articolo si è concentrata sugli aspetti di sicurezza legati al Cross-Site Scripting, un tipo di attacco basato sull'esecuzione di script che possono rendere molto spiacevole la navigazione dell'utente, la seconda parte dell'articolo si focalizza principalmente sugli aspetti di sicurezza legati allo strato di accesso ai dati.
Protezione da SQL Injection
SQL Injection è una tecnica che sfrutta vulnerabilità nella sicurezza dello strato di accesso ai dati di un'applicazione. Chiariamo di cosa si tratta attraverso un paio di esempi.
Esempio 1
Supponiamo di avere un semplice form di login ("txtUsername" e "txtPassword") che verifichi le credenziali di accesso di un utente mediante l'istruzione SQL così costruita:
string sql = string.Format( "SELECT * FROM Utenti WHERE Username='{0}' AND Password='{1}'", txtUsername.Text, txtPassword.Text);
e che un utente immetta i seguenti valori:
txtUsername: Administrator';--
txtPassword: (qualsiasi valore)
L'istruzione SQL risulterebbe così composta:
SELECT * FROM Utenti WHERE Username='Administrator';-- AND Password='...'
con il risultato che la condizione sul campo Password non sarebbe considerata nella clausola di WHERE in quanto preceduta dall'apertura di un commento ("--") con l'effetto di autenticare l'utente solo sulla base dello username fornito.
Esempio 2
Immaginiamo un semplice motore di ricerca, costituito da una casella di testo (ad es. "TextBox1") in cui l'utente inserisce il criterio di ricerca desiderato; la pagina, ricevuta la query di ricerca, costruisce l'istruzione SQL da inviare al database per l'estrazione dei record corrispondenti:
string sql = string.Format( "SELECT * FROM MyTable WHERE MyField = '{0}'", TextBox1.Text);
Un utente malintenzionato potrebbe digitare nella casella di testo la sequenza "a'; DROP TABLE MyTable; --", generando il seguente statement SQL:
SELECT * FROM MyTable WHERE MyField = 'a'; DROP TABLE MyTable; --'
che, una volta eseguito, comporta... la cancellazione della tabella MyTable!
Soluzione: usare le query parametriche
ADO.NET fornisce un valido sistema per proteggersi da attacchi di questo tipo: l'uso dei parametri. Il modo corretto di effettuare l'operazione presentata nell'esempio numero 2 su un database SQL Server è quindi:
SqlConnection cn = new SqlConnection("..."); string sql = "SELECT * FROM MyTable WHERE MyField = @MyValue"; SqlCommand cmd = new SqlCommand(sql, cn); SqlParameter p = new SqlParameter("@MyValue", SqlDbType.NVarChar, 100); p.Value = TextBox1.Text; cmd.Parameters.Add(p); SqlDataReader dr = cmd.ExecuteReader();
Scenari a rischio
Come abbiamo visto, l'uso di query parametriche protegge la base dati da attacchi di SQL Injection; esistono tuttavia alcuni casi in cui non è possibile utilizzare i parametri per impostare i valori oppure dove anche l'uso degli stessi non risulta sufficiente. Alcuni scenari di questo tipo sono le condizioni basate sulla clausola "IN" (esempio: SELECT * FROM MyTable WHERE Id IN (1,3,27)) e su "TOP(n)", supportato in modo parametrico solo a partire da SQL Server 2005 (esempio: SELECT TOP 10 * FROM MyTable). Inoltre rientrano tra i casi a rischio le ricerche full-text che utilizzano l'operatore "LIKE" oppure le query particolari dove la componente parametrica è costituita dal nome di una tabella o di un campo.
Questi casi e, in generale, tutte quelle situazioni in cui le istruzioni SQL sono ottenute dalla concatenazione di stringhe, richiedono una particolare attenzione per evitare l'iniezione di codice dannoso. A tale proposito due utili funzioni per rimuovere il codice potenzialmente pericoloso possono essere le seguenti:
// Per rimuovere i delimitatori di stringa public static string SafeSqlLiteral(string sql) { return sql.Replace("'", "''"); } // Per rimuovere le wildcard per la clausola LIKE public static string SafeSqlLikeClause(string sql) { string s = sql; s = s.Replace("[", "[[]"); s = s.Replace("%", "[%]"); s = s.Replace("_", "[_]"); return s; }
È comunque importante notare che il semplice raddoppiamento dei carattere di quotatura singola non è sufficiente a garantire la protezione da SQL Injection. È sempre buona norma verificare l'input proveniente dall'utente in modo dettagliato, forzando quando possibile il tipo mediante cast a tipi nativi del .NET Framework come Int32, Boolean, DateTime, ecc.
Si consideri ad esempio l'istruzione:
string sql = "SELECT * FROM MyTable WHERE Id = " + SafeSqlLiteral( Request.QueryString["id"] );
Con la querystring "?id=23;DROP TABLE MyTable;--" la query risultante, nonostante l'uso della funzione SafeSqlLiteral, diventa:
SELECT * FROM MyTable WHERE Id = 23; DROP TABLE MyTable;--
Il modo corretto per trattare questo caso è forzare la conversione ad intero del valore di ID ricevuto in querystring:
int id = -1; // valore predefinito int.TryParse(Request.QueryString["id"], out id); string sql = string.Format("SELECT * FROM MyTable WHERE Id = {0}", id);
Come si può facilmente dedurre dagli esempi proposti, vulnerabilità di questo tipo sono molto pericolose e, data la relativa semplicità di realizzazione, anche molto frequenti: sono stati registrati numerosissimi casi di attacchi a siti Web mediante SQL Injection, anche nei confronti di enti pubblici e governativi tra cui, lo scorso agosto, il sito ufficiale delle Nazioni Unite (vedi: "United Nations VS SQL Injections" per maggiori dettagli).
Attenzione: Questo articolo contiene un allegato.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Autenticazione basata su certificati con ASP.NET Core
Real world .NET Architecture
Impostare il forward degli header in un sito ASP.NET Core dietro a un reverse proxy
Abilitare automaticamente Dependabot in tutti i repository di una organizzazione su GitHub
Usare gateway dedicati con Azure Cosmos DB per migliorare le prestazioni
Leggere la configurazione da Azure KeyVault con logica di retry in ASP.NET Core
Chiamare un endpoint ASP.NET Core protetto da Certificate Authentication
Usare Azure Application Gateway come reverse proxy per ASP.NET Core
Leggere i dati di configurazione di ASP.NET Core da Azure Key Vault
Gestire dati sensibili nella configurazione in ASP.NET Core
AWS, EKS, gestione domini e TLS con Ingress