Creazione di codice di accesso ai dati protetto

In questa pagina
Argomenti del moduloArgomenti del modulo
ObiettiviObiettivi
Ambito di applicazioneAmbito di applicazione
Utilizzo del moduloUtilizzo del modulo
Pericoli e contromisurePericoli e contromisure
Considerazioni sulla progettazioneConsiderazioni sulla progettazione
Convalida dell'inputConvalida dell'input
SQL injectionSQL injection
AutenticazioneAutenticazione
AutorizzazioneAutorizzazione
Gestione della configurazioneGestione della configurazione
Dati riservatiDati riservati
Gestione delle eccezioniGestione delle eccezioni
Creazione di codice di accesso ai dati protettoCreazione di codice di accesso ai dati protetto
Considerazioni sulla protezione dall'accesso di codiceConsiderazioni sulla protezione dall'accesso di codice
Considerazioni sulla distribuzioneConsiderazioni sulla distribuzione
RiepilogoRiepilogo
Ulteriori risorseUlteriori risorse

Argomenti del modulo

Con il termine accesso ai dati si indica il processo con cui si accede a un database da un'applicazione Web ASP.NET tramite uno dei numerosi provider di dati ADO.NET disponibili.

Questo database è uno dei primi obiettivi degli attacchi a livello di applicazione. Gli attacchi a livello di applicazione sono utilizzati per sfruttare le vulnerabilità insite nel codice di accesso ai dati e ottenere un accesso non autorizzato al database. Se tutti gli altri vettori di attacco sono chiusi, la porta anteriore dell'applicazione, la 80, diviene il percorso privilegiato attraverso il quale un pirata informatico tenterà di rubare, manipolare e distruggere i dati.

In questo modulo viene illustrato come creare codice di accesso ai dati protetto ed evitare le vulnerabilità e i problemi comuni. Viene inoltre presentata una serie di contromisure e tecniche difensive da utilizzare nel codice di accesso ai dati per ridurre i pericoli principali.

Inizio paginaInizio pagina

Obiettivi

Il modulo consente di:

Progettare, creare e distribuire codice di accesso ai dati protetto.

Utilizzare la protezione dall'accesso di codice e la protezione basata sui ruoli per impedire l'accesso da parte di chiamanti o codice non autorizzati.

Autenticare in modo sicuro gli utenti.

Impedire attacchi SQL injection.

Proteggere le stringhe di connessione al database.

Utilizzare la crittografia per proteggere i dati archiviati nel database.

Proteggere i dati inviati in rete al e dal database.

Archiviare in modo protetto le password (con hash con salt) in un database.

Implementare una gestione protetta delle eccezioni.

Imparare a utilizzare la protezione dall'accesso di codice per permettere ad applicazioni Web ad attendibilità media di utilizzare i provider di dati OLE DB, Oracle e ODBC (che richiedono l'attendibilità totale).

Sapere quali contromisure applicare per affrontare i comuni pericoli di accesso ai dati, inclusi gli attacchi SQL injection, la divulgazione dei dati di configurazione, la divulgazione dei dati riservati delle applicazioni, la divulgazione dei dettagli di connessione e dello schema di database, l'accesso non autorizzato e l'ascolto e intercettazione di rete non autorizzati.

Inizio paginaInizio pagina

Ambito di applicazione

Le informazioni contenute in questo modulo sono valide per i seguenti prodotti e tecnologie:

Microsoft® Windows® 2000 Server e Microsoft Windows Server™ 2003

Microsoft .NET Framework 1.1 e ASP.NET 1.1

Microsoft SQL Server™

Inizio paginaInizio pagina

Utilizzo del modulo

Per trarre il massimo vantaggio dal modulo, leggere i seguenti moduli prima o contemporaneamente a questo:

Leggere il modulo 2 "Pericoli e contromisure". Vengono illustrati più dettagliatamente i potenziali pericoli a cui sono sottoposte le applicazioni Web e le relative contromisure.

Leggere il modulo 4 "Linee guida di progettazione per applicazioni Web protette". Vengono illustrati l'architettura, i problemi di progettazione e le linee guida per creare una soluzione protetta.

Leggere il modulo 18 "Protezione del server database". In questo modulo viene spiegato come proteggere i server di database.

Leggere il modulo 7 "Creazione di assembly protetti". Le linee guida e le raccomandazioni contenute nel modulo 7 per la creazione di assembly protetti e per lo sviluppo di codice gestito protetto devono essere applicate anche al codice di accesso ai dati.

Utilizzare i moduli di valutazione. Per verificare la protezione dell'accesso ai dati ai vari livelli del ciclo del prodotto, fare riferimento alle sezioni sui servizi Web nei moduli seguenti: modulo 5 "Analisi dell'architettura e della progettazione ai fini della protezione", modulo 21 "Analisi del codice" e modulo 22 "Analisi della distribuzione".

Utilizzare l'elenco di controllo. Elenco di controllo: Protezione dell'accesso ai dati" nella sezione Elenchi di controllo di questa guida include un elenco di controllo di facile riferimento. Utilizzare questo elenco di controllo basato sulle attività come riepilogo delle raccomandazioni contenute nel presente modulo.

Tenere presente che nella versione corrente di .NET Framework (1.1) solo il provider dell'accesso ai dati SQL Server di ADO.NET supporta chiamanti ad attendibilità parziale e può essere utilizzato tranquillamente in applicazioni Web ad attendibilità parziale. I provider di dati OLE DB, Oracle e ODBC ADO.NET richiedono l'attendibilità totale.

Inizio paginaInizio pagina

Pericoli e contromisure

Per creare codice di accesso ai dati protetto, è importante conoscere quali sono i pericoli, quali sono le cause delle vulnerabilità comuni nel codice di accesso ai dati e come utilizzare contromisure appropriate per ridurre il rischio.

Qui di seguito sono elencati i principali pericoli per il codice di accesso ai dati:

SQL injection

Divulgazione dei dati di configurazione

Divulgazione dei dati riservati delle applicazioni

Divulgazione dei dettagli di connessione e dello schema di database

Accesso non autorizzato

Ascolto e intercettazione di rete non autorizzati

Nella Figura 14.1 sono illustrati i pericoli più importanti.

Pericoli e attacchi al codice di accesso ai dati

Figura 14.1
Pericoli e attacchi al codice di accesso ai dati

SQL injection

Gli attacchi SQL injection sfruttano il codice di accesso ai dati vulnerabile e permettono a un pirata informatico di eseguire comandi arbitrari nel database. Il pericolo è maggiore se l'applicazione utilizza un account non vincolato nel database perché dà al pirata informatico maggiore libertà di eseguire query e comandi.

Vulnerabilità

Le vulnerabilità comuni che espongono il codice di accesso ai dati agli attacchi SQL injection sono:

Convalida dell'input non adeguata

Costruzione dinamica delle istruzioni SQL senza l'utilizzo di parametri indipendenti dai tipi

Utilizzo di accessi al database con un numero eccessivo di privilegi

Contromisure

Per contrastare gli attacchi SQL injection, assicurasi di:

Vincolare e sterilizzare i dati di input.

Utilizzare parametri SQL indipendenti dai tipi per l'accesso ai dati. Questi parametri possono essere utilizzati con stored procedure o stringhe di comando SQL costruite dinamicamente. I parametri controllano il tipo e la lunghezza e assicurano che il codice introdotto sia trattato come dati letterali e non come istruzioni eseguibili nel database.

Utilizzare un account con autorizzazioni limitate nel database. Idealmente, si dovrebbero concedere autorizzazioni di esecuzione solo alle stored procedure selezionate nel database e non fornire un accesso diretto alla tabella.

Divulgazione dei dati di configurazione

I dati di configurazione più riservati utilizzati dal codice di accesso ai dati sono quelli della stringa di connessione al database. Se una stringa di connessione compromessa include un nome utente e una password, le conseguenze potrebbero essere ancora peggiori.

Vulnerabilità

Le seguenti vulnerabilità aumentano il rischio per la protezione associato a dati di configurazione compromessi:

Utilizzo dell'autenticazione SQL, che richiede la specifica di credenziali nella stringa di connessione

Stringhe di connessione incorporate nel codice

Stringhe di connessione con testo non crittografato nei file di configurazione

Mancata crittografia di una stringa di connessione

Contromisure

Per impedire la divulgazione dei dati di configurazione:

Utilizzare l'autenticazione di Windows in maniera che le stringhe di connessione non contengano credenziali.

