MSDN Magazin > Home > Ausgaben > 2007 > March >  Cutting Edge: Überprüfen der ASP.NET-...
Cutting Edge
Überprüfen der ASP.NET-Abfragezeichenfolgen
Dino Esposito

Codedownload verfügbar unter: CuttingEdge2007_03.exe (168 KB)
Browse the Code Online
Jahrelang haben klassische ASP-Entwickler Seitenauthentifizierung durch Einfügen einiger generischen Codes oben auf jeder Seite implementiert, die Benutzeranmeldeinformationen erfassen, ein Cookie anfügen und umleiten sollten. Dieser gesamte, sich wiederholende Code wurde mit den ASP.NET HTTP-Modulen für Authentifizierung überflüssig. Demzufolge müssen ASP.NET-Anwendungen nicht jede einzelne Seite verknüpfen, die an das gewünschte Authentifizierungsmodul angeschlossen ist. Alles kann deklarativ über die web.config-Datei und eine Reihe externer Ressourcen erledigt werden, z. B. die Anmeldungsseite und die Mitgliedschaftsdatenbank.
ASP.NET hat auch andere Systemmodule und Programmierverfahren eingeführt, die sich wiederholenden Code minimieren und die Implementierung von herkömmlichen Features einer der Webanwendung rationalisieren. Zum Beispiel sind Sitemaps, anonyme Benutzer und Profile jetzt integrierte Features, und Sie müssen den entsprechenden Code nicht immer wieder schreiben oder kopieren.
Der Fokus auf Sicherheit führte auch zu der Einbeziehung einer angemessenen Anzahl systemeigener Barrieren in der ASP.NET-Laufzeit, was den Entwickler insofern entlastet, da er die Eingabedaten nicht mehr im Hinblick auf einige mögliche Angriffsformen prüfen muss. Selbstverständlich bedeutet dies nicht, dass eine ASP.NET-Anwendung aufgrund ihres Aufbaus vollkommen sicher ist, jedoch ist das Sicherheitsniveau auf jeden Fall höher als in der Vergangenheit. Allerdings sind die Entwickler immer noch angehalten, dieses Niveau weiter zu erhöhen.
Die Seiten von ASP.NET sind so gestaltet, dass sie Daten eigenständig bereitstellen und Eingabeparameter im Text eines HTTP POST Paketes gruppieren. Die meisten ASP.NET-Anwendungen verwenden keine Abfragezeichenfolge, um Eingabedaten so oft wie traditionelle ASP-Anwendungen zu übergeben. Nichtsdestoweniger ist die Abfragezeichenfolge weiterhin eine Möglichkeit, externe Daten in eine ASP.NET Seite zu importieren. Aber wer überprüft diese Daten?
Neue Statistiken zeigen, dass siteübergreifende Script-Angriffe (XSS) immer mehr zunehmen und den Löwenanteil der aufgedeckten Angriffe ausmachen. Erfolgreiche XSS-Angriffe beruhen immer auf ungeprüften oder unsachgemäß geprüften Eingabedaten, und diese Daten kommen immer häufiger über die Abfragezeichenfolge durch.
Angefangen mit der Version 1.1 verarbeitet ASP.NET alle bereitgestellten Daten (Formulare und Abfragezeichenfolgen) vorab und sucht nach verdächtigen Zeichenkombinationen, die von XSS-Angreifern ausgenutzt werden können. Aber diese Barriere ist keine Wunderwaffe, und laut Michael Howards Artikel in der Novemberausgabe 2006 des MSDN®Magazins mit dem Titel „Secure Habits: 8 einfache Regeln zum Entwickeln von sicherem Code“ (verfügbar unter msdn.microsoft.com/msdnmag/issues/06/11/SecureHabits), müssen Sie die Verantwortung übernehmen. Wenn Ihre Seiten Parameter für Abfragezeichenfolgen verwenden, müssen Sie sicherstellen, dass sie vor der Verwendung korrekt überprüft werden. Wie machen Sie das?
In dieser Spalte erstelle ich ein HTTP-Modul, das eine XML-Datei liest, wo Sie die erwartete Struktur der Abfragezeichenfolge hartcodiert haben. Das Modul vergleicht dann die Abfragezeichenfolge jeder angeforderten Seite mit dem vorgegebenen Schema. Und Sie müssen an keiner Stelle den Code bearbeiten. (Weitere Informationen zur Vermeidung von XSS-Angriffen finden Sie unter Microsoft Anti-Cross Site Scripting Library v1. 5)

