Exporter (0) Imprimer
Développer tout

Instructions de codage sécurisé pour le .NET Framework

Résumé : le Common Language Runtime (CLR) et Microsoft .NET Framework appliquent sur toutes les applications de code managé, le principe de la sécurité basée sur les preuves. Ainsi, la plupart du code ne requiert peu voire aucun codage explicite pour la sécurité. Cet article présente brièvement le système de sécurité, aborde les problèmes possibles dans votre code, et donne des conseils pour classer vos composants afin que vous puissiez identifier les problèmes à résoudre pour garantir un code sûr.

Connaissances préalables : les lecteurs doivent connaître le Common Language Runtime et Microsoft® .NET Framework, notamment posséder des connaissances élémentaires sur la sécurité basée sur les preuves et la sécurité d'accès du code.


Sur cette page

Sécurité basée sur les preuves et sécurité d'accès du code Sécurité basée sur les preuves et sécurité d'accès du code
Objectifs du codage sécurisé Objectifs du codage sécurisé
Différentes approches du codage sécurisé Différentes approches du codage sécurisé
Meilleures pratiques en matière de codage sécurisé Meilleures pratiques en matière de codage sécurisé
Sécurisation des données d'état Sécurisation des données d'état
Sécurisation de l'accès à la méthode Sécurisation de l'accès à la méthode
Règle 7 : les données chiffrées ne sont en sécurité que si la clé de déchiffrement l'est également Règle 7 : les données chiffrées ne sont en sécurité que si la clé de déchiffrement l'est également
Code wrapper Code wrapper
Code non managé Code non managé
Entrées de l'utilisateur Entrées de l'utilisateur
Considérations sur l'accès distant Considérations sur l'accès distant
Objets protégés Objets protégés
Sérialisation Sérialisation
Questions relatives au franchissement de domaine d'application Questions relatives au franchissement de domaine d'application
Évaluation des autorisations Évaluation des autorisations
Autres technologies de sécurité Autres technologies de sécurité

Sécurité basée sur les preuves et sécurité d'accès du code

Deux technologies distinctes travaillent de concert pour protéger le code managé :

  • La sécurité basée sur les preuves détermine les autorisations à accorder au code.

  • La sécurité d'accès du code vérifie que l'ensemble du code de la pile bénéficie des autorisations requises pour exécuter une action.

Les autorisations lient ces deux technologies : une autorisation est le droit d'effectuer une opération protégée spécifique. Par exemple, « lire c:\temp » est une autorisation fichier et « se connecter à www.msn.com » est une autorisation réseau. La sécurité basée sur les preuves détermine les autorisations accordées au code. Les preuves sont des informations connues sur les assemblys (unités chargées d'accorder des autorisations), qui sont prises en compte au niveau du mécanisme de stratégie de sécurité. Une fois les preuves fournies, la stratégie de sécurité définie par l'administrateur est évaluée afin de déterminer les autorisations à accorder au code. Le code lui-même peut effectuer une demande d'autorisation pour modifier les autorisations accordées. La demande d'autorisation est exprimée comme une sécurité déclarative au niveau de l'assembly à l'aide d'une syntaxe d'attribut personnalisée. Toutefois, le code ne peut en aucun cas générer plus ou moins d'autorisations que ne l'autorise la stratégie. Les autorisations ne sont accordées qu'une fois. Elles définissent les droits de l'ensemble du code dans l'assembly. Pour consulter ou modifier votre stratégie de sécurité, utilisez l'outil .NET Framework Configuration Tool (Mscorcfg.msc).

Le tableau suivant répertorie les principaux types de preuve utilisés par le système de stratégie pour accorder des autorisations au code. Outre les types de preuve standards ci-dessous, fournis par le système de sécurité, il est également possible d'étendre le jeu de preuves à l'aide de nouveaux types pouvant être personnalisés par l'utilisateur.

Preuve

Description

Hachage

Hachage de l'assembly

Éditeur

Signataire AuthentiCode®

Nom fort

Clé publique+nom+version

Site

Site Web à l'origine du code

URL

URL de l'origine du code

Zone

Zone Internet Explorer de l'origine du code

La sécurité d'accès du code gère les vérifications de sécurité chargées d'appliquer les autorisations accordées. Ces vérifications de sécurité sont uniques dans le sens où elles s'appliquent non seulement au code qui tente de réaliser une opération protégée, mais également à tous ses appelants situés dans la pile. Pour que la vérification réussisse, l'ensemble du code vérifié doit bénéficier de l'autorisation requise (pouvant faire l'objet d'annulations). Les vérifications de sécurité constituent un avantage indéniable car elles empêchent les attaques par leurre, dans lesquelles du code non autorisé appelle votre code et l'amène par la ruse à réaliser une action pour son compte. Imaginez qu'une de vos applications lise un fichier et que la stratégie de sécurité accorde à votre code l'autorisation d'effectuer cette tâche. Comme l'ensemble du code de l'application bénéficie de l'autorisation, les vérifications de sécurité d'accès du code sont passées avec succès. Toutefois, si du code malveillant, n'ayant pas accès au fichier, appelle votre code de quelque manière que ce soit, la vérification de sécurité échoue car ce code moins fiable apparaît dans la pile en raison du fait qu'il appelle votre code.
Il est primordial de bien comprendre que cette sécurité repose sur la mise en application de ce que le code est autorisé à accomplir. L'autorisation des utilisateurs en fonction des informations d'ouverture de session est une fonction de sécurité bien à part du système d'exploitation sous-jacent. Imaginez que ces deux systèmes de sécurité fonctionnent comme un système de défense multiniveau : pour accéder à un fichier, par exemple, les autorisations du code et de l'utilisateur doivent être acceptées. L'autorisation de l'utilisateur est importante dans bon nombre d'applications qui dépendent des informations d'ouverture de session ou d'autres informations d'identification pour contrôler ce que peuvent faire ou ne pas faire certains utilisateurs, mais ce type de sécurité n'est pas l'objet de ce document.

Objectifs du codage sécurisé

Admettons que la stratégie de sécurité est correcte et que tout code potentiellement malveillant n'obtienne pas les autorisations accordées au code de confiance permettant à ce dernier de réaliser des tâches plus complexes en toute sécurité. (En émettant une hypothèse différente, il est alors impossible de distinguer un type de code d'un autre, ce qui rend le problème insoluble.) Si vous utilisez les autorisations appliquées au niveau de .NET Framework ainsi que d'autres mises en œuvre dans votre code, vous devez mettre en place des barrières afin d'empêcher le code malveillant d'obtenir des informations que vous ne souhaitez pas lui révéler, ou afin de l'empêcher d'effectuer des actions indésirables. En outre, il est nécessaire de trouver le juste équilibre entre sécurité et possibilité d’utiliser le code dans tous les scénarios prévus pour le code de confiance.
La stratégie de sécurité basée sur les preuves et la sécurité d'accès du code sont des mécanismes explicites, particulièrement efficaces, d'implémentation de la sécurité. La plus grande partie du code de l'application a uniquement besoin d'utiliser l'infrastructure mise en œuvre par le .NET Framework. Dans certains cas, une sécurité supplémentaire, spécifique à l'application, est requise. Elle peut être conçue en développant le système de sécurité ou en faisant appel à de nouvelles méthodes pertinentes.

Différentes approches du codage sécurisé

L'un des avantages de ces technologies de sécurité est que vous pouvez en général ne plus y penser. Si votre code bénéficie des autorisations dont il a besoin pour faire son travail, tout fonctionnera correctement (et vous pourrez vous réjouir d'être protégé des attaques éventuelles, telles que des attaques par leurre mentionnées précédemment). Il existe toutefois certaines situations dans lesquelles vous devez prendre la sécurité à bras-le-corps. Les sections suivantes décrivent ces approches. Même si ces sections ne s'appliquent pas directement à votre situation, il peut être utile de comprendre ces problèmes de sécurité.

Code indépendant de la sécurité

