Partager via


Ajout de la protection des scripts intersite à ASP.NET 1.0

 

Scott Hanselman
Architecte en chef
Corillian Corporation

Novembre 2003

Résumé : ASP.NET 1.1 a ajouté l’attribut ValidateRequest pour protéger votre site contre les scripts intersite. Que faites-vous, toutefois, si votre site web est toujours en cours d’exécution ASP.NET 1.0 ? Scott Hanselman montre comment ajouter des fonctionnalités similaires à vos sites Web ASP.NET 1.0. (12 pages imprimées)

Contenu

Le problème
C#-Eye pour le gars il
HttpModule
Intention du programmeur
Installation et configuration
Résultats
Conclusion

Le problème

J’ai un client qui a déployé un site sur Microsoft® ASP.NET et microsoft® .NET Framework 1.0. C’est un grand site, et ils sont un grand client, et en tant que grand client, ils ont tendance à se déplacer, eh bien, lent. Nous étions au milieu d’un déploiement volumineux lorsque ASP.NET/Framework 1.1 est sorti. L’équipe a estimé qu’il était trop risqué de tout déplacer vers ASP.NET/Framework 1.1 si près de la ligne d’arrivée. Nous avons donc décidé de passer à ASP.NET/Framework 1.1 plus tard dans l’année. Toutefois, étant donné que nous créons des sites web de banque en ligne complexes qui traversent de nombreux secteurs d’activité et traitent avec l’argent des gens, la sécurité est le travail n° 1 (ou le travail n° 0 si vous êtes basé sur zéro). Le client doit traiter de manière agressive les attaques de script intersite (souvent appelées « XSS »).

XSS est un type de piratage particulièrement sinistre, où un l33t hx0r (hacker d’élite) ou un « script kiddie » tente de récupérer des informations personnelles ou de tromper un site en faisant quelque chose qu’il ne devrait pas faire en entrant JavaScript dans un formulaire web ou en encodant le script dans un paramètre dans l’URL. Un simple exemple est un formulaire web qui a une seule zone de texte et un seul bouton. L’utilisateur entre son nom dans la zone de texte et envoie le formulaire. La page imprime ensuite « Hello firstname » ** par concaténation de chaîne, String.Format, response.write ou via une étiquette côté serveur.

ms972967.scriptingprotection_fig01(en-us,MSDN.10).gif

Figure 1. Entrée de texte ; semble suffisamment sûr

Étant donné que la page prend l’entrée des utilisateurs et la « régurgite » directement, si j’ai entré un mot de jure, j’obtiendrais un autre type de salutation! Mais que se passe-t-il si, au lieu d’entrer son nom, l’utilisateur entre un fragment de script comme «< alerte de script>('mauvaises choses se produisent') » ;</script>. » Le code derrière ressemble à ceci :

if (this.IsPostBack) Response.Write("Hello " + this.TextBox1.Text);

Vous pouvez voir que le contenu de la zone de texte sera écrit directement dans le flux de réponse et que le JavaScript sera évalué sur le navigateur de l’utilisateur ! Il s’agit d’un exemple trivial, mais imaginez si le Code JavaScript malveillant contenait du code pour accéder à la collection de cookies de l’utilisateur ou rediriger un formulaire vers un autre site ?

ms972967.scriptingprotection_fig02(en-us,MSDN.10).gif

Figure 2 : Entrée de JavaScript où le texte est attendu

ms972967.scriptingprotection_fig03(en-us,MSDN.10).gif

Figure 3. JavaScript s’exécute sur la réponse

Par souci de simplicité, nous préférons ne pas ajouter de complexité supplémentaire à notre niveau Web ou à notre logique métier pour traiter une personne entrant JavaScript dans un champ de formulaire ou dans un autre chicanery. Nous aimerions traiter XSS de manière centralisée, peut-être en tant que filtre, plus tôt dans la chaîne de requêtes de worker HTTP, certainement avant l’exécution réelle de la page. Eh bien, ASP.NET 1.1 inclut une nouvelle @Page directive pour ce faire! La validation d’entrée est activée par défaut et peut être contrôlée avec l’attribut ValidateRequest de la @Page directive.

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" 
    ValidateRequest="true" AutoEventWireup="false" 
    Inherits="Junk.WebForm1" %>

