Conseils d’atténuation des menaces pour le rendu statique côté serveur de Blazor ASP.NET Core

Cet article explique les considérations de sécurité que les développeurs doivent prendre en compte lors du développement d’applications web Blazor avec un rendu statique côté serveur.

Blazor combine trois modèles différents en un pour l’écriture d’applications web interactives. Le rendu côté serveur traditionnel, un modèle de type requête/réponse basé sur HTTP. Le rendu interactif côté serveur, un modèle de rendu basé sur SignalR. Enfin, le rendu côté client, un modèle de rendu basé sur WebAssembly.

Toutes les considérations de sécurité générales définies pour les modes de rendu interactif s’appliquent à Web Apps Blazor lorsqu’il existe un rendu de composants interactifs dans l’un des modes de rendu pris en charge. Les sections suivantes expliquent les considérations de sécurité spécifiques au rendu côté serveur non interactif dans les applications web Blazor et les aspects spécifiques qui s’appliquent lorsque les modes de rendu interagissent entre eux.

Considérations générales relatives au rendu côté serveur

Le modèle de rendu côté serveur (SSR) est basé sur le modèle de requête/réponse traditionnel de HTTP. Par conséquent, il existe des sujets de préoccupation courants entre le SSR et le protocole HTTP de requête/réponse. Les inquiétudes générales relatives à la sécurité et les menaces spécifiques doivent être atténuées avec succès. Le framework fournit des mécanismes intégrés pour gérer certaines de ces menaces, mais d’autres menaces sont spécifiques au code d’application et doivent être gérées par l’application. Ces menaces peuvent être classées ainsi :

  • Authentification et autorisation : l’application doit s’assurer que l’utilisateur est authentifié et autorisé à accéder à l’application et aux ressources qu’elle expose. Le framework fournit des mécanismes intégrés pour l’authentification et l’autorisation, mais l’application doit s’assurer que les mécanismes sont correctement configurés et utilisés. Les mécanismes intégrés pour l’authentification et l’autorisation sont expliqués dans Nœud de sécurité Serveur de la documentation Blazor et dans Sécurité et Identitynœud de la documentation ASP.NET Core. Il n’en sera donc pas question ici.

  • Validation et assainissement des entrées : toutes les entrées provenant d’un client doivent être validées et nettoyées avant l’utilisation. Sinon, l’application peut être exposée à des attaques, telles que l’injection SQL, le script intersite, la falsification de requête intersite, la redirection ouverte et d’autres formes d’attaques. L’entrée peut provenir de n’importe où dans la requête.

  • Gestion des sessions : la gestion correcte des sessions utilisateur est essentielle pour s’assurer que l’application n’est pas exposée aux attaques, telles que la fixation de session, le détournement de session et d’autres attaques. Les informations stockées dans la session doivent être correctement protégées et chiffrées, et le code de l’application doit empêcher un utilisateur malveillant de deviner ou de manipuler des sessions.

  • Gestion et journalisation des erreurs : l’application doit s’assurer que les erreurs sont correctement gérées et journalisées. Sinon, l’application peut être exposée à des attaques, telles que la divulgation d’informations. Cela peut se produire lorsque l’application retourne des informations sensibles dans la réponse ou lorsque l’application retourne des messages d’erreur détaillés avec des données qui peuvent être utilisées pour attaquer l’application.

  • Protection des données : les données sensibles doivent être correctement protégées, ce qui inclut la logique d’application lors de l’exécution sur WebAssembly, car elles peuvent être facilement inversées.

  • Déni de service : l’application doit s’assurer qu’elle n’est pas exposée aux attaques, telles que le déni de service. Cela se produit par exemple lorsque l’application n’est pas correctement protégée contre les attaques par force brute ou lorsqu’une action peut entraîner l’utilisation de trop de ressources par l’application.

Validation et assainissement des entrées

Toutes les entrées provenant du client doivent être considérées comme non approuvées, sauf si ses informations ont été générées et protégées sur le serveur, telles qu’un jeton CSRF, un cookie d’authentification, un identificateur de session ou toute autre charge utile protégée par le chiffrement authentifié.

L’entrée est normalement disponible pour l’application via un processus de liaison, par exemple via l’attribut [SupplyParameterFromQuery] ou l’attribut [SupplyParameterFromForm]. Avant de traiter cette entrée, l’application doit s’assurer que les données sont valides. Par exemple, l’application doit confirmer qu’aucune erreur de liaison n’a été détectée lors du mappage des données de formulaire à une propriété de composant. Sinon, l’application pourrait traiter des données non valides.

