Creazione di assembly protetti

In questa pagina
Argomenti del moduloArgomenti del modulo
ObiettiviObiettivi
Ambito di applicazioneAmbito di applicazione
Utilizzo del moduloUtilizzo del modulo
Pericoli e contromisurePericoli e contromisure
Codice con privilegiCodice con privilegi
Considerazioni sulla progettazione degli assemblyConsiderazioni sulla progettazione degli assembly
Considerazioni sulla progettazione delle classiConsiderazioni sulla progettazione delle classi
Nomi sicuriNomi sicuri
AutorizzazioneAutorizzazione
Gestione delle eccezioniGestione delle eccezioni
I/O fileI/O file
Registro eventiRegistro eventi
Registro di sistemaRegistro di sistema
Accesso ai datiAccesso ai dati
Codice non gestitoCodice non gestito
DelegatiDelegati
SerializzazioneSerializzazione
ThreadingThreading
ReflectionReflection
OffuscamentoOffuscamento
CrittografiaCrittografia
RiepilogoRiepilogo
Risorse aggiuntiveRisorse aggiuntive

Argomenti del modulo

Gli assembly sono i componenti di base delle applicazioni .NET Framework e costituiscono l'unità di distribuzione, del controllo della versione e del riutilizzo. Essi rappresentano inoltre l'unità di trust per la protezione dall'accesso di codice, poiché tutto il codice di un assembly è ugualmente attendibile.

Nel presente modulo vengono innanzitutto elencati e descritti i pericoli comuni che interessano gli assembly e le relative contromisure. Successivamente viene fornito un elenco completo dei settori della protezione che è necessario considerare per migliorare la progettazione della protezione e l'implementazione degli assembly. Ciò comporta la necessità di valutare le considerazioni sulla distribuzione, di adottare procedure di programmazione affidabili orientate a oggetti, di rendere il codice a prova di manomissione, di garantire che le informazioni interne a livello di sistema non vengano rivelate al chiamante e di limitare il numero di utenti autorizzati a eseguire chiamate al codice.

Inizio paginaInizio pagina

Obiettivi

Il modulo consente di:

Migliorare la protezione degli assembly mediante tecniche di codifica semplici e sperimentate.

Ridurre la superficie di attacco attraverso interfacce ben progettate e affidabili tecniche di programmazione orientate a oggetti.

Utilizzare nomi sicuri per impedire la manomissione degli assembly.

Ridurre i rischi associati alle chiamate a codice non gestito.

Scrivere codice protetto per l'accesso alle risorse, compreso I/O file, Registro di sistema, registro eventi, database e codice di accesso alla rete.

Conoscere le contromisure da applicare per neutralizzare i pericoli che comunemente interessano gli assembly, ad esempio incremento dei privilegi, code injection (inserimento di codice), divulgazione di informazioni e manomissioni.

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 2003 Server

Microsoft .NET Framework 1.1 e ASP.NET 1.1

Inizio paginaInizio pagina

Utilizzo del modulo

Per trarre il massimo vantaggio dal modulo:

Utilizzare questo modulo insieme al modulo 8 "Protezione dall'accesso di codice". Nel modulo 8 sono illustrate le modalità di utilizzo delle funzionalità di protezione dall'accesso di codice che consentono di migliorare ulteriormente la protezione degli assembly.

Utilizzare l'elenco di controllo corrispondente. Un elenco di controllo riepilogativo delle procedure consigliate e delle raccomandazioni contenute in entrambi i moduli è disponibile in "Elenco di controllo: analisi della protezione del codice gestito", nella sezione Elenchi di controllo di questa guida.

Occorre inoltre essere consapevoli dei problemi elencati di seguito e relativi alle versioni 1.0 e 1.1 di
.NET Framework:

È estremamente difficile creare ambienti parzialmente attendibili, a causa della quantità di assembly .NET essenziali che richiedono chiamanti con attendibilità totale.

Non è possibile utilizzare un nome sicuro per le pagine Web ASP.NET Web.

La pubblicazione di un assembly nella cache dell'assembly globale è l'unica soluzione per il sandboxing del codice.

Per utilizzare nomi sicuri negli assembly e pubblicarli nella cache dell'assembly globale è necessario disporre di privilegi sufficienti per poter eseguire comandi quali Sn.exe e Gacutil.exe nel server Web.

Le prove Authenticode non vengono caricate dall'host ASP.NET, il che significa che non è possibile utilizzarle per definire un criterio di protezione per le applicazioni Web ASP.NET.

Inizio paginaInizio pagina

Pericoli e contromisure

Comprendere i pericoli e i tipi di attacchi più diffusi consente di identificare le contromisure appropriate e di rendere gli assembly più protetti e affidabili. I principali pericoli sono:

Accesso non autorizzato, incremento dei privilegi o entrambi

Code injection (inserimento di codice)

Divulgazione di informazioni

Tampering (manomissione)

La figura 7.1 illustra i pericoli più importanti.

 Pericoli a livello di assembly

Figura 7.1
Pericoli a livello di assembly

Accesso non autorizzato, incremento dei privilegi o entrambi

Il rischio dell'accesso non autorizzato, che può comportare l'incremento dei privilegi, è che un utente o codice non autorizzato possa eseguire chiamate all'assembly, eseguire operazioni privilegiate e accedere a risorse riservate.

Vulnerabilità

Le vulnerabilità che possono favorire l'accesso non autorizzato e l'incremento dei privilegi comprendono:

Autorizzazione basata sui ruoli debole o assente

Esposizione involontaria di tipi e membri di tipi interni

Utilizzo non protetto di richieste in fase di linking e asserzioni relative alla protezione dall'accesso di codice

Classi di base senza restrizioni e non sealed, da cui può derivare qualsiasi codice

Attacchi

Fra gli attacchi più comuni vi sono:

Attacchi di tipo luring, in cui codice dannoso accede all'assembly tramite un assembly intermedio attendibile per superare i meccanismi di autorizzazione

Attacchi in cui codice dannoso supera i controlli di accesso effettuando chiamate dirette a classi che non fanno parte dell'API pubblica dell'assembly

Contromisure

Di seguito sono elencate alcune contromisure applicabili per impedire l'accesso non autorizzato e l'incremento dei privilegi:

Utilizzare l'autorizzazione basata sui ruoli per fornire controlli di accesso su tutte le classi pubbliche e i membri delle classi.

Limitare la visibilità dei membri e dei tipi per circoscrivere il codice accessibile pubblicamente.

Eseguire codice con privilegi nel sandbox e verificare che il codice chiamante sia autorizzato con le richieste di autorizzazioni appropriate.

Proteggere le classi non di base o limitare l'ereditarietà con protezione dall'accesso di codice.

Code injection (inserimento di codice)

Negli attacchi di questo tipo viene eseguito codice arbitrario utilizzando il contesto di protezione a livello di processo dell'assembly. Il rischio aumenta se vengono eseguite chiamate a codice non gestito e se l'assembly viene eseguito con un account con privilegi.

Vulnerabilità

Fra le vulnerabilità che possono portare all'inserimento di codice vi sono:

Convalida dell'input insufficiente, soprattutto nei casi in cui l'assembly esegue chiamate a codice non gestito

Accettazione di delegati da codice parzialmente attendibile

Account di processi con più privilegi del necessario

Attacchi

Gli attacchi più comuni con inserimento di codice includono:

Overflow del buffer

Invocazione di un delegato da un'origine non attendibile

Contromisure

Le contromisure utilizzabili per evitare l'inserimento di codice includono:

Convalida dei parametri di input.

Convalida dei dati passati ad API non gestite.

Rifiuto di delegati da origini non attendibili.

Utilizzo di delegati tipizzati in modo sicuro e rifiuto delle autorizzazioni prima della chiamata al delegato.

Per ridurre ulteriormente il rischio, eseguire gli assembly utilizzando account con privilegi minimi.

Information Disclosure (Diffusione non autorizzata di informazioni)

Gli assembly sono esposti a problemi di diffusione non autorizzata di informazioni se lasciano fuoriuscire dati sensibili, ad esempio dettagli relativi alle eccezioni o segreti non crittografati, accessibili sia per gli utenti legittimi che per i malintenzionati. Inoltre, rispetto al codice macchina binario il codice MSIL (Microsoft Intermediate Language) di un assembly è più facile da decodificare per risalire al codice sorgente. Questo rappresenta un pericolo per la proprietà intellettuale.

Vulnerabilità

Fra le vulnerabilità che possono portare alla diffusione non autorizzata di informazioni vi sono:

Gestione formale delle eccezioni poco efficace o assente

Segreti hardcoded nel codice

Attacchi

Fra gli attacchi più comuni vi sono:

Tentativi di causare errori passando input non valido all'assembly

Utilizzo di ILDASM su un assembly per appropriarsi di segreti

Contromisure

Le contromisure utilizzabili per evitare la diffusione non autorizzata di informazioni includono:

Convalida efficace dell'input

Gestione delle eccezioni strutturata e restituzione di errori generici ai client

Nessuna archiviazione di segreti nel codice

Strumenti di offuscamento per confondere i decompilatori e proteggere la proprietà intellettuale

Tampering (manomissione)

Il rischio del tampering è che l'assembly venga modificato alterando le istruzioni MSIL nel file binario DLL o EXE dell'assembly.

Vulnerabilità

La principale vulnerabilità che espone l'assembly al tampering è la mancanza di una firma con nome sicuro.

Attacchi

Fra gli attacchi più comuni vi sono:

Manipolazione diretta delle istruzioni MSIL

Decodifica delle istruzioni MSIL

Contromisure

Per contrastare il pericolo di manomissione, utilizzare un nome sicuro per firmare l'assembly con una chiave privata. Quando un assembly firmato viene caricato, Common Language Runtime rileva se l'assembly è stato alterato e, in questo caso, non lo carica.

Inizio paginaInizio pagina

