Säkerhetsöverväganden för data
När du hanterar data i Windows Communication Foundation (WCF) måste du överväga ett antal hotkategorier. I följande lista visas de viktigaste hotklasserna som är relaterade till databehandling. WCF tillhandahåller verktyg för att minimera dessa hot.
Denial of Service
När du tar emot ej betrodda data kan data få den mottagande sidan att komma åt en oproportionerlig mängd olika resurser, till exempel minne, trådar, tillgängliga anslutningar eller processorcykler genom att orsaka långa beräkningar. En överbelastningsattack mot en server kan leda till att den kraschar och inte kan bearbeta meddelanden från andra legitima klienter.
Körning av skadlig kod
Inkommande data som inte är betrodda gör att den mottagande sidan kör kod som den inte hade för avsikt att göra.
Avslöjande av information
Fjärranfallaren tvingar den mottagande parten att svara på sina begäranden på ett sådant sätt att mer information än den avser att lämna ut.
Säkerhet för kod och kod som tillhandahålls av användaren
Ett antal platser i WCF-infrastrukturen (Windows Communication Foundation) kör kod som tillhandahålls av användaren. Serialiseringsmotorn DataContractSerializer kan till exempel anropa egenskapsåtkomster set
och get
-åtkomstorer som tillhandahålls av användaren. WCF-kanalinfrastrukturen kan också anropa till härledda klasser som tillhandahålls av användaren i Message klassen.
Det är kodförfattarens ansvar att se till att det inte finns några säkerhetsrisker. Om du till exempel skapar en datakontraktstyp med en datamedlemsegenskap av typen heltal, och i set
implementeringen av accessor allokerar en matris baserat på egenskapsvärdet, exponerar du risken för en överbelastningsattack om ett skadligt meddelande innehåller ett extremt stort värde för den här datamedlemmen. Undvik i allmänhet allokeringar baserat på inkommande data eller lång bearbetning i kod som tillhandahålls av användaren (särskilt om lång bearbetning kan orsakas av en liten mängd inkommande data). När du utför säkerhetsanalys av kod som tillhandahålls av användaren bör du även överväga alla felfall (det vill: alla kodgrenar där undantag genereras).
Det ultimata exemplet på kod som tillhandahålls av användaren är koden i din tjänstimplementering för varje åtgärd. Säkerheten för din tjänstimplementering är ditt ansvar. Det är enkelt att oavsiktligt skapa osäkra åtgärdsimplementeringar som kan leda till sårbarheter i överbelastning. Till exempel en åtgärd som tar en sträng och returnerar listan över kunder från en databas vars namn börjar med strängen. Om du arbetar med en stor databas och strängen som skickas bara är en enda bokstav kan koden försöka skapa ett meddelande som är större än allt tillgängligt minne, vilket gör att hela tjänsten misslyckas. (En OutOfMemoryException kan inte återställas i .NET Framework och resulterar alltid i att programmet avslutas.)
Du bör se till att ingen skadlig kod är ansluten till de olika utökningspunkterna. Detta är särskilt relevant när du kör under partiellt förtroende, hanterar typer från delvis betrodda sammansättningar eller skapar komponenter som kan användas med delvis betrodd kod. Mer information finns i "Partiella förtroendehot" i ett senare avsnitt.
Observera att när den körs i partiellt förtroende stöder datakontraktets serialiseringsinfrastruktur endast en begränsad delmängd av programmeringsmodellen för datakontrakt – till exempel stöds inte privata datamedlemmar eller typer som använder SerializableAttribute attributet. Mer information finns i Partiellt förtroende.
Kommentar
Code Access Security (CAS) har föråldrats i alla versioner av .NET Framework och .NET. De senaste versionerna av .NET följer inte CAS-anteckningar och skapar fel om CAS-relaterade API:er används. Utvecklare bör söka alternativa sätt att utföra säkerhetsuppgifter.
Undvika oavsiktligt avslöjande av information
När du utformar serialiserbara typer med säkerhet i åtanke är avslöjande av information ett möjligt problem.
Tänk också på följande faktorer:
Programmeringsmodellen DataContractSerializer tillåter exponering av privata och interna data utanför typen eller sammansättningen under serialiseringen. Dessutom kan formen på en typ exponeras under schemaexporten. Var noga med att förstå typens serialiseringsprojektion. Om du inte vill att något ska exponeras inaktiverar du serialisering av det (till exempel genom att inte använda DataMemberAttribute attributet i händelse av ett datakontrakt).
Tänk på att samma typ kan ha flera serialiseringsprojektioner, beroende på vilken serialiserare som används. Samma typ kan exponera en uppsättning data när de DataContractSerializer används med och en annan uppsättning data när de XmlSerializeranvänds med . Om du oavsiktligt använder fel serialiserare kan det leda till att information avslöjas.
XmlSerializer Om du använder I RPC (Legacy Remote Procedure Call)/kodat läge kan det oavsiktligt exponera formen på objektdiagrammet på den sändande sidan till mottagarsidan.
Förhindra Överbelastningsattacker
Säljbudgetar
Att orsaka att den mottagande sidan allokerar en betydande mängd minne är en potentiell överbelastningsattack. Även om det här avsnittet fokuserar på minnesförbrukningsproblem som uppstår vid stora meddelanden, kan andra attacker inträffa. Meddelanden kan till exempel använda en oproportionerlig bearbetningstid.
Överbelastningsattacker minimeras vanligtvis med hjälp av kvoter. När en kvot överskrids genereras normalt ett QuotaExceededException undantag. Utan kvoten kan ett skadligt meddelande göra att allt tillgängligt minne nås, vilket resulterar i ett OutOfMemoryException undantag eller att alla tillgängliga staplar nås, vilket resulterar i en StackOverflowException.
Scenariot med överskriden kvot kan återställas. Om det påträffas i en tjänst som körs ignoreras meddelandet som bearbetas för närvarande och tjänsten fortsätter att köras och bearbetar ytterligare meddelanden. Scenarierna för slut på minne och stackspill kan dock inte återställas någonstans i .NET Framework. tjänsten avslutas om den stöter på sådana undantag.
Kvoter i WCF omfattar ingen förallokering. Om MaxReceivedMessageSize kvoten (som finns i olika klasser) till exempel är inställd på 128 KB betyder det inte att 128 KB allokeras automatiskt för varje meddelande. Det faktiska allokerade beloppet beror på den faktiska storleken på inkommande meddelanden.
Många kvoter är tillgängliga på transportlagret. Det här är kvoter som tillämpas av den specifika transportkanal som används (HTTP, TCP och så vidare). Även om det här avsnittet beskriver några av dessa kvoter beskrivs dessa kvoter i detalj i Transportkvoter.
Hashtable Vulnerability
Det finns en säkerhetsrisk när datakontrakt innehåller hashtables eller samlingar. Problemet uppstår om ett stort antal värden infogas i en hashtable där ett stort antal av dessa värden genererar samma hash-värde. Detta kan användas som en DOS-attack. Den här sårbarheten kan åtgärdas genom att ange bindningskvoten MaxReceivedMessageSize. Du måste vara försiktig när du ställer in den här kvoten för att förhindra sådana attacker. Den här kvoten sätter en övre gräns för storleken på WCF-meddelande. Undvik dessutom att använda hashtables eller samlingar i dina datakontrakt.
Begränsa minnesförbrukning utan direktuppspelning
Säkerhetsmodellen kring stora meddelanden beror på om strömning används. I det grundläggande, icke-strömmade fallet buffrade meddelanden i minnet. I det här fallet använder du MaxReceivedMessageSize kvoten på TransportBindingElement bindningarna eller på de bindningar som tillhandahålls av systemet för att skydda mot stora meddelanden genom att begränsa den maximala meddelandestorleken för åtkomst. Observera att en tjänst kan bearbeta flera meddelanden samtidigt, i vilket fall alla finns i minnet. Använd begränsningsfunktionen för att minimera det här hotet.
Observera också att MaxReceivedMessageSize
inte placerar en övre gräns för minnesförbrukning per meddelande, utan begränsar den till inom en konstant faktor. Om till exempel MaxReceivedMessageSize
är 1 MB och ett meddelande på 1 MB tas emot och sedan deserialiseras, krävs ytterligare minne för att innehålla diagram över deserialiserade objekt, vilket resulterar i total minnesförbrukning långt över 1 MB. Undvik därför att skapa serialiserbara typer som kan leda till betydande minnesförbrukning utan mycket inkommande data. Till exempel kan ett datakontrakt "MyContract" med 50 valfria datamedlemsfält och ytterligare 100 privata fält instansieras med XML-konstruktionen "<MyContract/>". Den här XML-koden resulterar i att minnet används för 150 fält. Observera att datamedlemmar är valfria som standard. Problemet förvärras när en sådan typ är en del av en matris.
MaxReceivedMessageSize
ensamt räcker inte för att förhindra alla överbelastningsattacker. Till exempel kan deserialiseraren tvingas att deserialisera ett djupt kapslat objektdiagram (ett objekt som innehåller ett annat objekt som innehåller ännu ett och så vidare) av ett inkommande meddelande. Både anropsmetoderna DataContractSerializerXmlSerializer och på ett kapslat sätt för att deserialisera sådana grafer. Djup kapsling av metodanrop kan resultera i en oåterkallelig StackOverflowException. Det här hotet minimeras genom att kvoten anges MaxDepth för att begränsa xml-kapslingsnivån, enligt beskrivningen i avsnittet "Använda XML Valv ly" senare i ämnet.
Det är särskilt viktigt att ange ytterligare kvoter när MaxReceivedMessageSize
du använder binär XML-kodning. Användning av binär kodning motsvarar något komprimering: en liten grupp byte i det inkommande meddelandet kan representera mycket data. Därför kan även ett meddelande som passar in i MaxReceivedMessageSize
gränsen ta upp mycket mer minne i fullständigt expanderad form. För att minimera sådana XML-specifika hot måste alla XML-läsarkvoter anges korrekt, enligt beskrivningen i avsnittet "Använda XML Valv ly" senare i det här avsnittet.
Begränsa minnesförbrukning med direktuppspelning
När du strömmar kan du använda en liten MaxReceivedMessageSize
inställning för att skydda mot överbelastningsattacker. Mer komplicerade scenarier är dock möjliga med strömning. En filuppladdningstjänst accepterar till exempel filer som är större än allt tillgängligt minne. I det här fallet anger du MaxReceivedMessageSize
till ett extremt stort värde och förväntar sig att nästan inga data buffras i minnet och att meddelandet strömmar direkt till disken. Om ett skadligt meddelande på något sätt kan tvinga WCF att buffras data i stället för att strömma dem i det här fallet, MaxReceivedMessageSize
skyddas inte längre mot meddelandet som kommer åt allt tillgängligt minne.
För att minimera det här hotet finns det specifika kvotinställningar för olika WCF-databearbetningskomponenter som begränsar buffring. Det viktigaste av dessa är MaxBufferSize
egenskapen för olika transportbindningselement och standardbindningar. Vid direktuppspelning bör den här kvoten anges med hänsyn till den maximala mängd minne som du är villig att allokera per meddelande. Precis som med MaxReceivedMessageSize
anger inställningen inte ett absolut maxvärde för minnesförbrukning utan begränsar den bara till inom en konstant faktor. Var också medveten om möjligheten att flera meddelanden bearbetas samtidigt, precis som med MaxReceivedMessageSize
.
MaxBufferSize-information
Egenskapen MaxBufferSize
begränsar alla massbuffertande WCF gör. Till exempel buffrar WCF alltid SOAP-huvuden och SOAP-fel, samt eventuella MIME-delar som inte finns i den naturliga läsordningen i ett MTOM-meddelande (Message Transmission Optimization Mechanism). Den här inställningen begränsar mängden buffring i alla dessa fall.
WCF åstadkommer detta genom att skicka MaxBufferSize
värdet till de olika komponenter som kan buffras. Vissa överlagringar av Message klassen tar till exempel CreateMessage en maxSizeOfHeaders
parameter. WCF skickar värdet till den MaxBufferSize
här parametern för att begränsa mängden SOAP-sidhuvudbuffertning. Det är viktigt att ange den här parametern när du Message använder klassen direkt. När du använder en komponent i WCF som tar kvotparametrar är det i allmänhet viktigt att förstå säkerhetskonsekvenserna av dessa parametrar och ställa in dem korrekt.
MTOM-meddelandekodaren har också en MaxBufferSize
inställning. När du använder standardbindningar anges detta automatiskt till värdet på transportnivå MaxBufferSize
. Men när du använder bindningselementet för MTOM-meddelandekodaren för att konstruera en anpassad bindning är det viktigt att ange MaxBufferSize
egenskapen till ett säkert värde när direktuppspelning används.
XML-baserade direktuppspelningsattacker
MaxBufferSize
ensamt räcker inte för att säkerställa att WCF inte kan tvingas till buffring när strömning förväntas. Till exempel buffar WCF XML-läsare alltid hela XML-elementets starttagg när du börjar läsa ett nytt element. Detta görs så att namnrymder och attribut bearbetas korrekt. Om MaxReceivedMessageSize
har konfigurerats för att vara stor (till exempel för att aktivera ett direkt-till-disk-scenario för stor filströmning) kan ett skadligt meddelande skapas där hela meddelandetexten är en stor START-tagg för XML-element. Ett försök att läsa det resulterar i en OutOfMemoryException. Detta är en av många möjliga XML-baserade överbelastningsattacker som alla kan minimeras med hjälp av XML-läsarkvoter, som beskrivs i avsnittet "Använda XML Valv ly" senare i det här avsnittet. När du strömmar är det särskilt viktigt att ange alla dessa kvoter.
Blanda programmeringsmodeller för strömning och buffring
Många möjliga attacker uppstår vid blandning av programmeringsmodeller för direktuppspelning och icke-direktuppspelning i samma tjänst. Anta att det finns ett tjänstkontrakt med två åtgärder: en tar en och en Stream annan tar en matris av någon anpassad typ. Anta också att MaxReceivedMessageSize
är inställt på ett stort värde för att aktivera den första åtgärden för att bearbeta stora strömmar. Tyvärr innebär det att stora meddelanden nu också kan skickas till den andra åtgärden, och deserialiseraren buffrar data i minnet som en matris innan åtgärden anropas. Detta är en potentiell överbelastningsattack: MaxBufferSize
kvoten begränsar inte storleken på meddelandetexten, vilket är vad deserialiseraren fungerar med.
Undvik därför att blanda strömbaserade och icke-strömmade åtgärder i samma kontrakt. Om du absolut måste blanda de två programmeringsmodellerna använder du följande försiktighetsåtgärder:
Inaktivera funktionen IExtensibleDataObject genom att ange IgnoreExtensionDataObject egenskapen för ServiceBehaviorAttribute till
true
. Detta säkerställer att endast medlemmar som ingår i kontraktet deserialiseras.MaxItemsInObjectGraph Ange egenskapen för DataContractSerializer värdet till ett säkert värde. Den här kvoten är också tillgänglig för ServiceBehaviorAttribute attributet eller via konfigurationen. Den här kvoten begränsar antalet objekt som deserialiseras i ett deserialiseringsavsnitt. Normalt deserialiseras varje åtgärdsparameter eller meddelandetextdel i ett meddelandekontrakt i ett avsnitt. När deserialisera matriser räknas varje matrispost som ett separat objekt.
Ange alla XML-läsarkvoter till säkra värden. Var uppmärksam MaxDepthpå , MaxStringContentLengthoch och MaxArrayLength undvik strängar i icke-strömningsåtgärder.
Granska listan över kända typer med tanke på att någon av dem kan instansieras när som helst (se avsnittet "Förhindra oavsiktliga typer från att läsas in" senare i det här avsnittet).
Använd inte några typer som implementerar gränssnittet IXmlSerializable som buffr mycket data. Lägg inte till sådana typer i listan över kända typer.
Använd inte de XmlElement, XmlNode matriser, Byte matriser eller typer som implementeras ISerializable i ett kontrakt.
Använd inte de XmlElement, XmlNode matriser, Byte matriser eller typer som implementeras ISerializable i listan över kända typer.
Föregående försiktighetsåtgärder gäller när den icke-strömmade åtgärden använder DataContractSerializer. Blanda aldrig programmeringsmodeller för direktuppspelning och icke-direktuppspelning på samma tjänst om du använder XmlSerializer, eftersom den inte har kvotens MaxItemsInObjectGraph skydd.
Slow Stream-attacker
En klass av överbelastningsattacker för direktuppspelning omfattar inte minnesförbrukning. I stället innebär attacken en långsam avsändare eller mottagare av data. I väntan på att data ska skickas eller tas emot är resurser som trådar och tillgängliga anslutningar uttömda. Den här situationen kan uppstå antingen till följd av en skadlig attack eller från en legitim avsändare/mottagare på en långsam nätverksanslutning.
För att minimera dessa attacker anger du tidsgränserna för transporten korrekt. Mer information finns i Transportkvoter. För det andra använder du aldrig synkrona Read
åtgärder eller Write
åtgärder när du arbetar med strömmar i WCF.
Använda XML Valv ly
Kommentar
Även om det här avsnittet handlar om XML gäller informationen även för JSON-dokument (JavaScript Object Notation). Kvoterna fungerar på samma sätt med mappning mellan JSON och XML.
Skydda XML-läsare
XML-informationsuppsättningen utgör grunden för all meddelandebearbetning i WCF. När du tar emot XML-data från en ej betrodd källa finns det ett antal överbelastningsattacker som måste minimeras. WCF tillhandahåller särskilda, säkra XML-läsare. Dessa läsare skapas automatiskt när du använder någon av standardkodningarna i WCF (text, binär eller MTOM).
Vissa av säkerhetsfunktionerna på dessa läsare är alltid aktiva. Läsarna bearbetar till exempel aldrig dokumenttypsdefinitioner (DTD), som är en potentiell källa till överbelastningsattacker och aldrig bör visas i legitima SOAP-meddelanden. Andra säkerhetsfunktioner är läsarkvoter som måste konfigureras, vilket beskrivs i följande avsnitt.
När du arbetar direkt med XML-läsare (till exempel när du skriver en egen anpassad kodare eller när du arbetar direkt med Message klassen) ska du alltid använda WCF-säkra läsare när det finns en chans att arbeta med data som inte är betrodda. Skapa säkra läsare genom att anropa en av de statiska fabriksmetodens överlagringar av CreateTextReader, CreateBinaryReadereller CreateMtomReader på XmlDictionaryReader klassen. När du skapar en läsare skickar du in säkra kvotvärden. Anropa inte metodens Create
överlagringar. Dessa skapar inte en WCF-läsare. I stället skapas en läsare som inte skyddas av de säkerhetsfunktioner som beskrivs i det här avsnittet.
Läsarkvoter
De säkra XML-läsarna har fem konfigurerbara kvoter. Dessa konfigureras normalt med egenskapen ReaderQuotas
på kodningsbindningselementen eller standardbindningarna, eller genom att använda ett XmlDictionaryReaderQuotas objekt som skickas när en läsare skapas.
MaxBytesPerRead
Den här kvoten begränsar antalet byte som läss i en enda Read
åtgärd när du läser elementets starttagg och dess attribut. (I icke-strömmade fall räknas inte själva elementnamnet mot kvoten.) MaxBytesPerRead är viktigt av följande skäl:
Elementnamnet och dess attribut buffrar alltid i minnet när de läss. Därför är det viktigt att ange den här kvoten korrekt i strömningsläge för att förhindra överdriven buffring när strömning förväntas.
MaxDepth
Se avsnittet kvot för information om den faktiska mängden buffring som äger rum.Om du har för många XML-attribut kan det ta upp oproportionerlig bearbetningstid eftersom attributnamn måste kontrolleras för unikhet.
MaxBytesPerRead
minimerar det här hotet.
MaxDepth
Den här kvoten begränsar det maximala kapslingsdjupet för XML-element. Dokumentet "<A><B><C//><B></A>" har till exempel ett kapslingsdjup på tre. MaxDepth är viktigt av följande skäl:
MaxDepth
interagerar medMaxBytesPerRead
: läsaren behåller alltid data i minnet för det aktuella elementet och alla dess överordnade element, så den maximala minnesförbrukningen för läsaren är proportionell mot produkten av dessa två inställningar.När du deserialiserar ett djupt kapslat objektdiagram tvingas deserialiseraren att komma åt hela stacken och utlösa en oåterkallelig StackOverflowException. Det finns en direkt korrelation mellan XML-kapsling och objektkapsling för både DataContractSerializer och XmlSerializer. Använd
MaxDepth
för att minimera det här hotet.
MaxNameTableCharCount
Den här kvoten begränsar storleken på läsarens namntabell. Namntabellen innehåller vissa strängar (till exempel namnområden och prefix) som påträffas när ett XML-dokument bearbetas. Eftersom dessa strängar buffras i minnet anger du den här kvoten för att förhindra överdriven buffring när strömning förväntas.
MaxStringContentLength
Den här kvoten begränsar den maximala strängstorlek som XML-läsaren returnerar. Den här kvoten begränsar inte minnesförbrukningen i själva XML-läsaren, utan i komponenten som använder läsaren. Till exempel när DataContractSerializer använder en läsare som skyddas med MaxStringContentLength, deserialisera inte strängar som är större än den här kvoten. När du använder XmlDictionaryReader klassen direkt respekterar inte alla metoder den här kvoten, utan endast de metoder som är särskilt utformade för att läsa strängar, till exempel ReadContentAsString metoden. Egenskapen Value på läsaren påverkas inte av den här kvoten och bör därför inte användas när det skydd som den här kvoten ger är nödvändigt.
MaxArrayLength
Den här kvoten begränsar den maximala storleken på en matris med primitiver som XML-läsaren returnerar, inklusive bytematriser. Den här kvoten begränsar inte minnesförbrukningen i själva XML-läsaren, utan i den komponent som använder läsaren. När en läsare som skyddas med MaxArrayLengthanvänds till exempel DataContractSerializer inte deserialisera bytematriser som är större än den här kvoten. Det är viktigt att ange den här kvoten när du försöker blanda strömmande och buffrade programmeringsmodeller i ett enda kontrakt. Tänk på att när du använder XmlDictionaryReader klassen direkt gäller endast de metoder som är särskilt utformade för att läsa matriser av godtycklig storlek för vissa primitiva typer, till exempel ReadInt32Array, den här kvoten.
Hot som är specifika för binär kodning
Den binära XML-kodningen som WCF stöder innehåller en funktion för ordlistesträngar . En stor sträng kan kodas med bara några få byte. Detta möjliggör betydande prestandavinster, men introducerar nya överbelastningshot som måste minimeras.
Det finns två typer av ordlistor: statiska och dynamiska. Den statiska ordlistan är en inbyggd lista över långa strängar som kan representeras med hjälp av en kort kod i den binära kodningen. Den här listan med strängar korrigeras när läsaren skapas och kan inte ändras. Ingen av strängarna i den statiska ordlistan som WCF använder som standard är tillräckligt stora för att utgöra ett allvarligt överbelastningshot, även om de fortfarande kan användas i en ordlisteexpansionsattack. I avancerade scenarier där du anger en egen statisk ordlista bör du vara försiktig när du introducerar stora ordlistesträngar.
Med funktionen dynamiska ordlistor kan meddelanden definiera sina egna strängar och associera dem med korta koder. Dessa sträng-till-kod-mappningar sparas i minnet under hela kommunikationssessionen, så att efterföljande meddelanden inte behöver skicka om strängarna och kan använda koder som redan har definierats. Dessa strängar kan vara godtyckliga och utgör därför ett allvarligare hot än de i den statiska ordlistan.
Det första hotet som måste åtgärdas är möjligheten att den dynamiska ordlistan (mappningstabellen för sträng-till-kod) blir för stor. Den här ordlistan kan utökas under flera meddelanden, så kvoten ger inget skydd eftersom den MaxReceivedMessageSize
endast gäller för varje meddelande separat. Därför finns det en separat MaxSessionSize egenskap på BinaryMessageEncodingBindingElement som begränsar ordlistans storlek.
Till skillnad från de flesta andra kvoter gäller även den här kvoten när du skriver meddelanden. Om det överskrids när du läser ett meddelande genereras det QuotaExceededException
som vanligt. Om det överskrids när du skriver ett meddelande skrivs alla strängar som gör att kvoten överskrids som den är, utan att funktionen dynamiska ordlistor används.
Ordlisteutökningshot
En betydande klass av binärspecifika attacker uppstår vid ordlisteexpansion. Ett litet meddelande i binär form kan förvandlas till ett mycket stort meddelande i fullständigt expanderad textform om det använder strängordlistor i stor utsträckning. Expansionsfaktorn för dynamiska ordlistesträngar begränsas av MaxSessionSize kvoten eftersom ingen dynamisk ordlistesträng överskrider den maximala storleken för hela ordlistan.
Egenskaperna MaxNameTableCharCount, MaxStringContentLength
och MaxArrayLength
begränsar bara minnesförbrukningen. De krävs normalt inte för att minimera eventuella hot i den icke-strömmade användningen eftersom minnesanvändningen redan är begränsad av MaxReceivedMessageSize
. Räknar dock MaxReceivedMessageSize
byte före expansion. När binär kodning används kan minnesförbrukningen potentiellt gå längre än MaxReceivedMessageSize
, begränsas endast av en faktor av MaxSessionSize. Därför är det viktigt att alltid ange alla läsarkvoter (särskilt MaxStringContentLength) när du använder den binära kodningen.
När du använder binär kodning tillsammans med DataContractSerializerkan IExtensibleDataObject
gränssnittet missbrukas för att montera en ordlisteexpansionsattack. Det här gränssnittet ger i princip obegränsad lagring för godtyckliga data som inte ingår i kontraktet. Om kvoterna inte kan anges tillräckligt lågt, så att multiplicerat MaxSessionSize
MaxReceivedMessageSize
med inte utgör något problem, inaktiverar du IExtensibleDataObject
funktionen när du använder den binära kodningen. Ange egenskapen IgnoreExtensionDataObject
till true
för attributet ServiceBehaviorAttribute
. Du kan också inte implementera IExtensibleDataObject
gränssnittet. Mer information finns i Framåtkompatibla datakontrakt.
Sammanfattning av kvoter
I följande tabell sammanfattas vägledningen om kvoter.
Villkor | Viktiga kvoter att ange |
---|---|
Ingen direktuppspelning eller direktuppspelning av små meddelanden, text eller MTOM-kodning | MaxReceivedMessageSize , MaxBytesPerRead , och MaxDepth |
Ingen direktuppspelning eller direktuppspelning av små meddelanden, binär kodning | MaxReceivedMessageSize , MaxSessionSize och alla ReaderQuotas |
Strömma stora meddelanden, text eller MTOM-kodning | MaxBufferSize och alla ReaderQuotas |
Strömma stora meddelanden, binär kodning | MaxBufferSize , MaxSessionSize och alla ReaderQuotas |
Tidsgränser på transportnivå måste alltid anges och använd aldrig synkrona läsningar/skrivningar när strömning används, oavsett om du strömmar stora eller små meddelanden.
När du är osäker på en kvot anger du den till ett säkert värde i stället för att lämna den öppen.
Förhindra körning av skadlig kod
Följande allmänna hotklasser kan köra kod och få oavsiktliga effekter:
Deserialiseraren läser in en skadlig, osäker eller säkerhetskänslig typ.
Ett inkommande meddelande gör att deserialiseraren konstruerar en instans av en normalt säker typ på ett sådant sätt att den får oavsiktliga konsekvenser.
I följande avsnitt beskrivs de här hotklasserna ytterligare.
DataContractSerializer
(Säkerhetsinformation om finns i XmlSerializerrelevant dokumentation.) Säkerhetsmodellen för XmlSerializer liknar DataContractSerializerden för , och skiljer sig främst i information. Attributet används till exempel XmlIncludeAttribute för typinkludering i stället för attributet KnownTypeAttribute . Vissa hot som är unika för XmlSerializer beskrivs dock senare i det här avsnittet.
Förhindra att oavsiktliga typer läses in
Inläsning av oavsiktliga typer kan få betydande konsekvenser, oavsett om typen är skadlig eller bara har säkerhetskänsliga biverkningar. En typ kan innehålla sårbarheter som kan utnyttjas, utföra säkerhetskänsliga åtgärder i konstruktorn eller klasskonstruktorn, ha ett stort minnesavtryck som underlättar överbelastningsattacker eller kan utlösa undantag som inte kan återställas. Typer kan ha klasskonstruktorer som körs så snart typen har lästs in och innan några instanser skapas. Därför är det viktigt att kontrollera vilken uppsättning typer som deserialiseraren kan läsa in.
Deserialiserar DataContractSerializer på ett löst kopplat sätt. Den läser aldrig CLR-typ (Common Language Runtime) och sammansättningsnamn från inkommande data. Detta liknar beteendet för XmlSerializer, men skiljer sig från beteendet för NetDataContractSerializer, BinaryFormatteroch SoapFormatter. Lös koppling ger en viss säkerhetsgrad, eftersom fjärranfallaren inte kan ange en godtycklig typ att läsa in bara genom att namnge den typen i meddelandet.
DataContractSerializer Tillåts alltid att läsa in en typ som för närvarande förväntas enligt kontraktet. Om ett datakontrakt till exempel har en datamedlem av typen Customer
DataContractSerializer kan du läsa in Customer
typen när den deserialiserar den här datamedlemmen.
Dessutom DataContractSerializer stöder polymorfism. En datamedlem kan deklareras som Object, men inkommande data kan innehålla en Customer
instans. Detta är endast möjligt om Customer
typen har gjorts "känd" för deserialiseraren genom någon av dessa mekanismer:
KnownTypeAttribute -attribut som tillämpas på en typ.
KnownTypeAttribute
attribut som anger en metod som returnerar en lista med typer.ServiceKnownTypeAttribute
attribut.Konfigurationsavsnittet
KnownTypes
.En lista över kända typer som uttryckligen skickas DataContractSerializer till under konstruktionen, om du använder serialiseraren direkt.
Var och en av dessa mekanismer ökar ytan genom att införa fler typer som deserialiseraren kan läsa in. Kontrollera var och en av dessa mekanismer för att säkerställa att inga skadliga eller oavsiktliga typer läggs till i listan över kända typer.
När en känd typ finns i omfånget kan den läsas in när som helst och instanser av typen kan skapas, även om kontraktet förbjuder att det faktiskt används. Anta till exempel att typen "MyDangerousType" läggs till i listan över kända typer med någon av mekanismerna ovan. Detta innebär att:
MyDangerousType
läses in och dess klasskonstruktor körs.Även när du deserialiserar ett datakontrakt med en strängdatamedlem kan ett skadligt meddelande fortfarande orsaka att en instans skapas
MyDangerousType
. Kod iMyDangerousType
, till exempel egenskapsuppsättningar, kan köras. När detta är klart försöker deserialiseraren tilldela den här instansen till strängdatamedlemmen och misslyckas med ett undantag.
När du skriver en metod som returnerar en lista över kända typer, eller när du skickar en lista direkt till DataContractSerializer konstruktorn, kontrollerar du att koden som förbereder listan är säker och endast fungerar på betrodda data.
Om du anger kända typer i konfigurationen kontrollerar du att konfigurationsfilen är säker. Använd alltid starka namn i konfigurationen (genom att ange den offentliga nyckeln för den signerade sammansättningen där typen finns), men ange inte vilken version av typen som ska läsas in. Typinläsaren väljer automatiskt den senaste versionen, om möjligt. Om du anger en viss version i konfigurationen löper du följande risk: En typ kan ha en säkerhetsrisk som kan åtgärdas i en framtida version, men den sårbara versionen läses fortfarande in eftersom den uttryckligen anges i konfigurationen.
Att ha för många kända typer har en annan konsekvens: Skapar DataContractSerializer en cache med serialiserings-/deserialiseringskod i programdomänen, med en post för varje typ som den måste serialisera och deserialisera. Cacheminnet rensas aldrig så länge programdomänen körs. Därför kan en angripare som är medveten om att ett program använder många kända typer orsaka deserialisering av alla dessa typer, vilket gör att cacheminnet förbrukar en oproportionerligt stor mängd minne.
Förhindra att typer är i ett oavsiktligt tillstånd
En typ kan ha interna konsekvensbegränsningar som måste tillämpas. Var noga med att undvika att dessa begränsningar bryts under deserialiseringen.
Följande exempel på en typ representerar tillståndet för ett luftsluss på en rymdfarkost och framtvingar begränsningen att både de inre och de yttre dörrarna inte kan öppnas samtidigt.
[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;
}
}
}
<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
En angripare kan skicka ett skadligt meddelande som detta, komma runt begränsningarna och få objektet i ett ogiltigt tillstånd, vilket kan få oavsiktliga och oförutsägbara konsekvenser.
<SpaceStationAirlock>
<innerDoorOpen>true</innerDoorOpen>
<outerDoorOpen>true</outerDoorOpen>
</SpaceStationAirlock>
Du kan undvika den här situationen genom att vara medveten om följande:
När deserialiserar DataContractSerializer de flesta klasser körs inte konstruktorer. Förlita dig därför inte på någon tillståndshantering som utförs i konstruktorn.
Använd återanrop för att säkerställa att objektet är i ett giltigt tillstånd. Återanropet OnDeserializedAttribute som har markerats med attributet är särskilt användbart eftersom det körs när deserialiseringen är klar och har en chans att undersöka och korrigera det övergripande tillståndet. Mer information finns i Versionstoleranta serialiseringsåteranrop.
Utforma inte datakontraktstyper för att förlita dig på någon särskild ordning i vilken egenskapsuppsättningar måste anropas.
Var försiktig med äldre typer som har markerats med attributet SerializableAttribute . Många av dem har utformats för att fungera med .NET Framework-fjärrkommunikation för användning endast med betrodda data. Befintliga typer som har markerats med det här attributet kanske inte har utformats med tillståndssäkerhet i åtanke.
Förlita dig inte på IsRequired attributets DataMemberAttribute egenskap för att garantera förekomsten av data när det gäller tillståndssäkerhet. Data kan alltid vara
null
,zero
ellerinvalid
.Lita aldrig på ett objektdiagram som deserialiserats från en ej betrodd datakälla utan att verifiera det först. Varje enskilt objekt kan vara i ett konsekvent tillstånd, men objektdiagrammet som helhet kanske inte är det. Även om objektdiagrammets konserveringsläge är inaktiverat kan det deserialiserade diagrammet dessutom ha flera referenser till samma objekt eller ha cirkelreferenser. Mer information finns i Serialisering och deserialisering.
Använda NetDataContractSerializer på ett säkert sätt
NetDataContractSerializer är en serialiseringsmotor som använder nära koppling till typer. Detta liknar BinaryFormatter och SoapFormatter. Det betyder att den avgör vilken typ som ska instansieras genom att läsa .NET Framework-sammansättning och typnamn från inkommande data. Även om det är en del av WCF finns det inget sätt att ansluta den här serialiseringsmotorn. anpassad kod måste skrivas. NetDataContractSerializer
Tillhandahålls främst för att underlätta migreringen från .NET Framework-fjärrkommunikation till WCF. Mer information finns i relevant avsnitt i Serialisering och deserialisering.
Eftersom själva meddelandet kan tyda på att alla typer kan läsas in är mekanismen NetDataContractSerializer i sig osäker och bör endast användas med betrodda data. Mer information finns i säkerhetsguiden för BinaryFormatter.
Även när de används med betrodda data kan inkommande data inte tillräckligt ange vilken typ som ska läsas in, särskilt om AssemblyFormat egenskapen är inställd på Simple. Alla som har åtkomst till programmets katalog eller till den globala sammansättningscacheminnet kan ersätta en skadlig typ i stället för den som ska läsas in. Se alltid till att programmets katalog och globala sammansättningscache är säkra genom att ange behörigheter korrekt.
Om du i allmänhet tillåter delvis betrodd kodåtkomst till din NetDataContractSerializer
instans eller på annat sätt kontrollerar surrogatväljaren (ISurrogateSelector) eller serialiseringsbindningen (SerializationBinder), kan koden utöva en hel del kontroll över serialiserings-/deserialiseringsprocessen. Det kan till exempel mata in godtyckliga typer, leda till avslöjande av information, manipulera det resulterande objektdiagrammet eller serialiserade data eller spilla över den resulterande serialiserade strömmen.
Ett annat säkerhetsproblem med NetDataContractSerializer
är en denial of service, inte ett skadligt kodkörningshot. När du använder NetDataContractSerializer
anger MaxItemsInObjectGraph du alltid kvoten till ett säkert värde. Det är enkelt att skapa ett litet skadligt meddelande som allokerar en matris med objekt vars storlek endast begränsas av den här kvoten.
XmlSerializer-specifika hot
Säkerhetsmodellen XmlSerializer liknar den för DataContractSerializer. Några hot är dock unika för XmlSerializer.
XmlSerializer Genererar serialiseringssammansättningar vid körning som innehåller kod som faktiskt serialiserar och deserialiserar. Dessa sammansättningar skapas i en temporär filkatalog. Om någon annan process eller användare har åtkomstbehörighet till den katalogen kan de skriva över serialiserings-/deserialiseringskoden med godtycklig kod. Sedan XmlSerializer kör den här koden med dess säkerhetskontext i stället för serialiserings-/deserialiseringskoden. Kontrollera att behörigheterna har angetts korrekt i katalogen för temporära filer för att förhindra att detta inträffar.
Har XmlSerializer också ett läge där den använder förgenererade serialiseringssammansättningar i stället för att generera dem vid körning. Det här läget utlöses när en XmlSerializer lämplig serialiseringssammansättning kan hittas. Kontrollerar XmlSerializer om serialiseringssammansättningen signerades av samma nyckel som användes för att signera sammansättningen som innehåller de typer som serialiseras. Detta ger skydd mot att skadliga sammansättningar döljs som serialiseringssammansättningar. Men om sammansättningen som innehåller dina serialiserbara typer inte är signerad kan XmlSerializer den inte utföra den här kontrollen och använder någon sammansättning med rätt namn. Detta gör det möjligt att köra skadlig kod. Signera alltid de sammansättningar som innehåller dina serialiserbara typer eller kontrollera åtkomsten till programmets katalog och den globala sammansättningscachen för att förhindra införandet av skadliga sammansättningar.
XmlSerializer Kan utsättas för en överbelastningsattack. Har XmlSerializer ingen MaxItemsInObjectGraph
kvot (som är tillgänglig på DataContractSerializer). Därför deserialiserar den en godtycklig mängd objekt, som endast begränsas av meddelandestorleken.
Partiella förtroendehot
Observera följande problem med hot som rör kod som körs med partiellt förtroende. Dessa hot omfattar skadlig delvis betrodd kod samt skadlig delvis betrodd kod i kombination med andra attackscenarier (till exempel delvis betrodd kod som konstruerar en specifik sträng och sedan deserialiserar den).
När du använder serialiseringskomponenter ska du aldrig kontrollera några behörigheter före sådan användning, även om hela serialiseringsscenariot ligger inom omfånget för din kontroll och du inte hanterar några data eller objekt som inte är betrodda. Sådan användning kan leda till säkerhetsrisker.
I de fall där delvis betrodd kod har kontroll över serialiseringsprocessen, antingen via utökningspunkter (surrogater), typer som serialiseras eller på annat sätt, kan den delvis betrodda koden leda till att serialiseraren matar ut en stor mängd data till den serialiserade strömmen, vilket kan orsaka Denial of Service (DoS) till mottagaren av den här strömmen. Om du serialiserar data som är avsedda för ett mål som är känsligt för DoS-hot ska du inte serialisera delvis betrodda typer eller på annat sätt låta delvis betrodd kodkontroll serialisering.
Om du tillåter delvis betrodd kodåtkomst till din DataContractSerializer instans eller på annat sätt kontrollerar datakontraktssurrogaten kan den ha stor kontroll över serialiserings-/deserialiseringsprocessen. Det kan till exempel mata in godtyckliga typer, leda till avslöjande av information, manipulera det resulterande objektdiagrammet eller serialiserade data eller spilla över den resulterande serialiserade strömmen. Ett motsvarande NetDataContractSerializer hot beskrivs i avsnittet "Använda NetDataContractSerializer säkert".
DataContractAttribute Om attributet tillämpas på en typ (eller den typ som har markerats som SerializableAttribute men inte ISerializableär ), kan deserialiseraren skapa en instans av en sådan typ även om alla konstruktorer inte är offentliga eller skyddade av krav.
Lita aldrig på resultatet av deserialisering om inte de data som ska deserialiseras är betrodda och du är säker på att alla kända typer är typer som du litar på. Observera att kända typer inte läses in från programkonfigurationsfilen (men läses in från datorkonfigurationsfilen) när de körs i delvis förtroende.
Om du skickar en DataContractSerializer instans med en surrogat som har lagts till i delvis betrodd kod kan koden ändra eventuella ändringsbara inställningar för surrogaten.
Om XML-läsaren (eller data däri) kommer från delvis betrodd kod för ett deserialiserat objekt behandlar du det resulterande deserialiserade objektet som ej betrodda data.
Det faktum att ExtensionDataObject typen inte har några offentliga medlemmar innebär inte att data i den är säkra. Om du till exempel deserialiserar från en privilegierad datakälla till ett objekt där vissa data finns, och sedan ger objektet till delvis betrodd kod, kan den delvis betrodda koden läsa data i
ExtensionDataObject
genom att serialisera objektet. Överväg att ange IgnoreExtensionDataObject närtrue
du deserialiserar från en privilegierad datakälla till ett objekt som senare skickas till delvis betrodd kod.DataContractSerializer och DataContractJsonSerializer stöder serialisering av privata, skyddade, interna och offentliga medlemmar med fullständigt förtroende. I partiellt förtroende kan dock endast offentliga medlemmar serialiseras. En SecurityException utlöses om ett program försöker serialisera en icke-offentlig medlem.
Om du vill tillåta att interna eller skyddade interna medlemmar serialiseras i partiellt förtroende använder du sammansättningsattributet InternalsVisibleToAttribute . Med det här attributet kan en sammansättning deklarera att dess interna medlemmar är synliga för någon annan sammansättning. I det här fallet deklarerar en sammansättning som vill att dess interna medlemmar serialiseras att dess interna medlemmar är synliga för System.Runtime.Serialization.dll.
Fördelen med den här metoden är att den inte kräver en upphöjd kodgenereringssökväg.
Samtidigt finns det två stora nackdelar.
Den första nackdelen är att egenskapen opt-in för InternalsVisibleToAttribute attributet är sammansättningsomfattande. Du kan alltså inte ange att endast en viss klass kan ha sina interna medlemmar serialiserade. Naturligtvis kan du fortfarande välja att inte serialisera en specifik intern medlem, genom att helt enkelt inte lägga till ett DataMemberAttribute attribut till den medlemmen. På samma sätt kan en utvecklare också välja att göra en medlem intern snarare än privat eller skyddad, med små synlighetsproblem.
Den andra nackdelen är att den fortfarande inte stöder privata eller skyddade medlemmar.
Tänk på följande program för att illustrera användningen av InternalsVisibleToAttribute attributet i partiellt förtroende:
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"; } }
I exemplet ovan
PermissionsHelper.InternetZone
motsvarar PermissionSet för partiellt förtroende. Nu, utan InternalsVisibleToAttribute attributet, kommer programmet att misslyckas, vilket SecurityException indikerar att icke-offentliga medlemmar inte kan serialiseras i partiellt förtroende.Men om vi lägger till följande rad i källfilen körs programmet.
[assembly:System.Runtime.CompilerServices.InternalsVisibleTo("System.Runtime.Serialization, PublicKey = 00000000000000000400000000000000")]
Andra tillståndshanteringsproblem
Några andra problem med hantering av objekttillstånd är värda att nämna:
När du använder den streambaserade programmeringsmodellen med en direktuppspelningstransport sker bearbetningen av meddelandet när meddelandet tas emot. Avsändaren av meddelandet kan avbryta sändningsåtgärden mitt i strömmen och lämna koden i ett oförutsägbart tillstånd om mer innehåll förväntades. I allmänhet ska du inte förlita dig på att strömmen är klar och inte utföra något arbete i en strömbaserad åtgärd som inte kan återställas om strömmen avbryts. Detta gäller även för situationer där ett meddelande kan vara felaktigt formaterat efter strömningstexten (till exempel kanske det saknas en sluttagg för SOAP-kuvertet eller kan ha en andra meddelandetext).
IExtensibleDataObject
Om du använder funktionen kan känsliga data genereras. Om du tar emot data från en ej betrodd källa i datakontrakt medIExtensibleObjectData
och senare återsänder dem på en säker kanal där meddelanden signeras kan du gå i god för data som du inte vet något om. Dessutom kan det övergripande tillståndet som du skickar vara ogiltigt om du tar hänsyn till både kända och okända datadelar. Undvik den här situationen genom att antingen selektivt ange egenskapen för tilläggsdata till eller genom attnull
selektivt inaktiveraIExtensibleObjectData
funktionen.
Schemaimport
Normalt sker processen med att importera schema för att generera typer endast vid designtillfället, till exempel när du använder Verktyget för ServiceModel-metadataverktyg (Svcutil.exe) på en webbtjänst för att generera en klientklass. I mer avancerade scenarier kan du dock bearbeta schema vid körning. Tänk på att detta kan utsätta dig för överbelastningsrisker. Vissa scheman kan ta lång tid att importera. Använd XmlSerializer aldrig schemaimportkomponenten i sådana scenarier om scheman eventuellt kommer från en ej betrodd källa.
Hot som är specifika för ASP.NET AJAX-integrering
När användaren implementerar WebScriptEnablingBehavior eller WebHttpBehaviorexponerar WCF en slutpunkt som kan acceptera både XML- och JSON-meddelanden. Det finns dock bara en uppsättning läsarkvoter som används både av XML-läsaren och JSON-läsaren. Vissa kvotinställningar kan vara lämpliga för en läsare men för stora för den andra.
När du implementerar WebScriptEnablingBehavior
har användaren möjlighet att exponera en JavaScript-proxy på slutpunkten. Följande säkerhetsproblem måste beaktas:
Information om tjänsten (åtgärdsnamn, parameternamn och så vidare) kan hämtas genom att undersöka JavaScript-proxyn.
När du använder JavaScript-slutpunkten kan känslig och privat information behållas i klientwebbläsarens cacheminne.
En anteckning om komponenter
WCF är ett flexibelt och anpassningsbart system. Det mesta av innehållet i det här avsnittet fokuserar på de vanligaste WCF-användningsscenarierna. Det är dock möjligt att skapa komponenter som WCF tillhandahåller på många olika sätt. Det är viktigt att förstå säkerhetskonsekvenserna av att använda varje komponent. Framför allt:
När du måste använda XML-läsare använder du de läsare XmlDictionaryReader som klassen tillhandahåller i stället för andra läsare. Valv läsare skapas med hjälp av CreateTextReader, CreateBinaryReadereller CreateMtomReader metoder. Använd Create inte metoden. Konfigurera alltid läsarna med säkra kvoter. Serialiseringsmotorerna i WCF är endast säkra när de används med säkra XML-läsare från WCF.
När du använder DataContractSerializer för att deserialisera potentiellt ej betrodda data anger du MaxItemsInObjectGraph alltid egenskapen.
När du skapar ett meddelande anger du parametern
maxSizeOfHeaders
omMaxReceivedMessageSize
inte ger tillräckligt skydd.När du skapar en kodare konfigurerar du alltid relevanta kvoter, till exempel
MaxSessionSize
ochMaxBufferSize
.När du använder ett XPath-meddelandefilter anger du NodeQuota för att begränsa mängden XML-noder som filtret besöker. Använd inte XPath-uttryck som kan ta lång tid att beräkna utan att besöka många noder.
När du använder en komponent som accepterar en kvot kan du i allmänhet förstå säkerhetskonsekvenserna och ange den till ett säkert värde.