ASP.NET validation de requête 1.1 intercepte le code de script malveillant dans la collection de cookies, queryString et Forms Posts. Il vérifie toutes les données d’entrée par rapport à une liste de valeurs potentiellement dangereuses. Si vous craignez que ce type de validation ne nuise d’une manière ou d’une autre aux fonctionnalités de vos utilisateurs, permettez-moi de vous assurer que si vos utilisateurs entrent JavaScript dans vos champs de formulaire, ils ne sont pas le type d’utilisateurs que vous souhaitez. ValdidateRequest=true n’entravera en aucun cas l’expérience de vos utilisateurs. Si un script malveillant est détecté dans certaines données d’entrée, une exception HttpRequestValidationException est levée. Vous pouvez certainement intercepter cette erreur dans Global.asax et remplacer la page d’erreur par défaut par vos propres menaces personnelles si vous le souhaitez.

C’est génial que ASP.NET 1.1 ait inclus ce filtre puissant gratuitement, mais cela ne m’aide pas et mon client en attente ASP.NET lancement du site 1.0. Comment puis-je me protéger contre les scripts intersite avec ASP.NET 1.0 pendant que j’attends la mise à niveau de mon client ? Nous avons lancé quelques idées telles que l’écriture d’expressions régulières et la recherche dans les en-têtes HTTP dans Application_BeginRequest, mais aucune de nos idées n’a été bonne. Je me suis également rappelé que je travaille pour une société de financement électronique, et non pour une entreprise qui fabrique des composants pour empêcher les attaques de script intersite. Pas besoin pour moi de tenter de réinventer la roue.

Puis j’ai réalisé que j’avais la solution assise juste devant mon visage; ASP.NET 1.1 avait déjà résolu ce problème, j’avais juste besoin de résoudre le problème à l’envers. J’ai donc décidé de rétro-porter la version 1.1 existante vers ASP.NET 1.0

C#-Eye pour le gars il

Pour explorer ce qui se passait à l’intérieur de ASP.NET 1.1, j’avais besoin d’un outil qui était un peu plus élevé que ILDASM.EXE, le désassembleur .NET inclus avec le KIT de développement logiciel (SDK) .NET Framework. Si j’étais une personne plus intelligente, peut-être que je pourrais prendre System.Web à part avec seulement ILDASM, mais la lecture de l’IL n’est pas trivial et j’avais un emploi du temps. J’ai trouvé cet outil dans le réflecteur de Lutz Roeder. Le réflecteur est un navigateur d’objets qui vous offre une excellente arborescence de tous les espaces de noms et classes que la bibliothèque de classes de base (BCL) fournit.

ms972967.scriptingprotection_fig04(en-us,MSDN.10).gif

Figure 4. Analyse de la classe CrossSiteScriptingValidation dans Reflector

ms972967.scriptingprotection_fig05(en-us,MSDN.10).gif

Figure 5. Exportation du code source pour la classe CrossSiteScriptingValidation

Toutefois, là où le réflecteur brille vraiment, c’est dans sa capacité à décompiler les assemblys .NET et à présenter les résultats, non pas en tant qu’il, mais en tant que code C# ou Microsoft® Visual Basic® .NET équivalent. Bien sûr, une certaine fidélité évidente est perdue dans le processus, comme les noms de variables locales, mais c’est la vie (et le code).

J’ai donc couru dans System.Web jusqu’à ce que j’ai trouvé une classe interne appelée CrossSiteScriptingValidation. Semblait prometteur. C’est là que les questions difficiles répondent, telles que IsDangerousString ou IsDangerousScriptString. Toutes les méthodes dans CrossSiteScriptingValidation retournent des booléens ; true sur la plupart des qualifiés de dangereux. Mais quelles chaînes évaluons-nous et qui appelle cette classe utilitaire ? Il me semble que la réponse se trouverait dans HttpRequest , car nous essayons de valider toutes les demandes.