Codice con privilegi

Quando si progettano e si realizzano assembly protetti è necessario essere in grado di identificare il codice con privilegi. Questo ha implicazioni notevoli per la protezione dall'accesso di codice. Il codice con privilegi è codice gestito che accede a risorse protette o esegue altre operazioni inerenti alla protezione, quali la chiamata a codice non gestito, l'utilizzo della serializzazione o della reflection. Viene definito codice con privilegi perché per funzionare richiede l'assegnazione di autorizzazioni mediante il criterio di protezione dall'accesso di codice. Il codice non dotato di privilegi richiede solo l'autorizzazione di esecuzione.

Risorse con privilegi

Le risorse per cui è necessario assegnare al codice autorizzazioni di protezione per l'accesso di codice comprendono file system, database, Registro di sistema, registro eventi, servizi Web, socket, database DNS, servizi directory e variabili di ambiente.

Operazioni con privilegi

Altre operazioni privilegiate per cui il codice richiede autorizzazioni di protezione per l'accesso di codice sono le chiamate a codice non gestito, l'utilizzo della serializzazione e della reflection, la creazione e il controllo dei domini applicazione, la creazione di oggetti Principal e la manipolazione del criterio di protezione.

Per ulteriori informazioni sui tipi specifici di autorizzazioni di protezione per l'accesso di codice richiesti per accedere alle risorse e per l'esecuzione di operazioni privilegiate, consultare la sezione "Codice con privilegi", nel modulo 8 "Protezione dall'accesso di codice".

Inizio paginaInizio pagina

Considerazioni sulla progettazione degli assembly

Una delle questioni più significative da considerare in fase di progettazione è il livello di trust dell'ambiente di destinazione dell'assembly, che incide sulle autorizzazioni di protezione per l'accesso di codice concesse al proprio codice e al codice che a sua volta lo chiama. Il livello di trust dipende dal criterio di protezione per l'accesso di codice definito dall'amministratore e determina a quali tipi di risorse il codice sarà autorizzato ad accedere e quali altre operazioni privilegiate potrà eseguire.

Nella progettazione di un assembly è necessario:

Identificare il codice con privilegi

Identificare il livello di trust supportato dall'ambiente di destinazione.

Eseguire codice con privilegi di alto livello nel sandbox

Progettare l'interfaccia pubblica

Identificare il codice con privilegi

Identificare il codice che dovrà accedere alle risorse protette o eseguire operazioni inerenti alla protezione. Per funzionare, questo tipo di codice richiede specifiche autorizzazioni di protezione per l'accesso di codice.

Identificare le risorse con privilegi

Identificando i tipi di risorse a cui il codice deve poter accedere è possibile individuare i problemi che potrebbero verificarsi se l'ambiente in cui l'assembly dovrà essere eseguito non dovesse concedere le relative autorizzazioni di protezione per l'accesso di codice. In questo caso si è costretti ad aggiornare il criterio di protezione dall'accesso di codice, se l'amministratore lo consente, oppure eseguire il codice con privilegi nel sandbox. Per ulteriori informazioni sul sandboxing, leggere il modulo 9 "Utilizzo della protezione dall'accesso di codice con ASP.NET".

Identificare le operazioni privilegiate

È necessario identificare le operazioni privilegiate che l'assembly dovrà eseguire, anche in questo caso per sapere quali autorizzazioni per l'accesso di codice che il codice richiederà durante l'esecuzione.

Identificare il livello di trust supportato dall'ambiente di destinazione.

L'ambiente in cui l'assembly verrà installato è importante, perché è possibile che il criterio di protezione dall'accesso di codice imponga limitazioni alle azioni eseguibili dall'assembly. Se, ad esempio, l'assembly è dipendente dall'utilizzo di OLE DB, potrà essere eseguito senza problemi soltanto in un ambiente con attendibilità totale.

Ambienti con attendibilità totale

Attendibilità totale significa che al codice è concesso un insieme di autorizzazioni di protezione per l'accesso di codice senza alcuna restrizione, per cui il codice può accedere a tutti i tipi di risorse ed eseguire operazioni privilegiate, ferma restando la protezione del sistema operativo. L'attendibilità totale è l'impostazione predefinita per gli ambienti delle applicazioni Web e degli assembly di supporto installati in un server Web, che è possibile modificare configurando l'elemento <trust> dell'applicazione.

Ambiente con attendibilità parziale

Un ambiente con attendibilità parziale è ovviamente qualcosa di meno rispetto a un ambiente con attendibilità totale. .NET Framework prevede diversi livelli di trust predefiniti che è possibile utilizzare direttamente o personalizzare in modo da renderli più rispondenti alle specifiche esigenze di protezione. Il livello di trust può anche essere limitato a causa dell'origine del codice. Il codice di una condivisione di rete è, ad esempio, meno attendibile di quello di un computer locale e ha di conseguenza una minore capacità di eseguire operazioni privilegiate.

Supporto di chiamanti con attendibilità parziale

Il rischio di una violazione della protezione aumenta sensibilmente se l'assembly supporta chiamanti con attendibilità parziale, vale a dire codice non totalmente attendibile. Per mitigare il rischio, la protezione dall'accesso di codice prevede misure di salvaguardia aggiuntive. Nel modulo 8 "Protezione dall'accesso di codice", vengono fornite linee guida aggiuntive applicabili agli assembly che supportano chiamanti con attendibilità parziale. Senza programmazione aggiuntiva, il codice supporta chiamanti con attendibilità parziale nelle due situazioni seguenti:

L'assembly non è dotato di un nome sicuro.

L'assembly è dotato di un nome sicuro e include l'attributo AllowPartiallyTrustedCallersAttribute (APTCA) a livello di assembly.

Perché preoccuparsi dell'ambiente di destinazione?

Il livello di trust dell'ambiente in cui l'assembly verrà eseguito è importante per i seguenti motivi:

Un assembly con attendibilità parziale può accedere a un insieme limitato di risorse ed eseguire un insieme limitato di operazioni, in funzione delle autorizzazioni di protezione per l'accesso di codice concesse dal criterio di protezione dall'accesso di codice.

Un assembly con attendibilità parziale non può eseguire chiamate a un assembly con nome sicuro, a meno che non includa l'attributo AllowPartiallyTrustedCallersAttribute.

Altri assembly con attendibilità parziale possono non essere in grado di eseguire chiamate all'assembly perché non dispongono delle autorizzazioni necessarie. Le autorizzazioni di cui un altro assembly chiamante deve essere dotato per poter chiamare l'assembly sono determinate da:

I tipi di risorse a cui accede l'assembly

I tipi di operazioni privilegiate che l'assembly esegue

Eseguire codice con privilegi di alto livello nel sandbox

Per evitare di concedere autorizzazioni avanzate a un'intera applicazione solo per rispondere alle esigenze di alcuni metodi che eseguono operazioni privilegiate, è possibile eseguire il codice con privilegi nel sandbox e inserirlo in un assembly separato. In questo modo l'amministratore può configurare il criterio di protezione dall'accesso di codice in modo da concedere le autorizzazioni estese al codice dello specifico assembly e non a tutta l'applicazione.

Se, ad esempio, l'applicazione deve eseguire chiamate a codice non gestito, è possibile eseguire il sandboxing delle chiamate non gestite in un assembly del wrapper, in modo che l'amministratore possa concedere l'autorizzazione UnmanagedCodePermission solo a quest'ultimo invece che all'intera applicazione.

Nota: il sandboxing implica l'utilizzo di un assembly separato e l'asserzione delle autorizzazioni di protezione per evitare che venga percorso l'intero stack.

Per ulteriori informazioni sul sandboxing delle chiamate ad API non gestite, consultare la sezione "Codice non gestito" nel modulo 8 "Protezione dall'accesso di codice".

Progettare l'interfaccia pubblica

È consigliabile valutare attentamente quali tipi e membri devono far parte dell'interfaccia pubblica dell'assembly. Riducendo al minimo il numero di punti di ingresso e adottando un'interfaccia pubblica contenuta e ben progettata è possibile limitare la superficie di attacco dell'assembly.

Inizio paginaInizio pagina

Considerazioni sulla progettazione delle classi

Oltre a utilizzare un'interfaccia pubblica minima e ben definita è possibile ridurre ulteriormente la superficie di attacco dell'assembly progettando classi protette. Le classi protette rispondono ai principi di una solida progettazione orientata a oggetti, impediscono l'ereditarietà laddove non è necessaria e consentono di limitare chi e quale codice può chiamarle. Attenendosi alle seguenti raccomandazioni è possibile progettare classi protette:

Limitare la visibilità di classi e membri

Proteggere le classi non di base

Circoscrivere gli utenti a cui è consentito eseguire chiamate al codice

Esporre i campi mediante proprietà

Limitare la visibilità di classi e membri

Utilizzare il modificatore di accesso public solo per i tipi e i membri compresi nell'interfaccia pubblica dell'assembly. In questo modo la superficie di attacco si riduce immediatamente, perché il codice esterno all'assembly può accedere solo ai tipi public. L'accesso a tutti gli altri tipi e membri deve essere limitato il più possibile. Se possibile, utilizzare il modificatore di accesso private. Utilizzare protected solo se il membro deve essere accessibile per le classi derivate e internal solo se deve esserlo per le altre classi dello stesso assembly.

Nota: anche C# consente di combinare protected e internal, creando un membro protected internal per limitare l'accesso all'assembly corrente o ai tipi derivati.

Proteggere le classi non di base

Se una classe non è progettata come classe base, è possibile impedire l'ereditarietà utilizzando la parola chiave sealed come indicato nell'esempio di codice seguente.

public sealed class NobodyDerivesFromMe
{}

Per le classi base, è possibile stabilire restrizioni relative a quale altro codice può derivare dalla classe utilizzando le richieste di ereditarietà della protezione dall'accesso di codice. Per ulteriori informazioni, consultare la sezione relativa all'autorizzazione del codice, nel modulo 8 "Protezione dall'accesso di codice".