Le code indépendant de la sécurité ne travaille pas explicitement avec le système de sécurité. Il s'exécute quelles que soient les autorisations qu'il reçoit. Bien que des applications qui ne parviennent pas à intercepter les exceptions de sécurité associées à des opérations protégées (comme l'utilisation de fichiers, la mise en réseau, etc.) puissent engendrer des expériences utilisateur fâcheuses (un arrêt de l’application dont la plupart des détails échappent totalement à bon nombre d'utilisateurs), le code indépendant de la sécurité bénéficie quand même des technologies de sécurité. Même un code bénéficiant d'une confiance élevée n'ouvre pas de brèche dans le système de protection. La pire situation qui pourrait se produire serait que les appelants aient besoin d'un grand nombre d'autorisations ou qu'ils soient arrêtés par le système de sécurité.
Une bibliothèque indépendante de la sécurité possède des caractéristiques particulières que vous devez connaître. Supposons que votre bibliothèque fournisse des éléments d'API qui utilisent des fichiers ou appellent du code non managé ; si votre code ne possède pas l'autorisation correspondante, il ne s'exécutera pas comme indiqué. Cependant, même si le code possède l'autorisation, tout code d'application qui l'appelle doit avoir la même autorisation pour fonctionner. Si le code appelant ne possède pas l'autorisation appropriée, une exception de sécurité apparaît suite à l’examen de la pile de la sécurité d'accès du code. Si vous pouvez vous permettre de demander aux programmes appelants de bénéficier d'autorisations pour chaque opération réalisable par votre bibliothèque, vous bénéficiez de façon simple et sûre d'un système de sécurité car celui-ci n'implique pas d'annulation de sécurité dangereuse. Toutefois, si vous souhaitez que le code de l'application qui appelle votre bibliothèque n’ait pas à demander des autorisations et qu'il n'ait pas besoin de bénéficier d'autorisations très puissantes, vous devez vous orienter vers le modèle de bibliothèque qui fonctionne avec des ressources protégées. Reportez-vous à la section Code de bibliothèque exposant des ressources protégées, plus loin sur cette page. Code d'application ne correspondant pas à un composant réutilisable
Si votre code fait partie d'une application qui ne sera pas appelée par un autre code, la sécurité est simple et un codage spécial n'est sans doute pas nécessaire. Cependant, n'oubliez pas que du code nuisible peut appeler votre code. Si la sécurité d'accès du code peut empêcher un code nuisible d'accéder à des ressources, ce type de code est toutefois capable de lire les valeurs de vos champs ou propriétés susceptibles de contenir des informations confidentielles. De plus, si votre code accepte des entrées de l'utilisateur à partir d'Internet ou d'autres ressources peu fiables, vous devez vous méfier des entrées malveillantes.
Pour plus d'informations, reportez-vous aux sections Sécurisation des données d'état et Entrées de l'utilisateur plus loin sur cette page.

Implémentation de wrapper managé vers du code natif

Dans ce scénario, certaines fonctionnalités sont implémentées dans du code natif que vous voulez mettre à disposition du code managé, sans avoir à réécrire votre code en code managé. Les wrappers managés sont faciles à écrire à l'aide soit de l'appel de plate-forme, soit de l’interopérabilité COM. Toutefois, si vous faites ce choix, les appelants de vos wrappers doivent avoir des droits de code non managé pour que l'opération réussisse. Dans le cadre de la stratégie par défaut, cela signifie que le code téléchargé depuis un intranet ou Internet ne fonctionnera pas avec les wrappers.
Au lieu d'accorder des droits de code non managé à toutes les applications qui utilisent ces wrappers, il est préférable d'accorder ces droits uniquement au code wrapper. Si les fonctionnalités sous-jacentes sont sécurisées (qu'elles n'exposent aucune ressource) et que l'implémentation est également sécurisée, le wrapper doit simplement déclarer ses droits, ce qui permet à n'importe quel code d'effectuer un appel via celui-ci. Lorsque des ressources sont concernées, le codage de sécurité doit être le même que dans le cas de code de bibliothèque décrit dans la section suivante. Comme le wrapper expose potentiellement les appelants à ces ressources, une vérification minutieuse de la sécurité du code natif est nécessaire, ce qui relève de la responsabilité du wrapper.
Pour plus d'informations, reportez-vous aux sections Code non managé et Évaluation des autorisations plus loin sur cette page.

Code de bibliothèque exposant des ressources protégées

Il s'agit de l'approche la plus puissante et donc potentiellement la plus risquée (si elle n'est pas exécutée correctement) pour le codage de sécurité : votre bibliothèque sert d'interface permettant à un autre code d'accéder à certaines ressources qui ne sont pas autrement disponibles, tout comme les classes du .NET Framework appliquent des autorisations pour les ressources qu'elles utilisent. Quel que soit l'emplacement où vous exposez une ressource, votre code doit en premier lieu demander l'autorisation appropriée à la ressource (c'est-à-dire effectuer une vérification de sécurité), puis généralement déclarer ses droits lui permettant d'effectuer l'opération proprement dite.
Pour plus d'informations, reportez-vous aux sections Code non managé et Évaluation des autorisations plus loin sur cette page.

Meilleures pratiques en matière de codage sécurisé

Remarque : les exemples de code sont écrits en C#, sauf indication contraire.

Les demandes d'autorisation constituent un moyen efficace d'intégrer la sécurité à votre code. Ces demandes vous permettent d'effectuer les deux opérations suivantes :

  • demander les autorisations minimales que doit recevoir votre code afin de s'exécuter.

  • garantir que votre code reçoit uniquement les autorisations dont il a réellement besoin.

Exemple :
assembly:FileIOPermissionAttribute (SecurityAction.RequestMinimum, Write="C:\\test.tmp")] 
[assembly:PermissionSet (SecurityAction.RequestOptional, Unrestricted=false)] 
…SecurityAction.RequestRefused…

Cet exemple indique au système que le code ne doit pas s'exécuter, sauf s'il reçoit une autorisation d'écrire sur C:\test.tmp. Si le code rencontre une stratégie de sécurité qui n'accorde pas cette autorisation, une exception PolicyException est levée et le code ne s'exécute pas. Grâce à cette demande, vous pouvez être sûr que votre code obtiendra cette autorisation et vous n'avez pas à vous préoccuper des erreurs provoquées par l'insuffisance d'autorisations.
Cet exemple indique également au système qu'aucune autorisation supplémentaire n'est voulue. En cas d'omission de ces informations, votre code recevra les autorisations que la stratégie décide de lui octroyer, quelles qu'elles soient. Si le fait d'avoir des autorisations supplémentaires ne présente pas de risque, lorsqu'un bogue de sécurité apparaît quelque part, avoir moins d'autorisations peut permettre de fermer la brèche. Disposer d'autorisations dont votre code n'a pas besoin peut entraîner des problèmes de sécurité.
Une autre méthode permettant de limiter aux privilèges minimaux les autorisations que reçoit votre code consiste à répertorier les autorisations spécifiques que vous souhaitez refuser. Les autorisations sont généralement refusées lorsque vous demandez que toutes les autorisations soient facultatives et excluez les autorisations spécifiques de cette demande.

Sécurisation des données d'état

Les applications qui gèrent des données confidentielles ou prennent des décisions de sécurité doivent conserver ces données sous leur contrôle et ne peuvent pas laisser un autre code, potentiellement nuisible, accéder directement aux données. Le meilleur moyen de protéger les données de la mémoire est de les déclarer sous forme de variables privées ou internes (avec portée limitée au même assembly). Cependant, même ces données font l'objet d'un accès que vous devez connaître :

  • À l'aide des mécanismes de réflexion, le code d'un niveau de confiance élevé possédant des références à votre objet peut obtenir et définir des membres privés.

  • À l'aide de la sérialisation, le code d'un niveau de confiance élevé peut obtenir et définir en pratique des membres privés s'il peut accéder aux données correspondantes dans la forme sérialisée de l'objet.

  • En débogage, ces données peuvent être lues.

Assurez-vous qu'aucune de vos méthodes ou propriétés n'expose ces valeurs involontairement. Dans certains cas, les données peuvent être déclarées comme « protégées » avec un accès limité à la classe et ses dérivés. Cependant, vous devez prendre les précautions supplémentaires suivantes en raison de l'exposition supplémentaire :

  • Contrôlez le code autorisé à effectuer une dérivation de votre classe en le limitant au même assembly ou en utilisant la sécurité déclarative pour exiger une identité ou des autorisations afin d'effectuer une dérivation du code à partir de votre classe (reportez-vous à la section Sécurisation de l'accès à la méthode, plus loin sur cette page).

  • Assurez-vous que toutes les classes dérivées implémentent une protection similaire ou soient scellées.

Types valeur boxed

Les types valeur boxed peuvent parfois être modifiés dans les cas où vous pensez avoir distribué une copie du type qui ne peut pas modifier l'original. Lorsque vous retournez un type valeur boxed, vous retournez une référence au type valeur lui-même plutôt qu'une référence à une copie du type valeur. Le code ayant appelé votre code peut ainsi modifier la valeur de votre variable.
L'exemple de code C# suivant montre comment des types valeur boxed peuvent être modifiés à l'aide d'une référence.