Das Problem
Entwickler können es sich nicht leisten, Seiten einfach unberücksichtigt zu lassen, die der Abfragezeichenfolge Eingabedaten entnehmen. Die Werte müssen verifiziert und das Format der Abfragezeichenfolge muss sorgfältig geprüft werden. So ein Prüfungsprozess umfasst zwei unterschiedliche Schritte: Statische Prüfung (die den Typ und die Existenz erforderlicher Parameter prüft) und dynamische Prüfung (die überprüft, ob spezifizierte Werte mit den Erwartungen des restlichen Codes übereinstimmen). Die dynamische Prüfung ist für jede Seite spezifisch und kann nicht an eine externe, seitenagnostische Komponente delegiert werden. Im Gegensatz dazu vertraut die statische Prüfung auf eine Liste mit allgemeinen Prüfkriterien (erforderliche Parameter, Typ, Länge), die ohne die Seite zu instanziieren ausgeführt werden kann.
Wie beim klassischen ASP, bei dem Sie den generischen Authentifizierungscode in jede einzelne der gesicherten Seiten einbinden mussten, müssen Sie in ASP.NET den Validierungscode für die Abfragezeichenfolge in jede Seite aufnehmen. ASP.NET hat den Standardcode zur Authentifizierung in eine kleine Gruppe systembezogener HTTP-Module verschoben, wobei jedoch die Abfragezeichenfolge außer Acht gelassen wurde. Andererseits wirft die Zunahme von XSS- und SQL-Injection-Angriffen seit kurzem das Problem der übergreifenden Prüfung von jeder möglichen Eingabequelle auf. Eine externe Komponente, die mit der Anwendung verknüpft wird, die eine strenge statische Validierung der Parameter der Abfragezeichenfolge implementiert, ist eine große Hilfe, weil sie automatisch sicherstellt, dass keine ASP.NET-Seitenanforderungen ausgeführt werden, wenn die Abfragezeichenfolge nicht dem deklarierten Schema entspricht.
Noch wichtiger ist, dass bei einer externen Komponente keine Änderungen am Quellcode der Seite erforderlich sind. Sie müssen lediglich die Komponente über die Konfigurationsdatei bei der Anwendung registrieren und eine XML-Datei hinzufügen, die die Syntax der Abfragezeichenfolge für jede interessierte Seite beschreibt. Nun wollen wir uns die Strategie ein bisschen genauer ansehen.

Definition eine Strategie
ASP.NET bietet HTTP-Module als Tool an, mit dem Sie Ihren eigenen Code in die Laufzeitpipeline einbinden können, bevor die angeforderte Seite instanziiert und verarbeitet ist. Aus der Perspektive der Syntax gesehen ist ein HTTP-Modul lediglich eine Klasse, die eine bestimmte Schnittstelle implementiert. Aus einer breiteren, architektonischen Perspektive gesehen ist ein HTTP-Modul eine Art von Beobachter mit der gleichen Lebensdauer wie die Anwendung. Das Modul beobachtet die Aktivität bei der Verarbeitung der Anforderung und registriert sich, um einige bestimmte Ereignisse abzufragen, z. B. BeginRequest, EndRequest oder PostMapRequestHandler. Die gesamte Liste von Anwendungsereignissen für eine ASP.NET-Anforderung findet sich in der Dokumentation der Klasse System.Web.HttpApplication (msdn2.microsoft.com/0dbhtdck.aspx).
Nach der Installation wird immer dann ein HTTP-Modul aktiv, wenn eine von der ASP.NET-Laufzeit verarbeitete Anforderung die Stufe erreicht, in der das beobachtete Ereignis abgestoßen wird. Beachten Sie, dass die ASP.NET-Laufzeit nicht unbedingt Anforderungen für alle Ressourcen verarbeitet, die von der ASP.NET-Anwendung gehostet werden. Standardmäßig werden statische Ressourcen, z. B. Cascading Style Sheets (CSS) und JPG-Dateien, direkt vom Webserver bedient, ohne die ASP.NET-Anwendung einzubeziehen, es sei denn, IIS ist so konfiguriert worden, dass es ASP.NET ermöglicht, diese Ressourcen zu handhaben.
Mein Abfragezeichenfolge-HTTP-Modul wird das anfängliche Anforderungsereignis abfragen und die Inhalte der Abfragezeichenfolge mit einem vorher geladenen Schema überprüfen. Wenn die Anzahl der Parameter passt und der bereitgestellte Wert kompatibel zu dem erwarteten Typ ist, lässt das Modul die Anforderung die nächste Stufe erreichen. Anderenfalls wird die Anforderung mit einem passenden HTTP-Statuscode beendet, oder es wird eine ASP.NET-Ausnahme aufgeworfen.
Ich habe eine XML-Datei erwähnt, in der die Syntax der Abfragezeichenfolge gespeichert würde. Dies muss nicht wirklich eine XML-Datei sein. (Wenn es eine XML-Datei ist, ist das Schema völlig Ihnen überlassen). Sie brauchen nur eine Datenquelle, die deklarativ Informationen zur erwarteten Struktur der Abfragezeichenfolge einer Seite bereithält. Es könnte eine einfache XML-Datei oder ein hoch entwickelter, anbieterbasierter Dienst sein. Meine im Juni 2006 veröffentlichte Rubrik bietet ein gutes Beispiel für einen benutzerdefinierten Anwendungsdienst, der zur Nutzung von Anbietern entwickelt wurde (msdn.microsoft.com/msdnmag/issues/06/06/CuttingEdge ).