Crittografare le stringhe di connessione e limitare l'accesso ai dati crittografati.

Divulgazione dei dati riservati delle applicazioni

Molte applicazioni contengono dati riservati, quali i numeri delle carte di credito dei clienti. Proteggere la riservatezza e l'integrità di questo tipo di dati è di importanza fondamentale.

Vulnerabilità

Le pratiche di codifica che potrebbero portare alla divulgazione di dati riservati delle applicazioni sono:

Archiviazione dei dati senza crittografarli

Autorizzazione non adeguata

Crittografia non adeguata

Contromisure

Per impedire la divulgazione dei dati riservati delle applicazioni:

Utilizzare una crittografia avanzata per proteggere i dati.

Autorizzare ogni chiamante prima di effettuare l'accesso ai dati in maniera che gli utenti possano vedere solo i propri dati.

Divulgazione dei dettagli di connessione e dello schema di database

Se il codice restituisce al client i dettagli sulle eccezioni, un utente malintenzionato può utilizzare le informazioni per attaccare il server. Le eccezioni nel codice di accesso ai dati possono rivelare informazioni riservate, quali i dettagli sullo schema del database, la natura dell'archivio dati e frammenti del codice SQL.

Vulnerabilità

Le seguenti vulnerabilità possono portare alla divulgazione di informazioni:

Gestione inadeguata delle eccezioni

Configurazione ASP.NET non adeguata che permette la restituzione al client dei dettagli sulle eccezioni non gestiti

Contromisure

Per impedire questi tipi di divulgazione:

Intercettare, registrare e gestire le eccezioni di accesso ai dati nel proprio codice di accesso ai dati.

Restituire messaggi di errore generici al chiamante. A questo fine è necessario configurare correttamente l'elemento <customErrors> nel file di configurazione Web.config o Machine.config.

Accesso non autorizzato

Se l'autorizzazione non è adeguata, gli utenti potrebbero essere in grado di vedere i dati di un altro utente e accedere ad altri dati vietati.

Vulnerabilità

Le pratiche che possono permettere l'accesso non autorizzato sono:

Mancanza di autorizzazione nel codice di accesso ai dati, con conseguente accesso illimitato

Account di database con un numero eccessivo di privilegi

Contromisure

Per impedire l'accesso non autorizzato:

Utilizzare le richieste di autorizzazione dell'identità per autorizzare il chiamante.

Utilizzare le richieste di autorizzazione di protezione per l'accesso di codice per autorizzare il codice chiamante.

Utilizzare autorizzazioni limitate per limitare l'accesso dell'applicazione al database e impedire l'accesso diretto alla tabella.

Ascolto e intercettazione di rete non autorizzati

L'architettura di distribuzione della maggior parte delle applicazioni prevede la separazione fisica del codice di accesso ai dati dal server di database. Di conseguenza, dati riservati quali quelli specifici sull'applicazione o le credenziali di accesso al database devono essere protetti dai tentativi di ascolto e intercettazione di rete non autorizzati.

Vulnerabilità

Le seguenti pratiche aumentano la vulnerabilità all'ascolto e intercettazione di rete non autorizzati:

Credenziali con testo non crittografato passate in rete durante l'autenticazione di SQL

Dati riservati non crittografati delle applicazioni inviati al e dal server di database

Contromisure

Per limitare la vulnerabilità all'ascolto e intercettazione di rete non autorizzati:

Utilizzare l'autenticazione di Windows per evitare l'invio di credenziali in rete.

Installare un certificato del server nel server di database. Questo comporta l'applicazione automatica della crittografia alle credenziali SQL in rete.

Utilizzare una connessione SSL tra il server Web e il server di database per proteggere i dati riservati delle applicazioni. È necessario un certificato del server di database.

Utilizzare un canale crittografato IPSec tra il server Web e il server di database.

Inizio paginaInizio pagina

Considerazioni sulla progettazione

Durante la fase di progettazione, prima di iniziare a creare il codice, è opportuno fermarsi a riflettere su alcuni punti molto importanti. I punti chiave sono:

Utilizzare l'autenticazione di Windows.

Utilizzare account con privilegi minimi.

Utilizzare stored procedure.

Proteggere i dati riservati in archivio.

Utilizzare assembly di accesso ai dati separati.

Utilizzare l'autenticazione di Windows

Idealmente, per migliorare la protezione il progetto dovrebbe prevedere l'utilizzo dell'autenticazione di Windows. L'autenticazione di Windows permette di evitare l'archiviazione delle stringhe di connessione al database con credenziali incorporate e di passare le credenziali in rete. Offre inoltre criteri di gestione di account e password protetti. È però opportuno valutare attentamente l'account da utilizzare per connettersi a SQL Server tramite l'autenticazione di Windows.

Per ulteriori informazioni, vedere "Autenticazione" più avanti in questo modulo.

Utilizzare account con privilegi minimi

L'applicazione deve utilizzare un account con privilegi minimi che abbia autorizzazioni limitate nel database. Controllare che l'accesso dell'applicazione al database sia correttamente autorizzato e limitato. Per ulteriori informazioni, vedere "Autorizzazione" più avanti in questo modulo.

L'utilizzo di account con privilegi minimi riduce il rischio e limita i danni potenziali nel caso in cui l'account sia compromesso o venga introdotto codice dannoso. In caso di attacchi SQL injection, il comando viene eseguito nel contesto di protezione definito dall'accesso dell'applicazione ed è soggetto alle autorizzazioni associate di cui dispone l'accesso nel database. Se ci si collega utilizzando un account con privilegi eccessivi, ad esempio come membro del ruolo sysadmin di SQL Server, il pirata informatico può effettuare qualsiasi operazione in qualsiasi database nel server. Può ad esempio inserire, aggiornare e cancellare i dati, eliminare tabelle ed eseguire comandi del sistema operativo.

Importante: Non connettersi a SQL Server utilizzando l'account sa o un qualsiasi account che sia membro dei ruolisysadmin o db_owner di SQL Server.

Utilizzare stored procedure

Le stored procedure offrono benefici in termini di prestazioni, manutenzione e protezione. Quando possibile, per accedere ai dati utilizzare stored procedure con parametri. Qui di seguito sono elencati i benefici relativi alla protezione:

È possibile limitare l'accesso dell'applicazione al database in maniera che disponga solo dell'autorizzazione per eseguire le stored procedure specificate. Non è necessario concedere l'accesso diretto alle tabelle. Così facendo si riduce il rischio associato agli attacchi SQL injection.

I controlli sulla lunghezza e sul tipo vengono effettuati su tutti i dati di input passati alla stored procedure. Inoltre, i parametri non possono essere trattati come codice eseguibile. Anche in questo caso si riduce il rischio associato agli attacchi SQL injection.

Se, per una qualche ragione, non è possibile utilizzare stored procedure con parametri ed è necessario creare istruzioni SQL dinamicamente, utilizzare a questo fine parametri con tipo e segnaposto di parametri per assicurare che vengano controllati lunghezza e tipo dei dati di input.

Proteggere i dati riservati in archivio

Identificare i dati archiviati che richiedono riservatezza e integrità garantite. Se si archiviano le password nel database unicamente a fini di verifica, considerare l'utilizzo di un hash one-way. Se la tabella delle password è compromessa, gli hash non possono essere utilizzati per ottenere la password non crittografata.

Se si archiviano i dati riservati forniti dall'utente, ad esempio i numeri delle carte di credito, utilizzare un algoritmo di crittografia simmetrica sicuro quale Triple DES (3DES) per crittografare i dati. Crittografare la chiave di crittografia 3DES utilizzando la Data Protection API (DPAPI) di Win32 e archiviare la chiave crittografata in una chiave del Registro di sistema con un ACL limitato che possa essere utilizzato solo dagli amministratori e dall'account di processo dell'applicazione.

Perché l'utilizzo di DPAPI è sconsigliato

DPAPI è consigliato per crittografare le stringhe di connessione e altre informazioni riservate quali le credenziali dell'account che possono essere ripristinate e ricostruite manualmente in caso di guasto del computer, ma non è molto idoneo per archiviare dati quali i numeri delle carte di credito. La non idoneità è data dai problemi di ripristino (se le chiavi vanno perdute, non esiste alcuna possibilità di ripristinare i dati crittografati) e da problemi di Web farm. Utilizzare pertanto un algoritmo di crittografia simmetrica quale 3DES e crittografare la chiave di crittografia tramite DPAPI.

Qui di seguito sono riassunte le ragioni principali che fanno di DPAPI uno strumento meno idoneo per archiviare dati riservati nel database:

Se DPAPI è utilizzato con la chiave computer e si passa CRYPTPROTECT_LOCAL_MACHINE alle funzioni CryptProtectData e CryptUnprotectData, l'account computer genera le chiavi di crittografia. Questo significa che ogni server in una Web farm ha una chiave diversa, il che impedisce a un server di poter accedere ai dati crittografati da un altro server. Inoltre, se il server Web viene distrutto, la chiave va perduta e risulta impossibile ripristinare i dati crittografati dal database.

Se si adotta l'approccio della chiave computer, qualsiasi utente di quel computer può decrittografare i dati, a meno che non si utilizzino ulteriori meccanismi di crittografia.

Se si utilizza DPAPI con una chiave utente e si utilizzano account utente locali, ogni account locale su ogni server Web ha un diverso ID di protezione (SID) e viene generata una chiave diversa, il che impedisce a un server di poter accedere ai dati crittografati da un altro server.

Se si utilizza DPAPI con una chiave utente e si utilizza un profilo utente comune nei computer nella Web farm, tutti i dati condivideranno la stessa chiave di crittografia/decrittografia. Tuttavia, se il controller di dominio responsabile dell'account del profilo utente comune viene danneggiato o distrutto, è impossibile ricreare un account utente con lo stesso ID di protezione (SID) e ripristinare i dati crittografati dal database.

Inoltre, con un profilo utente comune, se qualcuno riesce a ripristinare i dati, questi ultimi possono essere decrittografati in qualsiasi computer nella rete, a condizione che il pirata informatico possa eseguire il codice sotto l'account utente specifico. Questo aumenta l'area di un potenziale attacco e non è una condizione consigliata.

Utilizzare assembly di accesso ai dati separati

Se possibile, evitare di mettere la logica di accesso ai dati direttamente nelle pagine ASP.NET o nei file sottostanti il codice. L'inserimento della logica di accesso ai dati in un assembly separato e l'implementazione di un livello di accesso logico ai dati separato dalla logica aziendale e di presentazione dell'applicazione offre dei vantaggi in termini di protezione, riutilizzo e manutenzione.

Dal punto di vista della protezione, è possibile:

Utilizzare un nome sicuro per l'assembly, evitando così manomissioni.

Utilizzare il sandboxing per isolare il codice di accesso ai dati, il che è importante se il codice deve supportare chiamanti ad attendibilità parziale, ad esempio applicazioni Web ad attendibilità parziale.

Utilizzare metodi di accesso ai dati e classi che autorizzano il codice chiamante tramite richieste di autorizzazione dell'identità del codice.

Per una difesa a più livelli, effettuare l'autorizzazione basata sull'identità utilizzando richieste di autorizzazione dell'identità nei componenti aziendali e utilizzare richieste di autorizzazione dell'identità del codice per autorizzare il codice che chiama la logica di accesso ai dati, come illustrato nella Figura 14.2.

Separazione dei livelli di presentazione, aziendali e di accesso ai dati

Figura 14.2
Separazione dei livelli di presentazione, aziendali e di accesso ai dati

Per ulteriori informazioni sull'autorizzazione per il codice di accesso ai dati, vedere la sezione "Autorizzazione" più avanti in questo modulo.

Inizio paginaInizio pagina

Convalida dell'input

A parte l'esigenza aziendale di assicurare che i database mantengano dati validi e coerenti, è necessario convalidare i dati prima di inviarli al database per evitare attacchi SQL injection. Se il codice di accesso ai dati riceve l'input da altri componenti all'interno del confine corrente delle zone protette e si sa che i dati sono già stati convalidati (ad esempio da un componente aziendale o da una pagina Web ASP.NET), nel codice di accesso ai dati è possibile omettere la convalida estesa dei dati. Accertarsi comunque di utilizzare parametri SQL nel codice di accesso ai dati. Questi parametri convalidano il tipo e la lunghezza dei parametri di input. Nella prossima sezione viene illustrato l'utilizzo dei parametri SQL.

Inizio paginaInizio pagina

SQL injection

Gli attacchi SQL injection possono verificarsi quando l'applicazione utilizza l'input per creare istruzioni SQL dinamiche per accedere al database. Possono inoltre prodursi nel caso in cui il codice utilizzi stored procedure che vengono passate come stringhe che contengono input utente non filtrato. In caso di SQL injection, i pirati informatici possono eseguire comandi nel database utilizzando l'accesso dell'applicazione. Il problema risulta ancora più grave se l'applicazione utilizza un account con privilegi eccessivi per connettersi al database.

Nota: le normali misure di protezione, quali l'utilizzo di SSL e IPSec, non proteggono dagli attacchi SQL injection.

Difesa contro gli attacchi SQL Injection

Per impedire attacchi SQL injection, utilizzare le contromisure seguenti:

Vincolare l'input.

Utilizzare parametri SQL indipendenti dai tipi.

Vincolare l'input

Convalidare il tipo, la lunghezza, il formato e l'intervallo dell'input. Se non sono previsti valori numerici, non accettarli. Valutare la provenienza dell'input. Se proviene da una fonte attendibile e si sa che questa fonte ha effettuato una convalida accurata dell'input, è possibile scegliere di omettere la convalida dei dati nel codice di accesso ai dati. Se i dati provengono da una fonte non attendibile, o ai fini di una difesa a più livelli, i componenti e i metodi di accesso ai dati devono convalidare l'input.

Utilizzare parametri SQL indipendenti dai tipi

L'insieme Parameters in SQL fornisce il controllo del tipo e la convalida della lunghezza. Se si utilizza l'insieme Parameters, l'input viene trattato come valore letterale e SQL non lo tratta come codice eseguibile. L'utilizzo dell'insieme Parameters offre inoltre il vantaggio di poter applicare i controlli sul tipo e sulla lunghezza. I valori al di fuori dell'intervallo generano un'eccezione. Questo è un buon esempio di difesa a più livelli.

Importante: SSL non protegge dagli attacchi SQL injection. Qualsiasi applicazione che accede a un database senza un'adeguata convalida dell'input e una tecnica appropriata di accesso ai dati è soggetta agli attacchi SQL injection.

Utilizzare le stored procedure, ove possibile, e chiamarle con l'insieme Parameters.

Utilizzo dell'insieme Parameters con stored procedure

Nel seguente frammento di codice viene illustrato l'utilizzo dell'insieme Parameters:

SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", conn);
myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
SqlParameter parm = myCommand.SelectCommand.Parameters.Add(
                       "@au_id", SqlDbType.VarChar, 11);
parm.Value = Login.Text;

In questo caso, il parametro @au_id è trattato come valore letterale e non come codice eseguibile. Inoltre, del parametro vengono controllati tipo e lunghezza. Nell'esempio riportato qui sopra, il valore di input non può essere più lungo di 11 caratteri. Se i dati non sono conformi al tipo o alla lunghezza definiti dal parametro, viene generata un'eccezione.

Tenere presente che l'utilizzo di stored procedure non impedisce necessariamente gli attacchi SQL injection. L'importante è utilizzare stored procedure con parametri. Se non si utilizzano parametri, le stored procedure possono essere esposte agli attacchi SQL injection nel caso in cui utilizzino input non filtrato. Il seguente frammento di codice, ad esempio, è vulnerabile:

SqlDataAdapter myCommand = new SqlDataAdapter("LoginStoredProcedure '" + 
                               Login.Text + "'", conn);

Importante: Se si utilizzano stored procedure, accertarsi di utilizzare i parametri.

Utilizzo dell'insieme Parameters con linguaggio SQL dinamico

Se è impossibile utilizzare stored procedure, è comunque possibile utilizzare i parametri, come illustrato nel frammento di codice seguente:

SqlDataAdapter myCommand = new SqlDataAdapter(
"SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id", conn);
SqlParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id", 
                        SqlDbType.VarChar, 11);
parm.Value = Login.Text;

Utilizzo del raggruppamento dei parametri

Si ritiene, erroneamente, che se si concatenano diverse istruzioni SQL per inviarle in gruppo al server in un unico percorso andata/ritorno, non sia possibile utilizzare i parametri. L'utilizzo di questa tecnica è invece possibile a condizione di controllare che i nomi dei parametri non siano ripetuti. A questo fine, aggiungere un numero o un altro valore esclusivo a ciascun nome di parametro durante la concatenazione del testo SQL.

Utilizzo di routine di filtro

