Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Podczas pracy z danymi w programie Windows Communication Foundation (WCF) należy wziąć pod uwagę wiele kategorii zagrożeń. Na poniższej liście przedstawiono najważniejsze klasy zagrożeń związane z przetwarzaniem danych. Program WCF udostępnia narzędzia do eliminowania tych zagrożeń.
Odmowa usługi
Podczas odbierania niezaufanych danych dane mogą spowodować, że strona odbierającego uzyskuje dostęp do nieproporcjonalnej ilości różnych zasobów, takich jak pamięć, wątki, dostępne połączenia lub cykle procesora, powodując długie obliczenia. Atak typu "odmowa usługi" na serwer może spowodować jego awarię, uniemożliwiając przetwarzanie wiadomości od innych, prawidłowych klientów.
Złośliwe wykonywanie kodu
Przychodzące niezaufane dane powodują, że po stronie odbierającej zostanie uruchomiony kod, który nie zamierzał.
Ujawnienie informacji
Zdalny atakujący zmusza odbiorcę do odpowiadania na żądania w taki sposób, aby ujawniać więcej informacji, niż zamierza.
User-Provided kod i bezpieczeństwo dostępu do kodu
Wiele miejsc w infrastruktury programu Windows Communication Foundation (WCF) uruchamia kod, który jest dostarczany przez użytkownika. Na przykład aparat serializacji DataContractSerializer może wywołać udostępnione przez użytkownika metody dostępu właściwości set
i get
. Infrastruktura kanału WCF może również wywoływać klasy pochodne dostarczone przez użytkownika klasy Message.
Autor kodu odpowiada za zapewnienie, że nie istnieją żadne luki w zabezpieczeniach. Na przykład, jeśli utworzysz typ kontraktu danych ze składową członka danych typu liczba całkowita, a w implementacji akcesora przydzielisz tablicę na podstawie wartości tej właściwości, narażasz się na atak typu "odmowa usługi", jeśli złośliwy komunikat zawiera bardzo dużą wartość dla tej składowej danych. Ogólnie rzecz biorąc, należy unikać alokacji na podstawie danych przychodzących lub długotrwałego przetwarzania w kodzie dostarczonym przez użytkownika (zwłaszcza jeśli długotrwałe przetwarzanie może być spowodowane niewielką ilością danych przychodzących). Podczas przeprowadzania analizy zabezpieczeń kodu dostarczonego przez użytkownika należy również wziąć pod uwagę wszystkie przypadki awarii (czyli wszystkie gałęzie kodu, w których są zgłaszane wyjątki).
Ostatecznym przykładem kodu dostarczonego przez użytkownika jest kod wewnątrz implementacji usługi dla każdej operacji. Bezpieczeństwo implementacji usługi jest Twoim zadaniem. Łatwo jest przypadkowo utworzyć niezabezpieczone implementacje operacji, które mogą powodować luki w zabezpieczeniach typu "odmowa usługi". Na przykład operacja, która pobiera ciąg i zwraca listę klientów z bazy danych, której nazwa zaczyna się od tego ciągu. Jeśli pracujesz z dużą bazą danych, a przekazywany ciąg jest tylko jedną literą, kod może próbować utworzyć komunikat większy niż cała dostępna pamięć, powodując niepowodzenie całej usługi. (Element OutOfMemoryException nie jest możliwy do odzyskania w programie .NET Framework i zawsze powoduje zakończenie aplikacji).
Upewnij się, że żaden złośliwy kod nie jest podłączony do różnych punktów rozszerzalności. Jest to szczególnie istotne w przypadku uruchamiania w ramach częściowego zaufania, obsługi typów z częściowo zaufanych zestawów lub tworzenia składników używanych przez częściowo zaufany kod. Aby uzyskać więcej informacji, zobacz sekcję "Częściowe zagrożenia zaufania" w dalszej sekcji.
Należy pamiętać, że podczas działania w trybie częściowego zaufania infrastruktura serializacji kontraktu danych obsługuje tylko ograniczony podzestaw modelu programowania kontraktów danych — na przykład prywatne elementy danych lub typy korzystające z atrybutu SerializableAttribute nie są obsługiwane. Aby uzyskać więcej informacji, zobacz Częściowe zaufanie.
Uwaga / Notatka
Zabezpieczenia dostępu kodu (CAS) zostały wycofane we wszystkich wersjach programu .NET Framework i .NET. Najnowsze wersje platformy .NET nie honorują adnotacji CAS i generują błędy, jeśli są używane interfejsy API związane z usługą CAS. Deweloperzy powinni szukać alternatywnych sposobów wykonywania zadań zabezpieczeń.
Unikanie niezamierzonego ujawnienia informacji
Podczas projektowania serializowalnych typów z uwzględnieniem zabezpieczeń można się obawiać ujawnienia informacji.
Rozważ następujące kwestie:
Model DataContractSerializer programowania umożliwia ujawnienie prywatnych i wewnętrznych danych poza typem lub zestawem podczas serializacji. Ponadto podczas eksportowania schematu można uwidocznić kształt typu. Pamiętaj, aby zrozumieć projekcję serializacyjną typu. Jeśli nie chcesz uwidocznić niczego, wyłącz jego serializowanie (na przykład nie stosując atrybutu DataMemberAttribute w przypadku kontraktu danych).
Należy pamiętać, że ten sam typ może mieć wiele projekcji serializacji, w zależności od serializatora, który jest używany. Ten sam typ może uwidaczniać jeden zestaw danych w przypadku użycia z DataContractSerializer i inny zestaw danych w przypadku użycia z XmlSerializer. Przypadkowe użycie niewłaściwego serializatora może prowadzić do ujawnienia informacji.
Użycie XmlSerializer w trybie legacy remote procedure call (RPC)/zakodowanym może przypadkowo uwidocznić kształt grafu obiektu po stronie wysyłającej na stronę odbiorcy.
Zapobieganie atakom typu "odmowa usługi"
Kontyngenty
Powodowanie, by po stronie odbiorcy przydzielono znaczną ilość pamięci, może stanowić potencjalny atak typu "odmowa usługi". Chociaż ta sekcja koncentruje się na problemach z zużyciem pamięci wynikających z dużych komunikatów, mogą wystąpić inne ataki. Na przykład komunikaty mogą używać nieproporcjonalnego czasu przetwarzania.
Ataki typu "odmowa usługi" są zwykle ograniczane za pomocą limitów. W przypadku przekroczenia limitu zazwyczaj zgłaszany jest QuotaExceededException wyjątek. Bez limitu przydziału złośliwy komunikat może uzyskać dostęp do całej dostępnej pamięci, prowadząc do OutOfMemoryException wyjątku, lub uzyskać dostęp do wszystkich dostępnych stosów, co skutkuje StackOverflowException.
Przekroczenie limitu przydziału jest możliwe do odzyskania. W przypadku napotkania w działającej usłudze, komunikat obecnie przetwarzany jest odrzucany, a usługa nadal działa i przetwarza dalsze komunikaty. Scenariusze braku pamięci i przepełnienia stosu nie są jednak możliwe do odzyskania w dowolnym miejscu w programie .NET Framework; usługa kończy działanie, jeśli wystąpią takie wyjątki.
Przydziały w programie WCF nie obejmują żadnej wstępnej alokacji. Jeśli na przykład limit przydziału MaxReceivedMessageSize (znaleziony w różnych klasach) jest ustawiony na 128 KB, nie oznacza to, że 128 KB jest automatycznie przydzielane dla każdego komunikatu. Przydzielona kwota rzeczywista zależy od rzeczywistego rozmiaru komunikatu przychodzącego.
Wiele kwot jest dostępnych na warstwie transportu. Są to limity przydziału wymuszane przez określony używany kanał transportu (HTTP, TCP itd.). W tym temacie omówiono niektóre z tych limitów przydziału, ale te limity przydziału zostały szczegółowo opisane w temacie Limity przydziału transportu.
Luka w zabezpieczeniach w formie tabeli skrótu
Luka w zabezpieczeniach występuje, gdy kontrakty danych zawierają tabele skrótów lub kolekcje. Problem występuje, jeśli duża liczba wartości jest wstawiana do tabeli skrótów, w której duża liczba tych wartości generuje tę samą wartość skrótu. Może to być używane jako atak DOS. Tę lukę w zabezpieczeniach można rozwiązać, ustawiając limit przydziału powiązania MaxReceivedMessageSize. Należy zachować ostrożność podczas ustawiania tego limitu przydziału, aby zapobiec takim atakom. Przydział nakłada górny limit na rozmiar komunikatu WCF. Ponadto unikaj używania tabel skrótów lub kolekcji w kontraktach danych.
Ograniczanie zużycia pamięci bez przesyłania strumieniowego
Model zabezpieczeń wokół dużych komunikatów zależy od tego, czy przesyłanie strumieniowe jest używane. W podstawowym przypadku, gdy nie używa się strumieniowania, komunikaty są buforowane w pamięci. W tym przypadku należy użyć limitu przydziału MaxReceivedMessageSize dla TransportBindingElement lub powiązań dostarczonych przez system, aby chronić przed dużymi komunikatami, ograniczając maksymalny rozmiar komunikatu, do którego można uzyskać dostęp. Należy pamiętać, że usługa może jednocześnie przetwarzać wiele komunikatów, w takim przypadku wszystkie są w pamięci. Użyj funkcji ograniczania przepustowości, aby wyeliminować to zagrożenie.
Należy również pamiętać, że MaxReceivedMessageSize
nie umieszcza górnej granicy na zużycie pamięci dla komunikatów, ale ogranicza je do stałego współczynnika. Jeśli na przykład MaxReceivedMessageSize
zostanie odebrany komunikat o rozmiarze 1 MB, a następnie zostanie zdeserializowany, wymagana jest dodatkowa pamięć zawierająca deserializowany graf obiektu, co spowoduje całkowite zużycie pamięci ponad 1 MB. Z tego powodu należy unikać tworzenia typów możliwych do serializacji, które mogłyby spowodować znaczne zużycie pamięci bez dużej ilości danych przychodzących. Na przykład kontrakt danych "MyContract" z 50 opcjonalnymi polami składowymi danych i dodatkowymi 100 polami prywatnymi można utworzyć przy użyciu konstrukcji XML "<MyContract/>". Ten kod XML powoduje uzyskanie dostępu do pamięci dla 150 pól. Należy pamiętać, że domyślnie składowe danych są opcjonalne. Problem komplikuje się, gdy taki typ jest częścią tablicy.
MaxReceivedMessageSize
sam nie wystarcza, aby zapobiec wszystkim atakom typu "odmowa usługi". Na przykład deserializator może być zmuszony do deserializacji głęboko zagnieżdżonego grafu obiektu (obiektu zawierającego inny obiekt, który zawiera jeszcze jeden i tak dalej) przez komunikat przychodzący. Zarówno DataContractSerializer, jak i XmlSerializer wywołują metody w sposób zagnieżdżony w celu deserializacji takich grafów. Głębokie zagnieżdżenie wywołań metod może spowodować nieodwracalny StackOverflowException. Zagrożenie to jest ograniczane poprzez ustawienie MaxDepth limitu zagnieżdżenia XML, jak opisano w sekcji "Bezpieczne używanie XML" w dalszej części.
Ustawienie dodatkowych limitów przydziałów na MaxReceivedMessageSize
jest szczególnie ważne podczas korzystania z kodowania binarnego XML. Użycie kodowania binarnego jest nieco równoważne kompresji: mała grupa bajtów w komunikacie przychodzącym może reprezentować dużo danych. W związku z tym nawet komunikat pasujący do limitu MaxReceivedMessageSize
może zająć znacznie więcej pamięci w w pełni rozwiniętej formie. Aby wyeliminować takie zagrożenia specyficzne dla kodu XML, wszystkie limity przydziału czytnika XML muszą być ustawione poprawnie, zgodnie z opisem w sekcji "Bezpieczne używanie kodu XML" w dalszej części tego tematu.
Ograniczanie zużycia pamięci przy użyciu przesyłania strumieniowego
Podczas przesyłania strumieniowego można użyć małego MaxReceivedMessageSize
ustawienia, aby chronić przed atakami typu "odmowa usługi". Jednak bardziej skomplikowane scenariusze są możliwe w przypadku przesyłania strumieniowego. Na przykład usługa przekazywania plików akceptuje pliki większe niż cała dostępna pamięć. W takim przypadku ustaw MaxReceivedMessageSize
na niezwykle dużą wartość, oczekując, że prawie żadne dane nie będą buforowane w pamięci, a wiadomości będą strumieniowane bezpośrednio na dysk. Jeśli złośliwy komunikat może w jakiś sposób wymusić buforowanie danych przez usługę WCF zamiast przesyłania strumieniowego w tym przypadku MaxReceivedMessageSize
nie zapewnia już ochrony przed uzyskaniem przez ten komunikat dostępu do całej dostępnej pamięci.
Aby wyeliminować to zagrożenie, istnieją określone ustawienia limitu przydziału dla różnych składników przetwarzania danych WCF, które ograniczają buforowanie. Najważniejszą MaxBufferSize
z nich jest właściwość różnych elementów wiązania transportu i standardowych powiązań. Podczas przesyłania strumieniowego ten limit przydziału należy ustawić z uwzględnieniem maksymalnej ilości pamięci, którą chcesz przydzielić na komunikat. Podobnie jak w przypadku MaxReceivedMessageSize
parametru , ustawienie nie nakłada bezwzględnej maksymalnej ilości pamięci, ale ogranicza je tylko do stałego współczynnika. Ponadto, podobnie jak w przypadku MaxReceivedMessageSize
, należy pamiętać o możliwości jednoczesnego przetwarzania wielu komunikatów.
Szczegóły MaxBufferSize
Właściwość MaxBufferSize
ogranicza wszystkie operacje buforowania zbiorczego w programie WCF. Na przykład WCF zawsze buforuje nagłówki PROTOKOŁU SOAP i błędy PROTOKOŁU SOAP, a także wszystkie elementy MIME, które nie są w naturalnej kolejności odczytu w komunikacie mechanizmu optymalizacji transmisji komunikatów (MTOM). To ustawienie ogranicza ilość buforowania we wszystkich tych przypadkach.
WCF przekazuje wartość MaxBufferSize
do różnych składników, które mogą buforować. Na przykład niektóre przeciążenia klasy CreateMessage przyjmują parametr Message. Program WCF przekazuje wartość do tego parametru MaxBufferSize
, aby ograniczyć ilość buforowania nagłówka PROTOKOŁU SOAP. Ważne jest, aby ustawić ten parametr bezpośrednio podczas korzystania z Message klasy. Ogólnie rzecz biorąc, w przypadku używania składnika w programie WCF, który przyjmuje parametry limitu przydziału, ważne jest, aby zrozumieć implikacje zabezpieczeń tych parametrów i ustawić je poprawnie.
Koder komunikatów MTOM ma MaxBufferSize
również ustawienie. W przypadku używania powiązań standardowych jest ona ustawiana automatycznie na wartość na poziomie MaxBufferSize
transportu. Jednak w przypadku używania elementu powiązania kodera komunikatów MTOM do konstruowania powiązania niestandardowego należy ustawić MaxBufferSize
właściwość na bezpieczną wartość podczas przesyłania danych w trybie strumieniowym.
ataki przesyłania strumieniowego XML-Based
MaxBufferSize
samodzielnie nie wystarczy, aby zapewnić, że WCF nie zostanie zmuszony do buforowania, gdy oczekiwane jest przesyłanie strumieniowe. Na przykład czytniki XML programu WCF zawsze buforować cały tag początkowy elementu XML podczas rozpoczynania odczytywania nowego elementu. Odbywa się to tak, aby przestrzenie nazw i atrybuty były prawidłowo przetwarzane. Jeśli MaxReceivedMessageSize
jest skonfigurowany jako duży (na przykład w celu włączenia scenariusza przesyłania strumieniowego dużych plików bezpośrednio na dysk), może zostać skonstruowana złośliwa wiadomość, w której cała treść wiadomości jest dużym znacznikiem początkowym elementu XML. Próba odczytania go powoduje wyświetlenie elementu OutOfMemoryException. Jest to jeden z wielu możliwych ataków typu "odmowa usługi" opartych na formacie XML, które można ograniczyć przy użyciu przydziałów czytnika XML, omówionego w sekcji "Bezpieczne używanie kodu XML" w dalszej części tego tematu. Podczas przesyłania strumieniowego szczególnie ważne jest ustawienie wszystkich tych limitów.
Mieszanie modeli programowania przesyłania strumieniowego i buforowania
Wiele możliwych ataków wynika z mieszania modeli programowania przesyłania strumieniowego i niestrumieniowego w tej samej usłudze. Załóżmy, że istnieje kontrakt usługi z dwiema operacjami: jedna przyjmuje Stream, a druga przyjmuje tablicę niestandardowego typu. Załóżmy również, że MaxReceivedMessageSize
jest ona ustawiona na dużą wartość, aby umożliwić pierwszą operację przetwarzania dużych strumieni. Niestety, oznacza to, że istnieje teraz możliwość wysyłania dużych wiadomości do drugiej operacji, a deserializator buforuje dane w pamięci jako tablicę przed wywołaniem operacji. Jest to potencjalny atak typu "odmowa usługi": przydział MaxBufferSize
nie ogranicza rozmiaru treści komunikatu, który obsługuje deserializator.
Z tego powodu należy unikać mieszania operacji strumieniowych i niestrumieniowych w tym samym kontrakcie. Jeśli absolutnie musisz mieszać dwa modele programowania, należy użyć następujących środków ostrożności:
Wyłącz funkcję IExtensibleDataObject, ustalając właściwość IgnoreExtensionDataObject obiektu ServiceBehaviorAttribute na
true
. Dzięki temu tylko członkowie, którzy są częścią kontraktu, są deserializowani.MaxItemsInObjectGraph Ustaw właściwość elementu DataContractSerializer na bezpieczną wartość. Ten limit przydziału jest również dostępny dla atrybutu ServiceBehaviorAttribute lub za pośrednictwem konfiguracji. Ten limit ogranicza liczbę obiektów, które są deserializowane w jednym procesie deserializacji. Zwykle każdy parametr operacji lub część treści komunikatu w kontrakcie komunikatów jest deserializowany w jednym odcinku. Podczas deserializacji tablic każdy wpis tablicy jest liowany jako oddzielny obiekt.
Ustaw wszystkie limity przydziału czytnika XML na bezpieczne wartości. Zwróć uwagę na MaxDepth, MaxStringContentLength i MaxArrayLength, i unikaj ciągów w operacjach niestreamingowych.
Przejrzyj listę znanych typów, mając na uwadze, że w dowolnym momencie można utworzyć wystąpienie dowolnego z nich (zobacz sekcję "Zapobieganie niezamierzonym typom przed załadowaniem" w dalszej części tego tematu).
Nie należy używać żadnych typów, które implementują IXmlSerializable interfejs buforujący dużo danych. Nie należy dodawać takich typów do listy znanych typów.
Nie używaj tablic XmlElement, tablic XmlNode, tablic Byte ani typów implementujących ISerializable w kontrakcie.
Nie należy używać tablic XmlElement, tablic XmlNode, tablic Byte ani typów, które implementują ISerializable na liście znanych typów.
Powyższe środki ostrożności mają zastosowanie, gdy operacja niewymagająca przesyłania strumieniowego używa elementu DataContractSerializer. Nigdy nie mieszaj modeli programowania przesyłania strumieniowego i nieprzesyłania strumieniowego w tej samej usłudze, jeśli używasz XmlSerializerelementu, ponieważ nie ma ochrony przydziału MaxItemsInObjectGraph.
Powolne ataki strumieniowe
Klasa ataków typu "odmowa usługi" przesyłania strumieniowego nie obejmuje zużycia pamięci. Zamiast tego atak obejmuje powolnego nadawcę lub odbiorcę danych. Podczas oczekiwania na wysłanie lub odebranie danych zasoby, takie jak wątki i dostępne połączenia, zostaną wyczerpane. Taka sytuacja może wystąpić w wyniku złośliwego ataku lub z legalnego nadawcy/odbiorcy na wolnym połączeniu sieciowym.
Aby wyeliminować te ataki, należy prawidłowo ustawić limity czasu transportu. Aby uzyskać więcej informacji, zobacz Limity przydziału transportu. Po drugie, nigdy nie używaj synchronicznych Read
ani Write
operacji podczas pracy ze strumieniami w programie WCF.
Bezpieczne używanie kodu XML
Uwaga / Notatka
Chociaż ta sekcja dotyczy kodu XML, informacje dotyczą również dokumentów JavaScript Object Notation (JSON). Przydziały działają podobnie przy użyciu mapowania między formatami JSON i XML.
Zabezpieczanie czytników XML
Zestaw informacji XML stanowi podstawę całego przetwarzania komunikatów w programie WCF. W przypadku akceptowania danych XML z niezaufanego źródła istnieje wiele możliwości ataku typu "odmowa usługi", które muszą zostać złagodzone. Program WCF zapewnia specjalne, bezpieczne czytniki XML. Te czytniki są tworzone automatycznie podczas korzystania z jednego ze standardowych kodowań w programie WCF (tekst, binarny lub MTOM).
Niektóre funkcje zabezpieczeń tych urządzeń do odczytu są zawsze aktywne. Na przykład czytelnicy nigdy nie przetwarzają definicji typów dokumentów (DTD), które są potencjalnym źródłem ataków typu "odmowa usługi" i nigdy nie powinny pojawiać się w wiarygodnych komunikatach PROTOKOŁU SOAP. Inne funkcje zabezpieczeń obejmują limity przydziału czytelnika, które należy skonfigurować, które opisano w poniższej sekcji.
Podczas bezpośredniej pracy z czytnikami XML (na przykład podczas pisania własnego niestandardowego kodera lub pracy bezpośrednio z Message klasą) zawsze używaj bezpiecznych czytników WCF, gdy istnieje szansa na pracę z niezaufanymi danymi. Utwórz bezpieczne czytniki, wywołując jedno z przeciążeń metody fabryki statycznej CreateTextReader, CreateBinaryReader lub CreateMtomReader klasy XmlDictionaryReader. Podczas tworzenia czytnika należy ustawić bezpieczne wartości limitu przydziału. Nie wywołuj przeciążeń funkcji Create
. Nie tworzą one czytnika WCF. Zamiast tego tworzony jest czytelnik, który nie jest chroniony przez funkcje zabezpieczeń opisane w tej sekcji.
Limity czytelnika
Bezpieczne czytniki XML mają pięć konfigurowalnych limitów. Są one zwykle konfigurowane przy użyciu właściwości ReaderQuotas
na elementach wiązania kodowania lub standardowych wiązaniach, albo przez użycie obiektu XmlDictionaryReaderQuotas przekazanego podczas tworzenia czytnika.
MaksBajtyNaOdczyt
Przydział ten ogranicza liczbę bajtów odczytywanych podczas jednej operacji Read
przy odczytywaniu startowego tagu elementu i jego atrybutów. (W przypadku nieprzesyłania strumieniowego sama nazwa elementu nie jest liczone względem limitu przydziału). MaxBytesPerRead jest ważne z następujących powodów:
Nazwa elementu i jego atrybuty są zawsze buforowane w pamięci podczas ich odczytywania. Dlatego ważne jest prawidłowe ustawienie tego przydziału w trybie przesyłania strumieniowego, aby zapobiec nadmiernemu buforowaniu podczas strumieniowania. Zobacz sekcję
MaxDepth
limitu przydziału, aby uzyskać informacje o rzeczywistej ilości buforowania, która ma miejsce.Zbyt wiele atrybutów XML może używać nieproporcjonalnego czasu przetwarzania, ponieważ nazwy atrybutów muszą być sprawdzane pod kątem unikatowości.
MaxBytesPerRead
ogranicza to zagrożenie.
MaksymalnaGłębokość
Ten limit ogranicza maksymalną głębokość zagnieżdżania elementów XML. Na przykład dokument "<A><B><C/></B></A>" ma trzy poziomy zagnieżdżenia. MaxDepth jest ważne z następujących powodów:
MaxDepth
współdziała zMaxBytesPerRead
: czytelnik zawsze przechowuje dane w pamięci dla bieżącego elementu i wszystkich jego elementów nadrzędnych, więc maksymalne zużycie pamięci czytnika jest proporcjonalne do produktu tych dwóch ustawień.Podczas deserializacji głęboko zagnieżdżonego grafu obiektu deserializator jest zmuszony uzyskać dostęp do całego stosu i zgłosić błąd nieodwracalny StackOverflowException. Istnieje bezpośrednia korelacja między zagnieżdżaniem XML a zagnieżdżaniem obiektów zarówno dla DataContractSerializer, jak i XmlSerializer. Użyj polecenia
MaxDepth
, aby wyeliminować to zagrożenie.
MaxNameTableCharCount
Ten limit ogranicza rozmiar tabeli nazw czytelnika. Tabela nazw zawiera pewne ciągi (takie jak przestrzenie nazw i prefiksy), które są napotykane podczas przetwarzania dokumentu XML. Ponieważ te ciągi są buforowane w pamięci, ustaw ten limit, aby zapobiec nadmiernemu buforowaniu podczas przesyłania strumieniowego.
MaksymalnaDługośćTreściStringa
Ten przydział ogranicza maksymalną wielkość ciągu zwracaną przez czytnik XML-a. Ten limit przydziału nie ogranicza zużycia pamięci w samym czytniku XML, ale w składniku korzystającym z czytnika. Na przykład gdy DataContractSerializer używa czytnika zabezpieczonego za pomocą MaxStringContentLength, nie deserializuje ciągów większych niż ten limit. Jeśli używasz XmlDictionaryReader klasy bezpośrednio, nie wszystkie metody przestrzegają tego limitu, ale tylko te, które zostały specjalnie zaprojektowane do odczytywania ciągów, takie jak metoda ReadContentAsString. Na właściwość Value czytnika nie wpływa ten limit, dlatego nie należy jej używać, gdy potrzebna jest ochrona, którą ten limit zapewnia.
MaxArrayLength
Ten limit ogranicza maksymalny rozmiar tablicy typów prymitywnych zwracanych przez parser XML, w tym tablic bajtów. Ten limit przydziału nie ogranicza zużycia pamięci w samym czytniku XML, ale w każdym składniku korzystającym z czytnika. Na przykład gdy DataContractSerializer używa czytnika zabezpieczonego za pomocą MaxArrayLength, nie deserializuje tablic bajtów większych niż ten przydział. Ważne jest, aby ustawić ten limit przydziału w czasie próby połączenia strumieniowego i buforowanego modelów programowania w ramach pojedynczego kontraktu. Należy pamiętać, że przy bezpośrednim użyciu klasy XmlDictionaryReader tylko te metody, które zostały specjalnie zaprojektowane do odczytywania tablic o dowolnym rozmiarze niektórych typów pierwotnych, takich jak ReadInt32Array, przestrzegają tego limitu.
Zagrożenia specyficzne dla kodowania binarnego
Kodowanie binarne XML, które obsługuje WCF, zawiera funkcję ciągów słownikowych. Duży ciąg może być zakodowany przy użyciu tylko kilku bajtów. Umożliwia to znaczne zwiększenie wydajności, ale wprowadza nowe zagrożenia typu "odmowa usługi", które muszą zostać złagodzone.
Istnieją dwa rodzaje słowników: statyczne i dynamiczne. Słownik statyczny to wbudowana lista długich ciągów, które mogą być reprezentowane przy użyciu krótkiego kodu w kodowaniu binarnym. Ta lista ciągów jest stała po utworzeniu czytnika i nie można jej modyfikować. Żaden z ciągów w słowniku statycznym używanym domyślnie przez program WCF nie jest wystarczająco duży, aby stanowić poważne zagrożenie typu "odmowa usługi", chociaż nadal mogą być używane w ataku rozszerzenia słownika. W zaawansowanych scenariuszach, w których podajesz własny słownik statyczny, należy zachować ostrożność podczas wprowadzania dużych ciągów słownika.
Funkcja słowników dynamicznych umożliwia komunikatom definiowanie własnych ciągów znaków i kojarzenie ich z krótkimi kodami. Te mapowania ciąg-kod są przechowywane w pamięci podczas całej sesji komunikacji, tak aby kolejne komunikaty nie musiały ponownie wysłać ciągów i mogą korzystać z kodów, które są już zdefiniowane. Te ciągi mogą mieć dowolną długość i w związku z tym stanowią poważniejsze zagrożenie niż te w słowniku statycznym.
Pierwszym zagrożeniem, które należy złagodzić, jest możliwość, że słownik dynamiczny (tabela mapowania ciąg-kod) staje się zbyt duży. Ten słownik może zostać rozszerzony w ramach kilku wiadomości, dlatego że limit MaxReceivedMessageSize
nie zapewnia ochrony, ponieważ dotyczy oddzielnie każdej wiadomości. Dlatego istnieje oddzielna właściwość MaxSessionSize na BinaryMessageEncodingBindingElement, która ogranicza rozmiar słownika.
W przeciwieństwie do większości innych przydziałów, ten limit ma zastosowanie również podczas pisania wiadomości. Jeśli podczas odczytywania komunikatu limit zostanie przekroczony, QuotaExceededException
zostanie zgłoszony jak zwykle. Jeśli limit przydziału zostanie przekroczony podczas pisania wiadomości, wszystkie ciągi, które powodują przekroczenie limitu, są zapisywane jako as-is, bez użycia funkcji słowników dynamicznych.
Zagrożenia rozszerzenia słownika
Znaczna klasa ataków specyficznych dla danych binarnych wynika z rozszerzenia słownika. Mała wiadomość w postaci binarnej może przekształcić się w bardzo duży komunikat w w pełni rozwiniętym formularzu tekstowym, jeśli korzysta z funkcji słowników ciągów. Współczynnik rozszerzania ciągów w słowniku dynamicznym jest ograniczony przydziałem MaxSessionSize, ponieważ żaden ciąg słownika dynamicznego nie przekracza maksymalnego rozmiaru całego słownika.
Właściwości MaxNameTableCharCount, MaxStringContentLength
i MaxArrayLength
ograniczają tylko zużycie pamięci. Zwykle nie są one wymagane do złagodzenia żadnych zagrożeń w użyciu niestreamowanym, ponieważ użycie pamięci jest już ograniczone przez MaxReceivedMessageSize
. Jednak MaxReceivedMessageSize
zlicza bajty przed rozszerzeniem. Gdy kodowanie binarne jest używane, zużycie pamięci może potencjalnie wykraczać poza MaxReceivedMessageSize
, ograniczone jedynie współczynnikiem MaxSessionSize. Z tego powodu ważne jest, aby zawsze ustawiać wszystkie limity czytania (zwłaszcza MaxStringContentLength) podczas korzystania z odkodowania binarnego.
W przypadku używania kodowania binarnego razem z DataContractSerializer, interfejs IExtensibleDataObject
może być niewłaściwie użyty do przeprowadzenia ataku rozszerzenia słownika. Ten interfejs zasadniczo zapewnia nieograniczony magazyn dla dowolnych danych, które nie są częścią kontraktu. Jeśli limity nie mogą być ustawione na tyle niskie, aby MaxSessionSize
pomnożone przez MaxReceivedMessageSize
nie stanowiło problemu, wyłącz funkcję IExtensibleDataObject
podczas korzystania z kodowania binarnego. Ustaw właściwość IgnoreExtensionDataObject
na true
w atrybucie ServiceBehaviorAttribute
. Alternatywnie nie implementuj interfejsu IExtensibleDataObject
. Aby uzyskać więcej informacji, zobacz Forward-Compatible Kontrakty danych.
Podsumowanie limitów przydziału
Poniższa tabela zawiera podsumowanie wskazówek dotyczących limitów przydziałów.
Warunek | Ważne kwoty do ustawienia |
---|---|
Brak przesyłania strumieniowego ani przesyłania strumieniowego małych komunikatów, tekstu ani kodowania MTOM |
MaxReceivedMessageSize , MaxBytesPerRead i MaxDepth |
Brak przesyłania strumieniowego lub przesyłanie małych komunikatów, kodowanie binarne |
MaxReceivedMessageSize , MaxSessionSize i wszystkie ReaderQuotas |
Przesyłanie strumieniowe dużych komunikatów, tekstu lub kodowanie metodą MTOM |
MaxBufferSize i wszystkie ReaderQuotas |
Przesyłanie strumieniowe dużych komunikatów, kodowanie binarne |
MaxBufferSize , MaxSessionSize i wszystkie ReaderQuotas |
Zawsze należy ustawiać limity czasowe na poziomie transportu i nigdy nie używać synchronicznych odczytów/zapisów, gdy używane jest przesyłanie strumieniowe, niezależnie od tego, czy strumieniujesz duże czy małe wiadomości.
W razie wątpliwości co do limitu przydziału, ustaw go na bezpieczną wartość, a nie zostawiaj go otwartego.
Zapobieganie złośliwemu wykonaniu kodu
Następujące ogólne klasy zagrożeń mogą wykonywać kod i mieć niezamierzone skutki:
Mechanizm deserializacji ładuje złośliwy, niebezpieczny lub wrażliwy na bezpieczeństwo typ.
Przychodzący komunikat powoduje, że deserializator konstruuje instancję typu, który zwykle jest bezpieczny, w sposób prowadzący do niezamierzonych konsekwencji.
W poniższych sekcjach omówiono te klasy zagrożeń.
Serializator kontraktu danych
(Aby uzyskać informacje o zabezpieczeniach w XmlSerializer, zobacz odpowiednią dokumentację). Model zabezpieczeń dla elementu XmlSerializer jest podobny do modelu DataContractSerializer, a różni się głównie w szczegółach. Na przykład XmlIncludeAttribute atrybut jest używany do dołączania typów zamiast atrybutu KnownTypeAttribute . Jednak niektóre zagrożenia unikatowe dla XmlSerializer zostały omówione w dalszej części tego tematu.
Zapobieganie ładowaniu niezamierzonych typów
Ładowanie niezamierzonych typów danych może mieć znaczące konsekwencje, niezależnie od tego, czy typ jest złośliwy, czy po prostu ma skutki uboczne dotyczące bezpieczeństwa. Typ może zawierać lukę w zabezpieczeniach umożliwiającą wykorzystanie, wykonywać akcje wrażliwe na zabezpieczenia w konstruktorze lub konstruktorze klasy, mieć duży ślad pamięci, który ułatwia ataki typu "odmowa usługi" lub może zgłaszać wyjątki, które nie można odzyskać. Typy mogą mieć konstruktory klasowe, które uruchamiają się natychmiast po załadowaniu typu i przed utworzeniem jakichkolwiek instancji. Z tych powodów ważne jest kontrolowanie zestawu typów, które deserializator może załadować.
DataContractSerializer Deserializuje się w luźno sprzężony sposób. Nigdy nie odczytuje nazw typów i zestawów wspólnego środowiska uruchomieniowego (CLR) z danych przychodzących. Jest to podobne do zachowania elementu XmlSerializer, ale różni się od zachowania elementu NetDataContractSerializer, BinaryFormatter i SoapFormatter. Luźne sprzężenie wprowadza stopień bezpieczeństwa, ponieważ zdalny atakujący nie może wskazać dowolnego typu do załadowania tylko przez nadanie tego typu nazwy w komunikacie.
DataContractSerializer zawsze może załadować typ, który aktualnie jest wymagany zgodnie z kontraktem. Na przykład, jeśli kontrakt danych zawiera uczestnika danych typu Customer
, DataContractSerializer może załadować typ Customer
podczas deserializacji tego uczestnika danych.
Ponadto DataContractSerializer obsługuje polimorfizm. Element danych może być zadeklarowany jako Object, ale dane przychodzące mogą zawierać instancję Customer
. Jest to możliwe tylko wtedy, gdy Customer
typ został "znany" deserializatorowi za pomocą jednego z następujących mechanizmów:
KnownTypeAttribute atrybut zastosowany do typu.
KnownTypeAttribute
atrybut określający metodę zwracającą listę typów.Atrybut
ServiceKnownTypeAttribute
.Sekcja
KnownTypes
konfiguracji.Lista znanych typów jawnie przekazywanych do DataContractSerializer w trakcie konstrukcji, jeśli korzystasz z serializatora bezpośrednio.
Każdy z tych mechanizmów zwiększa obszar powierzchni, wprowadzając więcej typów, które deserializator może załadować. Kontroluj każdy z tych mechanizmów, aby upewnić się, że do listy znanych typów nie są dodawane żadne złośliwe lub niezamierzone typy.
Gdy znany typ znajduje się w zakresie, można go załadować w dowolnym momencie, a wystąpienia typu można utworzyć, nawet jeśli kontrakt faktycznie zabrania jego użycia. Załóżmy na przykład, że typ "MyDangerousType" jest dodawany do znanej listy typów przy użyciu jednego z powyższych mechanizmów. Oznacza to, że:
MyDangerousType
jest ładowany, a jego konstruktor klasy jest uruchamiany.Nawet w przypadku deserializacji kontraktu danych z elementem danych typu string, złośliwy komunikat nadal może powodować utworzenie wystąpienia
MyDangerousType
. Kod w programieMyDangerousType
, taki jak moduły ustawiania właściwości, mogą być uruchamiane. Po wykonaniu tej czynności deserializator próbuje przypisać to wystąpienie do elementu członkowskiego danych ciągu i zakończyć się niepowodzeniem z wyjątkiem.
Podczas pisania metody zwracającej listę znanych typów lub przekazując listę bezpośrednio do DataContractSerializer konstruktora, upewnij się, że kod, który przygotowuje listę, jest bezpieczny i działa tylko na zaufanych danych.
Jeśli określono znane typy w konfiguracji, upewnij się, że plik konfiguracji jest bezpieczny. Zawsze używaj silnych nazw w konfiguracji (określając klucz publiczny podpisanego zestawu, w którym znajduje się typ), ale nie określaj wersji typu do załadowania. Ładowarka typów automatycznie wybiera najnowszą wersję, jeśli to możliwe. Jeśli określisz konkretną wersję w konfiguracji, podejmujesz następujące ryzyko: Typ może mieć lukę w zabezpieczeniach, która może zostać naprawiona w przyszłej wersji, ale podatna wersja nadal jest ładowana, ponieważ jest jawnie określona w konfiguracji.
Posiadanie zbyt wielu znanych typów ma zaś kolejną konsekwencję: DataContractSerializer tworzy pamięć podręczną kodu serializacji/deserializacji w domenie aplikacji, zawierającą wpisy dla każdego typu, który musi być serializowany i deserializowany. Ta pamięć podręczna nigdy nie jest czyszczona tak długo, jak długo domena aplikacji jest uruchomiona. W związku z tym osoba atakująca, która zdaje sobie sprawę, że aplikacja używa wielu znanych typów, może spowodować deserializację wszystkich tych typów, powodując, że pamięć podręczna zużywa nieproporcjonalnie dużą ilość pamięci.
Zapobieganie wystąpieniu typów w stanie niezamierzonym
Typ może mieć ograniczenia spójności wewnętrznej, które muszą być wymuszane. Należy zachować ostrożność, aby uniknąć łamania tych ograniczeń podczas deserializacji.
Poniższy przykład typu reprezentuje stan blokady powietrza na statku kosmicznym i wymusza ograniczenie, że zarówno wewnętrzne, jak i zewnętrzne drzwi nie mogą być otwarte w tym samym czasie.
[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
Osoba atakująca może wysłać w ten sposób złośliwy komunikat, obejście ograniczeń i przejście obiektu do nieprawidłowego stanu, co może mieć niezamierzone i nieprzewidywalne konsekwencje.
<SpaceStationAirlock>
<innerDoorOpen>true</innerDoorOpen>
<outerDoorOpen>true</outerDoorOpen>
</SpaceStationAirlock>
Tę sytuację można uniknąć, zdając sobie sprawę z następujących kwestii:
Kiedy DataContractSerializer deserializuje większość klas, konstruktory nie są uruchamiane. Dlatego nie należy opierać się na żadnym zarządzaniu stanem realizowanym w konstruktorze.
Użyj wywołań zwrotnych, aby upewnić się, że obiekt jest w prawidłowym stanie. Wywołanie zwrotne oznaczone atrybutem OnDeserializedAttribute jest szczególnie przydatne, ponieważ jest uruchamiane po zakończeniu deserializacji i ma szansę sprawdzić i poprawić ogólny stan. Aby uzyskać więcej informacji, zobacz Version-Tolerant Serializacja i wywołania zwrotne.
Nie projektuj typów kontraktów danych, aby polegać na żadnej konkretnej kolejności, w której należy wywołać metody ustawiania właściwości.
Zachowaj ostrożność przy użyciu starszych typów oznaczonych atrybutem SerializableAttribute . Wiele z nich zostało zaprojektowanych do pracy ze zdalnym wywoływaniem w .NET Framework, do użytku tylko z zaufanymi danymi. Istniejące typy oznaczone tym atrybutem mogły nie zostać zaprojektowane z myślą o bezpieczeństwie stanu.
Nie należy polegać na IsRequired właściwości atrybutu DataMemberAttribute , aby zagwarantować obecność danych w zakresie bezpieczeństwa państwa. Dane mogą zawsze być
null
,zero
lubinvalid
.Nigdy nie ufaj grafowi obiektu deserializowanemu z niezaufanego źródła danych bez uprzedniego sprawdzania poprawności. Każdy pojedynczy obiekt może być w stanie spójnym, ale graf obiektów jako całość może nie być. Ponadto nawet jeśli tryb zachowywania grafu obiektów jest wyłączony, deserializowany graf może mieć wiele odwołań do tego samego obiektu lub mieć odwołania cykliczne. Aby uzyskać więcej informacji, zobacz Serializacja i Deserializacja.
Bezpieczne używanie narzędzia NetDataContractSerializer
Jest NetDataContractSerializer to silnik serializacji, który jest ściśle powiązany z typami. Jest to podobne do BinaryFormatter i SoapFormatter. Oznacza to, że określa typ do zainicjowania, odczytując assembly .NET Framework i nazwę typu z otrzymanych danych. Chociaż jest to część WCF, nie ma dostarczonego sposobu podłączenia tego mechanizmu serializacji; trzeba napisać kod niestandardowy. Jest NetDataContractSerializer
dostarczany głównie w celu ułatwienia migracji ze zdalnej komunikacji .NET Framework do WCF. Aby uzyskać więcej informacji, zobacz odpowiednią sekcję w temacie Serialization and Deserialization (Serializacja i deserializacja).
Ponieważ sam komunikat może wskazywać, że można załadować dowolny typ, NetDataContractSerializer mechanizm jest z natury niezabezpieczony i powinien być używany tylko z zaufanymi danymi. Aby uzyskać więcej informacji, zobacz Przewodnik po zabezpieczeniach BinaryFormatter.
Nawet w przypadku użycia z zaufanymi danymi, dane przychodzące mogą niewystarczająco określać typ do załadowania, zwłaszcza jeśli właściwość AssemblyFormat jest ustawiona na Simple. Każda osoba mająca dostęp do katalogu aplikacji lub globalnej pamięci podręcznej zestawów może zastąpić złośliwy typ zamiast tego, który ma zostać załadowany. Zawsze upewnij się, że bezpieczeństwo katalogu aplikacji oraz pamięci podręcznej zestawów globalnych jest zapewnione poprzez prawidłowe ustawienie uprawnień.
Ogólnie rzecz biorąc, jeśli pozwalasz częściowo zaufanemu kodowi na dostęp do wystąpienia NetDataContractSerializer
lub w inny sposób kontrolujesz selektor zastępczy (ISurrogateSelector) albo binder serializacji (SerializationBinder), kod może mieć znaczącą kontrolę nad procesem serializacji i deserializacji. Może na przykład wstrzyknąć dowolne typy, prowadzić do ujawnienia danych, manipulowania wynikowym grafem obiektów, serializacji danych albo przepełnienia wynikowego strumienia serializowanego.
Innym problemem z zabezpieczeniami NetDataContractSerializer
jest odmowa usługi, a nie złośliwe zagrożenie związane z wykonywaniem kodu. W przypadku korzystania z elementu NetDataContractSerializer
należy zawsze ustawić limit przydziału MaxItemsInObjectGraph na bezpieczną wartość. Łatwo jest utworzyć mały złośliwy komunikat, który przydziela tablicę obiektów, których rozmiar jest ograniczony tylko przez ten limit.
XmlSerializer-Specific zagrożenia
Model XmlSerializer zabezpieczeń jest podobny do DataContractSerializermodelu . Jednak kilka zagrożeń jest unikatowych dla elementu XmlSerializer.
XmlSerializer generuje zestawy serializacji w czasie wykonywania, które zawierają kod faktycznie dokonujący serializacji i deserializacji; te zestawy są tworzone w katalogu plików tymczasowych. Jeśli jakiś inny proces lub użytkownik ma prawa dostępu do tego katalogu, mogą zastąpić kod serializacji/deserializacji dowolnym kodem. Następnie XmlSerializer uruchamia ten kod przy użyciu kontekstu zabezpieczeń zamiast kodu serializacji/deserializacji. Upewnij się, że uprawnienia są poprawnie ustawione w katalogu plików tymczasowych, aby temu zapobiec.
Obiekt XmlSerializer ma również tryb, w którym używa wstępnie wygenerowanych zestawów serializacji zamiast generowania ich w czasie wykonywania. Ten tryb jest wyzwalany za każdym razem, gdy możliwe jest znalezienie odpowiedniego zestawu serializacyjnego. XmlSerializer sprawdza, czy zestaw serializacji został podpisany tym samym kluczem, który był użyty do podpisania zestawu zawierającego serializowane typy. Zapewnia to ochronę przed złośliwymi zestawami, które są przebrane za zestawy serializacji. Jeśli jednak zestaw zawierający serializowalne typy nie jest podpisany, XmlSerializer nie może wykonać tego sprawdzania i używa dowolnego zestawu z poprawną nazwą. Dzięki temu można uruchomić złośliwy kod. Zawsze podpisuj zestawy, które zawierają typy serializowalne, lub ściśle kontroluj dostęp do katalogu aplikacji oraz globalnej pamięci podręcznej zestawów, aby zapobiec wprowadzeniu złośliwych zestawów.
Obiekt XmlSerializer może podlegać atakowi typu "odmowa usługi". Obiekt XmlSerializer nie posiada limitu MaxItemsInObjectGraph
, tak jak to ma miejsce w obiekcie DataContractSerializer. W związku z tym deserializuje dowolną ilość obiektów, ograniczoną tylko przez rozmiar komunikatu.
Zagrożenia częściowego zaufania
Zwróć uwagę na następujące obawy dotyczące zagrożeń związanych z kodem uruchomionym z częściowym zaufaniem. Te zagrożenia obejmują złośliwy częściowo zaufany kod, a także złośliwy częściowo zaufany kod w połączeniu z innymi scenariuszami ataku (na przykład częściowo zaufany kod, który tworzy określony ciąg, a następnie deserializuje go).
W przypadku używania jakichkolwiek składników serializacji nigdy nie potwierdzaj żadnych uprawnień przed takim użyciem, nawet jeśli cały scenariusz serializacji mieści się w zakresie twojej asercji i nie masz do czynienia z żadnymi niezaufanymi danymi ani obiektami. Takie użycie może prowadzić do luk w zabezpieczeniach.
W przypadkach, gdy częściowo zaufany kod ma kontrolę nad procesem serializacji, za pośrednictwem punktów rozszerzalności (zastępczych), typów serializowanych lub za pośrednictwem innych środków, częściowo zaufany kod może spowodować, że serializator wyprowadzi dużą ilość danych do serializowanego strumienia, co może spowodować odmowę usługi (DoS) odbiorcy tego strumienia. W przypadku serializacji danych przeznaczonych dla elementu docelowego, który jest narażony na zagrożenia DoS, nie serializuj częściowo zaufanych typów ani w inny sposób nie pozwalaj kodowi częściowo zaufanemu kontrolować serializacji.
Jeśli zezwolisz na częściowo zaufany dostęp kodu do DataContractSerializer wystąpienia lub w inny sposób kontrolujesz zastępcze kontrakty danych, może to mieć dużą kontrolę nad procesem serializacji/deserializacji. Może na przykład wstrzyknąć dowolne typy, prowadzić do ujawnienia danych, manipulowania wynikowym grafem obiektów, serializacji danych albo przepełnienia wynikowego strumienia serializowanego. NetDataContractSerializer Równoważne zagrożenie zostało opisane w sekcji "Using the NetDataContractSerializer Securely" (Używanie bezpiecznego narzędzia NetDataContractSerializer).
DataContractAttribute Jeśli atrybut jest stosowany do typu (lub typu oznaczonego jako SerializableAttribute, ale nie ISerializable), deserializator może utworzyć instancję takiego typu, nawet jeśli wszystkie konstruktory są niepubliczne lub zabezpieczone wymogami.
Nigdy nie ufaj wynikowi deserializacji, chyba że dane do deserializacji są zaufane i masz pewność, że wszystkie znane typy są zaufanymi typami. Należy pamiętać, że znane typy nie są ładowane z pliku konfiguracji aplikacji (ale są ładowane z pliku konfiguracji komputera) podczas uruchamiania w częściowym zaufaniu.
Jeśli przekażesz DataContractSerializer wystąpienie z zastępczym dodanym do częściowo zaufanego kodu, kod może zmienić wszelkie ustawienia modyfikowalne dla tego zastępczego.
W przypadku obiektu deserializowanego, jeśli czytnik XML (lub zawarte w nim dane) pochodzi z kodu o częściowym zaufaniu, traktuj wynikowy obiekt deserializowany jako dane niezaufane.
Fakt, że typ ExtensionDataObject nie ma publicznych członków, nie oznacza, że dane w nim są bezpieczne. Jeśli na przykład deserializujesz z uprzywilejowanego źródła danych do obiektu, w którym znajdują się niektóre dane, przekaż ten obiekt do częściowo zaufanego kodu, częściowo zaufany kod może odczytać dane w
ExtensionDataObject
obiekcie, serializując obiekt. Rozważ ustawienie wartości IgnoreExtensionDataObject natrue
przy deserializacji z uprzywilejowanego źródła danych do obiektu, który następnie jest przekazywany do częściowo zaufanego kodu.DataContractSerializer i DataContractJsonSerializer obsługują serializację prywatnych, chronionych, wewnętrznych i publicznych członków przy pełnym zaufaniu. Jednak w przypadku częściowego zaufania tylko publiczni członkowie mogą być serializowani. Rzucany jest wyjątek SecurityException, jeśli aplikacja próbuje serializować element członkowski, który nie jest publiczny.
Aby zezwolić wewnętrznym lub chronionym wewnętrznym elementom na serializowanie przy częściowym zaufaniu, użyj atrybutu zestawu InternalsVisibleToAttribute. Ten atrybut umożliwia modułowi wykonawczemu zadeklarowanie, że jego wewnętrzne członkowskie elementy są widoczne dla innego modułu wykonawczego. W takim przypadku zestaw, który chce zserializować swoje elementy wewnętrzne, deklaruje, że udostępnia je dla System.Runtime.Serialization.dll.
Zaletą tego podejścia jest to, że nie wymaga ścieżki generowania kodu z podwyższonym poziomem uprawnień.
Jednocześnie istnieją dwie główne wady.
Pierwszą wadą jest to, że właściwość opt-in atrybutu InternalsVisibleToAttribute dotyczy całego zestawu. Oznacza to, że nie można określić, że tylko określona klasa może mieć jej wewnętrzne składowe zserializowane. Oczywiście nadal można zdecydować, aby nie serializować konkretnego wewnętrznego członka, po prostu nie dodając atrybutu DataMemberAttribute do tego członka. Podobnie deweloper może również zdecydować się na utworzenie członka wewnętrznego, a nie prywatnego lub chronionego, z niewielkimi obawami dotyczącymi widoczności.
Druga wadą jest to, że nadal nie obsługuje prywatnych ani chronionych członków.
Aby zilustrować użycie atrybutu InternalsVisibleToAttribute w częściowym zaufaniu, rozważ następujący program:
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"; } }
W powyższym przykładzie
PermissionsHelper.InternetZone
odpowiada PermissionSet dla częściowego zaufania. Bez atrybutu InternalsVisibleToAttribute teraz aplikacja ulegnie awarii, zgłaszając wyjątek SecurityException, wskazujący, że niepubliczni członkowie nie mogą być serializowani w kontekście częściowego zaufania.Jeśli jednak dodamy następujący wiersz do pliku źródłowego, program zostanie uruchomiony pomyślnie.
[assembly:System.Runtime.CompilerServices.InternalsVisibleTo("System.Runtime.Serialization, PublicKey = 00000000000000000400000000000000")]
Inne problemy związane z zarządzaniem stanem
Warto wspomnieć o kilku innych problemach dotyczących zarządzania stanem obiektu:
W przypadku korzystania z modelu programowania opartego na strumieniu z transportem strumieniowym przetwarzanie komunikatu następuje po nadejściu komunikatu. Nadawca wiadomości może przerwać operację wysyłania w środku strumienia, pozostawiając kod w nieprzewidywalnym stanie, jeśli oczekiwano większej ilości zawartości. Ogólnie rzecz biorąc, nie polegaj na zakończeniu strumienia i nie wykonuj żadnych działań w operacji opartej na strumieniu, których nie można cofnąć w przypadku przerwania strumienia. Dotyczy to również sytuacji, w której komunikat może być źle sformułowany po treści przesyłania strumieniowego (na przykład może brakować tagu końcowego dla koperty protokołu SOAP lub może mieć drugą treść komunikatu).
Użycie funkcji
IExtensibleDataObject
może spowodować ujawnienie poufnych danych. Jeśli akceptujesz dane z niezaufanego źródła do kontraktów danych zIExtensibleObjectData
, a następnie emitujesz je ponownie w bezpiecznym kanale, gdzie komunikaty są podpisywane, potencjalnie ręczysz za dane, o których nic nie wiesz. Ponadto ogólny stan, który wysyłasz, może być nieprawidłowy, jeśli uwzględnisz zarówno znane, jak i nieznane fragmenty danych. Unikaj tej sytuacji przez selektywne ustawienie właściwości danych rozszerzenia nanull
lub przez selektywne wyłączenieIExtensibleObjectData
funkcji.
Importowanie schematu
Zwykle proces importowania schematu do generowania typów odbywa się tylko w czasie projektowania, na przykład w przypadku używania narzędzia ServiceModel Metadata Tool (Svcutil.exe) w usłudze sieci Web w celu wygenerowania klasy klienta. Jednak w bardziej zaawansowanych scenariuszach schemat można przetwarzać w czasie wykonywania. Należy pamiętać, że może to narazić Cię na ryzyko odmowy usługi. Importowanie niektórych schematów może zająć dużo czasu. Nigdy nie używaj XmlSerializer składnika importu schematu w takich scenariuszach, jeśli schematy mogą pochodzić z niezaufanego źródła.
Zagrożenia specyficzne dla integracji ASP.NET AJAX
Gdy użytkownik implementuje WebScriptEnablingBehavior lub WebHttpBehavior, WCF udostępnia punkt końcowy, który może akceptować komunikaty XML i JSON. Istnieje jednak tylko jeden zestaw przydziałów czytników, używany zarówno przez czytnik XML, jak i czytnik JSON. Niektóre ustawienia limitu przydziału mogą być odpowiednie dla jednego czytnika, ale zbyt duże dla drugiego.
Podczas implementowania WebScriptEnablingBehavior
użytkownik ma możliwość uwidocznienia serwera proxy języka JavaScript w punkcie końcowym. Należy rozważyć następujące problemy z zabezpieczeniami:
Informacje o usłudze (nazwy operacji, nazwy parametrów itd.) można uzyskać, sprawdzając serwer proxy języka JavaScript.
W przypadku korzystania z punktu końcowego języka JavaScript poufne i prywatne informacje mogą być przechowywane w pamięci podręcznej przeglądarki internetowej klienta.
Uwaga dotycząca składników
WCF to elastyczny i dostosowywalny system. Większość treści tego tematu koncentruje się na najbardziej typowych scenariuszach użycia WCF. Można jednak tworzyć składniki WCF na wiele różnych sposobów. Ważne jest, aby zrozumieć implikacje zabezpieczeń związane z używaniem poszczególnych składników. W szczególności:
Jeśli musisz używać czytników XML, użyj czytników XmlDictionaryReader , które klasa udostępnia, w przeciwieństwie do innych czytników. Bezpieczne czytniki są tworzone przy użyciu CreateTextReader, CreateBinaryReader lub CreateMtomReader metod. Nie używaj Create metody . Zawsze konfiguruj czytelników przy użyciu bezpiecznych przydziałów. Mechanizmy serializacji w WCF są bezpieczne tylko wtedy, gdy są używane z bezpiecznymi czytnikami XML dostępnymi w WCF.
W przypadku używania DataContractSerializer elementu do deserializacji potencjalnie niezaufanych danych zawsze ustawiaj MaxItemsInObjectGraph właściwość .
Podczas tworzenia komunikatu ustaw parametr
maxSizeOfHeaders
, jeśliMaxReceivedMessageSize
nie zapewnia wystarczającej ochrony.Podczas tworzenia kodera zawsze należy skonfigurować odpowiednie przydziały, takie jak
MaxSessionSize
iMaxBufferSize
.Podczas korzystania z filtra wiadomości XPath ustaw wartość NodeQuota, aby ograniczyć liczbę węzłów XML, które filtr odwiedza. Nie używaj wyrażeń XPath, które mogą zająć dużo czasu do obliczenia bez odwiedzania wielu węzłów.
Ogólnie rzecz biorąc, w przypadku korzystania z dowolnego składnika, który akceptuje limit przydziału, należy zrozumieć jego wpływ na bezpieczeństwo i ustawić go na bezpieczną wartość.