Deklarative Abfragezeichenfolgen
Abbildung 1 zeigt das Beispiel einer XML-Datei und eines Schemas, das vom Abfragezeichenfolge-HTTP-Modul erkannt wird. Unter dem Stammknoten <Abfragezeichenfolge> gibt es viele <>Seitenknoten, da es Seiten in der Anwendung gibt, die Werte von der Abfragezeichenfolge verarbeiten können. Im begleitenden Code dieser Rubrik ist die in Abbildung 1 gezeigte Datei „web.querystring“ genannt. Der Name ist selbstverständlich willkürlich gewählt, genauso wie das Schema.
<!--
<page url="..." abortOnError="TRUE|false">
  <param name="..." 
         type="Int|Text|Bool" 
         optional="FALSE|true"
         length="number (for Text type only)"
         casesensitive="false|true" />
</page>
-->

<querystring>

  <page url="/source/test.aspx" abortOnError="true">
    <param name="id" type="Int" optional="true" casesensitive="true" />
    <param name="code" type="Text" length="5" optional="false" />
    <param name="detailed" type="Bool" optional="false" />
  </page>

  <page url="/source/Test1.aspx" abortOnError="false">
    <param name="guid" type="Int" />
  </page>

</querystring>

(Vom Sicherheitsstandpunkt aus gesehen besteht das Hauptproblem nicht darin, dass eine Seite Werte durch die Abfragezeichenfolge empfängt, sondern dass die Seite die Werte verwenden kann. Wenn ein Code in der Seite die Eingabe verarbeitet, die durch die Abfragezeichenfolge gesendet wird, dann müssen Sie als Entwickler sicherstellen, dass die Eingabe sicher und nicht riskant ist. Aus diesem Grund möchten Sie in die XML-Datei eventuell einen <> Seitenknoten speziell für die Seiten in der Anwendung hinzuzufügen, die tatsächlich Daten über die Abfragezeichenfolge nutzen).
Im Beispielschema hat das <> Seitenelement zwei Attribute: url und abortOnError. Ersteres zeigt die relative URL der Seite an, während Letzteres ein optionales, boolesches Attribut darstellt, das anzeigt, ob die Seitenanforderung im Fall einer ungültigen Eingabe abgebrochen werden sollte. Wenn Sie sich entschließen, das Laden der Seite abzubrechen, empfängt der Benutzer entweder eine HTTP-Fehlermeldung oder eine ASP.NET-Ausnahmemeldung, je nachdem, was Sie entscheiden zu tun, wenn unannehmbare Daten in der Abfragezeichenfolge gefunden wurden. Ungeachtet dessen, wie Sie das Ergebnis angeben, besteht keine Notwendigkeit, den in die ASP.NET-Seite verwendeten Code zu bearbeiten. Die mögliche Beendigung der Anforderung findet im HTTP-Modul statt, bevor die Seitenklasse identifiziert und instanziiert ist.
Es gibt einen alternativen Ansatz. Darin gibt das HTTP-Modul die Anforderung frei, fügt jedoch ausführliche Informationen zum HTTP-Kontext hinzu, um die Seitenklasse dessen, was erkannt wurde, bekannt zu geben. Die Seite hat dann die Verantwortung, angemessene Gegenmaßnahmen einzuleiten, zum Beispiel Anzeigen einer Ad-hoc-Fehlerseite. In dieser Lage muss der Autor der Seite alle Anomalien der Abfragezeichenfolge im Kontext der Fehlerbehandlungsstrategie integrieren, die für die Anwendung festgelegt wurden. Der Nachteil dieses Ansatzes besteht darin, dass er Änderungen am Code jeder Seite erforderlich macht, die mit der Abfragezeichenfolge verwendet werden. (Ich werde später noch einmal auf diesen Punkt zurückkommen).
Das Attribut abortOnError wird standardmäßig auf "True" gesetzt, was bedeutet, dass jede Anomalie in der Abfragezeichenfolge die Seitenanforderung abbricht. Unter jedem<> Seitenknoten gibt es eine Liste von <Parameter->Knoten, und zwar einen für alle unterstützten Parameter einer Abfragezeichenfolge. Im Beispielcode kann mit Hilfe der Attribute in Abbildung 2 ein Parameter definiert werden.