HttpRequest contient des collections pour les variables de formulaire , les cookies et queryString. Ces objets de type NameValueCollection (les cookies sont en fait une HttpCookieCollection, qui a des éléments supplémentaires trivials), donc si votre URL est https://localhost/junk/test.aspx?id=3, la collection QueryString contiendrait une entrée pour l’ID de nom avec la valeur 3. HttpRequest possède une propriété get publique pour cette collection. Par conséquent, lorsque vous codez Request.QueryString, vous accédez à cette propriété. C’est là que tout se passe. Lorsque vous accédez à la collection pour le prénom, les chaînes dangereuses sont vérifiées par le biais de ValidateNameValueCollection. Si une exception HttpRequestValidationException n’est pas levée, le QueryString désormais valide est retourné et un indicateur est défini pour éviter la surcharge liée à la vérification de la collection.

if (this._flags[1] != null)
{
    this._flags[1] = 0;
    this.ValidateNameValueCollection(this._queryString,
               "Request.QueryString");
}
return this._queryString;

Le code de validation de ce type s’effectue via les collections HttpRequest dans ASP.NET 1.1. Bien sûr, étant donné que je veux une solution qui s’exécute sur ASP.NET 1.0 et que je ne peux pas remplacer le comportement des collections Forms, QueryString et Cookie , je dois trouver une autre opportunité dans la pile des appels pour valider les collections.

HttpModule

Un HttpModule semblait le choix parfait. Une classe publique personnalisée simple qui implémente IHttpModule. L’interface IHttpModule se compose uniquement de deux méthodes, Init() et Dispose(). Init() est appelé une fois par ASP.NET avec HttpApplication comme seul paramètre, et c’est l’occasion pour moi de connecter tous les gestionnaires d’événements à l’application. Pour des raisons de performances, je voulais m’assurer que mon code de validation de script intersite ne s’est exécuté qu’une seule fois et s’est exécuté avant et indépendamment de la page et de la logique métier associée.

HttpApplication a les événements suivants qui se déclenchent dans l’ordre indiqué :

  1. Beginrequest
  2. AuthenticateRequest
  3. Authorizerequest
  4. ResolveRequestCache
  5. [Un gestionnaire (une page correspondant à l’URL de la requête) est créé à ce stade.]
  6. AcquireRequestState
  7. PreRequestHandlerExecute
  8. [Le gestionnaire est exécuté. Dans notre cas, la page]
  9. PostRequestHandlerExecute
  10. ReleaseRequestState
  11. [Les filtres de réponse, le cas échéant, filtrent la sortie.]
  12. UpdateRequestCache
  13. Endrequest

Il semble que le temps d’exécution du validateur soit pendant le gestionnaire d’événements PreRequestHandlerExecute , juste avant la page elle-même. Si je trouve quelque chose de potentiellement dangereux et que je lève une exception, la page ne s’exécutera jamais. Il s'agit du comportement voulu.

J’ai donc créé une classe appelée ValidateInput qui implémente IHttpModule et dans Init() raccorde un gestionnaire d’événements pour PreRequestHandlerExecute pour appeler ma fonction personnalisée, ValidateRequest. Il se trouve à l’intérieur de ValidateRequest où j’appelle les fonctions que j’apporterai à partir de ASP.NET 1.1.

Je vais également ajouter une version rapide case activée pour m’assurer que personne n’essaie d’utiliser ce module sur ASP.NET 1.1. Je détesterait que quelqu’un oublie de supprimer ce module quand nous mettons à niveau vers la version 1.1.