using System; using System.Reflection; using System.Reflection.Emit; 
using System.Threading; using System.Collections; 
class bug { 
// Suppose you have an API element that exposes a 
// field through a property with only a get accessor. 

public object m_Property; public Object Property { 
get { return m_Property;} 
set {m_Property = value;} 
// (if applicable) } 
// You can modify the value of this by doing 
// the byref method with this signature. 
public static void m1( ref int j ) 
{ j = Int32.MaxValue; } 
public static void m2( ref ArrayList j ) 
{ j = new ArrayList(); } 
public static void Main(String[] args) 
{ Console.WriteLine( "////// doing this with value type" ); 
{ bug b = new bug(); b.m_Property = 4; 
Object[] objArr = new Object[]{b.Property}; 
Console.WriteLine( b.m_Property ); 
typeof(bug).GetMethod( "m1" ).Invoke( null, objArr ); 
// Note that the property changed. Console.WriteLine( b.m_Property ); 
Console.WriteLine( objArr[0] ); } Console.WriteLine( "////// doing this with a normal type" ); 
{ bug b = new bug(); ArrayList al = new ArrayList(); 
al.Add("elem"); b.m_Property = al; Object[] objArr = new Object[]{b.Property}; 
Console.WriteLine( ((ArrayList)(b.m_Property)).Count ); 
typeof(bug).GetMethod( "m2" ).Invoke( null, objArr ); 
// Note that the property does not change. 
Console.WriteLine( ((ArrayList)(b.m_Property)).Count ); 
Console.WriteLine( ((ArrayList)(objArr[0])).Count ); } } } 

Sécurisation de l'accès à la méthode

Il est possible que certaines méthodes ne conviennent pas à l'appel par du code arbitraire non fiable. Ces méthodes présentent plusieurs risques : la méthode peut fournir des informations restreintes ; elle peut croire les informations qui lui sont transmises ; elle peut ne pas faire de vérification d'erreurs sur les paramètres ; ou dans le cas de paramètres erronés, la méthode peut montrer un dysfonctionnement ou avoir des effets préjudiciables. Vous devez connaître ces cas et prendre les mesures appropriées afin de sécuriser la méthode.
Dans certains cas, il vous faudra peut-être limiter les méthodes qui ne sont pas destinées à une utilisation publique, mais qui doivent cependant être publiques. Par exemple, vous disposez d’une interface qui doit être invoquée via vos DLL, d'où la nécessité qu'elle soit publique, mais vous ne souhaitez pas l'exposer au public afin d'empêcher que des clients ne l'utilisent ou pour empêcher un code nuisible d'exploiter le point d'entrée dans votre composant. Une autre raison courante de limiter une méthode qui n'est pas destinée à une utilisation publique (mais qui doit être publique) consiste à éviter d'avoir à documenter et à prendre en charge une interface qui peut se révéler très interne.

Le code managé propose plusieurs façons de limiter l'accès à la méthode :
• Limitez l'étendue de l'accès à la classe, à l'assembly ou aux classes dérivées, s'ils sont de confiance. C'est la méthode la plus simple permettant de limiter l'accès à la méthode. Notez que, en général, les classes dérivées peuvent être moins fiables que la classe dont elles dérivent même si, dans certains cas, elles partagent l'identité de la classe parent. En particulier, n'accordez pas de confiance au mot clé protected, qui ne s'utilise pas nécessairement dans le contexte de sécurité.
• Limitez l'accès à la méthode aux appelants d'une identité spécifiée (principalement, toute preuve particulière de votre choix).
• Limitez l'accès à la méthode aux appelants dont les autorisations correspondent à celles que vous choisissez.

De même, la sécurité déclarative vous permet de contrôler l'héritage de classes. Vous pouvez utiliser InheritanceDemand pour effectuer les opérations suivantes :
• Obliger des classes dérivées à posséder une identité ou une autorisation spécifiée.
• Obliger des classes dérivées qui substituent des méthodes spécifiques à posséder une identité ou une autorisation spécifiée.

Exemple : sécurisation de l'accès à une classe ou méthode
L'exemple suivant montre comment sécuriser une méthode publique pour un accès limité.

1. La commande sn -k crée une nouvelle paire de clés privées/publiques. La partie privée est nécessaire à la signature du code à l'aide d'un nom fort. Elle est conservée en sécurité par l'éditeur du code. (Si elle est divulguée, n'importe qui peut s'approprier votre signature pour son propre code et déjouer ainsi le système de protection.)

Sécurisation d'une méthode à l'aide d'une identité de nom fort

sn -k keypair.dat csc/r:App1.dll /a.keyfile:keypair.dat App1.cs sn -p 
keypair.dat public.dat sn -tp public.dat <publichex.txt 
[StrongNameIdentityPermissionAttribute (SecurityAction.LinkDemand, 
PublicKey="…",hex…",Name="App1", Version="0.0.0.0")] public class Class1 

2. La commande csc compile et signe l'App1, l'autorisant ainsi à accéder à la méthode protégée.
3. Les deux commandes sn suivantes extraient de la paire la partie publique de la clé et la formate en une valeur hexadécimale.
4. La deuxième partie de cet exemple est un extrait de code source de la méthode protégée. L'attribut personnalisé définit le nom fort et spécifie la clé publique de la paire de clés, avec les données au format hexadécimal à partir de la commande sn insérée pour l'attribut PublicKey.
5. Au moment de l'exécution, App1 possède la signature de nom fort requise et est autorisé à utiliser Class1. Cet exemple utilise LinkDemand pour protéger un élément API. Pour obtenir des informations importantes sur les limites d'utilisation de LinkDemand, reportez-vous aux sections ci-après.

Empêcher du code non fiable d'utiliser des classes et des méthodes

Utilisez les déclarations suivantes afin d'empêcher un code de niveau de confiance partiel d'utiliser des classes et des méthodes (y compris les propriétés et les événements). En appliquant ces déclarations à une classe, vous appliquez la protection à toutes ses méthodes, propriétés et événements ; cependant, notez que l'accès au champ n'est pas affecté par la sécurité déclarative. Notez que les demandes LinkDemand protègent uniquement des appelants immédiats et qu'elles peuvent toujours être victimes d'attaques par leurre. Pour plus d'informations, reportez-vous à la section Sécurité basée sur les preuves et sécurité d'accès du code de ce document.
Dans des assemblys avec nom fort, la sécurité déclarative est appliquée à toutes les méthodes, propriétés et événements accessibles publiquement afin de limiter leur utilisation aux appelants dont le niveau de confiance est total, sauf si l'assembly participe explicitement en appliquant l'attribut AllowPartiallyTrustedCallers. Ainsi, marquer les classes de manière explicite afin d'exclure des appelants non fiables se révèle nécessaire uniquement pour des assemblys non signés ou des assemblys possédant cet attribut, pour un sous-ensemble de types non destinés à des appelants non fiables. Pour plus d'informations, reportez-vous au document Version 1 Security Changes for the Microsoft .NET Framework.

Règle 7 : les données chiffrées ne sont en sécurité que si la clé de déchiffrement l'est également

Imaginons la situation suivante : vous venez d'acheter le verrou le plus gros, le plus robuste et le plus sûr du monde. Vous l'installez sur votre porte, mais vous glissez la clé sous le paillasson. Peu importe la fiabilité du verrou n'est–ce pas ? Le principal problème est que vous ne protégez pas votre clé, car si le voleur la trouve, il n'a besoin de rien d'autre pour ouvrir le verrou. Pour les données chiffrées, c'est la même chose, peu importe le niveau de complexité de l'algorithme de chiffrement, les données ne sont en sécurité que si la clé de déchiffrement l'est également.
Bon nombre de systèmes d'exploitation et de programmes cryptographiques vous permettent de stocker les clés de chiffrement sur l'ordinateur. C'est avant tout très pratique, vous n'avez pas à transporter la clé, mais c’est au détriment de la sécurité. En général, les clés sont dissimulées et il faut reconnaître que certaines méthodes de dissimulation sont parfois très efficaces. Mais finalement, peu importe l'efficacité de la dissimulation, si la clé se trouve sur l'ordinateur, il est possible de la localiser. C'est un fait. Elle peut être localisée. Après tout, si un logiciel peut la trouver, une personne malveillante suffisamment motivée est également capable de la localiser. Pour le stockage de vos clés, ayez si possible recours au stockage hors connexion. Si la clé est un mot ou une phrase, mémorisez-la. Si ce n'est pas le cas, exportez-la sur une disquette, faites une copie de sauvegarde et stockez les copies dans différents endroits sécurisés. (À tous les administrateurs qui utilisent Syskey en mode « stockage local » : vous allez immédiatement reconfigurer votre serveur, n'est-ce pas ?)

• Pour les classes publiques non scellées :

[System.Security.Permissions.PermissionSetAttribute(System.Security. 
Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")] 
[System.Security.Permissions.PermissionSetAttribute 
(System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")] 
public class CanDeriveFromMe 

• Pour les classes publiques scellées

[System.Security.Permissions.PermissionSetAttribute 
(System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")] 
public sealed class CannotDeriveFromMe 

• Pour les classes publiques abstraites :

[System.Security.Permissions.PermissionSetAttribute 
(System.Security.Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")]
[System.Security.Permissions.PermissionSetAttribute System.
Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")] 
public abstract class CannotCreateInstanceOfMe_CanCastToMe 

• Pour les fonctions virtuelles publiques :

class Base { [System.Security.Permissions.PermissionSetAttribute 
(System.Security.Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")] 
[System.Security.Permissions.PermissionSetAttribute
( System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")] 
public override void CanOverrideOrCallMe() { ... } 

• Pour les fonctions abstraites publiques :

class Base { [System.Security.Permissions.PermissionSetAttribute 
system.Security.Permissions.SecurityAction.InheritanceDemand, Name="FullTrust")] 
System.Security.Permissions.PermissionSetAttribute 
(System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")] 
public override void CanOverrideMe() { ... }
 

• Pour des fonctions de substitution publiques où la base n'exige pas un niveau de confiance suffisant :

class Derived { [System.Security.Permissions.PermissionSetAttribute 
(System.Security.Permissions.SecurityAction.Demand, 
Name="FullTrust")] public override void CanOverrideOrCallMe() { ... } 

• Pour les fonctions de substitution publiques où la base exige un niveau de confiance suffisant :

class Derived { [System.Security.Permissions.PermissionSetAttribute
(System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")] 
public override void CanOverrideOrCallMe() { ... } 

• Pour des interfaces publiques :

[System.Security.Permissions.PermissionSetAttribute 
(System.Security.Permissions.SecurityAction.InheritanceDemand, 
Name="FullTrust")] [System.Security.Permissions.PermissionSetAttribute 
System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")] 
public interface CanCastToMe 

Demand et LinkDemand

La sécurité déclarative propose deux types de vérification de sécurité similaires, mais qui effectuent des vérifications très différentes. Vous devez connaître les deux formes car en cas de mauvais choix, vous risquez de connaître une baisse des performances et d'affaiblir la sécurité. La section suivante n'est pas conçue pour fournir une description approfondie de leurs caractéristiques. Pour plus d'informations, reportez-vous à la documentation du produit.

La sécurité déclarative propose les vérifications de sécurité suivantes :
• Demand spécifie le parcours de pile de la sécurité d'accès au code : tous les appelants sur la pile doivent avoir l'autorisation ou l'identité requise pour passer. Demand se produit sur chaque appel car la pile peut contenir des appelants différents. Si vous appelez une méthode de façon répétée, cette vérification de sécurité se produit à chaque fois. Demand constitue une excellente protection contre les attaques par leurre ; tout code non autorisé qui essaie de passer sera détecté.
• LinkDemand se produit au moment de la compilation juste à temps (JIT, Just-In-Time) (dans l'exemple précédent, lorsque le code App1 qui référence Class1 est sur le point de s'exécuter) et vérifie uniquement l'appelant immédiat. Cette vérification de sécurité ne vérifie pas l'appelant de l'appelant. Une fois cette vérification effectuée, il n'y a pas de charge de sécurité supplémentaire, quel que soit le nombre d'appels passés. Cependant, il n'y a pas non plus de protection contre les attaques par leurre. Avec LinkDemand, votre interface est protégée mais tout code qui réussit le test et peut référencer votre code risque d'enfreindre la sécurité en permettant à du code nuisible d'appeler à l'aide du code autorisé.

Par conséquent, n'utilisez pas LinkDemand sauf s'il est possible d'éviter sans exception toutes les failles possibles.

Les précautions supplémentaires requises lors de l'utilisation de LinkDemand doivent être soigneusement mises en œuvre manuellement. Le système de sécurité peut contribuer à leur application. La moindre erreur ouvre une faille de sécurité. Tout le code autorisé qui utilise votre code doit prendre en charge l'implémentation de la sécurité supplémentaire en effectuant les opérations suivantes :
• Limiter l'accès du code appelant à la classe ou à l'assembly.
• Insérer les mêmes vérifications de sécurité sur ce code et obliger ses appelants à effectuer ces vérifications. Par exemple, si vous écrivez du code qui appelle une méthode protégée par LinkDemand pour l'autorisation SecurityPermission.UnmanagedCode, votre méthode doit également effectuer un LinkDemand (ou Demand, qui est plus fort) pour cette autorisation. L'exception : si votre code utilise la méthode protégée par LinkDemand d'une façon limitée toujours sécurisée ou que vous estimez être sécurisée étant donné les autres mécanismes de protection de la sécurité (comme les demandes) présents dans votre code. Dans ce cas exceptionnel, l'appelant prend la responsabilité d'affaiblir la protection de la sécurité sur le code sous-jacent.
• Veiller à ce que les appelants ne puissent pas tromper celui-ci et lui faire appeler le code protégé de leur part. En d'autres termes, les appelants ne peuvent pas forcer le code autorisé à transmettre des paramètres spécifiques au code protégé ou pour obtenir de ce dernier des résultats.

Interfaces et LinkDemand

Si une méthode, propriété ou événement virtuel avec LinkDemand substitue une méthode de la classe de base, cette dernière doit également avoir le même LinkDemand pour que la méthode substituée soit sécurisée. Il est possible pour le code nuisible d'effectuer un transtypage (cast) en type de base en retour et d'appeler la méthode de la classe de base. Notez également que les demandes LinkDemands peuvent être ajoutées implicitement aux assemblys qui ne possèdent pas l'attribut AllowPartiallyTrustedCallersAttribute de niveau assembly.
Il est recommandé de protéger les implémentations des méthodes avec LinkDemands lorsque des méthodes d'interface possèdent également des LinkDemands.

Prenez connaissance des informations suivantes sur l'utilisation de LinkDemands avec des interfaces :
• L'attribut AllowPartiallyTrustedCallers peut affecter des interfaces.
• Vous pouvez placer des LinkDemands sur des interfaces pour protéger de manière sélective certaines interfaces de leur utilisation par du code d'un niveau de confiance partiel, comme lors de l'utilisation de l'attribut AllowPartiallyTrustedCallers.
• Si vous avez une interface définie dans un assembly qui ne contient pas l'attribut AllowPartiallyTrustedCallers, vous pouvez implémenter cette interface sur une classe d'un niveau de confiance partiel.
• Si vous placez une demande LinkDemand sur une méthode publique d'une classe qui implémente une méthode d'interface, la demande LinkDemand ne sera pas appliquée si vous effectuez ensuite un cast en interface et appelez la méthode. Dans ce cas, comme vous avez effectué une liaison à l'interface, seule la demande LinkDemand sur l'interface est honorée.

Pour les questions de sécurité, passez en revue les éléments suivants :
• explicitez les demandes de liaison sur des méthodes d'interface ; veillez à ce que ces demandes de liaison offrent la protection attendue ; déterminez si du code nuisible peut utiliser une expression cast pour contourner les demandes de liaison comme décrit précédemment ;
• méthodes virtuelles avec demandes de liaison ;
• types et interfaces qu'ils implémentent doivent utiliser LinkDemands de manière cohérente.

Substitutions Virtual Internal

Vous devez connaître une nuance de l'accessibilité système des types lorsque vous confirmez que votre code n'est pas disponible aux autres assemblys. Une méthode qui est déclarée virtual et internal peut substituer l'entrée vtable de la classe parent et ne peut s'utiliser qu'au sein du même assembly car elle est internal. Cependant, l'accessibilité permettant les substitutions est déterminée par le mot clé virtual qui peut être substitué d'un autre assembly tant que ce code peut accéder à la classe elle-même. Si la possibilité d'une substitution présente un problème, utilisez la sécurité déclarative pour le corriger ou supprimez le mot clé virtual si celui-ci n'est pas strictement requis.

Code wrapper

Le code wrapper, en particulier lorsque le wrapper est d'un niveau de confiance supérieur au code qui l'utilise, peut faire apparaître un ensemble unique de failles de sécurité. Toutes les opérations effectuées au nom d'un appelant, où les autorisations limitées de l'appelant ne sont pas incluses dans la vérification de sécurité appropriée, constituent une faille potentielle exploitable.
N'activez jamais un élément par l'intermédiaire du wrapper que l'appelant ne peut pas effectuer lui-même. Toute opération impliquant une vérification de sécurité limitée représente un danger particulier (contrairement à une demande de parcours de pile complet). Dans le cadre de vérifications de niveau unique, l'interposition du code wrapper entre l'appelant réel et l'élément d'API dont il est question peut facilement amener la vérification de sécurité à aboutir quand elle ne le devrait pas, ce qui affaiblit la sécurité.

Délégués

Chaque fois que votre code prend des délégués à partir de code d'un niveau de confiance moindre et susceptible de l'appeler, veillez à ne pas permettre au code d'un niveau de confiance moindre d'élargir ses autorisations. Si vous prenez un délégué et que vous l'utilisez ultérieurement, le code qui a créé le délégué ne se trouve pas dans la pile des appels et ses autorisations ne seront pas testées si le code dans ou sous le délégué tente une opération protégée. Si votre code et le code de délégué possèdent des privilèges supérieurs à l'appelant, ce dernier peut gérer le chemin d'appel sans faire partie de la pile des appels.
Pour pallier ce problème, vous pouvez soit limiter vos appelants (en demandant une autorisation, par exemple) soit limiter les autorisations dans le cadre desquelles le délégué peut s'exécuter (en utilisant une substitution de pile Deny ou PermitOnly, par exemple).

LinkDemands et Wrappers

Un cas de protection particulier avec des demandes de liaison a fait l'objet d'une consolidation dans l'infrastructure de sécurité, mais il représente toujours une source de failles possibles dans votre code.
Si du code d'un niveau de confiance suffisant appelle une propriété, un événement ou une méthode protégé par une demande LinkDemand, l'appel aboutit si la vérification d'autorisation LinkDemand pour l'appelant est satisfaite. De plus, si le code d'un niveau de confiance suffisant expose une classe qui prend le nom d'une propriété et appelle son accesseur get à l'aide de la réflexion, cet appel à l'accesseur get aboutit même si le code utilisateur n'a pas le droit d'accéder à cette propriété. Cela tient au fait que LinkDemand contrôle uniquement l'appelant immédiat qui correspond au code d'un niveau de confiance suffisant. Essentiellement, le code d'un niveau de confiance suffisant effectue un appel privilégié pour le compte du code utilisateur sans s'assurer que celui-ci a le droit d'effectuer cet appel. Si vous enveloppez la fonctionnalité de réflexion, reportez-vous à l'article Version 1 Security Changes for the Microsoft .NET Framework pour obtenir plus d'informations.
Pour éviter de telles brèches dans la sécurité, le Common Language Runtime élargit le contrôle sous la forme d'une demande de parcours de pile complet sur n'importe quel appel (création de l'instance, invocation de la méthode, propriété set ou get) à une méthode, constructeur, propriété ou événement protégé par une demande de liaison. Cette protection entraîne des coûts de performance (la vérification LinkDemand de niveau unique était plus rapide) et change la sémantique de la vérification de sécurité ; la demande de parcours de pile complet risque d'échouer là où la vérification de niveau unique aurait réussi. Wrappers chargeant des assemblys
Plusieurs méthodes utilisées pour charger du code managé, y compris Assembly.Load(byte[]), chargent des assemblys avec la preuve de l'appelant. Si vous enveloppez une de ces méthodes, le système de sécurité peut utiliser l'octroi d'autorisation de votre code à la place des autorisations de l'appelant de votre wrapper afin de charger les assemblys. Vous ne devez pas permettre au code d'un niveau de confiance moindre de charger du code dont les autorisations sont supérieures à celles de l'appelant de votre wrapper.
Tout code dont le niveau de confiance est suffisant ou nettement supérieur à un appelant potentiel (y compris un appelant avec des autorisations au niveau d'Internet) risque d'affaiblir la sécurité dans ce contexte. Si votre code possède une méthode publique qui prend un tableau d'octets et le transmet à Assembly.Load(byte[]) créant ainsi un assembly au nom de l'appelant, il risque de perturber la sécurité.

Ce problème s'applique aux éléments d'API suivants :
• System.AppDomain.DefineDynamicAssembly
• System.Reflection.Assembly.LoadFrom
• System.Reflection.Assembly.Load

Gestion des exceptions

Une expression de filtre au sommet de la pile s'exécute avant toute instruction finally. Le bloc catch associé à ce filtre s'exécute après l'instruction finally. Examinez le pseudo-code suivant :

void Main() { try { Sub(); } except (Filter()) 
{ Console.WriteLine("catch"); } } bool Filter () 
{ Console.WriteLine("filter"); return true; } void Sub() { try 
{ Console.WriteLine("throw"); throw new Exception(); } finally 
{ Console.WriteLine("finally"); } }
 

Ce code imprime ce qui suit :

Throw Filter Finally Catch 

Le filtre s'exécute avant l'instruction finally ; par conséquent, des problèmes de sécurité peuvent être introduits par toute opération effectuant un changement d'état là où l'exécution d'un autre code pourrait en profiter. Par exemple :

try { Alter_Security_State(); 
// This means changing anything (state variables, 
// switching unmanaged context, impersonation, and so on) 
// that could be exploitable if malicious code ran before state is restored. Do_some_work(); } 
finally { Restore_Security_State(); 
// This simply restores the state change above. } 

Ce pseudo-code permet à un filtre plus haut dans la pile d'exécuter du code arbitraire. Parmi les autres exemples d'opérations aux effets similaires figurent notamment l'emprunt temporaire d'une autre identité, la définition d'un indicateur interne qui ignore la vérification de sécurité ou le changement de la culture associée au thread.

La solution recommandée consiste à introduire un gestionnaire d'exceptions pour isoler les changements de code apportés à l'état des threads des blocs de filtre des appelants. Cependant, il est important que le gestionnaire d'exceptions soit correctement introduit ou ce problème ne sera pas résolu. L'exemple Microsoft Visual Basic® suivant active la culture de l'interface utilisateur, mais tout type de changement d'état des threads peut être exposé de manière similaire.

YourObject.YourMethod() 
{ CultureInfo saveCulture = Thread.CurrentThread.CurrentUICulture; 
try { Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE"); 
// Do something that throws an exception. } finally 
{ Thread.CurrentThread.CurrentUICulture = saveCulture; } } 
Public Class UserCode Public Shared Sub Main() 
Try Dim obj As YourObject = new YourObject obj.YourMethod() 
Catch e As Exception When FilterFunc Console.WriteLine("An error occurred: '{0}'", e) 
Console.WriteLine("Current Culture: {0}", Thread.CurrentThread.CurrentUICulture) 
End Try End Sub 
Public Function FilterFunc As Boolean Console.WriteLine("Current Culture: {0}", 
Thread.CurrentThread.CurrentUICulture) Return True End Sub End Class 

Pour résoudre correctement ce cas, le bloc try/finally existant doit être enveloppé dans un bloc try/catch. La simple introduction d'une clause catch-throw dans le bloc try/finally existant ne résout pas le problème :

YourObject.YourMethod() { CultureInfo saveCulture = Thread.CurrentThread.CurrentUICulture; 
try { Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE"); 
// Do something that throws an exception. } catch { throw; } 
finally { Thread.CurrentThread.CurrentUICulture = saveCulture; } } 

Le problème n'est pas résolu car l'instruction finally ne s'est pas exécutée avant que FilterFunc prenne le contrôle.

L'exemple de code suivant corrige le problème en garantissant que la clause finally se soit exécutée avant de proposer une exception au sommet des blocs de filtre d'exceptions des appelants.

YourObject.YourMethod() { 
CultureInfo saveCulture = Thread.CurrentThread.CurrentUICulture; 
try { try { Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE"); 
// Do something that throws an exception. } finally 
{ Thread.CurrentThread.CurrentUICulture = saveCulture; } } 
catch { throw; } } 

Code non managé

Un code dans une bibliothèque doit invoquer un code non managé (par exemple, des API de code natif comme Win32). Cela signifie pour le code managé sortir du périmètre de sécurité, donc la prudence est de règle. Si votre code n'est pas dépendant de la sécurité (reportez-vous à la section Code indépendant de la sécurité de ce document), votre code ainsi que n'importe quel code qui appelle ce dernier doit avoir l'autorisation de code non managé (SecurityPermission.UnmanagedCode). Cependant, le plus souvent, il n'est pas recommandé que votre appelant possède des autorisations aussi puissantes. Dans ces cas-là, votre code de confiance peut servir d'intermédiaire de la même manière que le wrapper managé ou le code de bibliothèque décrit précédemment. Si les fonctionnalités de code non managé sous-jacentes sont totalement sécurisées, elles risquent d'être directement exposées ; si ce n'est pas le cas, une vérification d'autorisation appropriée (demande) est nécessaire dans un premier temps.
Lorsque votre code appelle du code non managé, mais que vous ne souhaitez pas que vos appelants soient obligés d'avoir cette autorisation, vous devez déclarer ce droit. Une assertion limite le parcours de la pile à votre cadre. Vous devez faire bien attention à ne pas créer de brèche dans la sécurité dans ce processus. Habituellement, cela signifie que vous devez exiger une autorisation appropriée de vos appelants, puis vous servir du code non managé uniquement pour effectuer ce que cette autorisation permet et rien de plus. Dans certains cas, (par exemple, une fonction définissant l'heure du jour), du code non managé peut être directement exposé aux appelants sans vérification de la sécurité. Dans tous les cas, tout code qui déclare doit être responsable de la sécurité.
Comme tout code managé qui fournit un chemin de code dans le code natif est une cible potentielle pour du code nuisible, déterminer le type de code non managé pouvant s'utiliser sans risque et la manière de l'utiliser nécessite une extrême prudence. Généralement, il ne faut jamais exposer du code non managé directement à des appelants d'un niveau de confiance partiel (reportez-vous aux sections ci-après). Deux considérations principales gouvernent l'évaluation de la sécurité du code non managé utilisé dans des bibliothèques pouvant être appelées par du code d'un niveau de confiance partiel :
• Fonctionnalité. Est-ce que l'API non managée fournit des fonctionnalités fiables qui ne permettent pas aux appelants d'effectuer des opérations potentiellement risquées ? La sécurité d'accès du code utilise des autorisations pour faire appliquer l'accès aux ressources, il vous faut donc déterminer si l'API utilise des fichiers, une interface utilisateur ou des threads, ou si elle expose des informations protégées. Si tel est le cas, le code managé qui l'englobe doit exiger les autorisations nécessaires avant d'autoriser son entrée. De plus, même s'il ne fait pas l'objet d'une protection par autorisation, le système de sécurité requiert que l'accès à la mémoire soit limité à la sécurité stricte des types.
• Vérification des paramètres. Une attaque courante transmet des paramètres inattendus à des méthodes API de code non managé exposées, dans le but de les faire fonctionner hors spécification. Le débordement de la mémoire tampon est un exemple courant de ce type d'attaque (utilisant des index non valides ou des valeurs offset), de même que tous les paramètres qui risquent d'exploiter un bogue dans le code sous-jacent. Ainsi, même si l'API du code non managé est sécurisée du point de vue fonctionnel pour des appelants d'un niveau de confiance partiel (après des demandes nécessaires), le code managé doit également vérifier la validité des paramètres de manière exhaustive pour garantir qu'aucun appel involontaire n'est possible par du code nuisible utilisant la couche wrapper du code managé.

Utilisation de SuppressUnmanagedCodeSecurity

Il faut prendre en compte l’aspect performance lié à la déclaration puis à l'appel de code non managé. Pour chaque appel de ce type, le système de sécurité exige automatiquement l'autorisation du code non managé, ce qui entraîne à chaque fois un parcours de pile. Si vous déclarez et appelez immédiatement du code non managé, le parcours de pile peut s'avérer inutile : il comporte votre déclaration et votre appel de code non managé.
Un attribut personnalisé nommé SuppressUnmanagedCodeSecurity peut être appliqué à des points d'entrée du code non managé pour désactiver la vérification de sécurité normale qui exige SecurityPermission.UnmanagedCode. Il faut effectuer ces opérations avec la plus grande prudence, car cette action crée une porte ouverte dans du code non managé sans vérification de sécurité au moment de l'exécution. Il faut remarquer que même si SuppressUnmanagedCodeSecurity est appliqué, une vérification de sécurité unique a lieu au moment de la compilation juste-à-temps (JIT, Just-In-Time) pour garantir que l'appelant immédiat possède l'autorisation d'appeler du code non managé.

Si vous utilisez l'attribut SuppressUnmanagedCodeSecurity, vérifiez les points suivants :
• Faites en sorte que le point d'entrée du code non managé ne soit pas accessible hors de votre code (par exemple, « interne »).
• Tout appel dans du code non managé représente une brèche possible en matière de sécurité. Veillez à ce que votre code ne soit pas une porte ouverte à un appel indirect du code nuisible dans du code non managé évitant ainsi une vérification de sécurité. Exigez des autorisations, si cela est approprié.
• Utilisez une convention d'affectation de noms pour identifier de manière explicite les cas où vous créez un chemin à risque dans du code non managé, comme indiqué dans la section ci-dessous.

Convention d'affectation de noms pour les méthodes de code non managé

Une convention utile et fortement recommandée a été créée afin de nommer des méthodes de code non managé. Toutes les méthodes de code non managé sont divisées en trois catégories : safe, native et unsafe. Ces mots clés peuvent être utilisés comme noms de classe dans lesquels les différents types de points d'entrée du code non managé sont définis. Dans le code source, ces mots clés doivent être ajoutés au nom de la classe ; par exemple, Safe.GetTimeOfDay, Native.Xyz ou Unsafe.DangerousAPI. Chacune de ces catégories doit envoyer un message fort aux développeurs qui les utilisent, comme le montre le tableau suivant.

Mot clé

Office 2000

safe

Ne présente aucun danger pour un appel par n'importe quel code, même du code nuisible. Peut s'utiliser comme n'importe quel autre code managé. Exemple : une fonction qui définit l'heure du jour.

native

Indépendant de la sécurité ; c'est-à-dire du code non managé qui nécessite l'autorisation du code non managé pour appeler. La sécurité est vérifiée, ce qui bloque un appelant non autorisé.

unsafe

Point d'entrée dans du code non managé potentiellement dangereux et sans sécurité. Les développeurs doivent faire preuve d'une grande prudence lorsqu'ils utilisent ce code peu fiable, en veillant à ce que les autres protections soient en place afin d'éviter toute faille de sécurité. Les développeurs doivent se montrer responsables car ce mot clé substitue le système de sécurité.

Entrées de l'utilisateur

Les données utilisateur, qui correspondent à n'importe quelles entrées (données provenant d'une demande Web ou URL, entrées dans les contrôles d'une application Microsoft Windows Forms, etc.), peuvent nuire au code car il arrive souvent que ces données soient utilisées directement comme paramètres pour appeler d'autre code. Cette situation est semblable au code nuisible appelant votre code avec des paramètres étranges, et les mêmes précautions doivent être observées. Les entrées de l'utilisateur sont en fait plus difficiles à sécuriser car il n'existe pas de cadre de pile pour détecter la présence de données potentiellement non fiables.
Ces données comptent parmi les bogues de sécurité les plus difficiles et les plus subtils car même s’ils peuvent exister dans du code apparemment sans rapport avec la sécurité, ces bogues constituent une passerelle permettant de transmettre les données incorrectes à d'autre code. Pour les rechercher, suivez n'importe quel type de données d'entrée, imaginez l'éventail des valeurs possibles, puis analysez si le code confronté à ces données peut traiter tous ces cas. Vous pouvez corriger ces bogues en vérifiant les plages et en rejetant toutes les entrées que le code ne peut pas traiter.

Parmi les erreurs courantes relatives aux données utilisateur figurent notamment les suivantes :

• Toutes les données utilisateur dans une réponse de serveur s'exécutent dans le contexte du site du serveur sur le client. Si votre serveur Web prend les données utilisateur et les insère dans la page Web retournée, il peut, par exemple, inclure une balise <script> et s'exécuter comme depuis un serveur.
• N'oubliez pas que le client peut demander n'importe quelle URL.

• Considérez des chemins d'accès non valides ou délicats :
• ..\ , des chemins d'accès particulièrement longs ;
• l'utilisation de caractères génériques (*) ;
• l'expansion de jeton (%token%) ;
• des formes étranges de chemin d'accès dont la signification est particulière ;
• l'alternance de noms de flux NTFS, comme par exemple filename::$DATA ;
• des versions courtes de noms de fichiers, comme par exemple longfi~1pour longfilename ;
• Eval(userdata) peut faire n'importe quoi.
• Une liaison tardive à un nom qui inclut des données utilisateur.
• Si vous traitez des données Web, vous devez tenir compte des différentes formes d'échappement autorisées, y compris : • échappements hexadécimaux (%nn) ;
• échappements Unicode (%nnn) ;
• échappements très longs UTF-8 (%nn%nn) ;
• double échappements (%nn devient %mmnn, où %mm correspond à l'échappement de '%').
• Méfiez-vous de noms d'utilisateur qui peuvent avoir plus d'un format canonique. Dans Microsoft Windows 2000, par exemple, vous pouvez souvent utiliser la forme REDMOND\username ou la forme username@redmond.microsoft.com.

Considérations sur l'accès distant

L'accès distant vous permet de définir des appels transparents entre des domaines d'application, des processus ou des ordinateurs. Cependant, le parcours de pile de la sécurité d'accès du code ne peut pas traverser des processus ou des limites de machine (il s'applique pourtant entre les domaines d'application du même processus).
Toute classe accessible à distance (dérivée depuis une classe MarshalByRefObject) doit être responsable de la sécurité. Soit le code ne doit être utilisé que dans des environnements fermés où le code appelant peut faire l'objet d'une confiance implicite, soit des appels distants doivent être conçus de façon à ne pas exposer du code protégé à une entrée externe qui pourrait être utilisée à des fins malveillantes.
Généralement, vous ne devez jamais exposer de méthodes, de propriétés ou d'événements protégés par des vérifications de sécurité LinkDemand et InheritanceDemand déclaratives. Avec l'accès à distance, ces contrôles ne sont pas appliqués. D'autres vérifications de sécurité, telles que Demand, Assert, etc. fonctionnent entre des domaines d'application au sein d'un processus, mais pas entre les processus ou les ordinateurs.

Objets protégés

Certains objets contiennent un état de sécurité en eux-mêmes. Ces objets ne doivent pas être transmis à du code non fiable qui obtiendrait dès lors des autorisations de sécurité qui dépassent ses propres autorisations
Un exemple consiste à créer un objet FileStream. FileIOPermission est exigé au moment de la création et si celui-ci aboutit, l'objet de fichier est retourné. Cependant, si cette référence d'objet est transmise au code sans les autorisations de fichier, l'objet sera capable de lire et d'écrire dans ce fichier particulier.
La défense la plus simple pour ce genre d'objet est d'exiger le même FileIOPermission de n'importe quel code cherchant à obtenir la référence d'objet par l'intermédiaire d'un élément d'API publique.

Sérialisation

L'utilisation de la sérialisation peut permettre à un autre code de voir ou de modifier les données de l'instance d'objet qui seraient autrement inaccessibles. Ainsi, le code effectuant la sérialisation doit posséder une autorisation particulière : SecurityPermission.SerializationFormatter. Dans le cadre de la stratégie par défaut, cette autorisation n'est pas accordée à du code téléchargé depuis Internet ou un intranet ; seul le code sur l'ordinateur local reçoit cette autorisation.
En règle générale, tous les champs d'une instance d'objet sont sérialisés, ce qui signifie que des données sont représentées dans les données sérialisées pour l'instance. Il est possible pour le code capable d'interpréter le format de déterminer les valeurs des données, quelle que soit l'accessibilité du membre. De même, la désérialisation extrait des données de la représentation sérialisée et définit l'état des objets directement, quelles que soient là encore les règles d'accessibilité. Si possible, faites en sorte qu'un objet pouvant contenir des données de sécurité sensibles soit non sérialisable. Si l'objet doit être sérialisable, faites en sorte que les champs spécifiques contenant des données sensibles ne soient pas sérialisables. Si cela n'est pas possible, gardez à l'esprit que ces données seront exposées à tout code autorisé à sérialiser et veillez à ce qu'aucun code nuisible n'obtienne cette autorisation.

L'interface ISerializable est destinée à être utilisée uniquement par l'infrastructure de sérialisation. Cependant, si elle n'est pas protégée, elle risque de révéler des informations sensibles. Si la sérialisation personnalisée est fournie par l'implémentation de ISerializable, veillez à prendre les précautions suivantes :
• La méthode GetObjectData doit être sécurisée de manière explicite soit en demandant l'autorisation SecurityPermission.SerializationFormatter, soit en vous assurant qu'aucune information sensible n'est divulguée avec la sortie de la méthode. Par exemple :

[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter =true)] 
public override void GetObjectData(SerializationInfo info, StreamingContext context) 

• Le constructeur spécial utilisé pour la sérialisation doit également effectuer une validation minutieuse de l'entrée et doit être soit protégé, soit privé afin d'éviter une utilisation par du code nuisible. Il doit appliquer les mêmes vérifications de sécurité et autorisations nécessaires afin d'obtenir une instance de cette classe par d'autres moyens (création explicite ou indirecte via un type de fabrique).

Questions relatives au franchissement de domaine d'application

Pour isoler du code dans des environnements d'hébergement managés, il est courant de générer plusieurs domaines d'application enfants dont la stratégie explicite réduit les niveaux d'autorisation pour divers assemblys. Cependant, la stratégie pour ces assemblys reste inchangée dans le domaine d'application par défaut. Si l'un des domaines d'application enfants peut forcer le domaine d'application par défaut à charger un assembly, l'effet de l'isolement du code est perdu et les types dans ces assemblys pourront exécuter du code à un niveau de confiance plus élevé.
Un domaine d'application peut forcer un autre domaine d'application à charger un assembly et à exécuter le code qu'il contient en appelant un proxy vers un objet hébergé dans l'autre domaine d'application. Pour obtenir un proxy entre domaines d'application, le domaine d'application qui héberge l'objet doit en distribuer un par l'intermédiaire d'un paramètre d'appel de méthode ou d'une valeur de retour. Ou, si le domaine d'application vient d'être créé, le créateur possède un proxy vers l'objet AppDomain. Par conséquent, pour éviter d'interrompre l'isolement, un domaine d'application dont le niveau de confiance est supérieur ne doit pas distribuer de références aux objets MarshalByRefObject dans son domaine à des domaines d'application dont les niveaux de confiance sont moindres.
Généralement, le domaine d'application par défaut crée des domaines d'application enfants avec un objet de contrôle dans chacun d'entre eux. L'objet de contrôle gère le nouveau domaine d'application et de manière occasionnelle reçoit des instructions du domaine d'application par défaut, mais il ne peut pas en fait contacter le domaine directement. De manière occasionnelle, le domaine d'application par défaut appelle son proxy vers l'objet de contrôle. Cependant, il se peut qu'il existe des cas où il est nécessaire que l'objet de contrôle soit capable de rappeler le domaine d'application par défaut. Dans ces cas, le domaine d'application par défaut transmet un objet de rappel marshalé-par-référence au constructeur de l'objet de contrôle. L'objet de contrôle est responsable de la protection de ce proxy. Si l'objet de contrôle devait placer le proxy sur un champ static public d'une classe publique ou exposer publiquement le proxy, cela ouvrirait un mécanisme dangereux permettant à un autre code de rappeler dans le domaine d'application par défaut. C'est pourquoi, les objets de contrôle sont toujours autorisés de manière implicite à maintenir le proxy privé.

Évaluation des autorisations

Le principe de la sécurité basée sur les preuves réside dans le fait que la confiance de niveau élevé (de nombreuses autorisations très puissantes) est accordée uniquement au code digne de confiance et peu voire aucune confiance n'est accordée au code nuisible. Pour l'octroi des autorisations, la stratégie par défaut fournie avec le .NET Framework utilise des zones, comme celles définies par Microsoft Internet Explorer. Voici une description simplifiée de la stratégie par défaut :

• La zone de l'ordinateur local (par exemple, c:\app.exe) reçoit une confiance totale. Il est considéré que les utilisateurs doivent placer uniquement sur leurs ordinateurs du code qu'ils estiment de confiance et que la plupart d'entre eux ne souhaitent pas partager leurs disques durs en plusieurs zones de confiance. Ce code peut, dans l'absolu, tout faire, et la sécurité du code managé n'est pas applicable. Par conséquent, il n'existe aucun moyen de se protéger d'un code nuisible dans cette zone.
• Le code de la zone Internet (par exemple, http://www.microsoft.com/) ne reçoit qu'un jeu d'autorisations limité, considérées comme pouvant être accordées en toute sécurité, même à du code nuisible. En général, le code n'est pas digne de confiance. Par conséquent il ne peut être exécuté en toute sécurité qu'avec un jeu d'autorisations limité, avec lequel il ne peut pas causer de dommages :
- WebPermission. Permet d'accéder au même serveur de site que celui dont elle provient.
- FileDialogPermission. Permet d'accéder uniquement aux fichiers choisis par l'utilisateur.
- IsolatedStorageFilePermission. Stockage persistant, isolé par le site Web.
- UIPermission. Peut écrire dans une fenêtre sécurisée de l'interface utilisateur.
• Le code de la zone intranet (par exemple, \\UNC\share) reçoit un jeu d'autorisations Internet légèrement plus fort, mais toujours aucune autorisation puissante :
- FileIOPermission. Un accès en lecture seule aux fichiers du répertoire dont elle provient.
- WebPermission. Permet d'accéder au serveur dont elle provient.
- DNSPermission. Autorise la résolution des noms DNS en adresses IP.
- FileDialogPermission. Permet d'accéder uniquement aux fichiers choisis par l'utilisateur.
- IsolatedStorageFilePermission. Stockage persistant, moins de restrictions.
- UIPermission. Peut utiliser librement sa propre fenêtre de niveau supérieur.
• Pour s'exécuter, le code de zone des sites sensibles reçoit uniquement l'autorisation minimale.

Vous devez repenser vos exigences en matière de sécurité et modifier la stratégie de sécurité de façon appropriée. Aucune configuration de sécurité unique ne peut répondre à tous les besoins : la stratégie par défaut est définie pour être pratique d'une façon générale, sans autoriser quoi que ce soit de dangereux.
Votre code reçoit différentes autorisations selon son déploiement. Assurez-vous que votre code reçoive les autorisations suffisantes pour fonctionner correctement. Lorsque vous souhaitez protéger votre code des attaques, essayez d'imaginer d'où pourrait provenir le code nuisible et comment il pourrait accéder à votre code.

Autorisations dangereuses

Plusieurs des opérations protégées pour lesquelles le .NET Framework fournit des autorisations risquent de permettre le contournement du système de sécurité. Ces autorisations dangereuses doivent être accordées uniquement au code digne de confiance, et uniquement en fonction des besoins. En général, il n'existe pas de défense contre du code nuisible si ces autorisations lui sont accordées.

Parmi les autorisations dangereuses figurent notamment les suivantes :
• SecurityPermission
- UnmanagedCode. Permet au code managé d'appeler dans du code non managé, ce qui est souvent dangereux.
- SkipVerification. En l'absence de vérification, le code peut tout faire.
- ControlEvidence. La création de preuves permet de tromper la stratégie de sécurité.
- ControlPolicy. La possibilité de modifier la stratégie de sécurité peut désactiver la sécurité.
- SerializationFormatter. L'utilisation de la sérialisation peut permettre de contourner l'accessibilité, comme indiqué précédemment.
- ControlPrincipal. La possibilité de définir l'entité de sécurité en cours peut tromper la sécurité basée sur les rôles.
- ControlThread. La manipulation des threads est dangereuse en raison de l'état de sécurité associé aux threads.
• ReflectionPermission
- MemberAccess. Déjoue les mécanismes d'accessibilité (peut utiliser des membres privés). Sécurité et conflits d'accès

Sécurité et conflits d'accès

Un autre problème concerne les risques de brèches dans la sécurité et leur exploitation par des conflits d'accès. Ce problème peut se manifester de plusieurs façons. Les sous-sections suivantes mettent en avant certains des principaux pièges que le développeur doit éviter.

Conflits d'accès dans la méthode Dispose

Si la méthode Dispose d'une classe n'est pas synchronisée, il est possible que le code de nettoyage à l'intérieur de Dispose soit exécuté plus d'une fois. Examinez le code suivant :

void Dispose() { if( _myObj != null ) { Cleanup(_myObj); _myObj = null; } } 

Comme cette implémentation Dispose n'est pas synchronisée, il est possible que Cleanup soit d'abord appelé par un thread puis par un deuxième thread avant que _myObj reçoive la valeur null. Qu'il s'agisse d'un problème de sécurité ou non dépend du résultat de l'exécution du code Cleanup. Un problème central lié aux implémentations Dispose non synchronisées concerne l'utilisation de handles de ressource comme des fichiers par exemple. Une suppression inappropriée peut entraîner l'utilisation d'un handle incorrect, ce qui génère souvent des failles en matière de sécurité.

Conflits d'accès dans des constructeurs

Dans certaines applications, il est parfois possible pour d'autres threads d'accéder à des membres de la classe avant l'exécution complète de leurs constructeurs de classe. Vous devez passer en revue tous les constructeurs de classe pour vous assurer qu'il n'y a pas de problème lié à la sécurité si cela doit se produire ou synchroniser des threads, si nécessaire.

Conflits d'accès avec les objets mis en cache

Le code qui met en cache des informations de sécurité ou utilise l'assertion peut également être soumis à des conflits d'accès si d'autres parties de la classe ne sont pas correctement synchronisées. Examinez le code suivant :

void SomeSecureFunction() { if(SomeDemandPasses()) 
{ _fCallersOk = true; DoOtherWork(); _fCallersOk = false(); } } 
void DoOtherWork() { if( _fCallersOK ) { DoSomethingTrusted(); } 
else { DemandSomething(); DoSomethingTrusted(); } } 

S'il existe d'autres chemins vers DoOtherWork qui peuvent être appelés à partir d'un autre thread à l'aide du même objet, un appelant non fiable peut ignorer une demande.
Si votre code met en cache des informations de sécurité, veillez à le passer en revue dans le cadre de cette faille.

Conflits d'accès dans des finaliseurs

Des conflits d'accès peuvent également se produire dans un objet qui référence une ressource statique ou non managée qu'il libère ensuite dans son finaliseur. Si plusieurs objets partagent une ressource manipulée dans un finaliseur de classe, les objets doivent synchroniser tous les accès à cette ressource.

Autres technologies de sécurité

Cette section présente d'autres technologies de sécurité qui peuvent s'appliquer à votre code. Elles ne sont toutefois pas complètement développées dans ce document.

Génération de code immédiate

Certaines bibliothèques fonctionnent en générant du code et en l'exécutant afin d'effectuer certaines opérations pour l'appelant. Le problème de base est la génération de code pour le compte de code d'un niveau de confiance moindre et son exécution à un niveau de confiance supérieur. Le problème se complique lorsque l'appelant peut influencer la génération du code, vous devez donc vous assurer que seul du code que vous estimez sécurisé est généré.
Vous devez toujours savoir précisément quel code vous générez. Cela signifie que vous devez avoir un contrôle strict sur toutes les valeurs que vous recevez d'un utilisateur, qu'il s'agisse de chaînes entre guillemets (qui devraient être échappées de façon à ce qu'elles ne puissent pas inclure d'éléments de code inattendus), d'identificateurs (dont la validité doit être vérifiée) ou de tout autre élément. Les identificateurs peuvent être dangereux car vous pouvez modifier un assembly compilé pour que ses identificateurs contiennent des caractères étranges qui risquent de le bloquer (bien qu'il s'agisse rarement d'une faille de sécurité).
Il est recommandé de générer votre code à l'aide de Reflection.Emit, qui vous aide souvent à éviter la plupart de ces problèmes.
Lors de la compilation du code, examinez s'il est possible pour un programme nuisible de le modifier. Existe-t-il un laps de temps durant lequel du code nuisible peut changer du code source sur le disque avant sa lecture par le compilateur ou avant le chargement par votre code du fichier .dll ? Si c'est le cas, vous devez protéger le répertoire contenant ces fichiers à l'aide de la sécurité d'accès du code ou d'une liste de contrôle d'accès dans le système de fichiers, selon le cas. Si un appelant peut influencer le code généré au point de provoquer une erreur de compilation, le risque dans ce cas d'une faille en matière de sécurité est possible.
Exécutez le code généré avec les plus petits paramètres d'autorisation possibles (en utilisant PermitOnly ou Deny).

Sécurité basée sur les rôles : Authentification et autorisation

Outre la sécurisation du code, dans certaines applications il est nécessaire d'implémenter une protection de sécurité qui limite l'utilisation à certains utilisateurs ou groupes d'utilisateurs. La sécurité basée sur les rôles, qui ne fait pas l'objet de ce document, est conçue pour répondre à ces besoins.

Gestion de la confidentialité des données

La confidentialité des données est assez efficace lorsque celles-ci se trouvent dans la mémoire. Préserver et maintenir cette confidentialité n'est pas chose facile. La première version du .NET Framework ne prend pas en charge le code managé pour la gestion de la confidentialité des données. Si vous possédez les compétences requises, la bibliothèque de chiffrement fournit la plupart des fonctionnalités élémentaires requises.

Chiffrement et signatures

L'espace de noms System.Security.Cryptography inclut un jeu important d'algorithmes de chiffrement. Effectuer des opérations de chiffrement en toute sécurité requiert certaines compétences et ne doit pas s'improviser. Vous devez concevoir et passer en revue attentivement chaque aspect de la gestion des données et des clés impliquées. Le chiffrement n'est l'objet de ce document. Pour plus d'informations, reportez-vous aux références standards.

Nombres aléatoires

Lorsqu'il est nécessaire d'avoir recours à des éléments véritablement aléatoires, utilisez System.Security.Cryptography.RandomNumberGenerator pour générer des nombres aléatoires pouvant être utilisés dans le cadre de la sécurité. L'utilisation de générateurs de nombres pseudo-aléatoires peut impliquer des probabilités qui risquent d'être exploitées.

Problèmes d'installation

Cette section présente les éléments à prendre en compte pour tester l'installation de l'application ou des composants, afin de garantir les meilleures pratiques de sécurité et de protéger le code installé. Les étapes suivantes sont recommandées lors de l'installation du code managé ou non managé pour garantir que l'installation elle-même est sécurisée. Ces étapes doivent être accomplies pour toutes les plates-formes qui prennent en charge le système de fichiers NTFS :
1. Configurez un système avec deux partitions.
2. Formatez la deuxième partition. Ne changez pas la liste de contrôle d'accès par défaut sur la racine du lecteur.
3. Installez le produit en remplaçant le répertoire d'installation pour qu'il pointe vers un nouveau répertoire sur la deuxième partition.

Vérifiez les points suivants :
1. Existe-t-il du code qui s'exécute en tant que service ou qui est normalement exécuté par des utilisateurs de niveau administrateur qui soit désormais accessible en écriture ?
2. Si le code était installé sur un système du serveur Terminal Server en mode serveur d'application, est-ce que vos utilisateurs peuvent désormais écrire des fichiers binaires exécutables par d'autres utilisateurs ?
3. Existe-t-il des éléments qui se retrouvent dans un domaine système ou un sous-répertoire d'un domaine qui peut être accessible en écriture par des non administrateurs ?

Par ailleurs, si le produit interagit avec le Web, gardez à l'esprit que certains serveurs Web permettent à des utilisateurs d'exécuter des commandes qui sont souvent exécutées dans le contexte du compte IUSR_MACHINE. Confirmez qu'il n'existe aucun fichier ou élément de configuration accessible en écriture dont un compte invité pourrait tirer parti dans ces conditions.

Afficher:
© 2015 Microsoft