Attribut Beschreibung
Name Zeigt den Namen des Abfrageparameters an.
Typ Zeigt den Typ des Abfragezeichenfolge-Parameters an. Mögliche Werte sind: Text, Int, Bool.
Optional Ein boolesches Attribut, das anzeigt, ob der Parameter optional ist oder nicht. Es ist standardmäßig auf „False“ eingestellt.
Lenght Zeigt die maximale Länge des Texttypparameters an.
CaseSensitive Ein boolesches Attribut, das anzeigt, ob die Klein- und Großschreibung des Parameternamens zu beachten ist. Es ist standardmäßig „False“, was bedeutet, dass der Parameter in jeder Abfragezeichenfolge mit einer beliebigen Kombination aus kleinen und großen Buchstaben angegeben werden kann.
Alle Werte, die die Abfragezeichenfolge weitergeben, werden von ASP.NET als Zeichenfolgen empfangen. Die QueryString-Eigenschaft, die auf dem HttpRequest-Objekt definiert wird, ist daher ein NameValueCollection-Objekt, bei dem Schlüssel und Werte als Zeichenfolgen spezifiziert sind. Das Zeichenfolgenformat ist jedoch ein reines Serialisierungsformat. Jeder Parameter der Abfragezeichenfolge kann sicherlich nicht nur eine Zeichenfolge, sondern auch einen booleschen oder numerischen Wert, plus besondere Zeichenfolge-Untertypen wie URL, GUID und Dateinamen darstellen. Spezifizieren Sie daher in der Datei „web.querystring“ den erwarteten Parametertyp mithilfe der Werte eines benutzerdefinierten Enumerationstyps, „QueryStringParamTypes“:
Friend Enum QueryStringParamTypes As Integer
    Text = 1
    Int = 2
    Bool = 3
End Enum
Die Liste unterstützter Typen kann erweitert werden, indem z. B. verschiedene numerische Typen hinzugefügt werden. Parameter des Typs TEXT können auch eine maximale Länge durch die Attributlänge vorgeben. Eine Seite, die beispielsweise eine 5 Zeichen lange Kunden-ID von einer Abfragezeichenfolge akzeptieren kann, hat keinen Grund, die Länge dieses Parameters nicht zu begrenzen. Außerdem kann web.querystring verwendet werden, um den Parameternamen auf Groß- und Kleinschreibung zu prüfen und ein Parameter als optional zu bestimmen. Der Inhalt der Datei „web.querystring“ wird von der Abfragezeichenfolge des HTTP-Moduls analysiert und in ein im Speicher abgelegtes Objekt umgewandelt.

Codierung des QueryString HTTP-Moduls
Der Quellcode des QueryString-HTTP-Moduls wird in Abbildung 3 gezeigt. Wie erwähnt implementiert eine HTTP-Modul-Klasse IHttpModule, bestehend aus Init- und Dispose-Methoden. Diese Methoden werden aufgerufen, wenn das Modul geladen und im Kontext der Anwendung abgelegt wird. Bei der Init-Methode registriert ein HTTP-Modul normalerweise einen Listener für die Anwendungsereignisse, die beobachtet werden sollen. In diesem Fall registriert es einen Handler für das BeginRequest-Ereignis. Außerdem, verarbeitet das Modul die Datei „web.querystring“ und erstellt eine im Speicher abgelegte Darstellung seines Inhalts. Die Init-Methode wird nur einmal pro Anwendung aufgerufen, d. h. der Inhalt der Konfigurationsdatei wird das erste Mal gelesen und zwischengespeichert, so dass Änderungen an der Datei „web.querystring“ erst erkannt werden, wenn die Webanwendung neu gestartet wird. Dies ist nicht unbedingt ein Problem, da es ziemlich unwahrscheinlich ist, dass Sie die Änderungen an der sich gerade in der Erstellung befindlichen Datei „web.querystring“ eingeben müssen, ohne die Anwendung zu stoppen und neu zu starten. Jedoch könnten Sie auch den Code in Abbildung 3 so erweitern, dass ein Datei-Watcher-Objekt verwendet wird, das jegliche Änderungen an der Datei „web.querystring“ erkennt und binnen eines angemessenen Zeitraums erneut lädt.
Imports System
Imports System.Web
Imports System.IO

