Sicherheitsüberlegungen zu Daten
Bei der Arbeit mit Daten in Windows Communication Foundation (WCF) müssen Sie eine Reihe verschiedener Bedrohungen berücksichtigen. In der folgenden Tabelle werden die meisten der Bedrohungskategorien der Datenverarbeitung aufgeführt. WCF enthält Tools zu deren Abwehr.
- Denial-of-Service-Angriffe
Der Erhalt nicht vertrauenswürdige Daten kann aufgrund langwieriger Berechnungen auf Empfängerseite einen überdurchschnittlich hohen Zugriff auf verschiedene Ressourcen, wie Arbeitsspeicher, Threads, verfügbare Verbindungen oder Prozessorzyklen verursachen. Ein Denial-of-Service-Angriff führt möglicherweise zum Absturz des Servers, wodurch er keine Nachrichten von anderen, legitimen Clients verarbeiten kann.
- Ausführung von schädlichem Code
Eingehende nicht vertrauenswürdige Daten bewirken, dass die Empfängerseite unbeabsichtigten Code ausführt.
- Offenlegung vertraulicher Informationen
Der Remoteangreifer zwingt die Empfängerseite, auf Anforderungen so zu antworten, dass dabei mehr Informationen als beabsichtigt offengelegt werden.
Vom Benutzer bereitgestellter Code und Codezugriffssicherheit
An mehreren Stellen in der Windows Communication Foundation (WCF)-Infrastruktur wird Code ausgeführt, der vom Benutzer bereitgestellt wird. Das Serialisierungsmodul DataContractSerializer z. B. kann vom Benutzer bereitgestellte set- und get-Eigenschaftenaccessoren aufrufen. Die WCF-Kanalinfrastruktur ruft möglicherweise auch vom Benutzer bereitgestellte, von der Message-Klasse abgeleitete Klassen auf.
Der Autor des Codes muss sicherstellen, dass kein Sicherheitsrisiko entsteht. Angenommen, Sie erstellen einen Datenvertragstyp mit einer Datenmembereigenschaft vom Typ Integer und ordnen in der Implementierung des set-Accessors basierend auf dem Eigenschaftswert ein Array zu. In diesem Fall setzen Sie den Server der Gefahr eines Denial-of-Service-Angriffs aus, wenn eine böswillige Nachricht einen extrem langen Wert für diesen Datenmember enthält. Vermeiden Sie in der Regel Zuordnungen, die auf eingehenden Daten oder langwierigen Verarbeitungen im Benutzercode basieren (insbesondere, wenn die langwierige Verarbeitung durch wenige eingehende Daten verursacht werden kann). Berücksichtigen Sie bei der Sicherheitsanalyse von Benutzercode auch alle möglichen Schwachstellen (d. h. alle Codeverzweigungen, an denen Ausnahmen ausgelöst werden).
Der Code, der sich innerhalb der Dienstimplementierung für die einzelnen Vorgänge befindet, ist das letzte Beispiel für Benutzercode. Für die Sicherheit Ihrer Dienstimplementierung sind Sie verantwortlich. Es kann leicht passieren, dass versehentlich unsichere Vorgänge implementiert werden, die das Risiko von Denial-of-Service-Angriffen mit sich bringen. Stellen Sie sich z. B. einen Vorgang vor, der anhand einer Zeichenfolge aus einer Datenbank eine Liste der Kunden zurückgibt, deren Name mit dieser Zeichenfolge beginnt. Wenn Sie mit großen Datenbanken arbeiten und die übergebene Zeichenfolge besteht nur aus einem einzelnen Buchstaben, ist die Nachricht, die der Code zu erstellen versucht, möglicherweise größer als der verfügbare Arbeitspeicher. Somit schlägt der gesamte Dienst fehl. (Eine OutOfMemoryException kann in .NET Framework nicht behoben werden und führt immer zum Beenden der Anwendung.)
Stellen Sie sicher, dass die verschiedenen Erweiterbarkeitspunkte keinen schädlichen Code enthalten. Dies ist besonders dann relevant, wenn er unter teilweiser Vertrauenswürdigkeit ausgeführt wird, und mit Typen arbeitet, die in nicht voll vertrauenswürdigen Assemblys deklariert sind, oder Komponenten erstellt, die von teilweise vertrauenswürdigem Code verwendet werden. Weitere Informationen finden Sie unter "Bedrohungen durch teilweise vertrauenswürdigen Code" in einem späteren Abschnitt.
Beachten Sie, dass bei Ausführung unter teilweiser Vertrauenswürdigkeit die Infrastruktur der Datenvertragsserialisierung nur eine beschränkte Teilmenge des Datenvertragsprogrammiermodells unterstützt – beispielsweise werden private Datenmember oder Typen, die das SerializableAttribute-Attribut verwenden, nicht unterstützt. Weitere Informationen finden Sie unter Teilweise Vertrauenswürdigkeit.
Vermeiden einer unbeabsichtigten Offenlegung von Informationen
Spielt beim Entwerfen serialisierbarer Typen der Sicherheitsgedanke eine Rolle, ist eine mögliche Offenlegung von Informationen von besonderem Belang.
Berücksichtigen Sie die folgenden Punkte:
Das Programmiermodell DataContractSerializer lässt das Offenlegen von privaten und internen Daten außerhalb des Typs oder der Assembly während der Serialisierung zu. Darüber hinaus kann die Form eines Typs während des Schemaexports offengelegt werden. Stellen Sie sicher, dass Sie das Serialisierungskonzept des Typs verstehen. Wenn Sie nichts offenlegen möchten, deaktivieren Sie dementsprechend die Serialisierung (z. B. bei einem Datenvertrag, indem Sie das DataMemberAttribute-Attribut nicht anwenden).
Denken Sie daran, dass derselbe Typ je nach verwendetem Serialisierungsprogramm mehrere Serialisierungskonzepte umfassen kann. Ein Typ macht möglicherweise eine Reihe von Daten zugänglich, wenn er mit DataContractSerializer verwendet wird und mit XmlSerializer eine andere Reihe von Daten. Wird versehentlich das falsche Serialisierungsprogramm verwendet, könnten Informationen in die falschen Hände gelangen.
Wenn Sie XmlSerializer im RPC/encoded-Modus des Legacy-Remoteprozeduraufrufs verwenden, legen Sie vielleicht unbeabsichtigt die Form des Objektdiagramms der Absenderseite für die Empfängerseite offen.
Verhindern von Denial-of-Service-Angriffen
Kontingente
Wenn die Empfängerseite dazu veranlasst wird, eine erhebliche Arbeitsspeichermenge zuzuordnen, kann es sich dabei um einen Denial-of-Service-Angriff handeln. Dieser Abschnitt behandelt zwar vorwiegend Probleme des Arbeitsspeicherverbrauchs, der durch großen Nachrichten verursacht wird, es können jedoch auch andere Angriffe stattfinden. Nachrichten können beispielsweise eine außergewöhnlich lange Verarbeitungsdauer beanspruchen.
Denial-of-Service-Angriffe werden häufig mithilfe von Kontingenten abgeschwächt. Beim Überschreiten eines Kontingents wird in der Regel eine QuotaExceededException-Ausnahme ausgelöst. Ohne Kontingent kann eine böswillige Nachricht den gesamten verfügbaren Arbeitsspeicher belegen und eine OutOfMemoryException-Ausnahme auslösen, oder es werden alle verfügbaren Stapel verbraucht, was zu einer StackOverflowException führt.
Das Problem eines überschrittenen Kontingents ist behebbar. Wenn dabei ein Dienst aktiv ist, wird die aktuell verarbeitete Nachricht verworfen, der Dienst wird weiterhin ausgeführt und verarbeitet weitere Nachrichten. Wenn jedoch kein Arbeitspeicher mehr verfügbar ist oder ein Stapelüberlauf eintritt, kann das Problem in .NET Framework nicht behoben werden. Der Dienst wird im Fall dieser Ausnahmen beendet.
Kontingente schließen in WCF keine Vorabzuordnungen ein. Ist beispielsweise das MaxReceivedMessageSize-Kontingent (das für verschiedene Klassen verwendet wir) auf 128 KB festgelegt, heißt das nicht, dass automatisch jeder Nachricht 128 KB zugeordnet werden. Die tatsächliche zugeordnete Menge hängt von der tatsächlichen Größe der eingehenden Nachricht ab.
Auf der Transportebene ist eine Vielzahl von Kontingenten verfügbar. Dabei handelt es sich um Kontingente, die vom jeweils verwendeten Transportkanal (HTTP, TCP usw.) eingesetzt werden. Einige dieser Kontingente werden in diesem Thema erläutert, eine ausführliche Beschreibung finden Sie jedoch unter Transportkontingente.
Einschränken des Arbeitsspeicherverbrauch ohne Streaming
Das Sicherheitsmodell zu großen Nachrichten hängt davon ab, ob Streaming verwendet wird. In einem einfachen Fall ohne Streaming werden Nachrichten im Arbeitsspeicher gepuffert. Verwenden Sie hier zur Abwehr großer Nachrichten das MaxReceivedMessageSize-Kontingent für TransportBindingElement oder für die vom System bereitgestellten Bindungen, und schränken Sie damit die maximal verarbeitete Nachrichtengröße ein. Beachten Sie, dass ein Dienst mehrere Nachrichten gleichzeitig verarbeiten kann, die sich dann alle im Arbeitsspeicher befinden. Verwenden Sie die Drosselungsfunktion, um diese Bedrohung zu verringern.
Beachten Sie auch, dass MaxReceivedMessageSize keine Obergrenze für den Arbeitsspeicherverbrauch der einzelnen Nachrichten festlegt, sondern den Verbrauch auf eine konstante Größe beschränkt. Angenommen, MaxReceivedMessageSize ist auf 1 MB festgelegt. Wenn dann eine 1-MB-Nachricht eingeht und anschließend deserialisiert wird, ist weiterer Arbeitsspeicher zur Aufnahme des deserialisierten Objektdiagramms erforderlich. Folglich liegt der insgesamt benötigte Arbeitsspeicher bei gut über 1 MB. Vermeiden Sie deshalb, serialisierbare Typen zu erstellen, die bei nur wenigen eingehenden Daten einen erheblichen Arbeitsspeicherverbrauch verursachen. Beispielsweise könnte ein Datenvertrag "MeinVertrag" mit 50 optionalen Datenmemberfeldern und weiteren 100 privaten Feldern mit dem XML-Konstrukt "<MeinVertrag/>" instanziiert werden. Bei diesem XML-Konstrukt wird Arbeitsspeicher für 150 Felder belegt. Datenmember sind standardmäßig optional. Das Problem wird verschärft, wenn ein solcher Typ Teil eines Arrays ist.
MaxReceivedMessageSize allein reicht nicht aus, um alle Denial-of-Service-Angriffe zu verhindern. Das Deserialisierungsprogramm kann z. B. von einer eingehenden Nachricht gezwungen werden, ein tief geschachteltes Objektdiagramm (ein Objekt, das ein anderes Objekt enthält, das wiederum ein Objekt enthält usw.) zu deserialisieren. Sowohl DataContractSerializer als auch XmlSerializer rufen zum Deserialisieren solcher Diagramme Methoden in einer geschachtelte Weise auf. Eine tiefe Schachtelung von Methodenaufrufen kann zu einer nicht behebbaren StackOverflowException führen. Diese Gefahr verringert sich, wenn das MaxDepth-Kontingent die Schachtelungstiefe von XML einschränkt (wie im Abschnitt "Sicheres Verwenden von XML" weiter unten in diesem Thema erläutert wird).
MaxReceivedMessageSize festzulegen, ist bei der Verwendung einer binären XML-Codierung besonders wichtig. Die Verwendung einer binären Codierung ähnelt in gewisser Weise einer Komprimierung: eine kleine Gruppe von Bytes in der eingehenden Nachricht kann eine große Menge von Daten darstellen. Deshalb kann selbst eine Nachricht, die im Rahmen des MaxReceivedMessageSize-Limits liegt, in erweiterter Form wesentlich mehr Arbeitsspeicher beanspruchen. Um diese speziellen Risiken durch XML zu verringern, müssen alle XML-Readerkontingente richtig festgelegt werden (siehe "Sicheres Verwenden von XML" weiter unten in diesem Thema).
Einschränken des Arbeitsspeicherverbrauch mit Streaming
Beim Streaming verwenden Sie zum Schutz vor Denial-of-Service-Angriffen möglicherweise eine niedrige MaxReceivedMessageSize-Einstellung. Mit Streaming sind komplexere Szenarien möglich. Ein Dateiuploaddienst akzeptiert z. B. Dateien, die größer sind als der gesamte verfügbare Arbeitsspeicher. Legen Sie in diesem Fall MaxReceivedMessageSize auf einen extrem hohen Wert fest, da voraussichtlich fast keine Daten im Arbeitsspeicher gepuffert werden und die Nachricht per Streaming direkt auf die Festplatte übertragen wird. Wenn jedoch eine böswillige Nachricht WCF auf irgendeine Weise zwingt, die Daten zu puffern, statt per Streaming zu übertragen, bietet MaxReceivedMessageSize keinen Schutz mehr vor einer Nachricht, die den gesamten Arbeitsspeicher belegt.
Um diese Gefahr zu verringern, hängen bestimmte Kontingenteinstellungen von verschiedenen WCF-Datenverarbeitungskomponenten ab, die die Pufferung einschränken. Am wichtigsten ist hierbei die MaxBufferSize-Eigenschaft für verschiedene Transportbindungselemente und Standardbindungen. Berücksichtigen Sie beim Streaming die maximale Arbeitsspeichermenge, die Sie für einzelne Nachrichten zuordnen möchten, wenn Sie dieses Kontingent festlegen. Wie bei MaxReceivedMessageSize wird mit dieser Einstellung kein absolutes Maximum für den Arbeitsspeicherverbrauch festgelegt, sondern der Verbrauch wird auf eine konstante Größe beschränkt. Denken Sie auch hier wie bei MaxReceivedMessageSize an die Möglichkeit, dass mehrere Nachrichten gleichzeitig verarbeitet werden.
Näheres zu MaxBufferSize
Die MaxBufferSize -Eigenschaft schränkt die Pufferung großer Datenmengen in WCF ein. SOAP-Header und SOAP-Fehler werden in WCF z. B. immer gepuffert ebenso wie eventuelle MIME-Teile, die sich nicht in der gewohnten Lesefolge in einer MTOM-Nachricht (Message Transmission Optimization Mechanism) befinden. Diese Einstellung schränkt die Menge der gepufferten Daten in all diesen Fällen ein.
In WCF wird dies erreicht, indem der MaxBufferSize-Wert an die verschiedenen Komponenten übergeben wird, die möglicherweise eine Pufferung ausführen. Einige CreateMessage-Überladungen der Message-Klasse verwenden z. B. einen maxSizeOfHeaders-Parameter. WCF übergibt den MaxBufferSize-Wert an diesen Parameter, um den Umfang der SOAP-Headerpufferung einzuschränken. Bei einer direkten Verwendung der Message-Klasse ist es wichtig, diesen Parameter festzulegen. Wenn Sie eine Komponente mit Kontingentparametern in WCF verwenden, sollten Sie im Allgemeinen unbedingt die Auswirkung dieser Parameter auf die Sicherheit kennen und die Parameter entsprechend festlegen.
Auch der MTOM-Nachrichtenencoder verfügt über eine MaxBufferSize-Einstellung. Bei der Verwendung von Standardbindungen wird hierfür automatisch der MaxBufferSize-Wert der Transportebene festgelegt. Wenn jedoch mit dem Bindungselement des MTOM-Nachrichtenencoders eine benutzerdefinierte Bindung erstellt wird, ist es wichtig, die MaxBufferSize-Eigenschaft beim Verwenden von Streaming auf einen sicheren Wert festzulegen.
XML-basierte Streamingangriffe
MaxBufferSize allein genügt nicht, um sicherzustellen, dass WCF nicht zur Pufferung gezwungen werden kann, wenn ein Streaming erwartet wird. Wenn z. B. die XML-Reader von WCF mit dem Lesen eines neuen Elements beginnen, puffern sie stets das gesamte Starttag des XML-Elements. Das dient der ordnungsgemäßen Verarbeitung von Namespaces und Attributen. Wenn MaxReceivedMessageSize hoch konfiguriert wird (z. B. um eine umfangreiches Streaming direkt auf die Festplatte zu ermöglichen), könnte eine böswillige Nachricht erstellt werden, in der der gesamte Nachrichtentext aus einem großen Starttag für XML-Elemente besteht. Der Versuch, dieses Starttag zu lesen, führt zu einer OutOfMemoryException. Dies ist einer von vielen möglichen XML-basierten Denial-of-Service-Angriffen, die mithilfe von XML-Readerkontingenten verhindert werden können (siehe "Sicheres Verwenden von XML" weiter unten in diesem Thema). Beim Streaming ist es besonders wichtig, alle diese Kontingente festzulegen.
Kombinieren von Streaming- und Pufferprogrammiermodellen
Viele mögliche Angriffe ergeben sich aus kombinierten Streaming- und anderen Programmiermodellen im gleichen Dienst. Angenommen, ein Dienst umfasst zwei Vorgänge: ein Vorgang verwendet einen Stream und ein anderer ein Array eines benutzerdefinierten Typs. MaxReceivedMessageSize wird außerdem auf einen großen Wert festgelegt, damit der erste Vorgang große Streams verarbeiten kann. Leider bedeutet das, dass große Nachrichten nun auch an den zweiten Vorgang gesendet werden können und dass das Deserialisierungsprogramm Daten als Array im Arbeitsspeicher puffert, bevor der Vorgang aufgerufen wird. Das stellt einen potenzieller Denial-of-Service-Angriff dar: das MaxBufferSize-Kontingent schränkt die Größe des Nachrichtentextes, womit das Deserialisierungsprogramm arbeitet, nicht ein.
Vermeiden Sie es deshalb, auf Streams basierende Vorgänge mit anderen Vorgängen im selben Vertrag zu kombinieren. Sollte eine Kombination der beiden Programmiermodelle zwingend erforderlich sein, treffen Sie die folgenden Vorkehrungen:
Deaktivieren Sie die IExtensibleDataObject-Funktion, indem Sie die IgnoreExtensionDataObject-Eigenschaft von ServiceBehaviorAttribute auf true festlegen. Dadurch stellen Sie sicher, dass nur Member deserialisiert werden, die zum Vertrag gehören.
Legen Sie die MaxItemsInObjectGraph-Eigenschaft von DataContractSerializer auf einen sicheren Wert fest. Dieses Kontingent ist auch für das ServiceBehaviorAttribute-Attribut oder die Konfiguration verfügbar. Mit diesem Kontingent wird Anzahl der Objekte eingeschränkt, die in einer Deserialisierungsfolge deserialisiert werden. In der Regel wird jeder Vorgangsparameter oder jeder Nachrichtentextteil in einem Nachrichtenvertrag in einer Folge deserialisiert. Beim Deserialisieren von Arrays wird jeder Arrayeintrag als separates Objekt betrachtet.
Legen Sie alle XML-Readerkontingente auf sichere Werte fest. Achten Sie auf MaxDepth, MaxStringContentLength und MaxArrayLength, und vermeiden Sie Zeichenfolgen in anderen als Streamingvorgängen.
Überprüfen Sie die Liste der bekannten Typen und denken Sie daran, dass jeder davon jederzeit instanziiert werden kann (siehe "Verhindern des Ladens unbeabsichtigter Typen" weiter unten in diesem Thema).
Verwenden Sie keine Typen, die die IXmlSerializable-Schnittstelle implementieren und eine große Datenmenge puffern. Fügen Sie keine Typen dieser Art der Liste der bekannten Typen hinzu.
Verwenden Sie kein XmlElement und keine XmlNode-Arrays, Byte-Arrays oder Typen, die ISerializable in einem Vertrag implementieren.
Verwenden Sie kein XmlElement und keine XmlNode-Arrays, Byte-Arrays oder Typen, die ISerializable in der Liste der bekannten Typen implementieren.
Die vorangehenden Vorkehrungen sind relevant, wenn der Vorgang ohne Streaming DataContractSerializer verwendet. Kombinieren Sie niemals Streaming- und andere Programmiermodelle im selben Dienst, wenn Sie XmlSerializer verwenden, denn der Schutz von MaxItemsInObjectGraph-Kontingenten ist dabei nicht vorhanden.
Angriffe durch langsame Streams
Eine Kategorie von Denial-of-Service-Angriffen beim Streaming wirkt sich nicht auf den Arbeitsspeicherverbrauch aus. Stattdessen konzentriert sich der Angriff auf das langsame Senden oder Empfangen von Daten. Während auf das Senden oder Empfang der Daten gewartet wird, erschöpfen sich die Ressourcen wie Threads und die verfügbaren Verbindungen. Dieser Fall kann als Ergebnis eines böswilligen Angriffs oder durch einen legitimen Absender/Empfänger bei einer langsamen Netzwerkverbindung entstehen.
Legen Sie die Transporttimeouts richtig fest, um diese Angriffe zu verhindern. Weitere Informationen finden Sie unter Transportkontingente. Verwenden Sie außerdem niemals Read- oder Write-Vorgänge, wenn Sie in WCF mit Streams arbeiten.
Sicheres Verwenden von XML
Hinweis: |
---|
Obwohl dieser Abschnitt XML behandelt, gelten die Informationen auch für JSON-Dokumente (JavaScript Objekt Notation). Die Kontingente funktionieren auf ähnliche Weise und verwenden Zuordnung zwischen JSON und XML. |
Sichere XML-Reader
Der XML-Infoset bildet die Basis der gesamten Nachrichtenverarbeitung in WCF. Durch die Annahme von XML-Daten einer nicht vertrauenswürdigen Quelle werden eine Reihe von Denial-of-Service-Angriffen möglich, die verhindert werden müssen. WCF bietet spezielle, sichere XML-Reader. Diese Reader werden bei der Verwendung einer Standardcodierung in WCF (Text, binär oder MTOM) automatisch erstellt.
Einige der Sicherheitsfunktionen dieser Reader sind immer aktiv. Die Reader verarbeiten z. B. keine Dokumenttypdefinitionen (DTDs), die eine mögliche Quelle von Denial-of-Service-Angriffen darstellen und niemals in rechtmäßigen SOAP-Nachrichten auftreten sollten. Zu weiteren Sicherheitsfunktionen gehören Readerkontingente, die konfiguriert werden müssen. Im folgenden Abschnitt werden diese Kontingente beschrieben.
Verwenden Sie stets die sicheren XML-Reader von WCF, wenn Sie direkt mit XML-Readern arbeiten (z. B. beim Schreiben eines eigenen benutzerdefinierten Encoders oder beim direkten Arbeiten mit der Message-Klasse) und Sie dabei möglicherweise mit nicht vertrauenswürdigen Daten umgehen. Erstellen Sie die sicheren Reader, indem Sie eine der Überladungen der statischen Factorymethode von CreateTextReader, CreateBinaryReader oder CreateMtomReader für die XmlDictionaryReader-Klasse aufrufen. Geben Sie beim Erstellen eines Readers sichere Kontingentwerte an. Rufen Sie nicht die Überladungen der Create-Methode auf. Damit wird kein WCF-Reader erstellt. Stattdessen wird ein Reader erstellt, der nicht durch die in diesem Abschnitt beschriebenen Sicherheitsfunktionen geschützt ist.
Readerkontingente
Die sicheren XML-Reader verfügen über fünf konfigurierbare Kontingente. Diese werden in der Regel mithilfe der ReaderQuotas-Eigenschaft für das Codierungsbindungselement oder die Standardbindungen konfiguriert oder mithilfe eines XmlDictionaryReaderQuotas-Objekts, das beim Erstellen eines Readers übergeben wird.
MaxBytesPerRead
Dieses Kontingent beschränkt die Anzahl der Bytes, die in einem einzelnen Read-Vorgang beim Lesen des Starttags des Elements und seiner Attribute gelesen werden können. (In Fällen ohne Streaming wird der Elementname selbst nicht in die Berechnung des Kontingents einbezogen.) MaxBytesPerRead ist aus den folgenden Gründen wichtig:
Der Elementname und dessen Attribute werden immer im Arbeitsspeicher gepuffert, wenn sie gelesen werden. Deshalb ist es wichtig, dass dieses Kontingent im Streamingmodus richtig festgelegt wird, um bei einem erwarteten Streaming eine übermäßige Pufferung zu vermeiden. Informationen zum tatsächlichen Pufferumfang finden Sie unten in den Ausführungen zum MaxDepth-Kontingent.
Bei zu vielen XML-Attributen kann die Verarbeitungszeit übermäßig lange dauern, da die Attributnamen auf Eindeutigkeit überprüft werden müssen. MaxBytesPerRead verringert dieses Risiko.
MaxDepth
Diese Kontingent schränkt die maximale Schachtelungstiefe von XML-Elementen ein. Das Dokument "<A><B><C/></B></A>" beispielsweise hat eine Schachtelungstiefe von drei Elementen. MaxDepth ist aus den folgenden Gründen wichtig:
MaxDepth interagiert mit MaxBytesPerRead: Der Reader behält stets Daten für das aktuelle Element und alle seiner übergeordneten Elemente im Arbeitsspeicher, weshalb der maximale Arbeitsspeicherverbrauch proportional zum Produkt aus diesen beiden Einstellungen ist.
Wenn Sie ein tief geschachteltes Objektdiagramm deserialisieren, ist das Deserialisierungsprogramm gezwungen, auf den gesamten Stapel zuzugreifen und eine nicht behebbare StackOverflowException auszulösen. Ein direkter Zusammenhang besteht zwischen der XML-Schachtelung und der Objektschachtelung sowohl für DataContractSerializer als auch für XmlSerializer. Verwenden Sie MaxDepth, um diese Bedrohung zu verringern.
MaxNameTableCharCount
Dieses Kontingent schränkt die Größe der Nametable des Readers ein. Die Nametable enthält bestimmte Zeichenfolgen (z. B. Namespaces and Präfixe), die beim Verarbeiten von XML-Dokumenten auftreten. Diese Zeichenfolgen werden im Arbeitsspeicher gepuffert. Legen Sie deshalb dieses Kontingent fest, um eine übermäßige Pufferung bei einem erwarteten Streaming zu verhindern.
MaxStringContentLength
Dieses Kontingent schränkt die maximale Größe der Zeichenfolgen ein, die vom XML-Reader zurückgegeben werden. Der Arbeitsspeicherverbrauch im XML-Reader selbst wird mit diesem Kontingent nicht eingeschränkt, jedoch in der Komponente, die den Reader verwendet. Wenn DataContractSerializer z. B. einen Reader verwendet, der mit MaxStringContentLength gesichert ist, werden keine Zeichenfolgen deserialisiert, deren Größe dieses Kontingent überschreitet. Wird die XmlDictionaryReader-Klasse direkt verwendet, beachten nicht alle Methoden dieses Kontingent, sondern nur die Methoden, die speziell für das Lesen dieser Zeichenfolgen entworfen wurden, z. B. die ReadContentAsString-Methode. Die Value-Eigenschaft für den Reader wird von diesem Kontingent nicht beeinflusst. Sie sollte deshalb nicht verwendet werden, wenn der Schutz benötigt wird, den dieses Kontingent bietet.
MaxArrayLength
Dieses Kontingent schränkt die maximale Größe eines Arrays von Primitiven ein, einschließlich Bytearrays, die vom XML-Reader zurückgegeben werden. Der Arbeitsspeicherverbrauch im XML-Reader selbst wird mit diesem Kontingent nicht eingeschränkt, jedoch in der Komponente, die den Reader verwendet. Wenn DataContractSerializer z. B. einen Reader verwendet, der mit MaxArrayLength gesichert ist, werden keine Bytearrays deserialisiert, deren Größe dieses Kontingent überschreitet. Sollen Streaming- und Pufferprogrammiermodelle in einem einzelnen Vertrag kombiniert werden, ist es wichtig, dieses Kontingent festzulegen. Denken Sie daran, dass bei einer direkten Verwendung der XmlDictionaryReader-Klasse nur die Methoden dieses Kontingent beachten, die speziell zum Lesen von Arrays beliebiger Größe von bestimmten primitiven Typen entworfen wurden, wie z. B. ReadInt32Array.
Spezielle Bedrohungen bei der binären Codierung
Die von WCF unterstützte binäre XML-Codierung schließt eine Wörterbuchzeichenfolgenfunktion ein. Eine große Zeichenfolge kann mit wenigen Bytes codiert werden. Das ermöglicht eine erhebliche Leistungsverbesserung, bringt jedoch neue Angriffsflächen für Denial-of-Service-Bedrohungen mit sich, die abgewehrt werden müssen.
Es gibt zwei Arten von Wörterbüchern: statisch und dynamisch. Das statische Wörterbuch besteht aus eine integrierten Liste langer Zeichenfolgen, die mithilfe eines kurzen Codes in der binären Codierung dargestellt werden können. Diese Zeichenfolgenliste steht beim Erstellen des Readers fest und kann nicht geändert werden. Keine der Zeichenfolgen des statischen Standardwörterbuchs in WCF ist lang genug, um eine ernste Denial-of-Service-Bedrohung darzustellen, auch wenn sie bei einem Angriff durch eine Wörterbucherweiterung eine Rolle spielen können. Seien Sie in komplexen Szenarien, in denen Sie ein eigenes statisches Wörterbuch bereitstellen, mit dem Einbringen langer Wörterbuchzeichenfolgen vorsichtig.
Die Funktion dynamischer Wörterbücher ermöglicht es Nachrichten, eigene Zeichenfolgen zu definieren und kurzen Codes zuzuordnen. Diese Zeichenfolgen-Code-Zuordnungen bleiben während der gesamten Kommunikationssitzung im Arbeitsspeicher. Auf diese Weise müssen anschließende Nachrichten die Zeichenfolgen nicht erneut senden und können bereits definierte Codes nutzen. Die Länge dieser Zeichenfolgen ist beliebig, weshalb sie eine ernstere Bedrohung darstellen als die Zeichenfolgen im statischen Wörterbuch.
Die erste Bedrohung, die es zu verringern gilt, ist die Möglichkeit eines zu großen dynamischen Wörterbuchs (die Zeichenfolgen-Code-Zuordnung). Dieses Wörterbuch kann im Verlauf mehrerer Nachrichten anwachsen. Das MaxReceivedMessageSize-Kontingent bietet somit keinen Schutz, da es sich gesondert auf die einzelnen Nachrichten bezieht. Deshalb gibt es eine gesonderte MaxSessionSize-Eigenschaft für BinaryMessageEncodingBindingElement, die die Größe des Wörterbuchs einschränkt.
Im Gegensatz zu den meisten anderen Kontingenten wird dieses Kontingent auch beim Schreiben von Nachrichten angewendet. Wird es beim Lesen einer Nachricht überschritten, wird wie üblich die QuotaExceededException ausgelöst. Wenn es beim Schreiben einer Nachricht überschritten wird, werden alle Zeichenfolgen, die das Überschreiten des Kontingents verursachen, ohne Einbeziehung der Funktion für dynamische Wörterbücher so geschrieben, wie sie sind.
Bedrohungen durch Wörterbucherweiterungen
Wörterbucherweiterungen stellen eine wichtige Kategorie von Angriffen binärer Natur dar. Eine kleine Nachricht in binärer Form kann sich in vollständig erweiterter Textform in eine extrem große Nachricht verwandeln, wenn die Wörterbuchzeichenfolgenfunktion umfassend angewendet wird. Der Erweiterungsfaktor für Zeichenfolgen des dynamischen Wörterbuchs wird durch das MaxSessionSize-Kontingent eingeschränkt, da keine Zeichenfolge des Wörterbuchs die maximale Größe des gesamten dynamischen Wörterbuchs überschreitet.
Die Eigenschaften MaxNameTableCharCount, MaxStringContentLength und MaxArrayLength schränken nur den Arbeitsspeicherverbrauch ein. In der Regel sind sie zur Abwehr von Bedrohungen in Szenarien ohne Streaming nicht erforderlich, da der Arbeitsspeicherverbrauch bereits durch MaxReceivedMessageSize eingeschränkt wird. MaxReceivedMessageSize berechnet jedoch die Bytes vor der Erweiterung. Bei der Verwendung der binären Codierung könnte der Arbeitsspeicherverbrauch MaxReceivedMessageSize überschreiten und würde lediglich durch den Faktor von MaxSessionSize eingeschränkt werden. Aus diesem Grund ist es wichtig, stets alle Kontingente des Readers festzulegen (insbesondere MaxStringContentLength), wenn die binäre Codierung verwendet wird.
Wird die binäre Codierung in Verbindung mit DataContractSerializer verwendet, kann die IExtensibleDataObject-Schnittstelle für einen Angriff durch eine Erweiterung des Wörterbuchs missbraucht werden. Diese Schnittstelle stellt im Wesentlichen unbegrenzten Speicher für beliebige Daten bereit, die nicht zum Vertrag gehören. Wenn Kontingente nicht so niedrig festgelegt werden können, dass MaxSessionSize multipliziert mit MaxReceivedMessageSize kein Problem darstellt, deaktivieren Sie die IExtensibleDataObject-Funktion bei der Verwendung der binären Codierung. Legen Sie die IgnoreExtensionDataObject -Eigenschaft für das ServiceBehaviorAttribute-Attribute auf true fest. Als Alternative können Sie auch darauf verzichten, die IExtensibleDataObject-Schnittstelle zu implementieren. Weitere Informationen finden Sie unter Aufwärtskompatible Datenverträge.
Zusammenfassung der Kontingente
In der folgenden Tabelle werden die Hinweise zu den Kontingenten zusammengefasst.
Bedingung | Wichtige Kontingente |
---|---|
Kein Streaming oder Streaming von kleinen Nachrichten, Text oder MTOM-Codierung |
MaxReceivedMessageSize, MaxBytesPerRead und MaxDepth |
Kein Streaming oder Streaming von kleinen Nachrichten und binäre Codierung |
MaxReceivedMessageSize, MaxSessionSize und alle ReaderQuotas |
Streaming von großen Nachrichten, Text oder MTOM-Codierung |
MaxBufferSize und alle ReaderQuotas |
Streaming von großen Nachrichten und binäre Codierung |
MaxBufferSize, MaxSessionSize und alle ReaderQuotas |
Timeouts auf Transportebene müssen immer festgelegt werden, und beim Streaming dürfen niemals synchrone Lese-/Schreibvorgänge verwendet werden, unabhängig davon, ob große oder kleine Nachrichten übertragen werden.
Wenn Sie sich über ein Kontingent nicht klar sind, legen Sie besser einen sicheren Wert fest, statt gar keinen.
Verhindern der Ausführung von schädlichem Code
Bei den folgenden allgemeinen Bedrohungskategorien kann Code ausführt werden und ein unerwünschtes Ergebnis eintreten:
Das Deserialisierungsprogramm lädt einen schädlichen, unsicheren oder sicherheitsrelevanten Typ.
Eine eingehende Nachricht veranlasst das Deserialisierungsprogramm, eine Instanz eines normalerweise sicheren Typs so zu erstellen, dass er unerwünschte Folgen hat.
In den folgenden Abschnitten werden diese Bedrohungskategorien näher erläutert.
DataContractSerializer
(Sicherheitsinformationen zu XmlSerializer finden Sie in der entsprechenden Dokumentation.) Das Sicherheitsmodell für XmlSerializer ähnelt dem von DataContractSerializer, unterscheidet sich jedoch vorwiegend in den Details. Beispielsweise wird das XmlIncludeAttribute-Attribut für die Inklusion von Typen verwendet statt des KnownTypeAttribute-Attributs. Einige Bedrohungen, die sich speziell auf XmlSerializer beziehen, werden jedoch weiter unten in diesem Thema erläutert.
Verhindern des Ladens unerwünschter Typen
Das Laden unerwünschter Typen kann schwerwiegende Folgen haben, unabhängig davon, ob der Typ schädlich ist oder nur sicherheitsrelevante Nebeneffekte aufweist. Ein Typ kann Sicherheitslücken verursachen, sicherheitsrelevante Aktionen in seinem Konstruktor oder dem Klassenkonstruktor ausführen, eine große Menge Arbeitsspeicher belegen, was Denial-of-Service-Angriffe vereinfacht, oder nicht behebbare Ausnahmen auslösen. Typen können Klassenkonstruktoren besitzen, die beim Laden des Typs vor dem Erstellen von Instanzen sofort ausgeführt werden. Aus diesen Gründen ist es wichtig, die Reihe der Typen zu steuern, die das Deserialisierungsprogramm laden kann.
DataContractSerializer führt die Desialisierung mit einer losen Verknüpfung durch. Es werden keine Namen von CRL-Typen (Common Language Runtime) oder von Assemblys in den eingehenden Daten gelesen. Das ist mit dem Verhalten von XmlSerializer vergleichbar, unterscheidet sich jedoch vom Verhalten von NetDataContractSerializer, BinaryFormatter und von SoapFormatter. Die lose Verknüpfung bietet eine gewisse Sicherheit, da Remoteangreifer das Laden eines beliebigen Typs nicht veranlassen können, indem sie diesen Typ einfach in der Nachricht benennen.
DataContractSerializer darf stets einen Typ laden, der in Übereinstimmung mit dem Vertrag gerade erwartet wird. Wenn z. B. ein Datenvertrag einen Datenmember vom Typ Customer
enthält, darf DataContractSerializer den Customer
-Typ beim Deserialisieren dieses Datenmembers laden.
Darüber hinaus unterstützt DataContractSerializer Polymorphie. Ein Datenmember kann als Object deklariert werden, die eingehenden Daten können jedoch eine Customer
-Instanz enthalten. Das ist nur möglich, wenn der Customer
-Typ für das Deserialisierungsprogramm anhand einer der folgenden Mechanismen "bekannt" gemacht wurde:
KnownTypeAttribute-Attribut wird auf einen Typ angewendet.
KnownTypeAttribute-Attribut gibt eine Methode an, die eine Liste mit Typen zurückgibt.
ServiceKnownTypeAttribute-Attribut.
KnownTypes-Konfigurationsabschnitt.
Eine Liste bekannter Typen wird bei direkter Verwendung des Serialisierungsprogramm während der Erstellung explizit an DataContractSerializer übergeben.
Jede dieser Mechanismen erhöht die Angriffsfläche, da sie die Anzahl der Typen erhöhen, die das Deserialisierungsprogramm laden kann. Überprüfen Sie diese Mechanismen und stellen Sie sicher, dass der Liste der bekannten Typen keine unerwünschten Typen hinzugefügt werden.
Sobald sich ein bekannter Typ im Bereich befindet, kann er jederzeit geladen und Instanzen des Typs erstellt werden, selbst wenn der Vertag dessen Verwendung an sich verbietet. Angenommen, der Typ "MyDangerousType" wird über einen dieser Mechanismen der Liste der bekannten Typen hinzugefügt. Dies bedeutet Folgendes:
MyDangerousType
wird geladen und sein Klassenkonstruktor wird ausgeführt.Auch wenn nur ein Datenvertrag mit einem Zeichenfolgendatenmember deserialisiert wird, kann eine böswillige Nachricht dazu führen, dass eine Instanz von
MyDangerousType
erstellt wird. Code inMyDangerousType
, z. B. ein Eigenschaftensetter, wird möglicherweise ausgeführt. Anschließend versucht das Deserialisierungsprogramm diese Instanz dem Zeichenfolgendatenmember zuzuordnen, schlägt fehl und löst eine Ausnahme aus.
Wenn Sie eine Methode schreiben, die eine Liste bekannter Typen zurückgibt, oder wenn Sie eine Liste direkt an den DataContractSerializer-Konstruktor übergeben, stellen Sie sicher, dass der Code zum Erstellen der Liste sicher ist und nur vertrauenswürdige Daten heranzieht.
Wenn Sie bekannte Typen in Konfiguration angeben, stellen Sie sicher, dass die Konfigurationsdatei sicher ist. Verwenden Sie stets starke Namen in der Konfiguration, indem Sie den öffentlichen Schlüssel der signierten Assembly angeben, in der sich der Typ befindet. Geben Sie jedoch nicht die Version des zu ladenden Typs an. Das Typladeprogramm wählt die neueste Version wenn möglich automatisch aus. Die Angabe einer bestimmten Version in der Konfiguration birgt folgendes Risiko: Falls ein Typ eine Sicherheitslücke aufweist, die eine spätere Version möglicherweise beseitigt, wird trotzdem die gefährdete Version geladen, da sie in der Konfiguration ausdrücklich angegeben ist.
Zu viele bekannte Typen haben eine weitere Konsequenz: DataContractSerializer erstellt einen Cache mit Serialisierungs-/Deserialisierungscode in der Anwendungsdomäne. Dabei erhält jeder zu serialisierende und zu deserialisierende Typ einen Eintrag. Dieser Cache wird nie gelöscht, solange die Anwendungsdomäne ausgeführt wird. Folglich kann ein Angreifer, dem die Verwendung vieler bekannter Typen in einer Anwendung bekannt ist, die Deserialisierung all dieser Typen veranlassen, wodurch der Cache außerordentlich viel Arbeitsspeicher verbraucht.
Verhindern eines unerwünschter Zustands der Typen
Ein Typ besitzt möglicherweise interne Konsistenzeinschränkungen, die beachtet werden müssen. Diese Einschränkungen dürfen bei der Deserialisierung nicht außer Kraft gesetzt werden.
Im folgenden Beispiel wird der Zustand einer Luftschleuse in einer Raumsonde dargestellt. Dabei gilt die Einschränkung, dass beide Türen nicht gleichzeitig geöffnet werden können.
<DataContract()> _
Public Class SpaceStationAirlock
<DataMember()> Private innerDoorOpenValue As Boolean = False
<DataMember()> Private outerDoorOpenValue As Boolean = False
Public Property InnerDoorOpen() As Boolean
Get
Return innerDoorOpenValue
End Get
Set(ByVal value As Boolean)
If (value & outerDoorOpenValue) Then
Throw New Exception("Cannot open both doors!")
Else
innerDoorOpenValue = value
End If
End Set
End Property
Public Property OuterDoorOpen() As Boolean
Get
Return outerDoorOpenValue
End Get
Set(ByVal value As Boolean)
If (value & innerDoorOpenValue) Then
Throw New Exception("Cannot open both doors!")
Else
outerDoorOpenValue = value
End If
End Set
End Property
End Class
[DataContract]
public class SpaceStationAirlock
{
[DataMember]
private bool innerDoorOpenValue = false;
[DataMember]
private bool outerDoorOpenValue = false;
public bool InnerDoorOpen
{
get { return innerDoorOpenValue; }
set
{
if (value & outerDoorOpenValue)
throw new Exception("Cannot open both doors!");
else innerDoorOpenValue = value;
}
}
public bool OuterDoorOpen
{
get { return outerDoorOpenValue; }
set
{
if (value & innerDoorOpenValue)
throw new Exception("Cannot open both doors!");
else outerDoorOpenValue = value;
}
}
}
Ein Angreifer kann folgende böswillige Nachricht senden, um die Einschränkungen zu umgehen und das Objekt in einen ungültigen Zustand zu versetzen, der unerwünschte und unvorhersehbare Folgen haben kann.
<SpaceStationAirlock>
<innerDoorOpen>true</innerDoorOpen>
<outerDoorOpen>true</outerDoorOpen>
</SpaceStationAirlock>
Diese Situation ist vermeidbar, wenn folgenden Punkte beachtet werden:
Wenn DataContractSerializer Klassen deserialisiert, werden in den meisten Fällen keine Konstruktoren ausgeführt. Verlassen Sie sich deshalb nicht darauf, dass der Konstruktor Zustände verwaltet.
Verwenden Sie Rückrufe, um sicherzustellen, dass sich das Objekt in einem gültigen Zustand befindet. Der mit dem OnDeserializedAttribute-Attribut gekennzeichnete Rückruf ist besonders hilfreich, wenn er nach Abschluss der Deserialisierung ausgeführt wird und den Gesamtzustand überprüfen und korrigieren kann. Weitere Informationen finden Sie unter Versionstolerante Serialisierungsrückrufe.
Entwerfen Sie Datenvertragstypen nicht nach einer bestimmten Reihenfolge, in der Eigenschaftensetter aufgerufen werden müssen.
Seien Sie vorsichtig, wenn Sie ältere Typen verwenden, die mit dem SerializableAttribute-Attribut gekennzeichnet sind. Viele davon wurden für .NET Framework Remoting zur ausschließlichen Verwendung mit vertrauenswürdigen Daten entwickelt. Bei der Entwicklung vorhandener Typen, die mit diesem Attribut gekennzeichnet sind, spielte die Sicherheit möglicherweise keine Rolle.
Verlassen Sie sich nicht darauf, dass die IsRequired-Eigenschaft des DataMemberAttribute-Attributs das Vorhandensein von Daten in einem sicheren Zustand garantiert. Deren Status könnte auch NULL, zero oder invalid lauten.
Vertrauen Sie niemals einem Objektdiagramm, das von einer nicht vertrauenswürdigen Datenquelle deserialisiert wurde, ohne es zuerst zu überprüfen. Zwar kann sich jedes einzelne Objekt in einem konsistenten Zustand befinden, das Objektdiagramm insgesamt vielleicht jedoch nicht. Außerdem kann es sein, dass selbst bei deaktiviertem Objektdiagramm-Beibehaltungsmodus das deserialisierte Diagramm in einem Zirkelverweis noch mehrere Verweise auf das selbe Objekt aufweist. Weitere Informationen finden Sie unter Serialisierung und Deserialisierung.
Sicheres Verwenden von NetDataContractSerializer
NetDataContractSerializer ist ein Serialisierungsmodul, das eng verknüpfte Typen verwendet. Das ist mit BinaryFormatter und SoapFormatter vergleichbar. Das heißt, der zu instanziierende Typ wird ermittelt, indem die .NET Framework-Assembly und der Typnamen in den eingehenden Daten gelesen wird. Dieses Serialisierungsmodul gehört zwar zu WCF, es gibt jedoch keine Möglichkeit, das Modul einzubinden. Dazu muss benutzerdefinierter Code geschrieben werden. NetDataContractSerializer soll in erster Linie die Migration von .NET Framework-Remoting zu WCF erleichtern. Weitere Informationen finden Sie unter dem entsprechenden Abschnitt unter Serialisierung und Deserialisierung
Da die Nachricht selbst möglicherweise alle ladbaren Typen angibt, ist der NetDataContractSerializer-Mechanismus grundsätzlich unsicher und sollte nur mit vertrauenswürdigen Daten verwendet werden. Sie können den Mechanismus sichern, indem Sie einen sicheren, Typen einschränkenden Typbinder schreiben, der nur das Laden sicherer Typen zulässt (mithilfe der Binder-Eigenschaft).
Selbst wenn das Modul mit vertrauenswürdigen Daten arbeitet, geben die eingehenden Daten möglicherweise den zu ladenden Typ nur ungenau an, insbesondere, wenn die AssemblyFormat-Eigenschaft auf Simple festgelegt ist. Jeder, der Zugriff auf das Verzeichnis der Anwendung oder den globalen Assemblycache besitzt, kann den vorhandenen zu ladenden Typ durch einen schädlichen Typ ersetzen. Stellen Sie stets sicher, dass das Verzeichnis der Anwendung und der globale Anwendungscache gesichert sind, indem Sie die Berechtigungen richtig festlegen.
Im Allgemeinen gilt: Wenn Sie teilweise vertrauenswürdigem Code erlauben, auf Ihre NetDataContractSerializer-Instanz zuzugreifen oder auf andere Weise den Ersatzselektor (ISurrogateSelector) bzw. den Serialisierungsbinder (SerializationBinder) zu steuern, kann er möglicherweise einen großen Teil des Serialisierungs-/Deserialisierungsprozesses steuern. Er kann beispielsweise beliebige Typen einfügen, was zur Offenlegung von Informationen führt, oder das sich ergebende Objektdiagramm und die serialisierten Daten manipulieren bzw. einen Überlauf des resultierenden serialisierten Streams herbeiführen.
Ein weiteres Sicherheitsproblem bei NetDataContractSerializer stellen Denial-of-Service-Angriffe dar, nicht die Gefahr durch das Ausführen von schädlichem Code. Legen Sie bei der Verwendung von NetDataContractSerializer das MaxItemsInObjectGraph-Kontingent immer auf einen sicheren Wert fest. Eine kleine böswillige Nachricht kann leicht erstellt werden, die ein Array von Objekten zuordnet, deren Größe nur durch dieses Kontingent eingeschränkt werden kann.
Spezielle Bedrohungen bei XmlSerializer
Das XmlSerializer-Sicherheitsmodell ähnelt dem von DataContractSerializer. Einige Bedrohungen sind jedoch speziell bei XmlSerializer relevant.
XmlSerializer generiert zur Laufzeit Serialisierungsassemblys mit Code, der die eigentliche Serialisierung und Deserialisierung ausführt. Diese Assemblys werden in einem temporären Dateiverzeichnis erstellt. Wenn ein anderer Prozess oder Benutzer Zugriffrechte für dieses Verzeichnis besitzt, kann er den Serialisierungs-/Deserialisierungscode mit beliebigem Code überschreiben. XmlSerializer führt dann diesen Code mit dessen Sicherheitskontext anstelle des Serialisierungs-/Deserialisierungscode aus. Stellen Sie sicher, dass die Berechtigungen für das temporäre Dateiverzeichnis richtig festgelegt sind, um dieses Szenario zu verhindern.
XmlSerializer besitzt außerdem einen Modus, in dem Serialisierungsassemblys nicht zur Laufzeit generiert werden, sondern bereits generierte Serialisierungsassemblys verwendet werden. Dieser Modus wird immer dann ausgelöst, wenn XmlSerializer eine geeignete Serialisierungsassembly findet. XmlSerializer überprüft, ob die Serialisierungsassembly mit demselben Schlüssel signiert wurde wie die Assembly, die die zu serialisierenden Typen enthält. Dies dient als Schutz vor schädlichen Assemblys, die als Serialisierungsassemblys getarnt sind. Wenn die Assembly mit den serialisierbaren Typen jedoch nicht signiert ist, kann XmlSerializer diese Überprüfung nicht ausführen und verwendet eine beliebige Assembly mit dem richtigen Namen. Auf diese Weise kann schädlicher Code ausgeführt werden. Signieren Sie stets die Assemblys, die die serialisierbaren Typen enthalten, oder schränken Sie den Zugriff auf das Verzeichnis der Anwendung und den globalen Assemblycache stark ein, um das Eindringen von schädlichen Assemblys zu verhindern.
XmlSerializer kann einem Denial-of-Service-Angriff ausgesetzt sein. XmlSerializer verfügt über kein MaxItemsInObjectGraph-Kontingent (wie es bei DataContractSerializer vorhanden ist). Deshalb wird eine beliebige Anzahl von Objekten deserialisiert, die nur durch die Nachrichtengröße eingeschränkt wird.
Bedrohungen durch teilweise vertrauenswürdigen Code
Berücksichtigen Sie beim Ausführen von teilweise vertrauenswürdigem Code die folgenden Überlegungen zu den damit verbundenen Bedrohungen. Zu diesen Bedrohungen gehören enthaltener schädlicher Code ebenso wie schädlicher Code in Verbindung mit anderen Angriffsszenarien (z. B. teilweise vertrauenswürdiger Code, der eine bestimmte Zeichenfolge enthält und diese anschließend deserialisiert).
Bestätigen Sie bei der Verwendung irgendwelcher Serialisierungskomponenten niemals im Voraus Berechtigungen, selbst wenn die gesamte Serialisierung innerhalb des Assertionsbereichs stattfindet und nur vertrauenswürdige Daten oder Objekte behandelt werden. Eine solche Vorgehensweise könnte zu Sicherheitslücken führen.
In Fällen, in denen teilweise vertrauenswürdiger Code entweder über Erweiterungspunkte (Ersatzzeichen), über zu serialisierende Typen oder über andere Mittel den Serialisierungsprozess bestimmt, kann dieser teilweise vertrauenswürdige Code bewirken, dass das Serialisierungsprogramm eine große Datenmenge in den serialisierten Stream ausgibt, was einen Denial-of-Service (DoS) beim Empfänger dieses Streams bewirken kann. Wenn Sie Daten serialisieren, die für ein Ziel bestimmt sind, das für DoS-Angriffe anfällig ist, dann serialisieren Sie keine teilweise vertrauenswürdigen Typen und lassen Sie nicht zu, dass teilweise vertrauenswürdiger Code die Serialisierung irgendwie steuert.
Im Allgemeinen gilt: Wenn Sie teilweise vertrauenswürdigen Code auf Ihre DataContractSerializer-Instanz zugreifen oder Datenvertrag-Ersatzzeichen auf andere Weise bestimmen lassen, kann er möglicherweise einen großen Teil des Serialisierungs-/Deserialisierungsprozesses steuern. Er kann beispielsweise beliebige Typen einfügen, was zur Offenlegung von Informationen führt, oder das sich ergebende Objektdiagramm und die serialisierten Daten manipulieren bzw. einen Überlauf des resultierenden serialisierten Streams herbeiführen. Eine entsprechende NetDataContractSerializer-Bedrohung wird im Abschnitt "Sicheres Verwenden von NetDataContractSerializer" beschrieben.
Wenn das DataContractAttribute-Attribut auf einen Typ angewendet wird (oder der Typ als
[Serializable]
gekennzeichnet wird, ohneISerializable
zu sein), kann das Deserialisierungsprogramm eine Instanz eines solchen Typs selbst dann erstellen, wenn alle Konstruktoren durch Aufrufe nicht öffentlich und/oder geschützt sind.Vertrauen Sie niemals dem Ergebnis der Deserialisierung, es sei denn , die Daten können als vertrauenswürdig eingestuft werden und Sie sind sicher, dass alle bekannten Typen vertrauenswürdige Typen sind. Beachten Sie, dass bei Ausführung unter teilweiser Vertrauenswürdigkeit bekannte Typen nicht aus der Anwendungskonfigurationsdatei, sondern aus der Computerkonfigurationsdatei geladen werden.
Wenn Sie eine DataContractSerializer-Instanz mit einem Ersatzselektor übergeben, der im teilweise vertrauenswürdigen Modus hinzugefügt wurde, kann der Code alle änderbaren Einstellungen dieses Ersatzselektors ändern.
Wenn ein XML-Reader (oder die enthaltenen Daten) auf teilweise vertrauenswürdigem Code basiert, dann betrachten Sie das resultierende deserialisierte Objekt als nicht vertrauenswürdige Daten.
Der Umstand, dass der ExtensionDataObject-Typ keine öffentlichen Member besitzt, bedeutet nicht, dass die darin enthaltenen Daten sicher sind. Wenn Sie z. B. eine Deserialisierung aus einer privilegierten Datenquelle in ein Objekt ausführen, in dem sich einige Daten befinden, und dieses Objekt anschließend an teilweise vertrauenswürdigen Code übergeben, kann dieser Code die Daten in ExtensionDataObject lesen, indem er das Objekt serialisiert. Legen Sie bei einer Deserialisierung aus einer privilegierten Datenquelle in ein Objekt, das später an teilweise vertrauenswürdigen Code übergeben wird, wenn möglich die IgnoreExtensionDataObject-Einstellung auf true fest.
DataContractSerializer und DataContractJsonSerializer unterstützen die Serialisierung von privaten, geschützten, internen und öffentlichen Membern mit vollständiger Vertrauenswürdigkeit. Bei teilweiser Vertrauenswürdigkeit können jedoch nur öffentliche Member serialisiert werden. Wenn eine Anwendung versucht, einen nicht öffentlichen Member zu serialisieren, wird eine SecurityException ausgelöst.
Verwenden Sie das System.Runtime.CompilerServices.InternalsVisibleTo-Assemblyattribut, um zuzulassen, dass interne oder geschützte interne Member serialisiert werden können. Dieses Attribut ermöglicht es einer Assembly zu deklarieren, dass eigene interne Member in anderen Assemblys sichtbar sein sollen. In diesem Fall deklariert eine Assembly, deren interne Member serialisiert werden sollen, dass ihre internen Member für System.Runtime.Serialization.dll sichtbar sein sollen.
Der Vorteil dieser Vorgehensweise besteht darin, dass kein Codegenerierungspfad mit erweiterten Berechtigungen erforderlich ist.
Gleichzeitig sind jedoch zwei gravierende Nachteile zu beachten.
Der erste Nachteil besteht darin, dass die Opt-In-Eigenschaft des InternalsVisibleTo-Attributs assemblyweit festgelegt ist. Das heißt, Sie können nicht angeben, dass nur die internen Member einer bestimmten Klasse serialisiert werden sollen. Natürlich können Sie immer noch festlegen, dass ein bestimmter interner Member nicht serialisiert werden soll, indem Sie dem betreffenden Member einfach kein DataMember-Attribut hinzufügen. Ebenso können Entwickler angeben, dass ein Member intern und nicht privat oder geschützt ist, wobei jedoch geringe Nachteile in Bezug auf die Sichtbarkeit zu erwarten sind.
Der zweite Nachteil besteht darin, dass nach wie vor keine privaten und geschützten Member unterstützt werden.
Betrachten Sie das folgende Programm, das die Verwendung des InternalsVisibleTo-Attributs bei teilweiser Vertrauenswürdigkeit veranschaulicht:
public class Program { public static void Main(string[] args) { try { // PermissionsHelper.InternetZone corresponds to the PermissionSet for partial trust. // PermissionsHelper.InternetZone.PermitOnly(); MemoryStream memoryStream = new MemoryStream(); new DataContractSerializer(typeof(DataNode)). WriteObject(memoryStream, new DataNode()); } finally { CodeAccessPermission.RevertPermitOnly(); } } [DataContract] public class DataNode { [DataMember] internal string Value = "Default"; } }
Im obigen Beispiel entspricht PermissionsHelper.InternetZone dem PermissionSet für teilweise Vertrauenswürdigkeit. Ohne InternalsVisibleToAttribute tritt nun ein Anwendungsfehler auf, wobei eine SecurityException ausgelöst wird, die angibt, dass nicht öffentliche Member mit teilweiser Vertrauenswürdigkeit nicht serialisiert werden können.
Wenn jedoch der Quelldatei die folgende Zeile hinzugefügt wird, wird das Programm erfolgreich ausgeführt.
[assembly:System.Runtime.CompilerServices.InternalsVisibleTo("System.Runtime.Serialization, PublicKey = 00000000000000000400000000000000")]
Weitere Überlegungen zur Zustandsverwaltung
Es gibt noch einige Überlegungen zur Verwaltung von Objektzuständen, die erwähnt werden sollten:
Bei der Verwendung eines auf Streams basierenden Programmiermodells mit einem Streamingtransport findet die Verarbeitung der Nachricht statt, sobald sie eingeht. Der Absender der Nachricht kann den Sendevorgang in der Mitte des Streams abbrechen, wodurch Ihr Code einen unvorhersehbaren Zustand annimmt, falls mehr Inhalt erwartet wurde. Verlassen Sie sich im Allgemeinen nicht darauf, dass Streams abgeschlossen werden, und führen Sie keine Arbeiten in einem streambasierten Vorgang aus, für die im Fall eines abgebrochenen Streams kein Rollback erfolgen kann. Das gilt auch für den Fall, dass eine Nachricht nach dem Streamingtext falsch formatiert ist (es fehlt z. B. ein Endtag für den SOAP-Umschlag oder es ist ein zweiter Nachrichtentext vorhanden).
Mit der IExtensibleDataObject-Funktion werden möglicherweise vertrauliche Daten ausgegeben. Wenn Sie Daten aus einer nicht vertrauenswürdigen Quelle in Datenverträgen mit IExtensibleObjectData zulassen und später über einen sicheren Kanal neu ausgeben, in dem Nachrichten signiert sind, bürgen Sie womöglich für völlig unbekannte Daten. Darüber hinaus sind vielleicht die gesendeten Daten insgesamt ungültig, wenn Sie sowohl die bekannten als auch die unbekannten Teile der Daten in Betracht ziehen. Vermeiden Sie diese Situation, indem Sie entweder die Eigenschaft der Erweiterungsdaten selektiv auf NULL festlegen oder indem Sie selektiv die IExtensibleObjectData-Funktion deaktivieren.
Schemaimport
Normalerweise wird ein Schema zum Generieren von Typen nur zur Entwurfszeit importiert, beispielsweise, wenn Sie mit dem ServiceModel Metadata Utility-Tool (Svcutil.exe) eine Clientklasse für einen Webdienst generieren. In komplexeren Szenarien jedoch verarbeiten Sie ein Schema vielleicht auch zur Laufzeit. Seien Sie sich jedoch bewusst, dass dabei das Risiko von Denial-of-Service-Angriffen besteht. Bei einigen Schemas dauert das Importieren sehr lange. Verwenden Sie in solchen Szenarien niemals die Schemaimportkomponente von XmlSerializer, falls Schemas von einer nicht vertrauenswürdigen Quelle stammen könnten.
Spezielle Bedrohungen bei der ASP.NET AJAX-Integration
Wenn der Benutzer WebScriptEnablingBehavior oder WebHttpBehavior implementiert, macht WCF einen Endpunkt verfügbar, der sowohl XML- als auch JSON-Nachrichten akzeptieren kann. Es gibt jedoch nur einen Satz von Readerkontingenten, der sowohl vom XML-Reader als auch von JSON-Reader verwendet wird. Einige Kontingenteinstellungen sind möglicherweise für einen Reader geeignet, aber zu groß für den anderen.
Mit der Implementierung von WebScriptEnablingBehavior hat der Benutzer die Möglichkeit, beim Endpunkt einen JavaScript-Proxy verfügbar zu machen. Die folgenden Sicherheitsprobleme müssen beachtet werden:
Informationen über den Dienst (Vorgangsnamen, Parameternamen usw.) können abgerufen werden, indem der JavaScript-Proxy überprüft wird.
Wird der JavaScript-Endpunkt verwendet, bleiben möglicherweise vertrauliche und private Informationen im Webbrowsercache des Clients gespeichert.
Hinweis zu Komponenten
WCF ist ein flexibles, anpassbares System. Die meisten Ausführungen dieses Themas konzentrieren sich auf die häufigsten Anwendungszenarien in WCF. Die von WCF bereitgestellten Komponenten können jedoch sehr unterschiedlich eingesetzt werden. Deshalb sollten Sie wissen, welche Auswirkung die Verwendung der einzelnen Komponenten auf die Sicherheit hat. Das bezieht sich insbesondere auf folgende Punkte:
Wenn XML-Reader notwendig sind, verwenden Sie nur die Reader der XmlDictionaryReader-Klasse. Sichere Reader werden mit den Methoden CreateTextReader, CreateBinaryReader oder CreateMtomReader erstellt. Verwenden Sie nicht die Create-Methode. Konfigurieren Sie die Reader immer mit sicheren Kontingenten. Die Serialisierungsmodule in WCF sind nur bei einer Verwendung mit sicheren XML-Readern von WCF sicher.
Wenn Sie potenziell nicht vertrauenswürdige Daten mit DataContractSerializer deserialisieren, legen Sie immer die MaxItemsInObjectGraph-Eigenschaft fest.
Legen Sie beim Erstellen einer Nachricht den maxSizeOfHeaders -Parameter fest, falls MaxReceivedMessageSize keinen ausreichenden Schutz bietet.
Wenn Sie einen Encoder erstellen, konfigurieren Sie stets die relevanten Kontingente, z. B. MaxSessionSize und MaxBufferSize.
Legen Sie bei Verwendung eines XPath-Nachrichtenfilters NodeQuota fest, um die Menge der XML-Knoten einzuschränken, die der Filter durchläuft. Verwenden Sie keine XPath-Ausdrücke, deren Berechnung lange dauert, ohne dass viele Knoten durchlaufen werden.
Machen Sie sich generell bei der Verwendung einer Komponente, die ein Kontingent akzeptiert, mit dessen Auswirkung auf die Sicherheit vertraut, und legen Sie einen sicheren Wert dafür fest.
Siehe auch
Verweis
DataContractSerializer
XmlDictionaryReader
XmlSerializer