Circoscrivere gli utenti a cui è consentito eseguire chiamate al codice

Annotare classi e metodi con richieste di autorizzazione identità principale dichiarative per controllare quali utenti possono eseguire chiamate alle classi e ai membri delle classi. Nell'esempio che segue, solo i membri del gruppo Windows specificato possono accedere alla classe Orders. Un attributo a livello di classe come questo si applica a tutti i membri della classe. Le richieste di autorizzazione identità principale dichiarative possono essere utilizzate anche sui singoli metodi. Gli attributi a livello di metodo prevalgono sugli attributi a livello di classe.

[PrincipalPermission(SecurityAction.Demand, 
                     Role=@"DomainName\WindowsGroup")]
public sealed class Orders()
{
}

Esporre i campi mediante proprietà

Tutti i campi devono essere resi private. Per rendere un valore di campo accessibile per tipi esterni, utilizzare una proprietà di sola lettura o di lettura/scrittura. Le proprietà consentono di aggiungere ulteriori vincoli, quali la convalida dell'input o le richieste di autorizzazione, come illustrato nel seguente esempio di codice.

public sealed class MyClass
{
  private string field; // field is private
  // Only members of the specified group are able to 
  // access this public property
  [PrincipalPermission(SecurityAction.Demand, 
          Role=@"DomainName\WindowsGroup")]
  public string Field
  {
    get {
        return field;
    }
  }
}
Inizio paginaInizio pagina

Nomi sicuri