Public Class QueryStringModule : Implements IHttpModule

    Private _app As HttpApplication
    Private _queryStringData As Hashtable

    Public Sub Init(ByVal context As System.Web.HttpApplication) _
            Implements System.Web.IHttpModule.Init
        _app = context
        AddHandler _app.BeginRequest, AddressOf OnEnter

        ' Load and cache the XML querystring file
        Dim fileName As String = _
            HttpContext.Current.Server.MapPath("web.querystring")
        _queryStringData = QueryStringHelper.LoadFromFile(fileName)
    End Sub

    Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
    End Sub

    Private Sub OnEnter(ByVal source As Object, ByVal e As EventArgs)
        ' Retrieve the query string data structure for the current page
        Dim currentPage As String = _
            HttpContext.Current.Request.Path.ToLower()
        Dim qsDesc As QueryStringDescriptor = _
            _queryStringData.Item(currentPage)

        ' Validate the query string
        Dim isValid As Boolean
        isValid = QueryStringHelper.Validate( _
           HttpContext.Current.Request.QueryString, qsDesc)

        ' Abort the request if validation fails
        If Not isValid Then
            If qsDesc.AbortOnError Then
                HttpContext.Current.Response.StatusCode = 500
                HttpContext.Current.Response.[End]()
            Else
                ' Add information about the error to Context.Items
                HttpContext.Current.Items( _
                    QueryStringHelper.QueryStringValidationStatus) = _
                        QueryStringHelper.GetErrorCode()
            End If
        Else
            ' Add information for the page to the Context.Items
            HttpContext.Current.Items( _
                QueryStringHelper.QueryStringValidationStatus) = _
                    QueryStringHelper.GetErrorCode()

            ' Add typed values  
            HttpContext.Current.Items( _
                QueryStringHelper.QueryStringValues) = _
                    QueryStringHelper.GetTypedValues( _
                        HttpContext.Current.Request.QueryString, qsDesc)
        End If
    End Sub
End Class

Der Inhalt derDatei „web.querystring“ ist einem Objekt des Typs QueryStringDescriptor zugeordnet, siehe Abbildung 4. Der Deskriptor enthält die URL der Seite, ein Kennzeichen zur Angabe, was im Falle einer fehlgeschlagener Validierung zu tun ist und die Liste der unterstützten Parameter der Abfragezeichenfolgen. Jeder Parameter ist durch eine Instanz der QueryStringParamInfo-Klasse beschrieben. Die QueryStringParamCollection ist die verwandte Auflistungsklasse. Es ist eine typische generische Auflistungsklasse, die durch einige Find-Methoden bereichert wird: eine zur Verifizierung, ob ein Parameter mit einem vorgegebenen Namen in der Auflistung gefunden werden kann und eine, um die Instanz des Parameterdeskriptors zurückzugeben.
Friend Class QueryStringDescriptor
    Public Url As String
    Public AbortOnError As Boolean
    Public Parameters As QueryStringParamCollection
End Class

Friend Class QueryStringParamInfo
    Public Name As String
    Public [Type] As QueryStringParamTypes
    Public Length As Integer
    Public [Optional] As Boolean
    Public CaseSensitive As Boolean
End Class

Friend Class QueryStringParamCollection : Inherits Collection( _
        Of QueryStringParamInfo)

    Public Overloads Function Contains(ByVal name As String) As Boolean
        For i As Integer = 0 To Count - 1
            Dim comparison As StringComparison = _
                StringComparison.OrdinalIgnoreCase
            Dim currentItem As QueryStringParamInfo = Item(i)
            If currentItem.CaseSensitive Then
                comparison = StringComparison.Ordinal
            End If

            If String.Equals(currentItem.Name, name, comparison) Then
                Return True
            End If
        Next

        Return False
    End Function

    Public Function Find(ByVal name As String) As QueryStringParamInfo
        For i As Integer = 0 To Count - 1
            Dim currentItem As QueryStringParamInfo = Item(i)
            If String.Equals(currentItem.Name, name, _
                    StringComparison.OrdinalIgnoreCase) Then
                Return currentItem
            End If
        Next

        Return Nothing
    End Function
End Class

<Flags()> _
Public Enum QueryStringErrorCodes
    NoError = 0
    TooManyParameters = 1
    InvalidQueryParameter = 2
    MissingRequiredParameter = 4
    InvalidContent = 8
End Enum

Der Abfragezeichenfolgendeskriptor speichert Informationen zur Abfragezeichenfolge einer vorgegebenen Seite in der Zwischenablage. Die Datei „web.querystring“ kann jedoch auf mehrere Seiten verweisen. Aus diesem Grund sind alle Deskriptoren für alle Seiten, auf die von web.querystring verwiesen wird, in einer Hashtabelle gruppiert, die die Seiten-URL als Schlüssel verwendet. Der folgende Codeausschnitt zeigt, wie der BeginRequest-Handler des HTTP-Moduls den Deskriptor für die gegenwärtig angeforderte Seite abruft:
Dim currentPage As String
currentPage = HttpContext.Current.Request.Path.ToLower()
Dim qsDesc As QueryStringDescriptor = _
    _queryStringData.Item(currentPage)