public class ValidateInput : IHttpModule
{
HttpContext context;
HttpApplication application;
public ValidateInput(){}
public void Init(HttpApplication app)
{
    Version v = System.Environment.Version;
    if (v.Major != 1 && v.Minor != 0)
        throw new NotSupportedException(@"The ValidateInput HttpModule is 
           not supported on this version of ASP.NET. 
           Remove it from your Web.config file!");
    app.PreRequestHandlerExecute += new EventHandler(this.ValidateRequest) ;
}

J’ai branché PreRequestHandlerExecute à la méthode ValidateRequest de ma classe. Étant donné que je ne parviens pas à me connecter aux collections Forms, QueryString et Cookies , je dois effectuer toute la validation des demandes ici afin de m’assurer que seules les demandes validées sont passées à mon gestionnaire de page .

public void ValidateRequest(Object src, EventArgs e)
{
   //Store away what may be useful during this Request...
   application = (HttpApplication)src;
   context = application.Context;
   this.ValidateNameValueCollection(context.Request.Form, "Request.Form"); 
   this.ValidateNameValueCollection(context.Request.QueryString, 
          "Request.QueryString"); 
   this.ValidateCookieCollection(context.Request.Cookies);
}

Dans ValidateRequest , j’ai appelé mes propres implémentations de ValidateNameValueCollection et ValidateCookieCollection. Chacun d’eux effectue un tour dans les collections déjà analysées représentant les données Form POST, y compris les cookies pré-analysés et queryString.

Il est important de savoir que l’analyse de ces données d’en-tête HTTP et l’organisation dans NameValueCollections sont sécurisées , car toutes les données potentiellement malveillantes de la requête n’ont pas encore atteint le gestionnaire de pages ou le navigateur. En outre, si j’avais choisi l’événement d’application BeginRequest au lieu de PreRequestHandlerExecute, j’aurais dû analyser la requête HTTP brute moi-même. Ainsi, j’obtiens le meilleur des deux mondes, une analyse fastidieuse a été effectuée pour moi (et est déjà dans du code bien testé) et la page n’a pas encore été exécutée, me donnant le temps de lever une exception et d’arrêter l’exécution de la demande.

Ensuite, j’ai extrait toutes les autres fonctions d’assistance dans ma nouvelle classe, y compris IsDangerousExpressionString, IsDangerousOnString, IsDangerousScriptString, IsDangerousString et IsAtoZ de Reflector. Il convient de mentionner que le code C# décompilé que Reflector affiche est en fait une nouvelle représentation C# de l’il contenu dans l’assembly. Les noms des variables locales ont été modifiés, et ce qui était autrefois une boucle peut maintenant être une série d’instructions goto et if. Ne jugez pas l’auteur du code à partir de la représentation IL ! N’oubliez pas que le compilateur doit prendre des libertés lors de la génération de l’il final et ce qui est plus important est le concept d’intention du programmeur. J’en parlerai un peu plus tard.

ms972967.scriptingprotection_fig06(en-us,MSDN.10).gif

Figure 6. Examiner la méthode IsAtoZ

Nous avons maintenant besoin d’une classe Exception personnalisée qui dérive d’ApplicationException qui doit être correctement nommée HttpRequestValidiationException. Il s’agit du même nom que celui utilisé par ASP.NET 1.1, mais dans un espace de noms différent. Cette exception est levée si un script potentiellement dangereux apparaît dans httpRequest. Si vous avez choisi d’afficher la page d’exception ou de journaliser l’exception, c’est à vous de choisir. Certains peuvent penser qu’une attaque de script potentielle est un événement significatif et peuvent choisir de gérer cette exception différemment. Dans les deux cas, veillez à mettre en place une stratégie de gestion des exceptions.

Intention du programmeur

Je voulais mention un peu quelque chose sur l’intention du programmeur. Ce qui a vraiment été décompilé ici, c’est l’intention du programmeur. Nous ne examinons pas le code source C# tel que l’auteur d’origine l’a écrit. Lors de la décompilation en IL, puis de la conversion en représentation C# de ce même IL, les choses changent. Par exemple, un peu de code de IsDangerousOnString ressemble à ceci dans Reflector :

goto L_0045;
L_0040:
index = (index + 1);
L_0045:
if (index >= len)
{
goto L_005E;
}
if (CrossSiteScriptingValidation.IsAtoZ(s[index]))
{
goto L_0040;
}

C’est difficile à lire pour le programmeur moyen, mais il véhicule correctement l’intention du programmeur. Mais quelle était cette intention ? Nous ne pouvons « plier » le code que jusqu’à présent. Il s’agit peut-être d’un appel à String.IndexOf qui a été aligné pour tout ce que nous savons. Toutefois, nous pouvons le réécrire comme ceci (ou une demi-douzaine d’autres façons) afin de mieux le comprendre :

//Programmer intent: look for non-alphas...
while (index < len)
{ 
   if (!CrossSiteScriptingValidation.IsAtoZ(s[index]))
   break;
   index++;
}

Rappelez-vous, « Gotos considéré comme dangereux » ne s’applique qu’à VOUS, pas au compilateur ! Notez également que ce code peut également avoir été exprimé sous la forme d’une boucle « for » ou d’une autre construction de boucle, et que l’intention est toujours correctement exprimée.

Installation et configuration

Pour installer ValidateInputASPNET10 sur le serveur Web, il nous suffit de l’ajouter à la liste des httpModules configurées dans notre web.config. L’assembly, dans ce cas ValidateInputASPNET10.dll doit résider dans le dossier \bin de notre site et dans tous les autres sites de notre zone que nous souhaitons protéger.

<configuration>
    <system.web>
        <httpModules>
            <add name="ValidateInput" 
               type="Corillian.Web.ValidateInput,ValidateInputASPNET10" />
        </httpModules>
    </system.web>
</configuration>

Les résultats

Lorsque j’ajoute httpModule à l'web.config, je peux lancer la même application ASP.NET sans recompilation, car httpModule est son propre assembly et le projet Microsoft® Visual Studio® .NET. Au démarrage, ASP.NET appellez Init() sur le nouveau HttpModule ValidateInputASPNET10, et il est lié à l’événement PreRequestHandlerExecute . Si j’essaie d’entrer JavaScript dans la collection Form (ou QueryString ou Cookies ) comme précédemment, ce message d’erreur s’affiche pour déclarer une exception HttpRequestValidationException. Notez qu’une partie du Code JavaScript est affichée, mais seulement une partie ; nous ne voulons pas que le message d’erreur sorte et exécute le code JavaScript contre lequel nous essayons de nous protéger.

ms972967.scriptingprotection_fig07(en-us,MSDN.10).gif

Figure 7. Protection de votre site web contre l’entrée de script

Note N’oubliez pas que la décompilation doit être utilisée principalement pour le débogage et votre éducation personnelle. N’oubliez pas de connaître les règles de propriété intellectuelle et n’oubliez pas que le fait que les assemblys nonobfusqués sont plus faciles à décompiler que les applications C++, cela ne nous donne pas carte blanche pour balayer du code. Si vous êtes préoccupé par votre code et la prospérité intellectuelle, jetez un coup d’œil à Dotfuscator Community Edition fourni avec Visual Studio .NET 2003.

Conclusion

Le script intersites est l’un des nombreux types de piratages dont vous devez vous soucier lors de la création de sites Web ASP.NET. Les pirates peuvent utiliser cette technique pour exécuter du code sur le serveur, ce qui peut entraîner une perte de données ou, pire, le vol d’informations client. La programmation défensive exige que vous vous protégez de ces attaques. L’ajout de la validation à l’entrée, comme cela a été fait dans cet article, est une première étape vers la protection de votre site Web.

À propos de l’auteur

Scott Hanselman est architecte en chef chez Corillian Corporation, un facteur de financement électronique. Il a plus de dix ans d’expérience dans le développement de logiciels en C, C++, Visual Basic, COM et actuellement Visual Basic .NET et C#. Scott est fier d’avoir été nommé directeur régional MSDN pour Portland( Oregon) au cours des trois dernières années, en développant du contenu et en prenant la parole lors des Developer Days et du lancement de Visual Studio .NET à Portland et Seattle. Scott a également parlé des lancements de Microsoft® Windows Server™ 2003 et de Visual Studio .NET 2003 dans 4 villes. Il a parlé internationalement des technologies Microsoft et a co-écrit deux livres de Wrox Press. En 2001, Scott a parlé d’une tournée nationale de 15 villes avec Microsoft, Compaq et Intel présentant les technologies Microsoft et évangélisant les bonnes pratiques de conception. Cette année, Scott a pris la parole lors de l’événement de lancement de Windows Server 2003 dans 4 villes pacWest, à TechEd aux États-Unis et en Malaisie, et à ASPLive à Orlando. Ses idées sur le zen de .NET, la programmation et les services web se trouvent à l’adresse http://www.computerzen.com.

© Microsoft Corporation. Tous droits réservés.