Un altro approccio per proteggersi dagli attacchi SQL injection consiste nello sviluppare routine di filtro che aggiungono caratteri di escape ai caratteri che hanno un significato speciale per SQL, ad esempio un carattere apostrofo singolo. Nel seguente frammento di codice è illustrata una routine di filtro che aggiunge un carattere di escape:

private string SafeSqlLiteral(string inputSQL)
{
  return inputSQL.Replace("'", "''");
}

Il problema con routine di questo tipo e il motivo per cui è sconsigliato fare affidamento completamente su di esse è dato dal fatto che un pirata informatico potrebbe utilizzare caratteri esadecimali ASCII per aggirare i controlli. È tuttavia opportuno filtrare l'input come parte della strategia di difesa a più livelli.

Nota: non fare affidamento sul filtraggio dell'input.

Utilizzo delle clausole LIKE

Tenere presente che se si utilizza una clausola LIKE, i caratteri jolly richiedono comunque caratteri di escape. Questa tecnica viene illustrata nel frammento di codice seguente:

s = s.Replace("[", "[[]");
s = s.Replace("%", "[%]");
s = s.Replace("_", "[_]");
Inizio paginaInizio pagina

Autenticazione

Quando l'applicazione si connette a un database SQL Server, è possibile scegliere l'autenticazione di Windows o quella di SQL. L'autenticazione di Windows è più sicura. Se si deve utilizzare l'autenticazione di SQL, ad esempio perché ci si deve connettere al database tramite diversi account e si desidera evitare di chiamare LogonUser, adottare altre misure di protezione per ridurre al massimo ulteriori rischi.

Nota: l'utilizzo di LogonUser per creare un token di rappresentazione richiede il potente privilegio "Agisci come parte del sistema operativo" su Microsoft Windows 2000, pertanto è consigliato evitare questo approccio.

Tenere presenti le raccomandazioni seguenti:

Utilizzare l'autenticazione di Windows.

Proteggere le credenziali per l'autenticazione di SQL.

Connettersi utilizzando un account con privilegi minimi.

Utilizzare l'autenticazione di Windows

L'autenticazione di Windows non invia credenziali in rete. Se si utilizza l'autenticazione di Windows per un'applicazione Web, nella maggior parte dei casi si utilizza un account di servizio o di processo, ad esempio ASPNET, per connettersi al database. Windows e SQL Server devono entrambi riconoscere l'account utilizzato nel server di database. All'account deve essere concesso l'accesso a SQL Server e l'accesso deve disporre di autorizzazioni per accedere a un database.

Quando si utilizza l'autenticazione di Windows, si utilizza una connessione attendibile. Nei seguenti frammenti di codice sono illustrate le tipiche stringhe di connessione che utilizzano l'autenticazione di Windows.

Nell'esempio seguente viene utilizzato il provider di dati ADO.NET per SQL Server:

SqlConnection pubsConn = new SqlConnection(
   "server=dbserver; database=pubs; Integrated Security=SSPI;");

Nell'esempio seguente viene utilizzato il provider di dati ADO.NET per le origini dati OLE DB:

OleDbConnection pubsConn = new OleDbConnection(
   "Provider=SQLOLEDB; Data Source=dbserver; Integrated Security=SSPI;" +
   "Initial Catalog=northwind");

Proteggere le credenziali per l'autenticazione di SQL

Se è necessario utilizzare l'autenticazione di SQL, accertarsi che in rete non vengano inviate credenziali non crittografate e crittografare la stringa di connessione al database perché contiene le credenziali.

Per permettere a SQL Server di crittografare automaticamente le credenziali inviate in rete, installare un certificato server nel server di database. In alternativa, utilizzare un canale crittografato IPSec tra il server Web e il server di database. Per proteggere la stringa di connessione, utilizzare DPAPI. Per ulteriori informazioni, vedere "Proteggere le stringhe di connessione" nella sezione "Gestione della configurazione", più avanti in questo modulo.

Connettersi utilizzando un account con privilegi minimi

L'applicazione deve connettersi al database utilizzando un account con privilegi minimi. Se per la connessione si utilizza l'autenticazione di Windows, l'account di Windows dovrebbe avere privilegi minimi dal punto di vista del sistema operativo e dovrebbe disporre di privilegi limitati e di una capacità limitata di accesso alle risorse di Windows. Inoltre, a prescindere dall'utilizzo dell'autenticazione di Windows o di SQL, il corrispondente accesso di SQL Server dovrebbe essere limitato dalle autorizzazioni nel database.

Per ulteriori informazioni sulla creazione di un account database con privilegi minimi e sulle opzioni per connettere un'applicazione Web ASP.NET a un database remoto utilizzando l'autenticazione di Windows, vedere "Accesso ai dati" nel modulo 19 "Protezione dell'applicazione ASP.NET e dei servizi Web".

Inizio paginaInizio pagina

Autorizzazione

Dal processo di autorizzazione dipende la possibilità di ripristinare e manipolare dati specifici. Esistono due approcci: il codice di accesso ai dati può utilizzare l'autorizzazione per stabilire se effettuare l'operazione richiesta e il database può effettuare l'autorizzazione per limitare le capacità dell'accesso di SQL utilizzato dall'applicazione.

Se l'autorizzazione non è adeguata, un utente potrebbe essere in grado di vedere i dati di un altro utente e un utente non autorizzato potrebbe accedere a dati vietati. Per difendersi da questi pericoli:

Limitare l'accesso da parte di chiamanti non autorizzati.

Limitare l'accesso da parte di codice non autorizzato.

Limitare l'autorizzazione dell'applicazione nel database.

Nella Figura 14.3 sono riassunti i punti di autorizzazione e le tecniche consigliate.

Autorizzazione di accesso ai dati, assembly e database

Figura 14.3
Autorizzazione di accesso ai dati, assembly e database

Tenere presente che il codice di accesso ai dati può utilizzare le richieste di autorizzazione per autorizzare l'utente chiamante o il codice chiamante. Le richieste di identità del codice sono una funzionalità della protezione dall'accesso di codice .NET.

Per autorizzare l'applicazione nel database, utilizzare un account di accesso di SQL Server con privilegi minimi che possieda solo l'autorizzazione per effettuare le stored procedure selezionate. Se non vi sono delle ragioni specifiche che giustificano il contrario, non autorizzare l'applicazione a effettuare operazioni CRUD (di creazione, ripristino, aggiornamento, distruzione/eliminazione) direttamente su nessuna tabella.

Nota: le stored procedure vengono eseguite nel contesto di protezione del sistema di database. Sebbene sia possibile vincolare le operazioni logiche di un'applicazione assegnandole delle autorizzazioni su particolari stored procedure, non è possibile vincolare le conseguenze delle operazioni effettuate dalla stored procedure. Le stored procedure sono codice attendibile. Le interfacce per le stored procedure devono essere protette utilizzando autorizzazioni per i database.

Limitare l'accesso da parte di chiamanti non autorizzati

Il codice dovrebbe autorizzare gli utenti sulla base di un ruolo o di un'identità prima che si connettano al database. I controlli dei ruoli vengono in genere utilizzati nella logica aziendale dell'applicazione ma, in assenza di una distinzione chiara tra la logica aziendale e la logica di accesso ai dati, utilizzare le richieste di autorizzazione dell'identità sui metodi che accedono al database.

Il seguente attributo assicura che solo gli utenti che sono membri del ruolo Manager possano chiamare il metodo DisplayCustomerInfo:

[PrincipalPermissionAttribute(SecurityAction.Demand, Role="Manager")]
public void DisplayCustomerInfo(int CustId)
{
}

Se è richiesta un'ulteriore granularità dell'autorizzazione ed è necessario effettuare la logica basata sui ruoli all'interno del metodo di accesso ai dati, utilizzare richieste di autorizzazione imperative dell'identità o controlli dei ruoli espliciti come illustrato nel frammento di codice seguente:

using System.Security;
using System.Security.Permissions;

public void DisplayCustomerInfo(int CustId)
{
  try
  {
    // Imperative principal permission role check to verify that the caller
    // is a manager
    PrincipalPermission principalPerm = new PrincipalPermission(
                                                   null, "Manager");
    // Code that follows is only executed if the caller is a member
    // of the "Manager" role
  }
  catch( SecurityException ex )
  {
   . . .
  }
}

Nel seguente frammento di codice è utilizzato un controllo dei ruoli esplicito e a livello di programmazione per accertarsi che il chiamante sia membro del ruolo Manager:

public void DisplayCustomerInfo(int CustId)
{
  if(!Thread.CurrentPrincipal.IsInRole("Manager"))
  {
    . . .
  }
}

Limitare l'accesso da parte di codice non autorizzato

Utilizzando la protezione dall'accesso di codice .NET Framework, nello specifico le richieste di identità del codice, è possibile limitare gli assembly che possono accedere alle classi e ai metodi di accesso ai dati.

Se si desidera semplicemente, ad esempio, che il codice scritto dall'azienda o da una specifica organizzazione di sviluppo possa utilizzare i propri componenti di accesso ai dati, utilizzare StrongNameIdentityPermission e chiedere che gli assembly chiamanti abbiano un nome sicuro con una chiave pubblica specificata, come illustrato nel frammento di codice seguente:

using System.Security.Permissions;
. . .
[StrongNameIdentityPermission(SecurityAction.LinkDemand, 
                              PublicKey="002...4c6")]
public void GetCustomerInfo(int CustId)
{
}

Per estrarre una rappresentazione testo della chiave pubblica per un determinato assembly, utilizzare il comando seguente:

sn -Tp assembly.dll

Nota: utilizzare una "T" maiuscola nell'opzione –Tp .

Dato che gli assembly dell'applicazione Web sono compilati dinamicamente, non è possibile utilizzare nomi sicuri per essi. È di conseguenza difficile limitare l'utilizzo di un assembly di accesso ai dati a una specifica applicazione Web. L'approccio migliore consiste nello sviluppare un'autorizzazione personalizzata e chiederla al componente di accesso ai dati. Le applicazioni Web ad attendibilità totale, o qualsiasi codice ad attendibilità totale, possono chiamare il componente. Il codice ad attendibilità parziale, tuttavia, può chiamare il componente di accesso ai dati solo se gli è stata concessa l'autorizzazione personalizzata.

Per un esempio di implementazione di un'autorizzazione personalizzata, vedere "Procedura: Creazione di un'autorizzazione per la crittografia personalizzata" nella sezione "Procedure" di questa guida.

Limitare l'autorizzazione dell'applicazione nel database

L'approccio preferibile consiste nel creare un accesso di SQL Server per l'account di Windows utilizzato dall'applicazione per connettersi al database. Mappare quindi l'accesso di SQL Server a un utente nel database. Inserire quell'utente in uno specifico ruolo di database definito dall'utente e concedergli autorizzazioni su quel ruolo. La soluzione ideale sarebbe concedere al ruolo solo l'accesso in esecuzione alle stored procedure utilizzate dall'applicazione.

Per ulteriori informazioni sulla configurazione di questo approccio, vedere "Configuring Data Access for Your ASP.NET Application" nel modulo 19 "Protezione dell'applicazione ASP.NET e dei servizi Web".

Inizio paginaInizio pagina

Gestione della configurazione

Le stringhe di connessione al database sono l'aspetto di gestione della configurazione più importante per il codice di accesso ai dati. Controllare attentamente dove sono archiviate e come sono protette, specie se includono delle credenziali. Per migliorare la protezione della gestione della crittografia:

Utilizzare l'autenticazione di Windows.

Proteggere le stringhe di connessione.

Proteggere i file UDL con ACL con restrizioni.

Utilizzare l'autenticazione di Windows

Quando si utilizza l'autenticazione di Windows, le credenziali vengono gestite automaticamente e non vengono trasmesse in rete. Si evita inoltre di incorporare nomi utente e password nelle stringhe di connessione.

Proteggere le stringhe di connessione

Se è necessario utilizzare l'autenticazione di SQL, la connessione contiene nome utente e password. Se un pirata informatico sfrutta la vulnerabilità di divulgazione di un codice sorgente sul server Web o riesce ad accedere al server, può recuperare le stringhe di connessione. In maniera analoga, chiunque possieda un accesso legittimo al server può vederle. Proteggere le stringhe di connessione utilizzando la crittografia.

Crittografare le stringhe di connessione

Crittografare le stringhe di connessione utilizzando DPAPI. La crittografia DPAPI consente di evitare i problemi di gestione della chiave di crittografia perché questa chiave viene gestita dalla piattaforma ed è legata o a uno specifico computer o a un account utente di Windows. Per utilizzare DPAPI, è necessario chiamare le funzioni DPAPI di Win32 tramite P/Invoke.

Per ulteriori informazioni sulla creazione di una classe wrapper gestita, vedere "How To: Create a DPAPI Library" nella sezione "How To" di "Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication" all'indirizzo http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/secnetlpMSDN.asp (in inglese).

Archiviare in modo protetto le stringhe di connessione crittografate

La stringa di connessione crittografata può essere messa nel Registro di sistema o nel file Web.config o Machine.config. Se si utilizza una chiave sotto HKEY_LOCAL_MACHINE, applicarle l'ACL seguente:

Administrators: Full Control
Process Account: Read

Nota: l'account di processo è determinato dal processo in cui viene eseguito l'assembly di accesso ai dati. In genere si tratta del processo ASP.NET o di un processo server Servizi Enterprise, nel caso in cui la soluzione utilizzi un livello intermedio di Servizi Enterprise.

In alternativa, si potrebbe valutare la possibilità di utilizzare HKEY_CURRENT_USER, che fornisce un accesso limitato. Per ulteriori informazioni, vedere la sezione "Registro di sistema" nel modulo 7 "Creazione di assembly protetti".

Nota: se si utilizzano le connessioni guidate al database di Microsoft Visual Studio® .NET, le stringhe di connessione sono archiviate come valore di proprietà non crittografato nel file sottostante il codice dell'applicazione Web o nel file Web.config. Evitare entrambi questi approcci.

Anche se è potenzialmente meno sicuro dell'utilizzo di una chiave del Registro di sistema con restrizioni, per semplificare la distribuzione si potrebbe voler archiviare la stringa crittografata in Web.config. In questo caso, utilizzare una coppia nome-valore <appSettings> personalizzata, come illustrato qui di seguito:


<configuration>
 <appSettings>  
   <add key="connectionString" value="AQA..bIE=" />
 </appSettings>
 <system.web>
   ...
 </system.web>
</configuration>

Per accedere al testo crittografato dall'elemento <appSettings>, utilizzare la classe ConfigurationSettings come illustrato qui di seguito:

using System.Configuration;
private static string GetConnectionString()
{
  return ConfigurationSettings.AppSettings["connectionString"];
}

Non utilizzare Persist Security Info='True' o 'Yes'

Quando si include l'attributo Persist Security Info in una stringa di connessione, la proprietà ConnectionString rimuove la password dalla stringa di connessione prima che venga restituita all'utente. L'impostazione predefinita di false (che equivale a omettere l'attributo Persist Security Info) fa sì che le informazioni vengano eliminate quando viene stabilita la connessione al database.

Proteggere i file UDL con ACL con restrizioni

Se l'applicazione utilizza file di collegamento dati universale (UDL,Universal Data Link) esterni con il provider di dati gestiti ADO.NET per OLE DB, utilizzare le autorizzazioni NTFS per limitare l'accesso. Utilizzare l'ACL con restrizioni seguente:

Administrators: Full Control
Process Account: Read

Nota: i file UDL non sono crittografati. Un approccio più sicuro consiste nel crittografare la stringa di connessione utilizzando DPAPI e nell'archiviarla in una chiave del Registro di sistema con restrizioni.

Inizio paginaInizio pagina

Dati riservati

Molte applicazioni Web archiviano i dati riservati nel database. Se un pirata informatico riesce a eseguire una query sul database, è imprescindibile crittografare in maniera adeguata tutte le voci dei dati riservati, quali ad esempio i numeri delle carte di credito.

Crittografare i dati riservati se è necessario archiviarli.

Proteggere i dati riservati in rete.

Archiviare gli hash delle password con salt.

Crittografare i dati riservati se è necessario archiviarli

Evitare, se possibile, di archiviare dati riservati. Se non è possibile, crittografarli.

Utilizzo della crittografia 3DES

Per archiviare nel database dati riservati, quali i numeri delle carte di credito, utilizzare un algoritmo di crittografia simmetrica sicuro quale 3DES.

Durante lo sviluppo, per attivare la crittografia 3DES

1.

Utilizzare la classe RNGCryptoServiceProvider per generare una chiave di crittografia sicura (192 bit, 24 byte).

2.

Effettuare il backup della chiave di crittografia e archiviarlo in una posizione fisicamente protetta.

3.

Crittografare la chiave con DPAPI e archiviarla in una chiave del Registro di sistema. Per proteggere la chiave del Registro di sistema, utilizzare l'ACL seguente:

Administrators: Full Control
Process Account (for example ASPNET): Read

In fase di esecuzione, per archiviare i dati crittografati nel database

1.

Recuperare i dati da crittografare.

2.

Recuperare la chiave di crittografia crittografata dal Registro di sistema.

3.

Utilizzare DPAPI per decrittografare la chiave di crittografia.

4.

Utilizzare la classe TripleDESCryptoServiceProvider con la chiave di crittografia per crittografare i dati.

5.

Archiviare i dati crittografati nel database.

In fase di esecuzione, per decrittografare i dati riservati crittografati

1.

Recuperare i dati crittografati dal database.

2.

Recuperare la chiave di crittografia crittografata dal Registro di sistema.

3.

Utilizzare DPAPI per decrittografare la chiave di crittografia.

4.

Utilizzare la classe TripleDESCryptoServiceProvider per decrittografare i dati.

Grazie a questo processo, se l'account DPAPI utilizzato per crittografare la chiave di crittografia è danneggiato, è possibile recuperare il backup della chiave 3DES dal percorso di backup e crittografare la chiave utilizzando DPAPI sotto un nuovo account. La nuova chiave crittografata può essere archiviata nel Registro di sistema e i dati presenti nel database possono comunque essere decrittografati.

Per ulteriori informazioni sulla creazione di una libreria DPAPI gestita, vedere "How To: Create a DPAPI Library" nella sezione "How To" di "Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication" all'indirizzo http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/secnetlpMSDN.asp (in inglese).

Proteggere i dati riservati in rete

I dati riservati passati in rete al e dal server di database possono includere dati specifici dell'applicazione o credenziali di accesso al database. Per assicurare la riservatezza e l'integrità dei dati in rete, utilizzare una soluzione a livello di piattaforma (ad esempio quella fornita da un centro dati protetto in cui, tra i server, vengono utilizzati canali di comunicazione crittografati IPSec) oppure configurare l'applicazione per stabilire connessioni SSL al database. Quest'ultimo approccio richiede che nel server di database sia installato un certificato server.

Per ulteriori informazioni sull'utilizzo di SSL e IPSec, vedere "How To: Use IPSec to Provide Secure Communication Between Two Servers" e "How To: Use SSL to Secure Communication to SQL Server 2000" nella sezione "How To" di "Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication" all'indirizzo http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/secnetlpMSDN.asp (in inglese).

Archiviare gli hash delle password con salt

Se è necessario implementare un archivio utente che contiene i nomi utente e le password, non archiviare le password né in formato non crittografato, né in formato crittografato. Invece di archiviare le password, archiviare i valori hash non reversibili (ndash) aggiungendo salt per ridurre il rischio di attacchi al dizionario.

Nota: un valore salt è un numero casuale crittograficamente sicuro.

Creazione di un valore salt

Nel codice seguente viene illustrato come generare un valore salt utilizzando la funzionalità di generazione di un numero casuale fornita dalla classe RNGCryptoServiceProvider all'interno dello spazio del nome System.Security.Cryptography.

public static string CreateSalt(int size)
{
  RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
  byte[] buff = new byte[size];
  rng.GetBytes(buff);
  return Convert.ToBase64String(buff);
}

Creazione di un valore hash (con salt)

Nel seguente frammento di codice viene illustrato come generare un valore hash dalla password e dal valore salt forniti.

public static string CreatePasswordHash(string pwd, string salt)
{
  string saltAndPwd = string.Concat(pwd, salt);
  string hashedPwd = 
        FormsAuthentication.HashPasswordForStoringInConfigFile(
                                             saltAndPwd, "SHA1");
  return hashedPwd;
}

Ulteriori informazioni

Per ulteriori informazioni sull'implementazione di un archivio utente in cui sono archiviati gli hash delle password con salt, vedere "How To: Use Forms Authentication with SQL Server 2000" nella sezione "How To" di "Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication" all'indirizzo http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/secnetlpMSDN.asp (in inglese).

Inizio paginaInizio pagina

Gestione delle eccezioni

Le condizioni di eccezione possono essere causate da errori di configurazione, errori nel codice o input dannoso. Senza una corretta gestione delle eccezioni, oltre a informazioni preziose sulla connessione queste condizioni possono rivelare informazioni riservate sul percorso e la natura dell'origine dei dati. Per il codice di accesso ai dati si applicano le raccomandazioni seguenti:

Intercettare e registrare le eccezioni ADO.NET.

Controllare che le connessioni al database siano sempre chiuse.

Utilizzare una pagina di errori generici nelle applicazioni ASP.NET.

Intercettare e registrare le eccezioni ADO.NET

Mettere il codice di accesso ai dati all'interno di un blocco try / catch e gestire le eccezioni. Quando si scrive il codice di accesso ai dati di ADO.NET, il tipo di eccezione generata da ADO.NET dipende dal provider di dati. Ad esempio:

Il provider di dati .NET Framework di SQL Server genera SqlExceptions.

Il provider di dati .NET Framework di OLE DB genera OleDbExceptions.

Il provider di dati .NET Framework di ODBC genera OdbcExceptions.

Intercettazione delle eccezioni

Nel seguente codice viene utilizzato il provider di dati .NET Framework di SQL Server e viene illustrato come intercettare le eccezioni di tipo SqlException.

try
{
  // Data access code
}
catch (SqlException sqlex) // more specific
{
}
catch (Exception ex) // less specific
{
}

Registrazione delle eccezioni

Registrare anche i dettagli dalla classe SqlException. Questa classe espone le proprietà che contengono informazioni dettagliate sulla condizione di eccezione, inclusa una proprietà Message che descrive l'errore, una proprietà Number che identifica in maniera esclusiva il tipo di errore e una proprietà State che contiene ulteriori informazioni. La proprietà State in genere è utilizzata per indicare una particolare ricorrenza di una specifica condizione di errore. Se, ad esempio, una stored procedure genera lo stesso errore da più di una riga, la proprietà State indica quella specifica ricorrenza. Infine, un insieme Errors contiene oggetti SqlError che forniscono informazioni dettagliate sugli errori di SQL Server.

Nel seguente frammento di codice viene illustrato come gestire una condizione di errore di SQL Server utilizzando il provider di dati .NET Framework di SQL Server:

using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;

// Method exposed by a Data Access Layer (DAL) Component
public string GetProductName( int ProductID )
{
  SqlConnection conn = new SqlConnection(
        "server=(local);Integrated Security=SSPI;database=products");
  // Enclose all data access code within a try block
  try
  {
    conn.Open();
    SqlCommand cmd = new SqlCommand("LookupProductName", conn );
    cmd.CommandType = CommandType.StoredProcedure;

    cmd.Parameters.Add("@ProductID", ProductID );
    SqlParameter paramPN = 
         cmd.Parameters.Add("@ProductName", SqlDbType.VarChar, 40 );
    paramPN.Direction = ParameterDirection.Output;

    cmd.ExecuteNonQuery();
    // The finally code is executed before the method returns
    return paramPN.Value.ToString();  
  }
  catch (SqlException sqlex)
  {
    // Handle data access exception condition
    // Log specific exception details
    LogException(sqlex);
    // Wrap the current exception in a more relevant
    // outer exception and re-throw the new exception
    throw new Exception(
                  "Failed to retrieve product details for product ID: " + 
                   ProductID.ToString(), sqlex );
  }
  finally
  {
    conn.Close(); // Ensures connection is closed
  }
}

// Helper routine that logs SqlException details to the 
// Application event log
private void LogException( SqlException sqlex )
{
  EventLog el = new EventLog();
  el.Source = "CustomAppLog";
  string strMessage;
  strMessage = "Exception Number : " + sqlex.Number + 
               "(" + sqlex.Message + ") has occurred";
  el.WriteEntry( strMessage );

  foreach (SqlError sqle in sqlex.Errors)
  {
    strMessage = "Message: " + sqle.Message +
                 " Number: " + sqle.Number +
                 " Procedure: " + sqle.Procedure +
                 " Server: " + sqle.Server +
                 " Source: " + sqle.Source +
                 " State: " + sqle.State +
                 " Severity: " + sqle.Class +
                 " LineNumber: " + sqle.LineNumber;
    el.WriteEntry( strMessage );
  }
}

Controllare che le connessioni al database siano sempre chiuse