Der Deskriptor der Abfragezeichenfolge ist eine im Speicher abgelegte Darstellung der richtigen Syntax für die Abfragezeichenfolge der Seite. Der nächste Schritt vergleicht die bereitgestellte Abfragezeichenfolge mit diesem Schema.

Überprüfung der Zeichenfolge
Der Prüfungsprozess besteht aus drei Schritten. Zuerst zählt das Modul die Anzahl der Parameter in der bereitgestellten Abfragezeichenfolge. Wenn die bereitgestellte Abfragezeichenfolge mehr Parameter hat als erwartet, ist die Prüfung negativ. Dann wiederholt das Modul die bereitgestellten Parameter der Abfragezeichenfolge und stellt sicher, dass jeder Parameter einem Eintrag im deklarierten Schema entspricht. Wenn zusätzliche, unbekannte Parameter gefunden werden, ist die Prüfung negativ. Schließlich wiederholt das Modul alle Parameter, die im Schema definiert sind, und verifiziert, dass alle erforderlichen Parameter angegeben sind und dass jeder angegebene Parameter einen Wert des richtigen Typs hat.
In dem Schritt der Datenprüfung wird versucht, den Wert eines Parameters im Verhältnis zu seinem deklarierten Typ zu analysieren. Hier ist der Codeausschnitt, der verwendet wird, um numerische Werte zu überprüfen:
If paramType = QueryStringParamTypes.Int Then
    Dim result As Integer
    Dim success As Boolean = Int32.TryParse(paramValue, result)
    If Not success Then Return False
End If
Mit Absicht werden boolesche Werte nur von Zeichenfolgen wie „True“ and „False“ analysiert. Das Prüfungssubsystem des querystring-HTTP-Moduls nimmt auch Zeichenfolgen wie z. B. „Yes“ und „No“ an.
Am Ende wird der Inhalt der Abfragezeichenfolge überprüft und gemäß Typ validiert, wie beim ersten Schritt in der Anforderungspipeline. Wenn alles „OK“ ist, wird die Anforderung bearbeitet. Anderenfalls wird die Anforderung umgehend mit einem richtigen HTTP-Statuscode beendet. Hier ein Beispiel:
HttpContext.Current.Response.StatusCode = 500
HttpContext.Current.Response.[End]()
Dem Benutzer eröffnet sich eine Seite wie die, die in Abbildung 5 gezeigt wird. Man könnte einwenden, dass nichts den wirklichen Grund für den IIS-Fehler anzeigt, obwohl der HTTP-Statuscode sowie die generische Beschreibung den Ursprung des Fehler erkennt, nämlich einen internen, serverseitigen Fehler während der Bearbeitung der Anforderung. Wie im oben erwähnten Artikel von Michael Howard erläutert, sollten so wenige Informationen wie möglich in Fehlerseiten offen gelegt werden, um das Risiko zu vermeiden, leichtfertig Details an mögliche Hacker zu übermitteln. In diesem Hinblick gibt eine HTTP 500 Fehlermeldung hinreichend Auskunft darüber, was stattgefunden hat. Ohnehin kann der HTTP-Statuscode, wie auch im vorherigen Codeausschnitt gezeigt, nach Belieben festgelegt werden.
Abbildung 5 Ergebnis einer ungültigen Abfragezeichenfolge (Klicken Sie zum Vergrößern auf das Bild)

Überlegungen und Alternativen
Sollen Sie die Anforderung im Fall ungültig formatierter Daten wirklich abbrechen oder ist es besser, das Ergebnis der Prüfung zwischenzuspeichern und den Seitencode die endgültige Entscheidung über den Benutzer treffen zu lassen? Sollen Sie ferner die Abfragezeichenfolge wirklich so früh im Anforderungslebenszyklus erfassen und verarbeiten? Befassen wir uns mit diesem letzten Punkt zuerst.
Abbildung 6 listet die anwendungsweiten Ereignisse auf, die die Anforderungsverarbeitung charakterisieren. Wenn nicht am Anfang der Anforderung, wo sonst sollten Sie die Abfragezeichenfolge prüfen? Ein guter Zeitpunkt ist sofort nach der Autorisierung. Wenn die Anforderung über die Autorisierungsphase hinausgeht, können Sie sich verhältnismäßig sicher sein, dass der Seiten-HTTP-Handler aufgerufen wird.

