Partager via


Considérations sur la sécurité des données

Lorsque vous traitez des données dans Windows Communication Foundation (WCF), vous devez considérer plusieurs catégories de menaces. Le tableau suivant répertorie les classes de menace les plus importantes concernant le traitement de données. WCF fournit des outils pour atténuer ces menaces.

  • Déni de service
    Lors de la réception de données non fiables, les données peuvent entraîner l'accès du côté réception à une quantité disproportionnée de ressources variées, telles que la mémoire, les threads, les connexions disponibles ou les cycles de processeur en provoquant de longs calculs. Une attaque par déni de service contre un serveur peut engendrer son blocage et son incapacité à traiter les messages en provenance d'autres clients légitimes.
  • Exécution de code malveillant
    Les données non fiables entrantes provoquent l'exécution de code imprévu par le côté réception.
  • Divulgation d'informations
    L'intrus distant force la partie réception à répondre à ses demandes de sorte qu'elle divulgue plus d'informations que prévu.

Code fourni par l'utilisateur et sécurité d'accès du code

Plusieurs emplacements dans l'infrastructure Windows Communication Foundation (WCF) exécutent du code fourni par l'utilisateur. Par exemple, le moteur de sérialisation DataContractSerializer peut appeler des accesseurs set de propriété fournis par l'utilisateur et des accesseurs get. L'infrastructure de canal WCF peut également appeler dans les classes dérivées fournies par l'utilisateur de la classe Message.

Il incombe à l'auteur du code de garantir qu'aucune faille de sécurité n'existe. Par exemple, si vous créez un type de contrat de données avec une propriété de membre de données de type entier, et que dans l'implémentation de l'accesseur set vous allouez un tableau en fonction de la valeur de la propriété, vous risquez une attaque par déni de service si un message malveillant contient une valeur extrêmement élevée pour ce membre. En général, évitez toutes les allocations basées sur des données entrantes ou un long traitement dans le code fourni par l'utilisateur (surtout si ce long traitement peut être provoqué par une petite quantité de données entrantes). Lorsque vous exécutez une analyse de sécurité du code fourni par l'utilisateur, assurez-vous de considérer également tous les cas d'échec (c'est-à-dire, toutes les branches de code où des exceptions sont levées).

Le dernier exemple de code fourni par l'utilisateur correspond au code figurant à l'intérieur de votre implémentation de service pour chaque opération. La sécurité de votre implémentation de service relève de votre responsabilité. Il est facile de créer par inadvertance des implémentations d'opérations incertaines qui peuvent provoquer des vulnérabilités au déni de service. Par exemple, une opération qui prend une chaîne et renvoie la liste des clients d'une base de données dont le nom commence par cette chaîne. Si vous utilisez une base de données volumineuse et que la chaîne passée comporte une seule lettre, votre code risque d'essayer de créer un message plus volumineux que toute la mémoire disponible, ce qui entraîne l'échec du service entier. (Une OutOfMemoryException n'est pas récupérable dans le .NET Framework et engendre toujours l'arrêt de votre application.)

Vous devez garantir qu'aucun code malveillant n'est intégré dans les divers points d'extensibilité. Cela est particulièrement vrai pour l'exécution en confiance partielle, pour le traitement des types issus d'assemblys d'un niveau de confiance partiel ou pour créer des composants utilisables par un code de niveau de confiance partiel. Pour plus d'informations, consultez « Menaces liées à une confiance partielle » dans une section ultérieure.

Notez que lors de l'exécution en confiance partielle, l'infrastructure de sérialisation de contrat de données prend en charge uniquement un sous-ensemble limité du modèle de programmation de contrat de données, par exemple, des types ou des membres de données privés qui utilisent l'attribut SerializableAttribute ne sont pas pris en charge. Pour plus d'informations, consultez Confiance partielle.

Éviter la divulgation d'informations involontaire

Dans le cadre de la conception de types sérialisables en gardant la sécurité à l'esprit, la divulgation d'informations est une préoccupation possible.

Considérez les points suivants :

  • Le modèle de programmation DataContractSerializer autorise l'exposition de données privées et internes en dehors du type ou assembly pendant la sérialisation. En outre, la forme d'un type peut être exposée pendant l'exportation de schéma. Veillez à comprendre la projection de sérialisation de votre type. Si vous souhaitez que rien ne soit exposé, désactivez-en la sérialisation (par exemple, en n'appliquant pas l'attribut DataMemberAttribute dans le cas d'un contrat de données).
  • Soyez conscient que le même type peut avoir plusieurs projections de sérialisation, selon le sérialiseur utilisé. Le même type peut exposer un jeu de données lorsqu'il est utilisé avec DataContractSerializer et un autre jeu de données lorsqu'il est utilisé avec XmlSerializer. Une utilisation accidentelle du mauvais sérialiseur peut entraîner une divulgation d'informations.
  • L'utilisation de XmlSerializer en mode d'appel de procédure distante (RPC)/codé hérité peut exposer involontairement la forme du graphique d'objets située sur le côté envoi au côté réception.

Prévention des attaques par déni de service

Quotas

Entraîner le côté réception à allouer une quantité de mémoire importante constitue une attaque par déni de service potentielle. Alors que cette section se concentre sur les problèmes de consommation de mémoire provenant de messages volumineux, d'autres attaques peuvent se produire. Par exemple, les messages peuvent utiliser une quantité disproportionnée de temps de traitement.

Les attaques par déni de service sont habituellement atténuées à l'aide de quotas. Lorsqu'un quota est dépassé, une exception QuotaExceededException est normalement levée. Sans le quota, un message malveillant peut provoquer l'accès à toute la mémoire disponible, ce qui engendre une exception OutOfMemoryException, ou l'accès à toutes les piles disponibles, ce qui engendre une StackOverflowException.

Le scénario du dépassement de quota est récupérable ; s'il se produit dans un service en cours d'exécution, le message en cours de traitement est ignoré et le service continue à s'exécuter et traite d'autres messages. En revanches, les scénarios de mémoire insuffisante et de dépassement de capacité de la pile ne sont pas récupérables où que ce soit dans le .NET Framework ; le service s'arrête s'il rencontre de telles exceptions.