Si l’entrée est utilisée pour effectuer une redirection, l’application doit s’assurer que l’entrée est valide et qu’elle ne pointe pas vers un domaine considéré comme non valide ou vers un sous-chemin non valide dans le chemin de base de l’application. Sinon, l’application peut être exposée à des attaques de redirection ouvertes, où un attaquant peut créer un lien qui redirige l’utilisateur vers un site malveillant.

Si l’entrée est utilisée pour effectuer une requête de base de données, l’application doit confirmer que l’entrée est valide et qu’elle n’expose pas l’application aux attaques par injection SQL. Sinon, un attaquant peut créer une requête malveillante qui pourrait être utilisée pour extraire des informations de la base de données ou pour modifier la base de données.

Les données qui pourraient provenir d’une entrée utilisateur doivent également être assainies avant d’être incluses dans une réponse. Par exemple, l’entrée peut contenir du code HTML ou JavaScript qui peut être utilisé pour effectuer des attaques de script intersite, qui peuvent à leur tour être utilisées pour extraire des informations de l’utilisateur ou pour effectuer des actions pour le compte de l’utilisateur.

L’infrastructure fournit les mécanismes suivants pour faciliter la validation et l’assainissement des entrées :

  • Toutes les données de formulaire liées sont validées pour l’exactitude de base. Si une entrée ne peut pas être analysée, le processus de liaison signale une erreur que l’application peut découvrir avant d’effectuer une action avec les données. Le composant EditForm intégré prend cela en compte avant d’appeler le rappel du formulaire OnValidSubmit. Blazor évite d’exécuter le rappel si une ou plusieurs erreurs de liaison sont présentes.
  • Le framework utilise un jeton d’antifalsification pour se protéger contre les attaques de falsification de requête intersite. Pour plus d’informations, consultez Authentification et autorisation Blazor ASP.NET Core et la vue d’ensemble des formulaires BlazorASP.NET Core.

Toutes les autorisations et entrées doivent être validées sur le serveur au moment de l’exécution d’une action donnée pour s’assurer que les données sont valides et précises à ce moment-là et que l’utilisateur est autorisé à effectuer l’action. Cette approche est conforme aux instructions de sécurité fournies pour le rendu côté serveur interactif.

Gestion des sessions

La gestion des sessions est gérée par l’infrastructure. L’infrastructure utilise un cookie de session pour identifier la session utilisateur. Le cookie de session est protégé à l’aide des API de protection des données principales ASP.NET. Le cookie de session n’est pas accessible au code JavaScript s’exécutant sur le navigateur et ne peut pas être facilement deviné ou manipulé par un utilisateur.

En ce qui concerne d’autres données de session, telles que les données stockées dans les services, les données de session doivent être stockées dans des services délimités, car les services délimités sont uniques par session utilisateur donnée, contrairement aux services singleton partagés entre toutes les sessions utilisateur dans une instance de processus donnée.

Quant au SSR, il n’existe pas beaucoup de différences entre les services délimités et temporaires dans la plupart des cas, car la durée de vie du service est limitée à une seule requête. Il existe une différence dans deux cas :

  • Si le service est injecté dans plusieurs emplacements ou à des moments différents pendant la demande.
  • Si le service peut être utilisé dans un contexte de serveur interactif, où il survit à plusieurs rendus, et qu’il est primordial que le service soit limité à la session utilisateur.

Gestion et journalisation des erreurs

L’infrastructure fournit une journalisation intégrée pour l’application au niveau de l’infrastructure. L’infrastructure journalise les événements importants, tels que lorsque le jeton d’antifalsification d’un formulaire ne parvient pas à être validé, lorsqu’un composant racine commence à s’afficher et lorsqu’une action est distribuée. L’application est responsable de la journalisation des autres événements susceptibles d’être importants à enregistrer.

L’infrastructure fournit une journalisation intégrée pour l’application au niveau de l’infrastructure. L’infrastructure gère les erreurs qui se produisent pendant le rendu d’un composant et utilise le mécanisme de limite d’erreur pour afficher un message d’erreur convivial ou permet à l’erreur de monter en bulles jusqu’au middleware de gestion des exceptions, qui est configuré pour afficher la page d’erreur.

Les erreurs qui se produisent lors du rendu de diffusion en continu une fois que la réponse a commencé à être envoyée au client sont affichées dans la réponse finale sous la forme d’un message d’erreur générique. Les détails sur la cause de l’erreur ne sont inclus que pendant le développement.

Protection des données