Ereignis Beschreibung
BeginRequest Zeigt den Anfang der Anforderungsverarbeitung an.
AuthenticateRequest
PostAuthenticateRequest
Schließt den Prozess der Anforderungsauthentifizierung ab.
AuthorizeRequest
PostAuthorizeRequestPostAuthorizeRequest
Schließt den Prozess der Anforderungsautorisierung ab.
ResolveRequestCache
PostResolveRequestCache>PostResolveRequestCache
Schließt den Prozess ab, der prüft, ob die Anforderung mit zwischengespeicherten Seiten bedient werden kann.
PostMapRequestHandler Zeigt an, dass der HTTP-Handler zur Bereitstellung der Anfrage gefunden wurde.
AcquireRequestState
PostAcquireRequestState
Schließt die Wiedergewinnung des Sitzungsstatus für die Anforderung ab.
PostRequestHandlerExecute Zeigt an, dass der HTTP-Handler zur Bereitstellung der Anfrage ausgeführt wurde.
ReleaseRequestState
PostReleaseRequestState
Schließt die Freigabe des Sitzungsstatus für die Anforderung ab.
UpdateRequestCache
PostUpdateRequestCache
Schließt den Prozess ab, der prüft, ob die Ausgabe der angeforderten Ressource für eine weitere Wiederverwendung zwischengespeichert werden sollte.
EndRequest Zeigt das Ende der Anforderungsverarbeitung an.
Aber können Sie es später machen? Im Allgemeinen würde jeder Ereignishandler bis einschließlich PostAcquireRequestState funktionieren. Der Benutzercode, also die Codeseite, die Autoren im Code hinter oder in die ASPX-Datei schreiben, wird nur nach dem PostAcquireRequestState-Ereignis ausgeführt. Anschließend gibt es keine Möglichkeit, dass die Seite die Abfragezeichenfolge aufnehmen kann, bevor das globale PostAcquireRequestState-Ereignis ausgelöst wurde. Sie sollten jedoch nicht so lange warten. Das Überprüfen der Abfragezeichenfolge nach der Autorisierung und vor der Seitenausführung kann Ihnen einige zusätzliche Vorgänge ersparen, was den Abruf des Sitzungsstatus und die Überprüfung des Ausgabezwischenspeichers angeht. Wenn Sie das Laden der Seite aufgrund einer ungültigen Abfragezeichenfolge abbrechen möchten, gibt es keinen Grund, zuerst den Sitzungszustand zu laden, insbesondere, wenn er von einer prozessinternen Quelle wie SQL Server™ stammt.
Am Ende gibt es nur zwei Anwendungsereignisse, bei denen die Überprüfung der Abfragezeichenfolge erfolgen sollte: BeginRequest oder PostAuthorizeRequest. Sie sollten Letzteres wählen, wenn die Benutzerinformationen erforderlich sind, um die Abfragezeichenfolge zu verarbeiten, z. B. wenn einige Benutzer berechtigt sind, bestimmte Parameter basierend auf ihrer Funktion anzugeben. In diesem Fall können Sie auch dem Schema der Abbildung 1 ein Funktionsattribut hinzufügen. In jedem anderen Szenario können Sie durch Ansetzen des Abfangens in BeginRequest die Seite in einer sehr frühen Phase abbrechen und somit eine weitere Verarbeitung verhindern.
Die Dinge liegen wiederum ganz anders, wenn Sie möchten, dass der Seitencode die ungültige Abfragezeichenfolge handhabt und versucht, sie allmählich herabzusetzen oder wiederherzustellen. Hier funktioniert meiner Ansicht nach jedes Ereignis, das vor der Ausführung der Seite liegt. Ich würde den PostAcquireRequestState wählen, der der neueste Punkt in der Pipeline ist, an dem Sie die Abfragezeichenfolge prüfen können, bevor der Seitencode ausgeführt wird. An diesem Punkt steht Ihnen auch der Sitzungsstatus zur Verfügung. Ich habe es noch nicht gemerkt, obwohl es der Kontext klar verdeutlicht: Die Informationen der Abfragezeichenfolge stehen ab dem Anfang der QueryString-Auflistung des systeminternen Request-Objekts zur Verfügung.
Gehen wir also davon aus, dass Sie möchten, dass das HTTP-Modul die Abfragezeichenfolge prüft und seine Ergebnisse bis hin zum Seitencode weiterleitet. Es gibt einige mögliche Ansätze, die Sie verfolgen könnten. Bevor ich sie erörtere, möchte ich anmerken, dass sich ein derartiger Ansatz auf den Code auswirkt und Änderungen am Quellcode jeder Seite mit einer Abfragezeichenfolge erfordert.
Der einfachste Weg für ein HTTP-Modul, mit dem Handler zu kommunizieren, der für eine vorgegebene Anforderung verantwortlich ist, besteht darin, Daten in die Items-Auflistung des HttpContext-Objekts zu packen. Die Items-Eigenschaft ist eine Hashtabelle für HTTP-Module und Handler zum Schreiben und Lesen von Informationen. Sämtliche Daten, die in der Items-Tabelle gespeichert sind, haben die gleiche Lebensdauer wie die Anforderung.
Das HTTP-Modul erhält Zugang zu dem Kontextobjekt der aktuellen Anforderung, indem die statische Current-Eigenschaft der HttpContext-Klasse wie folgt verwendet wird:
HttpContext.Current.Items("QueryStringStatus") = errorCode
Da es sich bei den Items um eine System.Collections.Hashtable handelt, können sowohl der Schlüssel als auch der Wert ein beliebiger .NET-Typ sein. Das Modul der Abfragezeichenfolge verwendet einen öffentlichen Enumerationstyp, um alle möglichen Fehlercodes aufzulisten:
<Flags()> _
Public Enum QueryStringErrorCodes
    NoError = 0
    TooManyParameters = 1
    InvalidQueryParameter = 2
    MissingRequiredParameter = 4
    InvalidContent = 8