Se si verifica un'eccezione, è fondamentale chiudere le connessioni al database e rilasciare qualsiasi altra risorsa limitata. Utilizzare blocchi finally o l'istruzione C# using per accertarsi che le connessioni vengano chiuse a prescindere dalla presenza di una condizione di eccezione. Nel codice riportato sopra viene illustrato l'utilizzo del blocco finally. È anche possibile utilizzare l'istruzione C# using, come indicato qui di seguito:

using ((SqlConnection conn = new SqlConnection(connString)))
{
  conn.Open();
  // Connection will be closed if an exception is generated or if control flow
  // leaves the scope of the using statement normally
}

Utilizzare una pagina di errori generici nelle applicazioni ASP.NET

Se il codice di accesso ai dati viene chiamato da un'applicazione Web o da un servizio Web ASP.NET, configurare l'elemento <customErrors> per impedire che i dettagli sull'eccezione si propaghino all'utente finale. È anche possibile specificare una pagina di errori generici utilizzando questo elemento, come indicato qui di seguito.

<customErrors mode="On" defaultRedirect="YourErrorPage.htm" />

Impostare mode="On" per i server di produzione. Utilizzare mode="Off" solo quando si sviluppa e testa il software prima del rilascio. L'inosservanza di questa indicazione fa sì che all'utente finale vengano restituite informazioni dettagliate sugli errori, come quelle riportate nella Figura 14.4. Le informazioni possono includere il nome del server di database, il nome del database e le credenziali di connessione.

Informazioni dettagliate sulle eccezioni che rivelano dati riservati

Figura 14.4
Informazioni dettagliate sulle eccezioni che rivelano dati riservati

Nella Figura 14.4 sono riportate anche numerose vulnerabilità nel codice di accesso ai dati vicino alla riga che ha causato l'eccezione. Nello specifico:

La stringa di connessione è hard-coded.

Per la connessione al database è utilizzato l'account sa con privilegi elevati.

L'account sa ha una password vulnerabile.

La costruzione del comando SQL è esposta all'attacco SQL injection; l'input non è convalidato e il codice non utilizza stored procedure con parametri.

Inizio paginaInizio pagina

Creazione di codice di accesso ai dati protetto

Nel seguente codice viene illustrato un esempio di implementazione di un metodo CheckProductStockLevel utilizzato per eseguire query in un database di prodotti sulla quantità di magazzino. Nel codice sono illustrate numerose importanti funzionalità di protezione per il codice di accesso ai dati presentato precedentemente in questo modulo.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using Microsoft.Win32;
using DataProtection;

public static int CheckProductStockLevel(string productCode)
{
  int quantity = 0;
  // (1) Code protected by try/catch block
  try
  {
    // (2) Input validated with regular expression
    //     Error messages should be retrieved from the resource assembly to help
    //     localization. The Localization code is omitted for the sake of brevity.
    if (Regex.IsMatch(productCode, "^[A-Za-z0-9]{12}$") == false)
      throw new ArgumentException("Invalid product code" );
    //(3) The using statement ensures that the connection is closed
    using (SqlConnection conn = new SqlConnection(GetConnectionString()))
    {
      // (4) Use of parameterized stored procedures is a countermeasure for
      //     SQL injection attacks
      SqlCommand cmd = new SqlCommand("spCheckProduct", conn);
      cmd.CommandType = CommandType.StoredProcedure;

      // Parameters are type checked
      SqlParameter parm = 
               cmd.Parameters.Add("@ProductCode", 
                                  SqlDbType.VarChar,12);
      parm.Value = productCode;
      // Define the output parameter
      SqlParameter retparm = cmd.Parameters.Add("@quantity", SqlDbType.Int);
      retparm.Direction = ParameterDirection.Output;
      conn.Open();
      cmd.ExecuteNonQuery();
      quantity = (int)retparm.Value;
    }
  }
  catch (SqlException sqlex)
  {
    // (5) Full exception details are logged. Generic (safe) error message
    //     is thrown back to the caller based on the SQL error code
    //     Log and error identification code has been omitted for clarity
    throw new Exception("Error Processing Request");
  }
  catch (Exception ex)
  {
    // Log full exception details
    throw new Exception("Error Processing Request");
  }
  return quantity;
}