Les quotas dans WCF n'impliquent aucune préallocation. Par exemple, si le quota MaxReceivedMessageSize (existant sur différentes classes) a la valeur 128 Ko, cela ne signifie pas que 128 Ko sont automatiquement alloués pour chaque message. La quantité réelle allouée dépend de la taille réelle du message entrant.

De nombreux quotas sont disponibles au niveau de la couche transport. Il s'agit de quotas appliqués par le canal de transport spécifique en cours d'utilisation (HTTP, TCP, et ainsi de suite). Même si cette rubrique présente une partie de ces quotas, ceux-ci sont décrits de manière détaillée dans Quotas de transport.

Limitation de la consommation de mémoire sans la diffusion en continu

Le modèle de sécurité lié aux messages volumineux dépend de l'utilisation de la diffusion en continu. Dans le cas simple de la non-diffusion en continu, les messages sont mis en mémoire tampon. Dans ce cas, utilisez le quota MaxReceivedMessageSize sur TransportBindingElement ou sur les liaisons fournies par le système à des fins de protection contre les messages volumineux en limitant la taille maximale des messages auxquels accéder. Notez qu'un service peut traiter plusieurs messages en même temps. Dans ce cas, ils sont tous en mémoire. Utilisez la fonctionnalité de limitation pour atténuer cette menace.

Notez également que MaxReceivedMessageSize n'impose pas de limite supérieure à la consommation de mémoire par message, mais la restreint à un facteur constant. Par exemple, si MaxReceivedMessageSize s'élève à 1 Mo et qu'un message de 1 Mo est reçu puis désérialisé, de la mémoire supplémentaire est nécessaire pour contenir le graphique d'objets désérialisé, ce qui engendre une consommation de mémoire totale bien supérieure à 1 Mo. Pour cette raison, évitez de créer des types sérialisables qui pourraient provoquer une consommation de mémoire importante sans beaucoup de données entrantes. Par exemple, un contrat de données « MonContrat » avec 50 champs de membre de données facultatifs et 100 champs privés supplémentaires pourrait être instancié avec la construction XML « <MonContrat/> ». Ce XML provoque un accès de la mémoire pour 150 champs. Notez que les membres de données sont facultatifs par défaut. Le problème est aggravé lorsqu'un tel type fait partie d'un tableau.

MaxReceivedMessageSize uniquement ne suffit pas à empêcher toutes les attaques par déni de service. Par exemple, le désérialiseur peut être forcé à désérialiser un graphique d'objets très imbriqué (un objet qui contient un autre objet qui en contient encore un autre, et ainsi de suite) par un message entrant. DataContractSerializer et XmlSerializer appellent des méthodes d'une manière imbriquée pour désérialiser de tels graphiques. L'imbrication profonde des appels de méthode peut provoquer une StackOverflowException irrécupérable. Cette menace est atténuée en définissant le quota MaxDepth de sorte à limiter le niveau d'imbrication XML, comme indiqué dans la section « Utilisation du XML en toute sécurité » plus loin dans cette rubrique.

L'affectation de la valeur MaxReceivedMessageSize à des quotas supplémentaires s'avère particulièrement importante lors de l'utilisation du codage XML binaire. L'utilisation du codage binaire équivaut presque à la compression : un petit groupe d'octets dans le message entrant peut représenter beaucoup de données. Ainsi, même un message qui respecte la limite MaxReceivedMessageSize peut consommer beaucoup plus de mémoire dans sa forme complètement développée. Pour atténuer de telles menaces propres au XML, tous les quotas de lecteur XML doivent être définis correctement, comme indiqué dans la section « Utilisation du XML en toute sécurité » plus loin dans cette rubrique.

Limitation de la consommation de mémoire avec la diffusion en continu

Dans le cadre de la diffusion en continu, vous pouvez utiliser un petit paramètre MaxReceivedMessageSize à des fins de protection contre les attaques par déni de service. Toutefois, des scénarios plus compliqués sont possibles avec la diffusion en continu. Par exemple, un service de téléchargement de fichiers accepte des fichiers plus volumineux que toute la mémoire disponible. Dans ce cas, affectez à MaxReceivedMessageSize une valeur extrêmement élevée, en pensant que quasiment aucune donnée n'est mise en mémoire tampon et que le message se diffuse en continu directement sur le disque. Si un message malveillant peut d'une façon ou d'une autre forcer WCF à mettre en mémoire tampon des données au lieu de les diffuser en continu dans un tel cas, MaxReceivedMessageSize ne permet plus d'empêcher le message d'accéder à toute la mémoire disponible.

Pour atténuer cette menace, des paramètres de quotas spécifiques existent sur différents composants de traitement des données WCF qui limitent la mise en mémoire tampon. Le plus important de ces quotas est la propriété MaxBufferSize sur différents éléments de liaison de transport et liaisons standard. Dans le cadre de la diffusion en continu, ce quota doit être défini en tenant compte de la quantité de mémoire maximale que vous êtes disposé à allouer par message. Comme avec MaxReceivedMessageSize, le paramètre n'impose pas de maximum absolu à la consommation de mémoire mais il la limite à un facteur constant. En outre, comme avec MaxReceivedMessageSize, soyez conscient de la possibilité de traiter plusieurs messages en même temps.

Détails sur MaxBufferSize

La propriété MaxBufferSize limite toutes les mises en mémoire tampon en bloc que WCF effectue. Par exemple, WCF met toujours en mémoire tampon les en-têtes SOAP et les erreurs SOAP, ainsi que toutes les parties MIME qui ne sont pas dans l'ordre de lecture naturel dans un message MTOM (Message Transmission Optimization Mechanism). Ce paramètre limite la quantité de mise en mémoire tampon dans tous ces cas.

WCF accomplit cette limitation en transférant la valeur MaxBufferSize aux divers composants susceptibles d'effectuer une mise en mémoire tampon. Par exemple, certaines surcharges CreateMessage de la classe Message prennent un paramètre maxSizeOfHeaders. WCF transfère la valeur MaxBufferSize à ce paramètre pour limiter la quantité de mise en mémoire tampon des en-têtes SOAP. Il est important de définir ce paramètre lors de l'utilisation directe de la classe Message. En général, lorsque vous utilisez un composant dans WCF qui prend des paramètres de quota, il est important de comprendre les implications en matière de sécurité de ces paramètres et de les définir correctement.