Un nome sicuro di un assembly consiste in un nome testuale, un numero di versione, facoltativamente una lingua, una chiave pubblica (che spesso rappresenta l'organizzazione di sviluppo) e una firma digitale. È possibile visualizzare i vari componenti del nome sicuro esaminando in che modo si fa riferimento a un assembly con nome sicuro in Machine.config.

L'esempio che segue mostra in che modo si fa riferimento all'assembly System.Web in Machine.config. Nell'esempio, l'attributo assembly contiene il nome testuale, la versione, la lingua e il token della chiave pubblica, che è una forma abbreviata della chiave pubblica.

<add assembly="System.Web, Version=1.0.5000.0, Culture=neutral, 
               PublicKeyToken=b03f5f7f11d50a3a" />

L'opportunità di assegnare un nome sicuro a un assembly dipende dal modo in cui si desidera che venga utilizzato. Alcuni dei principali motivi che consigliano l'aggiunta di un nome sicuro a un assembly sono i seguenti:

Si intende garantire che codice con attendibilità parziale non possa eseguire chiamate all'assembly.
Common language runtime impedisce che codice con attendibilità parziale esegua chiamate a un assembly con nome sicuro, aggiungendo richieste in fase di linking per le autorizzazioni FullTrust. Questo comportamento può essere evitato utilizzando AllowPartiallyTrustedCallersAttribute (APTCA), anche se è consigliabile farlo con cautela.

Per ulteriori informazioni su APTCA, consultare la relativa sezione nel modulo 8 "Protezione dall'accesso di codice".

L'assembly è progettato per essere condiviso tra più applicazioni.
In questo caso, l'assembly deve essere installato nella cache dell'assembly globale. Ciò richiede un nome sicuro. La cache dell'assembly globale supporta il controllo delle versioni side-by-side che consente l'associazione di diverse applicazioni a diverse versioni dello stesso assembly.

Si intende utilizzare il nome sicuro come prova della protezione.
La parte del nome sicuro con la chiave pubblica fornisce una prova efficace dal punto di vista della crittografia per la protezione dall'accesso di codice. È possibile utilizzare il nome sicuro per identificare in modo univoco l'assembly quando si configura il criterio di protezione dall'accesso di codice per concedere all'assembly specifiche autorizzazioni di accesso al codice. Altri tipi di prove valide dal punto di vista della crittografia comprendono la firma Authenticode (se sono stati utilizzati certificati X.509 per firmare l'assembly) e l'hash dell'assembly.

Nota: le prove Authenticode non vengono caricate dall'host ASP.NET, il che significa che non è possibile utilizzarle per definire un criterio di protezione per le applicazioni Web ASP.NET.

Per ulteriori informazioni sui tipi di prove, consultare il modulo 8 "Protezione dall'accesso di codice".

Vantaggi dei nomi sicuri per la protezione

I nomi sicuri forniscono una serie di vantaggi per la protezione oltre che per il controllo delle versioni:

Gli assembly con nomi sicuri sono firmati mediante una firma digitale e in questo modo vengono protetti dalle modifiche. Qualsiasi manomissione fa sì che il processo di verifica che ha luogo al caricamento dell'assembly non riesca. Viene generata un'eccezione e l'assembly non viene caricato.

Gli assembly con nomi sicuri non possono essere chiamati da codice con attendibilità parziale, a meno che non si aggiunga specificamente AllowPartiallyTrustedCallersAttribute (APTCA).

Nota: se si utilizza APTCA, consultare il modulo 8 "Protezione dall'accesso di codice", in cui vengono fornite linee guida aggiuntive per migliorare ulteriormente la protezione degli assembly.

I nomi sicuri forniscono prove valide dal punto di vista della crittografia per la valutazione dei criteri di protezione dall'accesso di codice. Ciò consente agli amministratori di concedere autorizzazioni a assembly specifici e agli sviluppatori di utilizzare StrongNameIdentityPermission per circoscrivere il codice che può eseguire chiamate a un membro pubblico o derivare da una classe non sealed.

Utilizzo di nomi sicuri

.NET Framework include l'utilità Sn.exe che consente di assegnare nomi sicuri agli assembly. Per aggiungere un nome sicuro a un assembly non è richiesto un certificato X.509.

Per assegnare un nome sicuro a un assembly

1.

Generare il file di chiave nella directory del progetto dell'assembly utilizzando il comando seguente:

sn.exe -k keypair.snk

2.

Aggiungere un attributo AssemblyKeyFile ad Assemblyinfo.cs per fare riferimento al file di chiave generato, come illustrato nell'esempio di codice che segue.

// The keypair file is usually placed in the project directory
[assembly: AssemblyKeyFile(@"..\..\keypair.snk")]

Firma ritardata

Ritardare la firma degli assembly durante lo sviluppo dell'applicazione è una valida procedura di protezione. Ne deriva che la chiave pubblica viene collocata nell'assembly, il che significa che sarà disponibile come prova per il criterio di protezione dall'accesso di codice, ma l'assembly non è firmato e come tale non è a prova di manomissione. Dal punto di vista della protezione, la firma ritardata presenta due vantaggi principali:

La chiave privata utilizzata per firmare l'assembly e creare la relativa firma digitale viene conservata e protetta in una posizione centrale. La chiave è accessibile solo da poche unità di personale attendibili. Di conseguenza, si riduce considerevolmente la possibilità che la chiave privata venga violata.

Tutti i membri del team di sviluppo adoperano un'unica chiave pubblica, che può essere utilizzata per rappresentare l'organizzazione di sviluppo o l'autore del software, in alternativa all'utilizzo da parte di ciascuno sviluppatore di una propria coppia di chiavi, pubblica e privata, tipicamente generata mediante il comando sn –k.

Per creare un file chiave pubblica per la firma ritardata

Questa procedura viene eseguita dall'autorità di firma per creare un file chiave pubblica che gli sviluppatori possono utilizzare per la firma ritardata dei propri assembly.

1.

Creare una coppia di chiavi per l'organizzazione.

sn.exe-k keypair.snk

2.

Estrarre la chiave pubblica dal file della coppia di chiavi.

sn-p keypair.snk publickey.snk

3.

Proteggere Keypair.snk, che contiene sia la chiave pubblica che la chiave privata, ad esempio memorizzandolo in un disco floppy o un CD e proteggendolo fisicamente.

4.

Publickey.snk deve essere disponibile per tutti gli sviluppatori. È possibile, ad esempio, posizionarlo in una condivisione di rete.

Per la firma ritardata di un assembly

La procedura viene eseguita dagli sviluppatori.

1.

Aggiungere un attributo a livello di assembly per fare riferimento al file di chiave che contiene solo la chiave pubblica.

// The keypair file is usually placed in the project directory
[assembly: AssemblyKeyFile(@"..\..\publickey.snk")]

2.

Aggiungere l'attributo seguente per indicare la firma ritardata.

[assembly: AssemblyDelaySign(true)]

3.

Il processo di firma ritardata e l'assenza di una firma dell'assembly comportano che al caricamento dell'assembly la verifica non riuscirà. Per risolvere il problema, utilizzare i comandi seguenti nei computer di sviluppo e di prova.

Per disattivare la verifica per uno specifico assembly, utilizzare il comando seguente.

sn -Vr assembly.dll

Per disattivare la verifica per tutti gli assembly con una particolare chiave pubblica, utilizzare il comando seguente.

sn -Vr *,publickeytoken

Per estrarre la chiave pubblica e il token della chiave (un hash troncato della chiave pubblica), utilizzare il comando seguente.

sn -Tp assembly.dll

Nota: utilizzare l'opzione –T (in maiuscolo).

4.

Per completare il processo di firma e creare una firma digitale in modo da proteggere l'assembly da manomissioni, eseguire il comando seguente. L'operazione richiede la chiave privata e pertanto di solito viene eseguita nell'ambito del processo formale di sviluppo e rilascio.

sn-r assembly.dll keypair.snk

ASP.NET e i nomi sicuri

Al momento non è ancora possibile utilizzare nomi sicuri per gli assembly di pagine Web ASP.NET, a causa del modo in cui vengono compilate dinamicamente. Anche utilizzando un file di codice sottostante per creare un assembly precompilato contenente il codice di implementazione per la classe della pagina, ASP.NET crea e compila in modo dinamico una classe che contiene gli elementi visivi della pagina. Tale classe deriva dalla classe della pagina, e ciò comporta ancora una volta l'impossibilità di utilizzare nomi sicuri.

Nota: è possibile assegnare nomi sicuri a qualsiasi altro assembly chiamato dal codice della pagina Web, ad esempio ad assembly contenenti codice per l'accesso ai dati o alle risorse o alla logica aziendale, sebbene in questo caso l'assembly debba essere posizionato nella cache dell'assembly globale.

Requisiti della cache dell'assembly globale

Tutti gli assembly con nomi sicuri chiamati da un'applicazione Web ASP.NET configurata per l'attendibilità parziale devono essere installati nella cache dell'assembly globale. Questo perché l'host ASP.NET carica tutti gli assembly con nomi sicuri come indipendenti dal dominio.

Il codice di un assembly indipendente dal dominio viene condiviso da tutti i domini applicazione nel processo ASP.NET. Questo crea problemi se un unico assembly con nome sicuro è utilizzato da più applicazioni Web e ciascuna applicazione gli concede autorizzazioni diverse o se la concessione di autorizzazioni varia tra i riavvii dei domini applicazione. In questa situazione può essere visualizzato il messaggio di errore seguente: "L'insieme delle autorizzazioni di protezione dell'assembly <assembly>.dll non può essere applicato a domini di applicazioni diverse".

Per evitare questo errore occorre posizionare gli assembly con nomi sicuri nella cache dell'assembly globale e non nella directory \bin dell'applicazione.

Authenticode e nomi sicuri a confronto

Authenticode e i nomi sicuri sono due modi diversi per aggiungere una forma digitale a un assembly. Authenticode consente di firmare un assembly utilizzando un certificato X.509. Per farlo, si utilizza l'utilità Signcode.exe, che aggiunge all'assembly la parte della chiave pubblica di un certificato X.509 completo. In questo modo viene garantita l'attendibilità attraverso tutta la catena di certificati e autorità di certificazione. Con Authenticode, invece, l'implementazione dell'attendibilità dell'autore è complessa e richiede la comunicazione in rete durante la verifica dell'identità dell'autore.

Le firme Authenticode e i nomi sicuri sono stati sviluppati per risolvere problemi distinti e non vanno confusi. In specifico:

Un nome sicuro identifica in modo univoco un assembly.

Una firma Authenticode identifica in modo univoco un autore di codice.
Le firme Authenticode devono essere utilizzate per il codice mobile, ad esempio per controlli e file eseguibili scaricati con Internet Explorer, così da fornire attendibilità e integrità dell'autore.

È possibile configurare criteri di protezione dall'accesso di codice utilizzando sia nomi sicuri che firme Authenticode per concedere autorizzazioni a specifici assembly. L'oggetto Publisher evidence ottenuto da una firma Authenticode viene tuttavia creato solo dall'host Internet Explorer e non da quello ASP.NET. Pertanto, sul lato server non è possibile utilizzare la firma Authenticode per identificare uno specifico assembly attraverso un gruppo di codice e devono essere utilizzati invece nomi sicuri.

Per ulteriori informazioni sulla protezione dall'accesso di codice, sui relativi criteri e sui gruppi di codice, consultare il modulo 8 "Protezione dall'accesso di codice".

Nella tabella 7.1 sono messe a confronto le funzionalità dei nomi sicuri e delle firme Authenticode.

Tabella 7.1 Nomi sicuri e firme Authenticode a confronto

FunzionalitàNome sicuroAuthenticode

Identificazione univoca dell'assembly

No

Identificazione univoca dell'autore

Non necessariamente. Dipende dal fatto che lo sviluppatore dell'assembly abbia utilizzato una chiave pubblica per rappresentare l'autore.

La chiave pubblica dell'autore può essere revocata

No

Controllo delle versioni

No

Univocità dello spazio dei nomi e del nome tipo

No

Integrità (controlla che l'assembly non sia stato alterato)

Prove utilizzate come input per il criterio di protezione dall'accesso di codice

Host IE: sì.
Host ASP.NET: no

Input dell'utente richiesto per la decisione sul trust

No

Sì (finestra di dialogo pop-up)

Inizio paginaInizio pagina

Autorizzazione

Negli assembly è possibile utilizzare due tipi di autorizzazione per controllare l'accesso a classi e membri delle classi:

Autorizzazione basata sui ruoli, per autorizzare l'accesso in base all'identità dell'utente e all'appartenenza a un ruolo. Quando si utilizza l'autorizzazione basata sui ruoli in assembly che fanno parte di un servizio Web o di un'applicazione Web ASP.NET, si autorizza l'identità che è rappresentata da un oggetto IPrincipal associato alla richiesta Web corrente e disponibile attraverso Thread.CurrentPrincipal e HttpContext.Current.User. Si tratta dell'identità dell'utente finale autenticato o dell'utente Internet anonimo. Per ulteriori informazioni sull'utilizzo di autorizzazioni basate sull'identità principale nelle applicazioni Web, consultare la sezione "Autorizzazione" nel modulo 10 "Creazione di comandi e pagine Web ASP.NET protetti".

Protezione dall'accesso di codice per autorizzare il codice chiamante, sulla base delle prove, ad esempio il nome sicuro o la posizione di un assembly. Per ulteriori informazioni, consultare la sezione "Autorizzazione" nel modulo 8 "Protezione dall'accesso di codice".

Inizio paginaInizio pagina

Gestione delle eccezioni

Nei messaggi di errore restituiti al client non devono essere rivelati dettagli relativi all'implementazione dell'applicazione. Queste informazioni possono aiutare utenti malintenzionati a pianificare attacchi all'applicazione. Per una corretta gestione delle eccezioni è necessario:

Utilizzare una gestione delle eccezioni strutturata.

Non attivare la registrazione dei dati sensibili.

Non rivelare informazioni riservate sul sistema o sull'applicazione.

Considerare i problemi legati ai filtri eccezioni.

Considerare il ricorso a una struttura per la gestione delle eccezioni.

Utilizzare una gestione delle eccezioni strutturata

Microsoft Visual C# e Microsoft Visual Basic .NET forniscono costrutti per la gestione delle eccezioni strutturata. C# offre il costrutto try / catch e finally. È possibile proteggere il codice posizionandolo all'interno di blocchi try e implementare blocchi catch per registrare ed elaborare le eccezioni. Inoltre, il costrutto finally consente di garantire che le risorse critiche del sistema, ad esempio le connessioni, vengano chiuse indipendentemente dal verificarsi di una condizione di eccezione.

try
{
   // Code that could throw an exception
}
catch (SomeExceptionType ex)
{
   // Code to handle the exception and log details to aid
   // problem diagnosis
}
finally
{
   // This code is always run, regardless of whether or not
   // an exception occurred. Place clean up code in finally
   // blocks to ensure that resources are closed and/or released.
}

Utilizzare una gestione delle eccezioni strutturata, invece di restituire codici di errore dai metodi, perché è facile dimenticare di controllare un codice restituito e di conseguenza ricadere in una modalità non protetta.

Non attivare la registrazione dei dati sensibili

Gli ampi dettagli inclusi negli oggetti Exception sono preziosi tanto per gli sviluppatori che per i pirati informatici. Registrare i dettagli nel server, scrivendoli nel registro eventi, per facilitare la diagnosi dei problemi. Dati sensibili o riservati quali le password degli utenti non dovrebbero essere registrati. Inoltre, impedire la propagazione dei dettagli delle eccezioni al client oltre i confini dell'applicazione, come descritto nel prossimo argomento.

Non rivelare informazioni riservate sul sistema o sull'applicazione

Non rivelare troppe informazioni al chiamante. I dettagli relativi alle eccezioni possono includere numeri di versione del sistema operativo e di .NET Framework, nomi di metodi, nomi di computer, istruzioni SQL, stringhe di connessione e altre informazioni molto utili per i pirati informatici. Registrare nel server i messaggi di errore dettagliati, restituendo invece all'utente finale messaggi di errore generici.

Nel contesto di un servizio Web o di un'applicazione Web ASP.NET è possibile ottenere questo risultato configurando nel modo appropriato l'elemento <customErrors>. Per ulteriori informazioni, consultare il modulo 10 "Creazione di comandi e pagine Web ASP.NET protetti".

Considerare i problemi legati ai filtri eccezioni

Se vengono utilizzati filtri eccezioni nel codice, quest'ultimo è potenzialmente vulnerabile a problemi di protezione, perché il codice di un filtro a un livello più alto dello stack di chiamate può essere eseguito prima del codice di un blocco finally. Non fare affidamento sulle modifiche dello stato nel blocco finally, perché esse non avranno luogo prima dell'esecuzione del filtro eccezione. Si consideri, ad esempio, il codice seguente:

// Place this code into a C# class library project
public class SomeClass
{
  public void SomeMethod()
  {
    try
    {
      // (1) Generate an exception
      Console.WriteLine("1> About to encounter an exception condition");
      // Simulate an exception
      throw new Exception("Some Exception");
    }
    // (3) The finally block
    finally
    {
      Console.WriteLine("3> Finally");
    }
  }
}

// Place this code into a Visual Basic.NET console application project and
// reference the above class library code
Sub Main()
    Dim c As New SomeClass
    Try
        c.SomeMethod()
    Catch ex As Exception When Filter()
        ' (4) The exception is handled
        Console.WriteLine("4> Main: Catch ex as Exception")
    End Try
End Sub

' (2) The exception filter
Public Function Filter() As Boolean
    ' Malicious code could do something here if you are relying on a state
    ' change in the Finally block in SomeClass in order to provide security
    Console.WriteLine("2> Filter")
    Return True ' Indicate that the exception is handled
End Function

Nell'esempio precedente, per chiamare il codice della libreria di classi C# viene utilizzato Visual Basic .NET perché, diversamente da C#, supporta i filtri eccezione.

Se si creano due progetti e quindi si esegue il codice, si ottiene il risultato seguente:

1> About to encounter an exception condition
2> Filter
3> Finally
4> Main: Catch ex as Exception

Come si vede, il filtro eccezione viene eseguito prima del codice del blocco finally. Se nel blocco finally il codice imposta uno stato che influisce su una decisione relativa alla protezione, codice dannoso che esegua chiamate al codice potrebbe aggiungere un filtro eccezione per sfruttare questa vulnerabilità.

Considerare il ricorso a una struttura per la gestione delle eccezioni

Un sistema di gestione delle eccezioni formalizzato consente di migliorare la supportabilità e la manutenibilità del sistema e di garantire che le eccezioni vengano rilevate, registrate ed elaborate in modo uniforme.

Per informazioni su come creare una struttura per la gestione delle eccezioni e sulle procedure consigliate per la gestione delle eccezioni per le applicazioni .NET, consultare l'articolo MSDN "Exception Management Architecture Guide", disponibile all'indirizzo http://msdn.microsoft.com/library/en-us/dnbda/html/exceptdotnet.asp (in inglese).

Inizio paginaInizio pagina

I/O file

La canonicalization è uno dei principali problemi per il codice che accede al file system. Potendo scegliere, è opportuno non basare le decisioni di protezione sui nomi dei file di input, perché un unico nome file può essere rappresentato in molti modi. Se è necessario che il codice acceda a un file con un nome file fornito da un utente, applicare le misure necessarie affinché l'assembly non possa essere utilizzato da un utente malintenzionato per accedere a dati sensibili o sovrascriverli.

Attenendosi alle seguenti raccomandazioni è possibile migliorare la protezione dell'I/O file:

Evitare input non attendibile per i nomi file.

Non considerare attendibili le variabili di ambiente.

Convalidare i parametri di input.

Vincolare l'I/O file nel contesto dell'applicazione.

Evitare input non attendibile per i nomi file

Evitare di scrivere codice che accetti input di file o percorso dal chiamante e utilizzare invece nomi di file e di percorso fissi durante le operazioni di lettura e scrittura dei dati. In questo modo il codice non potrà essere forzato ad accedere a file arbitrari.

Non considerare attendibili le variabili di ambiente

Utilizzare percorsi assoluti per i file ogni volta che è possibile. Non considerare attendibili le variabili di ambiente per la costruzione dei percorsi dei file, perché non è possibile garantirne il valore.

Convalida dei nomi file di input

Se è necessario ricevere nomi file di input dal chiamante, questi devono essere formulati secondo uno schema rigido in modo che sia possibile stabilire se sono validi. Nello specifico, la convalida dei percorsi dei file di input implica due aspetti. È necessario:

Controllare che i nomi del file system siano validi.

Controllare che il percorso sia valido, secondo la definizione del contesto dell'applicazione. Ad esempio, controllare se si trovano nella gerarchia di directory dell'applicazione.

Per convalidare il nome del file e del percorso, utilizzare il metodo System.IO.Path.GetFullPath, come illustrato nell'esempio di codice che segue. Questo metodo consente anche di rendere canonico il nome file fornito.

using System.IO;

public static string ReadFile(string filename)
{
  // Obtain a canonicalized and valid filename
  string name = Path.GetFullPath(filename);
  // Now open the file
}

Nell'ambito del processo di canonicalization, mediante GetFullPath vengono eseguiti i seguenti controlli:

Che il nome file non contenga caratteri non validi, secondo la definizione di Path.InvalidPathChars.

Che il nome file rappresenti un file e non un altro tipo di dispositivo come un'unità fisica, una named pipe, un mail slot o una periferica DOS quale LPT1, COM1, AUX o altro.

Che il nome del file e del percorso combinati non siano troppo lunghi.

Elimina i caratteri ridondanti, ad esempio i punti finali.

Rifiuta i nomi file in cui è utilizzato il formato //?/.

Vincolare l'I/O file nel contesto dell'applicazione

Dopo aver verificato che il nome file del file system sia valido, spesso occorre controllare che esso sia valido nel contesto della specifica applicazione. Può essere necessario, ad esempio, controllare che si trovi all'interno della gerarchia di directory dell'applicazione e accertarsi che il codice non possa accedere a file arbitrari nel file system. Per ulteriori informazioni sulle modalità di utilizzo della protezione dall'accesso di codice per vincolare l'I/O file, consultare la sezione relativa all'I/O file nel modulo 8 "Protezione dall'accesso di codice".

Inizio paginaInizio pagina

Registro eventi

Nella scrittura di codice per la registrazione degli eventi è necessario tener conto dei pericoli di manomissione e diffusione non autorizzata di informazioni. Occorre, ad esempio, verificare la possibilità che un pirata informatico recuperi dati sensibili accedendo al registro eventi oppure copra le sue tracce eliminando i file registro o cancellando specifici record.

L'accesso diretto ai registri eventi mediante strumenti per l'amministrazione del sistema come il Visualizzatore eventi è limitato dalla protezione di Windows. La principale preoccupazione dovrebbe essere quella di garantire che il codice scritto per la registrazione degli eventi non possa essere utilizzato da un utente malintenzionato per l'accesso non autorizzato al registro eventi.

Per impedire la diffusione non autorizzata di dati sensibili, la migliore precauzione consiste nel non registrarli affatto. Le credenziali degli account, ad esempio, non dovrebbero essere registrate. Inoltre, il codice non può essere sfruttato per leggere record esistenti o per eliminare registri eventi se l'unica azione che compie è la scrittura di nuovi record mediante EventLog.WriteEvent. In questo caso il principale pericolo da evitare è che un chiamante malintenzionato esegua una enorme quantità di chiamate nel tentativo di forzare un ciclo del file di registro in modo da sovrascrivere le precedenti voci occultare coprire così le proprie tracce. L'approccio più efficace a questo problema consiste nell'utilizzare un meccanismo fuori banda, ad esempio impiegando gli strumenti di Windows per avvisare gli operatori quando il registro eventi sta per raggiungere la soglia stabilita.

Infine, è possibile utilizzare la protezione dall'accesso di codice e EventLogPermission per porre vincoli specifici sulle attività che il codice può eseguire quando accede al registro eventi. Se, ad esempio, il codice deve unicamente poter leggere record dal registro eventi, sarebbe opportuno stabilire una limitazione autorizzando la sola esplorazione mediante EventLogPermission. Per ulteriori informazioni su come vincolare il codice per la registrazione di eventi, consultare la sezione "Registro eventi" nel modulo 8 "Protezione dall'accesso di codice".

Inizio paginaInizio pagina

Registro di sistema

Il Registro di sistema rappresenta una posizione protetta per l'archiviazione dei dati di configurazione sensibili delle applicazioni, ad esempio delle stringhe di connessione al database crittografate. I dati di configurazione possono essere archiviati nell'unica chiave per il computer locale (HKEY_LOCAL_MACHINE) o nella chiave utente corrente (HKEY_CURRENT_USER). Sia in un modo che nell'altro, è necessario che i dati vengano crittografati con DPAPI e che vengano archiviati i dati crittografati e non quelli in testo non crittografato.

HKEY_LOCAL_MACHINE

Se i dati di configurazione vengono archiviati in HKEY_LOCAL_MACHINE, potenzialmente qualsiasi processo nel computer locale può accedervi. Per limitare l'accesso è opportuno applicare un elenco di controllo di accesso (ACL) restrittivo alla specifica chiave del Registro di sistema, in modo da limitare l'accesso agli amministratori e al processo specifico o token del thread. L'utilizzo di HKEY_LOCAL_MACHINE rende effettivamente più facile memorizzare i dati di configurazione al momento dell'installazione e aggiornarli successivamente.

HKEY_CURRENT_USER

Se i requisiti di protezione impongono una soluzione di archiviazione ancora meno accessibile, è possibile utilizzare una chiave in HKEY_CURRENT_USER. Questo approccio implica che non è necessario configurare esplicitamente elenchi di controllo di accesso, perché l'accesso alla chiave dell'utente corrente è automaticamente limitato in base all'identità di processo.

HKEY_CURRENT_USER consente una maggiore limitazione dell'accesso perché un processo può accedere alla chiave utente corrente soltanto se viene caricato il profilo utente associato al thread o al token di processo corrente.

La versione 1.1 di .NET Framework carica il profilo utente per l'account ASPNET in Windows 2000. In Windows Server 2003 il profilo per questo account viene caricato solo se è utilizzato il modello dei processi ASP.NET. Esso non viene caricato esplicitamente da Internet Information Services (IIS) 6 se il modello dei processi IIS 6 è utilizzato in Windows Server 2003.

Nota: la versione 1.0 di .NET Framework non carica il profilo utente ASPNET, caratteristica che riduce la praticità dell'opzione HKEY_CURRENT_USER.

Lettura dal Registro di sistema

Il frammento di codice riportato di seguito mostra come leggere una stringa di connessione al database crittografata dalla chiave HKEY_CURRENT_USER utilizzando la classe Microsoft.Win32.Registry.

using Microsoft.Win32;
public static string GetEncryptedConnectionString()
{
  return (string)Registry.
                 CurrentUser.
                 OpenSubKey(@"SOFTWARE\YourApp").
                 GetValue("connectionString");
}

Per ulteriori informazioni sulle modalità di utilizzo della protezione dall'accesso di codice RegistryPermission per vincolare il codice di accesso al Registro di sistema, ad esempio limitandolo a specifiche chiavi, consultare la sezione "Registro di sistema" nel modulo 8 "Protezione dall'accesso di codice".

Inizio paginaInizio pagina

Accesso ai dati

Due dei principali fattori da considerare quando il codice accede a un database sono: come gestire le stringhe di connessione al database in modo protetto e come costruire istruzioni SQL e convalidare l'input per impedire gli attacchi di tipo SQL injection. Inoltre, quando si scrive codice per l'accesso ai dati occorre considerare i requisiti relativi alle autorizzazioni del provider di dati ADO.NET che si è scelto. Per ulteriori informazioni su questi ed altri problemi relativi all'accesso ai dati, consultare il modulo 14 "Creazione di codice di accesso ai dati protetto".

Per informazioni sull'utilizzo di SqlClientPermission per limitare l'accesso ai dati in SQL Server mediante il provider di dati SQL Server ADO.NET, consultare la sezione relativa all'accesso ai dati nel modulo 8 "Protezione dall'accesso di codice".

Inizio paginaInizio pagina

Codice non gestito

Il codice gestito, .NET Framework e Common Language Runtime eliminano numerose importanti vulnerabilità relative alla protezione che spesso sono presenti nel codice non gestito. La verifica del codice indipendente dai tipi è un valido esempio dell'utilità di .NET Framework. Rende praticamente impossibile che nel codice gestito si verifichino overflow del buffer, eliminando quasi del tutto il pericolo di inserimento di codice basato sullo stack.

Tuttavia, in presenza di componenti COM o DLL Win32 da riutilizzare è consigliabile avvalersi di Platform Invocation Services (P/Invoke) o livelli COM Interop per associarli all'assembly.

Quando viene eseguita una chiamata a codice non gestito, è essenziale che il codice gestito convalidi tutti i parametri di input passati all'API non gestita, come difesa contro potenziali overflow del buffer. Anche la gestione dei parametri di output passati dall'API non gestita richiede attenzione.

È bene isolare le chiamate a codice non gestito in un assembly del wrapper separato. In questo modo è possibile eseguire codice con privilegi di alto livello nel sandbox e limitare a uno specifico assembly i requisiti relativi all'autorizzazione di protezione per l'accesso di codice. Per ulteriori dettagli sul sandboxing e per altre linee guida relative alla protezione dall'accesso di codice, da applicare nel caso di chiamate a codice non gestito, consultare la sezione "Codice non gestito" nel modulo 8 "Protezione dall'accesso di codice". Attenendosi alle seguenti raccomandazioni è possibile migliorare la protezione delle chiamate ad API non gestite, senza utilizzare tecniche di codifica esplicite per la protezione dall'accesso di codice:

Convalidare i parametri delle stringhe di input e di output.

Convalidare i limiti della matrice.

Verificare la lunghezza dei percorsi dei file.

Compilare il codice non gestito con il parametro /GS.

Controllare se nel codice non gestito sono presenti API "pericolose".

Convalidare i parametri delle stringhe di input e di output

I parametri di stringa passati ad API non gestite sono una delle principali cause di overflow del buffer. Verificare la lunghezza delle stringhe di input contenute nel codice del wrapper per controllare che non superi il limite definito dall'API non gestita. Se l'API non gestita accetta puntatori a caratteri è possibile che non si conosca la massima lunghezza consentita per le stringhe, a meno di non avere accesso all'origine non gestita. Una vulnerabilità comune è, ad esempio, la seguente:

void SomeFunction( char *pszInput )
{
  char szBuffer[10];
  // Look out, no length checks. Input is copied straight into the buffer
  // Check length or use strncpy
  strcpy(szBuffer, pszInput);
  . . .
}

Se non è possibile esaminare il codice non gestito perché non se ne è proprietari, occorre eseguire prove rigorose sull'API, passando deliberatamente stringhe di input lunghe.

Se nel codice la ricezione di stringhe passate da un'API non gestita avviene mediante StringBuilder, controllare che sia in grado di contenere la stringa più lunga che l'API non gestita può restituire.

Convalidare i limiti della matrice

Se viene passato input a un'API non gestita utilizzando una matrice, controllare che il wrapper gestito verifichi che non venga superata la capacità della matrice.

Verificare la lunghezza dei percorsi dei file

Se l'API non gestita accetta un nome e un percorso di file, controllare che esso non superi i 260 caratteri. Questo limite è definito dalla costante Win32 MAX_PATH. L'allocazione di buffer di questa lunghezza per manipolare i percorsi di file è molto comune nel codice non gestito.

Nota: la lunghezza massima consentita per i nomi di directory e le chiavi del Registro di sistema è di 248 caratteri.

Compilare il codice non gestito con il parametro /GS

Se si è proprietari del codice non gestito, è opportuno compilarlo utilizzando il parametro /GS per attivare probe dello stack per agevolare il rilevamento degli overflow del buffer. Per ulteriori informazioni sul parametro /GS, vedere l'articolo 325483 della Microsoft Knowledge Base, "WebCast: Compiler Security Checks: The /GS compiler switch" all'indirizzo: http://support.microsoft.com/default.aspx?scid=kb;EN-US;q325483 (in inglese).

Controllare se nel codice non gestito sono presenti API "pericolose"

Se si ha accesso al codice sorgente per il codice non gestito a cui si esegue la chiamata, è opportuno sottoporlo a una verifica esauriente, prestando particolare attenzione alla gestione dei parametri, in modo da impedire gli overflow del buffer e l'utilizzo di API potenzialmente pericolose. Per ulteriori informazioni, consultare il modulo 21 "Analisi del codice".

Inizio paginaInizio pagina

Delegati

I delegati sono l'equivalente gestito dei puntatori a funzione indipendenti dai tipi e vengono utilizzati in .NET Framework per supportare eventi. L'oggetto delegato mantiene un riferimento a un metodo, che viene chiamato quando il delegato viene richiamato. Nel caso degli eventi è possibile registrare più metodi come gestori eventi. Quando l'evento si verifica vengono chiamati tutti i gestori eventi.

Rifiuto di delegati da origini non attendibili

Se l'assembly espone un delegato o un evento, è necessario tenere presente che qualsiasi codice può associare un metodo al delegato e non è possibile sapere in anticipo quali operazioni eseguirà. Il criterio più sicuro consiste nel non accettare delegati da chiamanti non attendibili. Nel caso di un assembly con nome sicuro che non include AllowPartiallyTrustedCallersAttribute, solo chiamanti con attendibilità totale possono passare un delegato.

Se l'assembly supporta chiamanti con attendibilità parziale, è necessario considerare il pericolo aggiuntivo rappresentato dalla possibilità che venga passato un delegato mediante codice dannoso. Per le tecniche atte a mitigare questo rischio, consultare la sezione "Delegati" nel modulo 8 "Protezione dall'accesso di codice".

Inizio paginaInizio pagina

Serializzazione

Può essere necessario aggiungere a una classe il supporto della serializzazione, se occorre poterne eseguire il marshalling per valore oltre il confine dei servizi remoti .NET (vale a dire, tra domini applicazione, processi o computer) o se si desidera poter rendere persistente lo stato dell'oggetto per creare un flusso di dati flat, ad esempio per la memorizzazione nel file system.

Per impostazione predefinita, non è possibile serializzare le classi. Una classe può essere serializzata se è contrassegnata con SerializableAttribute o se deriva da ISerializable. Se si utilizza la serializzazione:

Non serializzare i dati sensibili.

Convalidare i flussi di dati serializzati.

Non serializzare i dati sensibili

Idealmente, la serializzazione non dovrebbe essere supportata se la classe contiene dati sensibili. Se la classe deve essere necessariamente serializzata e contiene dati sensibili, evitare di serializzare i campi che li contengono. A questo scopo, implementare ISerializable per controllare la serializzazione oppure decorare i campi che contengono dati sensibili con l'attributo [NonSerialized]. Per impostazione predefinita, tutti i campi privati e pubblici vengono serializzati.

L'esempio che segue illustra come utilizzare l'attributo [NonSerialized] per garantire che un campo specifico contenente dati sensibili non possa essere serializzato.

[Serializable]
public class Employee {
  // OK for name to be serialized
  private string name;
  // Prevent salary being serialized
  [NonSerialized] private double annualSalary;
  . . .
}

In alternativa, implementare l'interfaccia ISerializable e controllare in modo esplicito il processo di serializzazione. Se è necessario serializzare uno o più elementi di dati sensibili, valutare l'opportunità di crittografare preliminarmente i dati. Il codice che deserializza l'oggetto deve avere accesso alla chiave di decrittografia.

Convalidare i flussi di dati serializzati

Quando si crea un'istanza di un oggetto da un flusso di dati serializzato, non si deve presumere che il flusso contenga dati validi. Per evitare che dati potenzialmente dannosi vengano inseriti nell'oggetto è necessario convalidare tutti i campi quando vengono ricostituiti, come illustrato nell'esempio di codice seguente.

public void DeserializationMethod(SerializationInfo info, StreamingContext cntx)
{
  string someData = info.GetString("someName");
  // Use input validation techniques to validate this data.
}

Per ulteriori informazioni sulle tecniche di convalida dell'input, consultare la sezione "Script tra siti" nel modulo 10 "Creazione di comandi e pagine Web ASP.NET protetti".

Considerazioni sull'attendibilità parziale

Se il codice supporta chiamanti con attendibilità parziale, è necessario affrontare ulteriori pericoli. Codice dannoso potrebbe, ad esempio, passare un flusso di dati serializzato o tentare di serializzare i dati nell'oggetto. Per le tecniche atte a mitigare questi rischi, consultare la sezione "Serializzazione" nel modulo 8 "Protezione dall'accesso di codice".

Inizio paginaInizio pagina

Threading

Gli errori causati da condizioni di competizione nel codice a più thread possono provocare vulnerabilità di protezione e in genere codice instabile, soggetto a errori relativi agli intervalli. Nello sviluppo di assembly a più thread è utile tenere in considerazione le seguenti raccomandazioni:

Non memorizzare nella cache i risultati dei controlli di protezione.

Considerare i token di rappresentazione.

Sincronizzare i costruttori delle classi statiche.

Sincronizzare i metodi Dispose.

Non memorizzare nella cache i risultati dei controlli di protezione

Se il codice a più thread memorizza nella cache i risultati di un controllo di protezione, ad esempio in una variabile statica, il codice è potenzialmente vulnerabile, come illustrato nell'esempio che segue.

   public void AccessSecureResource()
   {
     _callerOK = PerformSecurityDemand();
     OpenAndWorkWithResource();
     _callerOK = false;
   }
   private void OpenAndWorkWithResource()
   {
     if (_callerOK)
       PerformTrustedOperation();
     else
     {
       PerformSecurityDemand();
       PerformTrustedOperation();
     }
   }

Se esistono altri percorsi per OpenAndWorkWithResource e un thread separato esegue la chiamata al metodo sullo stesso oggetto, è possibile per il secondo thread omettere la richiesta di protezione, perché rileva _callerOK=true, impostato da un altro thread.

Considerare i token di rappresentazione

Quando si crea un nuovo thread, esso assume il contesto di protezione definito dal token a livello di processo. In caso di rappresentazione di un thread padre durante la creazione di un nuovo thread, il token di rappresentazione non viene passato al nuovo thread.

Sincronizzare i costruttori delle classi statiche

Se si utilizzano costruttori delle classi statiche, è necessario accertarsi che non siano vulnerabili alle condizioni di competizione. Se, ad esempio, manipolano lo stato statico, aggiungendo la sincronizzazione dei thread è possibile evitare le potenziali vulnerabilità.

Sincronizzare i metodi Dispose

Se si sviluppano implementazioni non sincronizzate del metodo Dispose, il codice Dispose può essere chiamato più volte su diversi thread. Questa situazione è illustrata nell'esempio che segue.

void Dispose()
{
  if (null != _theObject)
  {
    ReleaseResources(_theObject);
    _theObject = null;
  }
}

Nell'esempio è possibile che due thread eseguano il codice prima che il primo thread abbia impostato il riferimento _theObject su Null. A seconda delle funzionalità fornite dal metodo ReleaseResources è possibile che si verifichino vulnerabilità di protezione.

Inizio paginaInizio pagina

Reflection

Mediante la reflection è possibile caricare gli assembly in modo dinamico, rilevare informazioni sui tipi ed eseguire codice. È inoltre possibile ottenere un riferimento a un oggetto e ottenere o impostare i relativi membri privati. Ne conseguono una serie di implicazioni per la protezione:

Se il codice utilizza la reflection su altri tipi, controllare che solo codice attendibile possa eseguire chiamate. Per autorizzare il codice chiamante, utilizzare richieste di autorizzazioni di protezione per l'accesso di codice. Per ulteriori informazioni, consultare il modulo 8 "Protezione dall'accesso di codice".

Se gli assembly vengono caricati in modo dinamico, ad esempio utilizzando System.Reflection.Assembly.Load, non utilizzare nomi di tipi o assembly passati da origini non attendibili.

Se l'assembly genera in modo dinamico codice per eseguire operazioni per un chiamante, accertarsi che quest'ultimo non possa in alcun modo influenzare il codice generato. Questo problema è più significativo se il chiamante opera a un livello di trust più basso rispetto all'assembly che genera il codice.

Se la generazione del codice si basa sull'input del chiamante, è necessario essere particolarmente attenti alle vulnerabilità della protezione. Tutte le stringhe di input utilizzate come variabili letterali nel codice generato devono essere convalidate e le virgolette devono essere definite come caratteri di escape per impedire al chiamante di sconfinare dalla variabile letterale e inserire codice. In generale, se esiste un modo per il chiamante di influenzare la generazione del codice e far sì che la compilazione non riesca, è probabile che sussista una vulnerabilità della protezione.

Per ulteriori informazioni, consultare l'articolo MSDN "Secure Coding Guidelines for the .NET Framework" (in inglese).

Inizio paginaInizio pagina

Offuscamento

Se si intende proteggere la proprietà intellettuale, con un offuscatore è possibile rendere estremamente difficile l'utilizzo di un decompilatore sul codice MSIL degli assembly. Gli offuscatori consentono di confondere l'interpretazione umana delle istruzioni MSIL e di evitare la successiva decompilazione.

L'offuscamento non è a prova di violazione e non è consigliabile realizzare soluzioni di protezione che si basino esclusivamente su di esso. Tuttavia, mediante l'offuscamento è possibile contrastare le minacce fondate sulla capacità di decodificare il codice. Gli offuscatori, in genere, offrono i seguenti vantaggi:

Consentono di proteggere la proprietà intellettuale.

Consentono di oscurare i percorsi del codice. Questo rende più difficile per i pirati informatici identificare la logica di protezione.

Modificano i nomi delle variabili membro interne. In questo modo diventa più difficile comprendere il codice.

Consentono di crittografare le stringhe. Spesso i pirati informatici ricercano specifiche stringhe per individuare logica riservata. Crittografando le stringhe questo diventa più difficile.

Per .NET Framework sono disponibili numerosi offuscatori di terze parti. Uno di questi, la Community Edition dello strumento Dotfuscator di PreEmptive Solutions, viene fornito insieme al sistema di sviluppo Microsoft Visual Studio® .NET 2003. Lo strumento è disponibile anche all'indirizzo http://www.preemptive.com/dotfuscator (in inglese). Per ulteriori informazioni, vedere l'elenco degli offuscatori disponibile all'indirizzo http://www.gotdotnet.com/team/csharp/tools/default.aspx (in inglese).

Inizio paginaInizio pagina

Crittografia

La crittografia è uno degli strumenti più importanti per la protezione dei dati. Fornisce la riservatezza dei dati e gli algoritmi di hash, che producono una rappresentazione fissa e condensata dei dati, possono essere utilizzati per proteggere i dati dalle manomissioni. Inoltre, per l'autenticazione è possibile utilizzare firme digitali.

Per proteggere i dati sia mentre sono in transito che quando vengono archiviati è consigliabile utilizzare la crittografia. Alcuni algoritmi di crittografia sono più efficienti di altri, e alcuni sono più sicuri. Normalmente, il livello di protezione è direttamente proporzionale alle dimensioni della chiave di crittografia.

Due tra gli errori che vengono commessi più comunemente nell'utilizzo della crittografia sono lo sviluppo di algoritmi in proprio e la mancata protezione delle chiavi di crittografia. Le chiavi di crittografia devono essere maneggiate con cura. Un pirata informatico in possesso della chiave di crittografia può accedere ai dati crittografati.

I principali punti da tenere in considerazione sono:

Utilizzo dei servizi di crittografia forniti con la piattaforma

Generazione delle chiavi

Archiviazione delle chiavi

Scambio delle chiavi

Manutenzione delle chiavi

Utilizzo dei servizi di crittografia forniti con la piattaforma

La creazione di implementazioni personalizzate della crittografia è sconsigliata. È altamente improbabile che un'implementazione di questo tipo possa offrire la stessa protezione degli algoritmi standard forniti dalla piattaforma, vale a dire il sistema operativo e .NET Framework. Nel codice gestito dovrebbero essere utilizzati gli algoritmi forniti dallo spazio dei nomiSystem.Security.Cryptography per crittografia, decrittografia, hash, generazione di numeri casuali e firme digitali.

Numerosi tipi di questo spazio dei nomi contengono la CryptoAPI del sistema operativo, mentre altri implementano algoritmi nel codice gestito.

Generazione delle chiavi

Le seguenti raccomandazioni sono applicabili alla creazione di chiavi di crittografia:

Generare chiavi casuali.

Utilizzare PasswordDeriveBytes per la crittografia basata su password.

Utilizzare di preferenza chiavi di grandi dimensioni.

Generare chiavi casuali

Se le chiavi di crittografia devono essere generate a livello di programmazione, utilizzare RNGCryptoServiceProvider per creare chiavi e vettori di inizializzazione e non la classe Random. Diversamente dalla classe Random, RNGCryptoServiceProvider consente di creare numeri casuali che garantiscono un livello di crittografia alto e sono conformi a FIPS-140. Il codice che segue illustra come utilizzare questa funzione.

using System.Security.Cryptography;
. . .
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] key = new byte[keySize];
rng.GetBytes(key);

Utilizzare PasswordDeriveBytes per la crittografia basata su password

Lo spazio dei nomi System.Security.Cryptography.DeriveBytes prevede l'utilizzo di PasswordDeriveBytes per crittografare i dati basati su un password fornita dall'utente. Per decrittografare i dati l'utente deve indicare la stessa password utilizzata per crittografarli.

Questo approccio non è applicabile all'autenticazione delle password. Per autenticare le password degli utenti è possibile memorizzare un sistema di verifica delle password in forma di valore hash con un ordine di valori salt. È possibile utilizzare PasswordDeriveBytes per generare chiavi per la crittografia basata su password.

PasswordDeriveBytes accetta password, salt, un algoritmo di crittografia, un algoritmo di hash, dimensione delle chiavi (in bit) e dati del vettore di inizializzazione per creare una chiave simmetrica da utilizzare per la crittografia.

Dopo aver utilizzato la chiave per crittografare i dati, eliminarla dalla memoria, conservando però vettore di inizializzazione e salt. Questi valori devono essere protetti e sono necessari per generare nuovamente la chiave per la decrittografia.

Per ulteriori informazioni sull'archiviazione di hash delle password con salt, consultare il modulo 14 "Creazione di codice di accesso ai dati protetto".

Utilizzare di preferenza chiavi di grandi dimensioni

Quando si genera una chiave o una coppia di chiavi di crittografia, è consigliabile utilizzare per l'algoritmo la massima dimensione possibile della chiave. In questo modo l'algoritmo non sarà necessariamente più protetto, ma aumenterà considerevolmente il tempo necessario per eseguire con successo un attacco di tipo "brute force" sulla chiave. Nel codice riportato di seguito è illustrato il modo per trovare la massima dimensione supportata per le chiavi in uno specifico algoritmo.

private int GetLargestSymKeySize(SymmetricAlgorithm symAlg)
{
  KeySizes[] sizes = symAlg.LegalKeySizes;
  return sizes[sizes.Length].MaxSize;
}

private int GetLargestAsymKeySize(AsymmetricAlgorithm asymAlg)
{
  KeySizes[] sizes = asymAlg.LegalKeySizes;
  return sizes[sizes.Length].MaxSize;
}

Archiviazione delle chiavi

Se possibile, dovrebbe essere adottata una soluzione di crittografia fornita dalla piattaforma che consenta di evitare la gestione delle chiavi nell'applicazione. Tuttavia, a volte è necessario utilizzare soluzioni di crittografia che non lo consentono e archiviare la chiave in una posizione protetta è essenziale. Le tecniche descritte di seguito consentono di evitare le vulnerabilità nell'archiviazione delle chiavi:

Utilizzare DPAPI per evitare la gestione delle chiavi.

Non memorizzare le chiavi nel codice.

Limitare l'accesso alle chiavi permanenti.

Utilizzare DPAPI per evitare la gestione delle chiavi

DPAPI è una funzionalità di crittografia/decrittografia originale fornita da Microsoft Windows 2000. Uno dei principali vantaggi dell'utilizzo di DPAPI è che la chiave di crittografia viene gestita dal sistema operativo, perché deriva dalla password associata all'account di processo (o del thread, in caso di rappresentazione del thread) che esegue chiamate alle funzioni DPAPI.

Chiave utente e chiave computer a confronto

La crittografia con DPAPI può essere eseguita con la chiave utente o con la chiave computer. Per impostazione predefinita, viene utilizzata una chiave utente. Ciò significa che solo un thread eseguito nel contesto di protezione dell'account utente che ha crittografato i dati sarà in grado di decrittografarli. Per utilizzare la chiave computer in DPAPI è possibile passare il flag CRYPTPROTECT_LOCAL_MACHINE all'API CryptProtectData. In questo caso, tutti gli utenti dal computer corrente potranno decrittografare i dati.

L'opzione della chiave utente può essere utilizzata solo se per l'account che esegue la crittografia è stato caricato un profilo utente. Se si esegue codice in un ambiente in cui il profilo utente non è caricato, l'archivio utenti non può essere utilizzato facilmente ed è opportuno optare invece per l'archivio computer.

Nella versione 1.1 di .NET Framework viene caricato il profilo utente per l'account ASPNET utilizzato per eseguire applicazioni Web su Windows 2000. Nella versione 1.0 di .NET Framework il profilo per questo account non viene caricato, pertanto è più difficile utilizzare DPAPI con la chiave utente.

Se si utilizza l'opzione chiave computer, è consigliabile ricorrere a un ACL per proteggere i dati crittografati, ad esempio in una chiave di registro, e utilizzare questo approccio per limitare gli utenti che hanno accesso ai dati crittografati. Per una maggiore protezione sarebbe anche opportuno passare un valore di entropia opzionale alle funzioni DPAPI.

Nota: un valore di entropia è un valore casuale aggiuntivo che è possibile passare alle funzioni DPAPI CryptProtectData e CryptUnprotectData. Lo stesso valore utilizzato per crittografare i dati deve essere utilizzato per decrittografarli. Con l'opzione chiave computer qualsiasi utente può decrittografare i dati dal computer. Se viene aggiunta l'entropia, l'utente deve conoscere anche il relativo valore.

Lo svantaggio dell'utilizzo dell'entropia è che il valore di entropia deve essere gestito nello stesso modo di una chiave. Per evitare problemi relativi alla gestione dell'entropia, è possibile utilizzare l'archivio computer senza entropia e convalidare accuratamente utenti e codice (utilizzando la protezione dall'accesso di codice) prima di eseguire la chiamata al codice DPAPI.

Per ulteriori informazioni sull'utilizzo di DPAPI da applicazioni Web ASP.NET, vedere "How To: Create a DPAPI Library", nella sezione dedicata alle procedure dell'articolo "Building Secure ASP.NET Applications", all'indirizzo http://msdn.microsoft.com/library/en-us/dnnetsec/html/SecNetHT07.asp (in inglese).

Non memorizzare le chiavi nel codice

Le chiavi non devono essere archiviate nel codice, perché le chiavi hardcoded nell'assembly compilato possono essere disassemblate mediante strumenti simili a ILDASM, che le convertono in testo semplice.

Limitare l'accesso alle chiavi permanenti

Se le chiavi vengono archiviate nella memoria permanente per essere utilizzate durante l'esecuzione è necessario utilizzare ACL appropriati e limitare l'accesso. L'accesso alla chiave dovrebbe essere concesso solo ad Administrators, SYSTEM e all'identità del codice durante l'esecuzione, ad esempio l'account ASPNET o Servizio di rete.

Il backup delle chiavi non deve essere memorizzato in formato testo semplice, ma crittografato mediante DPAPI o con una password complessa e archiviato in un supporto rimovibile.

Scambio delle chiavi

Alcune applicazioni richiedono lo scambio protetto di chiavi di crittografia su una rete non protetta. Può essere necessario comunicare verbalmente la chiave o inviarla tramite posta elettronica protetta. Un metodo più sicuro per lo scambio di una chiave simmetrica consiste nell'utilizzo della crittografia a chiave pubblica. Con questo approccio la chiave simmetrica da scambiare viene crittografata utilizzando la chiave pubblica dell'altra parte da un certificato che può essere convalidato. Un certificato è considerato valido quando:

Viene utilizzato entro gli intervalli di data specificati nel certificato.

Tutte le firme della catena di certificati possono essere verificate.

È del tipo corretto. Ad esempio, un certificato di posta elettronica non deve essere utilizzato come certificato di un server Web.

Può essere verificato fino a risalire a un'autorità principale attendibile.

Non si trova in un elenco di revoche di certificati (CRL) dell'autorità emittente.

Aggiornamento delle chiavi

La protezione dipende dalla capacità di tenere la chiave al sicuro per un periodo di tempo prolungato. Per la manutenzione delle chiavi è utile attenersi alle seguenti raccomandazioni:

Cambiare periodicamente le chiavi.

Proteggere le chiavi private esportate.

Cambiare periodicamente le chiavi

Le chiavi di crittografia dovrebbero essere cambiate di tanto in tanto, perché con il passare del tempo un segreto statico ha più probabilità di essere scoperto. Sono state annotate per iscritto? L'amministratore ha cambiato mansione o ha lasciato l'azienda? La chiave di sessione per crittografare le comunicazioni è in uso da molto tempo? Non utilizzare troppo a lungo le chiavi.

Violazione delle chiavi

Le chiavi possono essere violate in numerosi modi. Ad esempio, possono essere perdute oppure possono essere sottratte o scoperte da un pirata informatico.

Se la chiave privata utilizzata per la crittografia asimmetrica e lo scambio delle chiavi viene violata, essa non deve essere ulteriormente utilizzata ed è necessario informare della violazione gli utenti della chiave pubblica. Se la chiave è stata utilizzata per la firma di documenti è necessario firmarli di nuovo.

Se la chiave privata del certificato è stata violata, è necessario contattare l'autorità di certificazione emittente per richiedere l'inserimento del certificato in un elenco di revoche di certificati. È opportuno, inoltre, modificare le modalità di archiviazione delle chiavi per evitare future violazioni.

Proteggere le chiavi private esportate

Quando si esporta una chiave privata RSA (Rivest, Shamir, Adleman) o DSA (Digital Signature Algorithm), utilizzare PasswordDeriveBytes. Le classi RSA e DSA contengono un metodo ToXmlString che consente di esportare la chiave pubblica o privata, o entrambe, dal contenitore della chiave. Con questo metodo la chiave pubblica viene esportata in testo semplice. Se si esporta la chiave privata da installare su più server di una Web farm, un metodo consigliato è quello di crittografarla dopo averla esportata mediante PasswordDeriveBytes per generare una chiave simmetrica, come illustrato nell'esempio di codice seguente.

PasswordDeriveBytes deriver = new PasswordDeriveBytes(<strong password>, null);
byte[] ivZeros = new byte[8];//This is not actually used but is currently 
required.
//Derive key from the password
byte[] pbeKey = deriver.CryptDeriveKey("TripleDES", "SHA1", 192, ivZeros);
Inizio paginaInizio pagina

Riepilogo

In questo modulo è stata illustrata l'applicazione di varie tecniche che consentono di migliorare la protezione del codice gestito. Tali tecniche possono essere applicate a tutti i tipi di assembly gestiti, incluse pagine Web, controlli, librerie di utilità e altro. Per raccomandazioni specifiche applicabili a specifici tipi di assembly, consultare gli altri moduli della Parte III di questa guida.

Per migliorare ulteriormente la protezione degli assembly, è possibile utilizzare tecniche di codifica esplicite per la protezione dall'accesso di codice, che sono particolarmente importanti se gli assembly supportano chiamanti con attendibilità parziale. Per ulteriori informazioni sulla protezione dall'accesso di codice, consultare il modulo 8 "Protezione dall'accesso di codice".

Inizio paginaInizio pagina

Risorse aggiuntive

Per ulteriori informazioni, consultare le risorse seguenti:

Per ulteriori informazioni sull'utilizzo di DPAPI da applicazioni Web ASP.NET, 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).

Per ulteriori informazioni sulle linee guida per la scrittura di codice protetto per .NET Framework, consultare l'articolo MSDN, "Secure Coding Guidelines for the .NET Framework", all'indirizzo http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/seccodeguide.asp (in inglese).

Michael Howard analizza le tecniche per la scrittura di codice protetto e fornisce indicazioni su come aggiungerlo alle applicazioni nella sua rubrica in MSDN, "Code Secure", all'indirizzo http://msdn.microsoft.com/security/securecode/columns/default.aspx (in inglese).


Inizio paginaInizio pagina