// (6) Encrypted database connection string is held in the registry
private static string GetConnectionString()
{
  // Retrieve the cipher text from the registry; the process account must be
  // granted Read access by the key's ACL
  string encryptedString = (string)Registry.LocalMachine.OpenSubKey(
                                        @"Software\OrderProcessing\")
                                        .GetValue("ConnectionString");
  // Use the managed DPAPI helper library to decrypt the string
  DataProtector dp = new DataProtector(DataProtector.Store.USE_MACHINE_STORE);
  byte[] dataToDecrypt = Convert.FromBase64String(encryptedString);
  return Encoding.ASCII.GetString(dp.Decrypt(dataToDecrypt,null));
}

Il codice precedente presenta le seguenti caratteristiche di protezione (contrassegnate dai numeri nelle righe di commento).

1.

Il codice di accesso ai dati è posto all'interno di un blocco try/catch. Questa posizione è essenziale per impedire che, in caso di eccezione, al chiamante vengano restituite informazioni a livello di sistema. L'applicazione Web o il servizio Web ASP.NET chiamante potrebbe gestire l'eccezione e restituire un messaggio idoneo di errore generico al client, ma il codice di accesso ai dati non fa affidamento su questa possibilità.

2.

L'input viene convalidato utilizzando un'espressione normale. L'ID del prodotto fornito viene controllato per assicurarsi che contenga solo caratteri compresi nell'intervallo A–Z e 0–9 e che non superi i 12 caratteri. Questa è la prima di una serie di contromisure progettate per impedire attacchi SQL injection.

3.

L'oggetto SqlConnection viene creato all'interno di un'istruzione using di Microsoft Visual C#®. Questo assicura la chiusura della connessione all'interno del metodo a prescindere dalla presenza di un'eccezione. Si riduce di conseguenza il pericolo di attacchi di tipo Denial of Service, che tentano di utilizzare tutte le connessioni disponibili al database. È possibile ottenere funzionalità simili utilizzando un blocco finally.

4.

Per l'accesso ai dati vengono utilizzate stored procedure con parametri. Questa è un'altra contromisura per impedire attacchi SQL injection.

5.

Al client non vengono restituite informazioni dettagliate sugli errori. Per agevolare la diagnostica del problema, vengono registrati i dettagli sull'eccezione.

6.

La stringa crittografata di connessione al database viene archiviata nel Registro di sistema. Uno dei modi più sicuri per archiviare le stringhe di connessione al database consiste nell'utilizzare DPAPI per crittografare la stringa e archiviare il testo crittografato in una chiave del Registro di sistema protetta con un ACL con restrizioni. (Utilizzare ad esempio Administrators: Controllo completo e ASP.NET o account di processo Servizi Enterprise: Lettura, a seconda del processo che ospita il componente.)

Nota: nel codice viene illustrato come recuperare la stringa di connessione dal Registro di sistema e poi decrittografarla utilizzando la libreria dell'helper DPAPI gestita. Questa libreria si trova in "How To: Create a DPAPI Library" nella sezione "How To" di "Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication" (in inglese).

Inizio paginaInizio pagina

Considerazioni sulla protezione dall'accesso di codice

Ogni accesso ai dati è soggetto alle richieste di autorizzazione di protezione per l'accesso di codice. I requisiti esatti dipendono dal provider di dati gestiti ADO.NET scelto. Nella seguente tabella sono riportate le autorizzazioni da concedere agli assembly di accesso ai dati per ogni provider di dati ADO.NET.

Tabella 14.1 Autorizzazioni di protezione per l'accesso di codice richieste dai provider di dati ADO.NET

Provider di dati ADO.NETAutorizzazione di protezione richiesta per l'accesso di codice

SQL Server

SqlClientPermission
Supporta chiamanti ad attendibilità parziale, incluse applicazioni Web ad attendibilità media.

OLE DB

OleDbPermission*

Oracle

OraclePermission*

ODBC

OdbcPermission*

*Al momento della stesura del presente modulo, i provider OLE DB, Oracle e ODBC supportano solo chiamanti ad attendibilità totale sulle versioni 1.0 e 1.1 di .NET Framework. Per utilizzare questi provider da applicazioni Web ad attendibilità parziale, è necessario effettuare il sandboxing del codice di accesso ai dati, operazione che richiede un assembly di accesso ai dati dedicato. Per un esempio in cui sia illustrato come effettuare il sandboxing del codice di accesso ai dati e l'utilizzo del provider di dati OLE DB da un'applicazione Web ad attendibilità media, vedere il modulo 9 "Utilizzo della protezione dall'accesso di codice con ASP.NET".

Se si utilizza il provider di dati ADO.NET per SQL Server, è necessario concedere al codice SqlClientPermission tramite il criterio di protezione dall'accesso di codice. Le applicazioni Web ad attendibilità totale e media dispongono di questa autorizzazione.

Dalla concessione di SqlClientPermission al codice dipende la connessione del codice a SQL Server. È possibile utilizzare l'autorizzazione anche per mettere restrizioni all'utilizzo delle stringhe di connessione al database. È ad esempio possibile imporre a un'applicazione l'utilizzo di protezione integrata oppure accertarsi che, se si utilizza la protezione SQL Server, le password vuote non vengano accettate. Le violazioni delle regole specificate tramite SqlClientPermission risultano in eccezioni di protezione in fase di esecuzione.

Per ulteriori informazioni sull'utilizzo di SqlClientPermission per vincolare l'accesso ai dati, vedere "Accesso dai dati" nel modulo 8 "Protezione dall'accesso di codice".

Inizio paginaInizio pagina

Considerazioni sulla distribuzione

Un componente di accesso ai dati progettato e sviluppato in maniera protetta può essere comunque vulnerabile a un attacco se non viene distribuito in maniera protetta. È pratica di distribuzione comune che il codice di accesso ai dati e il database si trovino in server separati. I server sono spesso separati da un firewall interno, il che introduce ulteriori considerazioni sulla distribuzione. Gli sviluppatori e gli amministratori devono essere consapevoli delle seguenti problematiche:

Restrizioni a livello di firewall

Gestione delle stringhe di connessione

Configurazione dell'account di accesso

Controllo dell'accesso

Riservatezza e integrità dei dati in rete

Restrizioni a livello di firewall

Se ci si collega a SQL Server tramite un firewall, configurare firewall, client e server. Per configurare il client utilizzare l'utilità Configurazione di rete client di SQL Server, mentre per configurare il server di database utilizzare l'utilità Configurazione di rete di SQL Server. Per impostazione predefinita, SQL Server esegue l'ascolto sulla porta TCP 1433, anche se è possibile cambiare questa impostazione. Aprire la porta scelta nel firewall.

A seconda della modalità di autenticazione di SQL Server scelta e dell'utilizzo delle transazioni distribuite da parte dell'applicazione, potrebbe essere necessario aprire numerose porte aggiuntive nel firewall:

Se l'applicazione utilizza l'autenticazione di Windows per collegarsi a SQL Server, le porte necessarie per supportare l'autenticazione Kerberos o NTLM devono essere aperte.

Se le reti non utilizzano Active Directory, in genere per l'autenticazione di Windows è richiesta la porta TCP 139. Per ulteriori informazioni sui requisiti delle porte, vedere gli articoli di TechNet, "TCP and UDP Port Assignments" all'indirizzo http://www.microsoft.com/technet/prodtechnol/windows2000serv/reskit/tcpip/part4/tcpappc.asp (in inglese) e "Security Considerations for Administrative Authority" all'indirizzo http://www.microsoft.com/technet/security/bestprac/bpent/sec2/seconaa.asp (in inglese).

Se l'applicazione utilizza transazioni distribuite, ad esempio transazioni COM+ automatizzate, potrebbe anche essere necessario configurare il firewall in maniera da permettere il flusso del traffico DTC tra istanze DTC separate e tra DTC e programmi di gestione delle risorse come ad esempio SQL Server.

Per informazioni complete sulla configurazione, vedere la sezione "Porte" nel modulo 18 "Protezione del server database".

Gestione delle stringhe di connessione

In molte applicazioni, le stringhe di connessione vengono archiviate nel codice essenzialmente per motivi di prestazioni. I benefici, in termini di prestazioni, sono però trascurabili e l'utilizzo della messa in cache del file system aiuta ad assicurare che l'archiviazione delle stringhe di connessione in file esterni dia prestazioni analoghe. L'utilizzo di file esterni per archiviare le stringhe di connessione è ottimale per l'amministrazione del sistema.

Per aumentare la protezione, si consiglia di utilizzare DPAPI per crittografare la stringa di connessione. Questa procedura è particolarmente importante se la stringa di connessione contiene nomi utente e password. Decidere quindi dove archiviare la stringa crittografata. Il Registro di sistema è una posizione sicura specie se si utilizza HKEY_CURRENT_USER, perché l'accesso è limitato ai processi che vengono eseguiti sotto l'account utente associato. Un'alternativa per semplificare la distribuzione consiste nell'archiviare la stringa crittografata nel file Web.config. Entrambi gli approcci sono stati trattati nella sezione "Gestione della configurazione" in precedenza in questo modulo.

Configurazione dell'account di accesso

È fondamentale che l'applicazione utilizzi un account con privilegi minimi per connettersi al database. Questa è una delle principali tecniche per ridurre gli attacchi SQL injection.

Lo sviluppatore deve comunicare all'amministratore di database esattamente le stored procedure e, possibilmente, le tabelle a cui l'accesso dell'applicazione deve poter accedere. Idealmente, l'accesso dell'applicazione dovrebbe avere autorizzazioni di esecuzione solo su un gruppo ristretto di stored procedure che vengono distribuite assieme all'applicazione.

Utilizzare password complesse per gli account di Windows o SQL o per quelli utilizzati dall'applicazione per connettersi al database.

Vedere la sezione "Autorizzazione" in precedenza in questo modulo, per la strategia di autorizzazione consigliata per l'account per le applicazioni nel database.

Controllo dell'accesso

È necessario configurare SQL Server in maniera che registri i tentativi di accesso non riusciti e, possibilmente, anche quelli riusciti. Il controllo dei tentativi di accesso non riusciti è utile per individuare un pirata informatico che tenta di scoprire le password degli account.

Per ulteriori informazioni sulla configurazione del controllo di SQL Server, vedere il modulo 18 "Protezione del server database".

Riservatezza e integrità dei dati in rete

Se si utilizza l'autenticazione di SQL per connettersi a SQL Server, controllare che le credenziali di accesso non siano esposte in rete. Installare un certificato nel server di database (che fa sì che SQL Server crittografi le credenziali) oppure utilizzare un canale crittografato IPSec verso il database.

L'utilizzo di IPSec o SSL verso il database è consigliato per proteggere i dati riservati a livello di applicazione passati al e dal database. Per ulteriori informazioni, vedere il modulo 18 "Protezione del server database".

Inizio paginaInizio pagina

Riepilogo

In questo modulo sono stati illustrati i pericoli principali per il codice di accesso ai dati e sono state evidenziate le vulnerabilità comuni. Gli attacchi SQL injection sono uno dei pericoli principali di cui essere consapevoli. Se non si applicano le contromisure corrette trattate in questo modulo, un pirata informatico potrebbe sfruttare il codice di accesso ai dati per eseguire comandi arbitrari nel database. Le misure di protezione convenzionali, quali i firewall e SSL, non forniscono una difesa dagli attacchi SQL injection. Come difesa minima, convalidare attentamente l'input e utilizzare stored procedure con parametri.

Inizio paginaInizio pagina

Ulteriori risorse

Per ulteriori informazioni, vedere le risorse seguenti:

Per un elenco di controllo stampabile, vedere "Elenco di controllo: Protezione dell'accesso ai dati" nella sezione "Elenchi di controllo" di questa guida.

Per informazioni sulla protezione della workstation di sviluppo, vedere "Procedura: Protezione delle workstation degli sviluppatori" nella sezione "Procedure" di questa guida.

Per informazioni sull'utilizzo di SSL con SQL Server, vedere "How To: Use SSL to Secure Communication with SQL Server 2000" nella sezione "How To" di "Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication" all'indirizzo http://msdn.microsoft.com/library/en-us/dnnetsec/html/SecNetHT19.asp (in inglese).

Per informazioni sull'utilizzo di IPSec, vedere "How To: Use IPSec to Provide Secure Communication Between Two Servers" nella sezione "How To" di "Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication" all'indirizzo http://msdn.microsoft.com/library/en-us/dnnetsec/html/SecNetHT18.asp (in inglese).

Per informazioni sull'utilizzo di DPAPI, vedere "How To: Create a DPAPI Library" nella sezione "How To" di "Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication" all'indirizzo http://msdn.microsoft.com/library/en-us/dnnetsec/html/SecNetHT07.asp (in inglese).


Inizio paginaInizio pagina