Le codeur de message MTOM possède également un paramètre MaxBufferSize. Lorsque vous utilisez des liaisons standard, la valeur MaxBufferSize au niveau du transport est automatiquement affectée à ce paramètre. Toutefois, lorsque vous utilisez l'élément de la liaison du codeur de message MTOM pour construire une liaison personnalisée, il est important d'affecter à la propriété MaxBufferSize une valeur sûre lorsque la diffusion en continu est utilisée.

Attaques de diffusion en continu basées sur XML

MaxBufferSize uniquement ne suffit pas à garantir que WCF ne peut pas être forcé à effectuer une mise en mémoire tampon alors qu'une diffusion en continu est prévue. Par exemple, les lecteurs XML WCF mettent toujours en mémoire tampon la balise de début d'élément XML dans son ensemble lorsqu'ils commencent à lire un nouvel élément. Cette mise en mémoire tampon permet de traiter correctement les espaces de noms et les attributs. Si le paramètre MaxReceivedMessageSize est configuré afin d'être élevé (par exemple, pour permettre un scénario de diffusion en continu d'un fichier volumineux directement sur le disque), un message malveillant peut être construit dans lequel le corps du message entier est une balise de début d'élément XML volumineuse. Une tentative de le lire provoque une OutOfMemoryException. Il s'agit de l'une des nombreuses attaques par déni de service basées sur XML possibles que vous pouvez toutes neutraliser à l'aide des quotas de lecteurs XML, présentés dans la section « Utilisation du XML en toute sécurité » plus loin dans cette rubrique. Dans le cadre de la diffusion en continu, il s'avère particulièrement important de définir tous ces quotas.

Combinaison des modèles de programmation de diffusion en continu et de mise en mémoire tampon

De nombreuses attaques possibles émanent du mélange de modèles de programmation de diffusion en continu et de non-diffusion en continu dans le même service. Supposez qu'il existe un contrat de service contenant deux opérations : une qui prend un Stream et une autre qui prend un tableau de type personnalisé. Supposez également qu'une valeur élevée est affectée à MaxReceivedMessageSize pour permettre à la première opération de traiter des flux volumineux. Malheureusement, cela signifie que des messages volumineux peuvent à présent être envoyés à la seconde opération, et que le désérialiseur met en mémoire tampon des données sous la forme d'un tableau avant d'appeler l'opération. Il s'agit d'une attaque par déni de service potentielle : le quota MaxBufferSize ne limite pas la taille du corps du message, avec laquelle le désérialiseur fonctionne.

Pour cette raison, évitez de combiner des opérations de diffusion et des opérations non diffusées dans le même contrat. Si vous devez absolument associer les deux modèles de programmation, prenez les précautions suivantes :

  • Désactivez la fonctionnalité IExtensibleDataObject en affectant à la propriété IgnoreExtensionDataObject de ServiceBehaviorAttribute la valeur true. Cette désactivation permet de veiller à ce que seuls les membres qui font partie du contrat soient désérialisés.
  • Affectez à la propriété MaxItemsInObjectGraph de DataContractSerializer une valeur sûre. Ce quota est également disponible sur l'attribut ServiceBehaviorAttribute ou via la configuration. Ce quota limite le nombre d'objets désérialisés au cours d'un épisode de désérialisation. Normalement, chaque paramètre d'opération ou partie de corps du message dans un contrat de message sont désérialisés dans un seul épisode. Lors de la désérialisation de tableaux, chaque entrée de tableau est comptée comme un objet distinct.
  • Affectez à tous les quotas de lecteurs XML des valeurs sûres. Faites attention à MaxDepth, MaxStringContentLength et MaxArrayLength et évitez les chaînes dans les opérations de non-diffusion en continu.
  • Consultez la liste des types connus, en n'oubliant pas que l'un d'eux peut être instancié à tout moment (consultez la section « Prévention du chargement de types involontaires » plus loin dans cette rubrique).
  • N'utilisez pas les types qui implémentent l'interface IXmlSerializable qui met beaucoup de données en mémoire tampon. N'ajoutez pas de tels types à la liste des types connus.
  • N'utilisez pas les tableaux XmlElement, XmlNode, Byte ou des types qui implémentent ISerializable dans un contrat.
  • N'utilisez pas les tableaux XmlElement, XmlNode, Byte ou des types qui implémentent ISerializable dans la liste des types connus.

Les précautions précédentes s'appliquent lorsque l'opération de non-diffusion en continu utilise DataContractSerializer. Ne mélangez jamais les modèles de programmation de diffusion en continu et de non-diffusion en continu sur le même service si vous utilisez XmlSerializer, parce qu'il ne possède pas la protection du quota MaxItemsInObjectGraph.

Attaques de flux lent

Une classe d'attaques par déni de service de diffusion en continu n'implique pas la consommation de mémoire. Cette attaque implique plutôt un expéditeur ou destinataire lent des données. En attendant que les données soient envoyées ou reçues, des ressources telles que des threads et des connexions disponibles s'épuisent. Cette situation peut survenir soit à la suite d'une attaque malveillante, soit à partir d'un expéditeur/destinataire légitime sur une connexion réseau lente.

Pour réduire ces attaques, définissez correctement les délais d'attente de transport. Pour plus d'informations, consultez Quotas de transport. Ensuite, n'utilisez jamais des opérations Read ou Write synchrones lorsque vous utilisez des flux dans WCF.

Utilisation du XML en toute sécurité

ms733135.note(fr-fr,VS.90).gifRemarque :
Même si cette section est consacrée au XML, les informations s'appliquent aussi aux documents JSON (JavaScript Object Notation). Les quotas fonctionnent de manière identique et utilisent Mappage entre JSON et XML.

Lecteurs XML sécurisés

Le jeu d'informations XML forme la base de tout traitement de message dans WCF. Lors de l'acceptation de données XML provenant d'une source non fiable, il existe plusieurs possibilités d'attaques par déni de service qu'il convient d'atténuer. WCF fournit des lecteurs XML sécurisés spéciaux. Ces lecteurs sont créés automatiquement lors de l'utilisation de l'un des codages standard dans WCF (texte, binaire ou MTOM).

Certaines des fonctionnalités de sécurité sur ces lecteurs sont toujours actives. Par exemple, les lecteurs ne traitent jamais les définitions de type de document (DTD), qui constituent une source potentielle d'attaques par déni de service et ne doivent jamais apparaître dans les messages SOAP légitimes. D'autres fonctionnalités de sécurité incluent des quotas de lecteurs à configurer, lesquels sont décrits dans la section suivante.

Lorsque vous utilisez directement des lecteurs XML (notamment lorsque vous écrivez votre propre codeur personnalisé ou utilisez directement la classe Message), utilisez toujours les lecteurs sécurisés WCF lorsqu'il existe un risque d'utilisation de données non fiables. Créez les lecteurs sécurisés en appelant l'une des surcharges de méthode de fabrique statique de CreateTextReader, CreateBinaryReader ou CreateMtomReader sur la classe XmlDictionaryReader. Lorsque vous créez un lecteur, passez des valeurs de quotas sécurisées. N'appelez pas les surcharges de la méthode Create. Celles-ci ne créent pas de lecteur WCF, Elles créent un lecteur qui n'est pas protégé par les fonctionnalités de sécurité décrites dans cette section.

Quotas de lecteurs

Les lecteurs XML sécurisés possèdent cinq quotas configurables. Ceux-ci sont normalement configurés à l'aide de la propriété ReaderQuotas sur les éléments de liaison de codage ou les liaisons standard, ou encore en utilisant un objet XmlDictionaryReaderQuotas passé lors de la création d'un lecteur.

MaxBytesPerRead

Ce quota limite le nombre d'octets lus dans une seule opération Read lors de la lecture de la balise de début d'élément et ses attributs. (Dans les cas non diffusés en continu, le nom de l'élément lui-même n'est pas compté dans le quota.) Le quota MaxBytesPerRead est important pour les raisons suivantes :

  • Le nom de l'élément et ses attributs sont toujours mis en mémoire tampon lorsqu'ils sont lus. Par conséquent, il est important de définir correctement ce quota en mode de diffusion en continu afin d'empêcher une mise en mémoire tampon excessive lorsqu'une diffusion en continu est prévue. Reportez-vous à la section sur les quotas MaxDepth ci-dessous pour plus d'informations sur l'importance réelle de la mise en mémoire tampon qui se produit.
  • Un nombre trop élevé d'attributs XML risque d'utiliser un temps de traitement disproportionné parce que le caractère unique des noms d'attributs doit être vérifié. MaxBytesPerRead atténue cette menace.

MaxDepth

Ce quota limite la profondeur d'imbrication maximale des éléments XML. Par exemple, le document « <A><B><C/></B></A> » a une profondeur d'imbrication égale à trois. Le quota MaxDepth est important pour les raisons suivantes :

  • MaxDepth interagit avec MaxBytesPerRead : le lecteur conserve toujours des données en mémoire pour l'élément actuel et tous ses ancêtres, donc la consommation de mémoire maximale du lecteur est proportionnelle au produit de ces deux paramètres.
  • Lors de la désérialisation d'un graphique d'objets très imbriqué, le désérialiseur est obligé d'accéder à la pile entière et de lever une exception StackOverflowException irrécupérable. Une corrélation directe existe entre l'imbrication XML et l'imbrication d'objets pour DataContractSerializer et XmlSerializer. Utilisez MaxDepth pour atténuer cette menace.

MaxNameTableCharCount

Ce quota limite la taille du nametable du lecteur. Le nametable contient certaines chaînes (telles que des espaces de noms et des préfixes) rencontrées lors du traitement d'un document XML. Étant donné que ces chaînes sont mises en mémoire tampon, définissez ce quota afin d'empêcher une mise en mémoire tampon excessive lorsqu'une diffusion en continu est prévue.

MaxStringContentLength

Ce quota limite la taille maximale de la chaîne que le lecteur XML renvoie. Ce quota ne limite pas la consommation de mémoire dans le lecteur XML lui-même, mais dans le composant qui utilise le lecteur. Par exemple, lorsque DataContractSerializer utilise un lecteur sécurisé avec MaxStringContentLength, il ne désérialise pas les chaînes supérieures à ce quota. Lorsque vous utilisez directement la classe XmlDictionaryReader, toutes les méthodes ne respectent pas ce quota, mais uniquement les méthodes particulièrement conçues pour lire des chaînes, telles que la méthode ReadContentAsString. La propriété Value sur le lecteur n'est pas affectée par ce quota. Vous ne devez donc pas utiliser cette propriété lorsque la protection que ce quota fournit est nécessaire.

MaxArrayLength

Ce quota limite la taille maximale d'un tableau de primitives que le lecteur XML renvoie, notamment un tableau d'octets. Ce quota ne limite pas la consommation de mémoire dans le lecteur XML lui-même, mais dans le composant qui utilise le lecteur. Par exemple, lorsque DataContractSerializer utilise un lecteur sécurisé avec MaxArrayLength, il ne désérialise pas les tableaux d'octets supérieurs à ce quota. Il est important de définir ce quota lorsque vous essayez de combiner des modèles de programmation mis en mémoire tampon et de diffusion en continu dans un contrat unique. N'oubliez pas que lorsque vous utilisez directement la classe XmlDictionaryReader, seules les méthodes particulièrement conçues pour lire les tableaux de taille arbitraire de certains types de primitives, tels que ReadInt32Array, respectent ce quota.

Menaces propres au codage binaire

Le codage XML binaire que WCF prend en charge inclut une fonctionnalité de chaîne de dictionnaire. Une grande chaîne peut être codée à l'aide de seulement quelques octets. Cette fonctionnalité permet des gains de performance significatifs, mais introduit de nouvelles menaces de déni de service à atténuer.

Il existe deux types de dictionnaires : statiques et dynamiques. Le dictionnaire statique est une liste intégrée de longues chaînes qui peuvent être représentées à l'aide d'un code court dans le codage binaire. Cette liste de chaînes est déterminée lorsque le lecteur est créé et ne peut pas être modifiée. Aucune des chaînes figurant dans le dictionnaire statique que WCF utilise par défaut n'est suffisamment grande pour représenter une menace de déni de service sérieuse, bien qu'elles puissent quand même être utilisées dans une attaque par expansion de dictionnaire. Dans les scénarios complexes où vous fournissez votre propre dictionnaire statique, faites preuve de prudence lorsque vous introduisez de grandes chaînes de dictionnaire.

Les dictionnaires dynamiques permettent aux messages de définir leurs propres chaînes et de les associer à des codes courts. Ces mappages de chaînes à du code sont conservés en mémoire pendant toute la session de communication, de sorte à ce que les messages suivants n'aient pas à renvoyer les chaînes et puissent utiliser les codes déjà définis. Ces chaînes peuvent être de longueur arbitraire et représentent donc une menace plus sérieuse que celles du dictionnaire statique.

La première menace à atténuer est la possibilité qu'a le dictionnaire dynamique (le tableau de mappage des chaînes à du code) de devenir trop volumineux. Ce dictionnaire peut se développer au cours de plusieurs messages, et ainsi, le quota MaxReceivedMessageSize n'offre aucune protection parce qu'il s'applique uniquement à chaque message séparément. Par conséquent, une propriété MaxSessionSize distincte existe sur BinaryMessageEncodingBindingElement qui limite la taille du dictionnaire.

À la différence de la plupart des autres quotas, ce quota s'applique également lors de l'écriture de messages. S'il est dépassé lors de la lecture d'un message, QuotaExceededException est levée comme d'habitude. S'il est dépassé lors de l'écriture d'un message, toutes les chaînes qui provoquent le dépassement du quota sont écrites en l'état, sans utiliser la fonctionnalité des dictionnaires dynamiques.

Menaces d'expansion de dictionnaire

Une importante classe d'attaques propres au binaire provient de l'expansion des dictionnaires. Un petit message au format binaire peut devenir un message très volumineux au format texte complètement développé s'il utilise de manière excessive la fonctionnalité des dictionnaires de chaînes. Le facteur d'expansion pour les chaînes de dictionnaires dynamiques est limité par le quota MaxSessionSize, parce qu'aucune chaîne de dictionnaire dynamique ne dépasse la taille maximale du dictionnaire entier.

Les propriétés MaxNameTableCharCount, MaxStringContentLength et MaxArrayLength limitent seulement la consommation de mémoire. Elles ne sont normalement pas nécessaires pour atténuer toutes les menaces liées à la non-diffusion en continu parce que l'utilisation de la mémoire est déjà limitée par MaxReceivedMessageSize. Toutefois, MaxReceivedMessageSize compte les octets de préexpansion. Lorsque vous utilisez le codage binaire, la consommation de mémoire peut dépasser la valeur MaxReceivedMessageSize, uniquement limitée par un facteur de MaxSessionSize. Pour cette raison, il est important de toujours définir tous les quotas de lecteurs (surtout MaxStringContentLength) lors de l'utilisation du codage binaire.

Lors de l'utilisation du codage binaire avec DataContractSerializer, l'interface IExtensibleDataObject peut être employée à mauvais escient pour monter une attaque par expansion de dictionnaire. Cette interface fournit principalement le stockage illimité des données arbitraires qui ne font pas partie du contrat. S'il n'est pas possible de définir les quotas à un niveau suffisamment bas pour que la valeur MaxSessionSize multipliée par la valeur MaxReceivedMessageSize ne pose pas de problème, désactivez la fonctionnalité IExtensibleDataObject lors de l'utilisation du codage binaire. Affectez à la propriété IgnoreExtensionDataObject la valeur true sur l'attribut ServiceBehaviorAttribute. Vous pouvez également ne pas implémenter l'interface IExtensibleDataObject. Pour plus d'informations, consultez Contrats de données à compatibilité ascendante.

Résumé des quotas

Le tableau suivant résume les conseils relatifs aux quotas.

Condition Quotas importants à définir

Aucune diffusion en continu ou diffusion en continu de petits messages, texte ou codage MTOM

MaxReceivedMessageSize, MaxBytesPerRead et MaxDepth

Aucune diffusion en continu ou diffusion en continu de petits messages, codage binaire

MaxReceivedMessageSize, MaxSessionSize et tous les ReaderQuotas

Diffusion en continu de messages, texte ou codage MTOM volumineux

MaxBufferSize et tous les ReaderQuotas

Diffusion en continu de messages, codage binaire volumineux

MaxBufferSize, MaxSessionSize et tous les ReaderQuotas

  • Les délais d'attente au niveau du transport doivent toujours être définis et ne jamais utiliser des lectures/écritures synchrones lorsque la diffusion en continu est utilisée, que vous diffusiez en continu des messages volumineux ou non.
  • Lorsque vous n'êtes pas sûr d'un quota, affectez-lui une valeur sûre plutôt que de le laisser ouvert.

Prévention de l'exécution de code malveillant

Les classes générales suivantes de menaces peuvent exécuter du code et avoir des effets involontaires :

  • Le désérialiseur charge un type malveillant, potentiellement dangereux ou sensible pour la sécurité.
  • Un message entrant provoque la construction par le désérialiseur d'une instance d'un type normalement sûr de façon à ce qu'elle engendre des conséquences involontaires.

Les sections suivantes présentent de manière plus approfondie ces classes de menaces.

DataContractSerializer

(Pour obtenir des informations de sécurité sur XmlSerializer, consultez la documentation correspondante.) Le modèle de sécurité pour XmlSerializer est similaire à celui de DataContractSerializer et diffère principalement dans les détails. Par exemple, l'attribut XmlIncludeAttribute est utilisé pour l'inclusion de type au lieu de l'attribut KnownTypeAttribute. Toutefois, certaines menaces propres à XmlSerializer sont présentées plus loin dans cette rubrique.

Prévention du chargement de types involontaires

Le chargement de types involontaires peut avoir de lourdes conséquences, que le type soit malveillant ou qu'il implique simplement des effets secondaires sensibles pour la sécurité. Un type peut contenir une faille de sécurité exploitable, exécuter des actions sensibles pour la sécurité dans son constructeur ou son constructeur de classe, subir un grand encombrement de mémoire qui facilite les attaques par déni de service ou lever des exceptions irrécupérables. Les types peuvent avoir des constructeurs de classe qui s'exécutent dès que le type est chargé et avant qu'une instance ne soit créée. Pour ces raisons, il est important de contrôler le jeu de types que le désérialiseur peut charger.

DataContractSerializer désérialise d'une manière faiblement couplée. Il ne lit jamais le type CLR (Common Language Runtime) ni les noms d'assembly à partir des données entrantes. Ce comportement est similaire à celui de XmlSerializer, mais diffère de celui de NetDataContractSerializer, BinaryFormatter et de SoapFormatter. Le couplage faible introduit un degré de sécurité, parce que l'intrus distant ne peut pas indiquer un type arbitraire à charger en se contentant de le nommer dans le message.

DataContractSerializer est toujours autorisé à charger un type actuellement prévu conformément au contrat. Par exemple, si un contrat de données comporte un membre de données du type Customer, DataContractSerializer est autorisé à charger le type Customer lorsqu'il désérialise ce membre de données.

En outre, DataContractSerializer prend en charge le polymorphisme. Un membre de données peut être déclaré comme Object, mais les données entrantes peuvent contenir une instance Customer. Cette situation est possible uniquement si le type Customer a été indiqué au désérialiseur via l'un de ces mécanismes :

  • attribut KnownTypeAttribute appliqué à un type ;
  • attribut KnownTypeAttribute qui spécifie une méthode qui renvoie une liste de types ;
  • attribut ServiceKnownTypeAttribute ;
  • section de configuration KnownTypes ;
  • liste des types connus passée explicitement à DataContractSerializer pendant la construction, en cas d'utilisation directe du sérialiseur.

Chacun de ces mécanismes augmente la surface d'exposition en introduisant plus de types que le désérialiseur est capable de charger. Contrôlez chacun de ces mécanismes pour vérifier qu'aucun type malveillant ou involontaire n'est ajouté à la liste des types connus.

Une fois qu'un type connu se trouve dans la portée, il peut être chargé à tout moment, et des instances du type peuvent être créées, même si le contrat en interdit l'utilisation. Par exemple, supposez que le type « MyDangerousType » soit ajouté à la liste des types connus à l'aide de l'un des mécanismes ci-dessus. Cela signifie que :

  • MyDangerousType est chargé et son constructeur de classe s'exécute.
  • Même lors de la désérialisation d'un contrat de données avec un membre de données de chaîne, un message malveillant peut quand même provoquer la création d'une instance de MyDangerousType. Le code inclus dans MyDangerousType, tel que les accesseurs Set de propriété, peuvent s'exécuter. À l'issue de cette exécution, le désérialiseur essaie d'assigner cette instance au membre de données de chaîne et échoue avec une exception.

Lors de l'écriture d'une méthode qui renvoie la liste des types connus, ou lors du passage directe d'une liste au constructeur DataContractSerializer, vérifiez que le code qui prépare la liste est sécurisé et qu'il fonctionne uniquement sur les données approuvées.

Lors de la spécification de types connus dans la configuration, vérifiez que le fichier de configuration est sécurisé. Utilisez toujours des noms forts dans la configuration (en spécifiant la clé publique de l'assembly signé où le type réside), mais ne spécifiez pas la version du type à charger. Le chargeur de type choisit automatiquement la version la plus récente, si possible. Si vous spécifiez une version particulière dans la configuration, vous exécutez le risque suivant : un type peut comporter une faille de sécurité qui peut être résolue dans une future version, mais la version vulnérable continue de se charger parce qu'elle sera spécifiée explicitement dans la configuration.

Un nombre trop élevé de types connus a une autre conséquence : DataContractSerializer crée un cache de code de sérialisation/désérialisation dans le domaine d'application, avec une entrée pour chaque type qu'il doit sérialiser et désérialiser. Ce cache n'est jamais effacé tant que le domaine d'application s'exécute. Par conséquent, un intrus qui sait qu'une application utilise de nombreux types connus peut provoquer la désérialisation de tous ces types, ce qui engendre la consommation d'une quantité de mémoire disproportionnellement élevée par le cache.

Prévention des états involontaires affectés aux types

Un type peut avoir des contraintes de cohérence interne qui doivent être appliquées. Il faut veiller à ne pas violer ces contraintes pendant la désérialisation.

L'exemple de type suivant représente l'état d'un sas dans un vaisseau spatial et applique la contrainte selon laquelle les portes intérieure et extérieure ne peuvent pas être ouvertes en même temps.

Un intrus peut envoyer un message malveillant comme celui-ci, qui contourne les contraintes et fait passer l'objet dans un état non valide, susceptible d'avoir des conséquences involontaires et imprévisibles.

<SpaceStationAirlock>
    <innerDoorOpen>true</innerDoorOpen>
    <outerDoorOpen>true</outerDoorOpen>
</SpaceStationAirlock>

Cette situation peut être évitée en prenant conscience des points suivants :

  • Lorsque DataContractSerializer désérialise la plupart des classes, les constructeurs ne s'exécutent pas. Par conséquent, ne comptez pas sur la gestion d'état effectuée dans le constructeur.
  • Utilisez des rappels pour veiller à ce que l'objet soit dans un état valide. Le rappel marqué avec l'attribut OnDeserializedAttribute s'avère particulièrement utile parce qu'il s'exécute à la fin de la désérialisation et parce qu'il obtient une occasion d'examiner et de corriger l'état global. Pour plus d'informations, consultez Rappels de sérialisation avec tolérance de version.
  • Ne concevez pas de types de contrat de données de sorte à compter sur un ordre particulier dans lequel les accesseurs Set de propriété doivent être appelés.
  • Faites preuve de prudence lorsque vous utilisez des types hérités marqués avec l'attribut SerializableAttribute. Beaucoup d'entre eux ont été conçus pour fonctionner avec l'accès distant du .NET Framework à des fins d'utilisation avec des données approuvées uniquement. Les types existants marqués avec cet attribut n'ont peut-être pas été conçus en tenant compte de la sécurité des états.
  • Ne comptez pas sur la propriété IsRequired de l'attribut DataMemberAttribute pour garantir la présence des données en ce qui concerne la sécurité des états. Les données pourraient toujours être null, zero ou invalid.
  • N'approuvez jamais un graphique d'objets désérialisé provenant d'une source de données non fiable sans le valider au préalable. Chaque objet individuel peut être dans un état cohérent, à la différence du graphique d'objets dans son ensemble. En outre, même si le mode de conservation des graphiques d'objets est désactivé, le graphique désérialisé peut avoir plusieurs références au même objet ou des références circulaires. Pour plus d'informations, consultez Sérialisation et désérialisation.

Utilisation de NetDataContractSerializer en toute sécurité

NetDataContractSerializer est un moteur de sérialisation qui utilise le couplage étroit des types. Ce moteur est similaire à BinaryFormatter et à SoapFormatter. Autrement dit, il détermine le type à instancier en lisant l'assembly du .NET Framework et le nom du type à partir des données entrantes. Bien qu'il fasse partie de WCF, aucun moyen n'est fourni pour brancher ce moteur de sérialisation ; le code personnalisé doit être écrit. NetDataContractSerializer est fourni principalement pour faciliter la migration depuis l'accès distant du .NET Framework vers WCF. Pour plus d'informations, consultez la section correspondante dans Sérialisation et désérialisation.

Parce que le message lui-même peut indiquer la possibilité de charger tout type, le mécanisme NetDataContractSerializer est par sa nature incertain et doit être utilisé uniquement avec des données approuvées. Il est possible de le sécuriser en écrivant un binder sécurisé limitant les types qui permet uniquement aux types sûrs d'être chargés (à l'aide de la propriété Binder).

Même en cas d'utilisation avec des données approuvées, les données entrantes peuvent spécifier insuffisamment le type à charger, surtout si la propriété AssemblyFormat a la valeur Simple. Toute personne ayant accès au répertoire de l'application ou au Global Assembly Cache peut remplacer par un type malveillant celui qui est supposé se charger. Vérifiez toujours la sécurité du répertoire de votre application et du Global Assembly Cache en définissant correctement les autorisations.

En général, si vous autorisez l'accès du code de niveau de confiance partiel à votre instance NetDataContractSerializer ou le contrôle du sélecteur de substitut (ISurrogateSelector) ou le binder de sérialisation (SerializationBinder), le code risque de contrôler en grande partie le processus de sérialisation/désérialisation. Par exemple, il peut injecter des types arbitraires, entraîner la divulgation d'informations, falsifier les données sérialisées ou le graphique résultant ou dépasser le flux sérialisé résultant.

Un autre problème de sécurité avec NetDataContractSerializer est le déni de service, qui n'est pas une menace d'exécution de code malveillant. Lorsque vous utilisez NetDataContractSerializer, affectez toujours au quota MaxItemsInObjectGraph une valeur sûre. Il est facile de construire un petit message malveillant qui alloue un tableau d'objets dont la taille est limitée uniquement par ce quota.

Menaces propres à XmlSerializer

Le modèle de sécurité XmlSerializer est similaire à celui de DataContractSerializer. Toutefois, quelques menaces sont propres à XmlSerializer.

XmlSerializer génère des assemblys de sérialisation au moment de l'exécution qui contiennent du code qui sérialise et désérialise réellement ; ces assemblys sont créés dans un répertoire de fichiers temporaires. Si un autre processus ou utilisateur a des droits d'accès à ce répertoire, il peut remplacer le code de sérialisation/désérialisation par du code arbitraire. XmlSerializer exécute alors ce code à l'aide de son contexte de sécurité, au lieu du code de sérialisation/désérialisation. Assurez-vous que les autorisations sont correctement définies sur le répertoire de fichiers temporaires afin d'empêcher ce remplacement.

XmlSerializer possède également un mode dans lequel il utilise des assemblys de sérialisation préalablement générés au lieu de les générer au moment de l'exécution. Ce mode est déclenché chaque fois que XmlSerializer peut détecter un assembly de sérialisation approprié. XmlSerializer vérifie si l'assembly de sérialisation a été signé ou non par la même clé que celle utilisée pour signer l'assembly qui contient les types en cours de sérialisation. Cette vérification offre une protection contre les assemblys malveillants déguisés en assemblys de sérialisation. Toutefois, si l'assembly qui contient vos types sérialisables n'est pas signé, XmlSerializer ne peut pas effectuer cette vérification et utilise alors tout assembly dont le nom est correct. L'exécution de code malveillant est alors possible. Signez toujours les assemblys qui contiennent vos types sérialisables ou contrôlez de près l'accès au répertoire de votre application et au Global Assembly Cache pour empêcher l'introduction d'assemblys malveillants.

XmlSerializer peut être l'objet d'une attaque par déni de service. XmlSerializer n'a pas de quota MaxItemsInObjectGraph (tel que celui disponible sur DataContractSerializer). Ainsi, il désérialise une quantité arbitraire d'objets, limitée uniquement par la taille du message.

Menaces liées à une confiance partielle

Notez les préoccupations suivantes concernant des menaces liées à l'exécution de code avec un niveau de confiance partielle. Ces menaces incluent le code malveillant de niveau de confiance partiel aussi bien que le code malveillant de niveau de confiance partiel en association avec d'autres scénarios d'attaque (par exemple, du code de niveau de confiance partiel qui construit une chaîne spécifique, puis qui la désérialise).

  • Lorsque vous utilisez des composants de sérialisation, ne déclarez jamais des autorisations avant cette utilisation, même si le scénario de sérialisation entier s'intègre dans la portée de votre assertion et si vous ne traitez pas des données ou objets non fiables. Une telle utilisation peut engendrer des failles de sécurité.
  • Dans les cas où le code de confiance partielle contrôle le processus de sérialisation, soit par le biais des points d'extensibilité (substituts), des types sérialisés, soit par d'autres moyens, ce code peut forcer le sérialiseur à générer une grande quantité de données dans le flux sérialisé, ce qui peut entraîner un déni de service auprès du récepteur du flux. Si vous sérialisez des données destinées à une cible exposée à des menaces de déni de service, ne sérialisez pas les types de confiance partielle, ou sinon laissez le code de confiance partielle contrôler la sérialisation.
  • Si vous autorisez l'accès du code de niveau de confiance partiel à votre instance DataContractSerializer, ou le contrôle du Substituts de contrats de données, il risque de contrôler une grande partie du processus de sérialisation/désérialisation. Par exemple, il peut injecter des types arbitraires, entraîner la divulgation d'informations, falsifier les données sérialisées ou le graphique résultant ou dépasser le flux sérialisé résultant. Une menace NetDataContractSerializer équivalente est décrite dans la section « Utilisation de NetDataContractSerializer en toute sécurité ».
  • Si l'attribut DataContractAttribute est appliqué à un type (ou au type marqué comme [Serializable] mais qu'il n'est pas ISerializable), le désérialiseur peut créer une instance d'un tel type même si tous les constructeurs sont non publics ou protégés par des demandes.
  • N'approuvez jamais le résultat de la désérialisation sauf si les données à désérialiser sont approuvées et que vous êtes certain que tous les types connus sont des types que vous approuvez. Notez que les types connus ne sont pas chargés depuis le fichier de configuration de l'application (mais sont chargés depuis le fichier de configuration de l'application) lorsqu'ils s'exécutent en confiance partielle.
  • Si vous passez une instance DataContractSerializer avec un substitut ajouté au code de confiance partielle, le code peut modifier tous les paramètres modifiables sur ce substitut.
  • Pour un objet désérialisé, si le lecteur XML (ou les données qui s'y trouvent) vient de code de niveau de confiance partiel, traitez l'objet désérialisé obtenu comme des données non fiables.
  • Le fait que le type ExtensionDataObject n'ait pas de membres publics ne signifie pas que les données qu'il contient sont sécurisées. Par exemple, si vous désérialisez à partir d'une source de données privilégiée dans un objet dans lequel certaines données résident, puis remettez cet objet à du code de niveau de confiance partiel, le code de niveau de confiance partiel peut lire les données dans ExtensionDataObject en sérialisant l'objet. Envisagez d'affecter à IgnoreExtensionDataObject la valeur true lors d'une désérialisation à partir d'une source de données privilégiée dans un objet qui est passé ultérieurement à du code de niveau de confiance partiel.

Autres préoccupations liées à la gestion d'état

Il est judicieux de mentionner quelques autres préoccupations liées à la gestion d'état d'objet :

  • Lors de l'utilisation du modèle de programmation basé sur le flux avec un transport de diffusion en continu, le traitement du message se produit au moment où le message arrive. L'expéditeur du message peut abandonner l'opération d'envoi en cours de flux, ce qui laisse votre code dans un état imprévisible si davantage de contenu était attendu. En général, ne comptez pas sur l'exhaustivité du flux et n'exécutez rien dans une opération basée sur le flux qui ne puisse pas être restauré au cas où le flux de données serait abandonné. Ces recommandations s'appliquent également au cas dans lequel un message peut s'avérer incorrect après la diffusion en continu du corps (par exemple, il peut manquer une balise de fin pour l'enveloppe SOAP ou le message peut comporter un deuxième corps).
  • L'utilisation de la fonctionnalité IExtensibleDataObject peut engendrer l'émission de données sensibles. Si vous acceptez des données d'une source non fiable dans des contrats de données avec IExtensibleObjectData, puis vous les émettez de nouveau sur un canal sécurisé où les messages sont signés, vous vous portez potentiellement garant de données dont vous ne savez rien. De plus, l'état global que vous envoyez risque de ne pas être valide si vous prenez à la fois en considération les morceaux de données connus et inconnus. Évitez cette situation en affectant sélectivement à la propriété des données d'extension la valeur null ou en désactivant sélectivement la fonctionnalité IExtensibleObjectData.

Importation de schéma

Normalement, le processus d'importation du schéma pour générer des types s'exécute uniquement au moment de la conception, par exemple, lors de l'utilisation de ServiceModel Metadata Utility Tool (Svcutil.exe) sur un service Web pour générer une classe de client. Toutefois, dans des scénarios plus avances, vous pouvez traiter le schéma au moment de l'exécution. N'oubliez pas que ce traitement peut vous exposer à des risques de déni de service. L'importation de certains schémas peut nécessiter beaucoup de temps. N'utilisez jamais le composant d'importation de schéma XmlSerializer dans de tels scénarios si les schémas peuvent venir d'une source non fiable.

Menaces spécifiques à l'intégration ASP.NET AJAX

Lorsque l'utilisateur implémente WebScriptEnablingBehavior ou WebHttpBehavior, WCF expose un point de terminaison qui peut accepter des messages XML et JSON. Cependant, il n'existe qu'un jeu de quotas de lecteurs utilisés par les lecteurs XML et JSON. Certains paramètres de quota peuvent convenir à un lecteur mais sont trop grands pour l'autre.

Lors de l'implémentation de WebScriptEnablingBehavior, l'utilisateur peut exposer un proxy JavaScript au point de terminaison. Tenez compte des éléments suivants en examinant les questions de sécurité :

  • Les informations sur le service (noms d'opération, noms de paramètre etc.) peuvent être obtenues par l'examen du proxy JavaScript.
  • Lors de l'utilisation du point de terminaison JavaScript, des informations privées et sensibles peuvent être conservées dans le cache du navigateur Web du client.

Remarque sur les composants

WCF est un système flexible et personnalisable. La majorité du contenu de cette rubrique se concentre sur les scénarios d'utilisation de WCF les plus courants. Toutefois, il est possible de composer les composants fournis par WCF de nombreuses manières différentes. Il est important de comprendre les implications en matière de sécurité de l'utilisation de chaque composant. En particulier :

  • Lorsque vous devez utiliser des lecteurs XML, utilisez les lecteurs que la classe XmlDictionaryReader fournit au lieu de tous les autres lecteurs. Les lecteurs sûrs sont créés à l'aide des méthodes CreateTextReader, CreateBinaryReader ou CreateMtomReader. N'utilisez pas la méthode Create. Configurez toujours les lecteurs avec des quotas sûrs. Les moteurs de sérialisation dans WCF sont sécurisés uniquement lorsqu'ils sont utilisés avec les lecteurs XML sécurisés de WCF.
  • Lorsque vous utilisez DataContractSerializer pour désérialiser des données potentiellement non fiables, affectez toujours une valeur à la propriété MaxItemsInObjectGraph.
  • Lorsque vous créez un message, définissez le paramètre maxSizeOfHeaders si MaxReceivedMessageSize n'offre pas assez de protection.
  • Lorsque vous créez un codeur, configurez toujours les quotas correspondants, tels que MaxSessionSize et MaxBufferSize.
  • Lorsque vous utilisez un filtre de message XPath, affectez une valeur à NodeQuota de sorte à limiter le nombre de nœuds XML visités par le filtre. N'utilisez pas les expressions XPath qui peuvent nécessiter beaucoup de temps de calcul sans pour autant visiter de nombreux nœuds.
  • En général, lorsque vous utilisez un composant qui accepte un quota, comprenez ses implications en matière de sécurité et affectez-lui une valeur sûre.

Voir aussi

Référence

DataContractSerializer
XmlDictionaryReader
XmlSerializer

Concepts

Types connus de contrats de données