Le framework offre des mécanismes de protection des informations sensibles pour une session utilisateur donnée et garantit que les composants intégrés utilisent ces mécanismes pour protéger les informations sensibles, telles que la protection de l’identité de l’utilisateur lors de l’authentification cookie. En dehors des scénarios gérés par l’infrastructure, le code du développeur est responsable de la protection d’autres informations spécifiques à l’application. La méthode la plus courante consiste à utiliser les API de protection des données principales ASP.NET ou toute autre forme de chiffrement. En règle générale, l’application a pour responsabilité de :

  • S’assurer qu’un utilisateur ne peut pas inspecter ou modifier les informations privées d’un autre utilisateur.
  • S’assurer qu’un utilisateur ne peut pas modifier les données utilisateur d’un autre utilisateur, comme un identificateur interne.

En ce qui concerne la protection des données, vous devez comprendre clairement où le code s’exécute. Pour le rendu statique côté serveur (Static SSR) et le rendu interactif côté serveur (Interactive SSR), le code est stocké sur le serveur et n’atteint jamais le client. Pour le mode de rendu WebAssembly interactif, le code d’application atteint toujours le client, ce qui signifie que toute information sensible stockée dans le code de l’application est disponible pour toute personne ayant accès à l’application. L’obfuscation et autre technique similaire pour « protéger » le code ne sont pas efficaces. Une fois que le code atteint le client, il peut être rétro-conçu pour extraire les informations sensibles.

Denial of service (déni de service)

Au niveau du serveur, l’infrastructure fournit des limites sur les paramètres de requête/réponse, tels que la taille maximale de la requête et la taille de l’en-tête. En ce qui concerne le code d’application, le système de mappage de formulaires de Blazor définit des limites similaires à celles définies par le système de liaison de modèle de MVC :

  • Limite du nombre maximum d'erreurs.
  • Limite de la profondeur maximale de récursivité pour le Binder.
  • Limite du nombre maximal d’éléments liés dans une collection.

En outre, des limites sont définies pour le formulaire, telles qu’une taille maximale de clé de formulaire et de valeur, et un nombre maximal d’entrées.

En général, l’application doit évaluer lorsqu’il y a une chance qu’une demande déclenche une quantité asymétrique de travail par le serveur. Par exemple, lorsque l’utilisateur envoie une requête paramétrée par N et que le serveur effectue une opération en réponse qui est N fois plus coûteuse, où N est un paramètre qu’un utilisateur contrôle et peut augmenter indéfiniment. Normalement, l’application doit imposer une limite au nombre maximal de N qu’elle est prête à traiter ou doit s’assurer que toute opération est inférieure, égale ou plus coûteuse que la demande par un facteur constant.

Cet aspect relève davantage de la différence de croissance entre le travail effectué par le client et le travail effectué par le serveur, que d’une comparaison spécifique 1→N. Par exemple, un client peut envoyer un élément de travail (en insérant des éléments dans une liste) qui prend N unités de temps à effectuer, alors que le serveur a besoin du traitement de N^2^ (qui peut faire quelque chose de très naïf). C’est la différence entre N et N^2^ qui compte.

Ainsi, la quantité de travail que le serveur accepte de faire est limitée, selon l’application. Cet aspect s’applique aux charges de travail côté serveur, car les ressources se trouvent sur le serveur, mais ne s’appliquent pas nécessairement aux charges de travail WebAssembly sur le client dans la plupart des cas.

L’autre aspect important est que ce n’est pas seulement réservé au temps processeur. Cela s’applique également à toutes les ressources, telles que la mémoire, le réseau et l’espace sur le disque.

Pour les charges de travail WebAssembly, il n’y a généralement pas à s’inquiéter de la quantité de travail effectuée par le client, car celui-ci est normalement limité par les ressources disponibles sur le client. Toutefois, il existe certains cas où le client peut être affecté, si par exemple, une application affiche des données d’autres utilisateurs et qu’un utilisateur est capable d’ajouter des données au système qui obligent les clients qui affichent les données à effectuer une quantité de travail qui n’est pas proportionnelle à la quantité de données ajoutées par l’utilisateur.

  • Vérifiez que l’utilisateur est authentifié et autorisé à accéder à l’application et aux ressources qu’elle expose.
  • Validez et assainissez toutes les entrées provenant d’un client avant de l’utiliser.
  • Gérez correctement les sessions utilisateur pour vous assurer que l’état n’est pas partagé par erreur entre les utilisateurs.
  • Gérez et consignez correctement les erreurs pour éviter d’exposer des informations sensibles.
  • Consignez les événements importants dans l’application pour identifier les problèmes potentiels et les actions d’audit effectuées par les utilisateurs.
  • Protégez les informations sensibles à l’aide d’API de protection des données ASP.NET Core ou de l’un des composants disponibles (Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage, PersistentComponentState).
  • Assurez-vous que l’application comprend les ressources qui peuvent être consommées par une demande donnée et qu’elle dispose des limites en place pour éviter les attaques par déni de service.