End Enum
Die Kombination dieser Codes, die besser beschreibt, was bei der Abfragezeichenfolge falsch ist, wird an einen konventionell bezeichneten Feld in der Hashtabelle gepackt. Das HTTP-Modul und die Seiten müssen sich auf eine Namenskonvention einigen, damit die Seite diese Informationen abrufen und verwenden kann. Das HTTP-Modul definiert eine öffentliche Konstante, die den Namen des Feldes vertritt:
Public Const QueryStringValidationStatus As String = _
       "QueryStringValidationStatus"
Die Seite kann den folgenden Code verwenden, um die Nachricht vom HTTP-Modul abzurufen und zu entscheiden, was mit der Information geschehen soll:
Dim result As QueryStringErrorCodes = _ 
    DirectCast(Context.Items( _
        QueryStringHelper.QueryStringValidationStatus), _
        QueryStringErrorCodes)
Angenommen, Sie möchten, dass das Modul der Seite eingegebene Werte bereitstellt, die einer gültigen Abfragezeichenfolge entnommen werden. Sehen Sie sich die folgende URL an und gehen Sie davon aus, dass die Abfragezeichenfolge richtig ist:
http://www.yourserver.com/page.aspx?detailed=true
Die Seite soll Code integrieren, um den Wert der Abfragezeichenfolge zu analysieren und in einen booleschen Wert umzuwandeln. So eine Konvertierung ist bereits während des Prüfungsschritts im HTTP-Modul erfolgt. Nichts ist leichter, als diese eingegebenen Werte auch für die Zielseite bereitzustellen, und zwar indem eine zugehörige Hashtabelle in einem anderen Items-Feld platziert wird (Details finden Sie im Quellcode).
Ein genauerer Ansatz besteht darin, eine neue, schreibgeschützte Eigenschaft mit einer Abfragezeichenfolge hinzufügen. Wenn Sie sie IsValidQueryString nennen, sieht sie folgendermaßen aus:
Public Property IsValidQueryString As Boolean
   Get 
      Dim result As QueryStringErrorCodes = DirectCast( _
        Context.Items(QueryStringHelper.QueryStringValidationStatus), _
            QueryStringErrorCodes)
      Return (result = QueryStringErrorCodes.NoError)
   End Get
End Property
Sie könnten eine derartige Eigenschaft sogar in einer Basisklasse definieren und alle über eine Abfragezeichenfolge aktivierten Seiten dieser Klasse ableiten.

Zusammenfassung
Nicht alle ASP.NET-Seiten verwenden die Abfragezeichenfolge. Die Abfragezeichenfolge kann jedoch als Eingabe für Webseiten verwendet werden. In dieser Form ermöglicht sie potentiell Angriffen auf Seiten mit Sicherheitslücken. Wenn Ihre Seiten eine Barriere für die Abfragezeichenfolge brauchen, stellen Sie sich darauf ein, den gleichen Code immer wieder in alle Seiten schreiben zu müssen, auf denen Sie die Abfragezeichenfolge verwenden.
Das QueryString-Modul, das in dieser Spalte anzeigt wird, erfordert keinen Code in Quellseiten und vergleicht automatisch die bereitgestellte Abfragezeichenfolge mit einem vorgegebenen Schema, das in einer getrennten XML-Datei gespeichert wird. Dies bedeutet, dass es keine Auswirkungen auf vorhandenen Code gibt, während eine weitere, integrierte Schutzbarriere gegen Angreifer geboten wird. Aber denken Sie daran, dass dies keine Wunderwaffe ist.

Senden Sie Fragen und Kommentare für Esposito an cutting@microsoft.com cutting@microsoft.com.


Dino Esposito ist Mentor bei Solid Quality Learning und Autor von Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Er lebt in Italien und ist ein weltweit gefragter Referent bei Branchenveranstaltungen. Sie können Esposito über cutting@microsoft.com erreichen oder dem Weblog unter weblogs.asp.net/despos beitreten.

Page view tracker