Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
Zabezpieczanie aplikacji w ASP.NET z poziomu konfiguracji
Wstęp
Poniższy artykuł rozpoczyna serię wpisów na temat istniejących zabezpieczeń dostępnych z poziomu konfiguracji aplikacji zbudowanych z wykorzystaniem platformy ASP.NET, skierowaną przede wszystkim do wdrożeniowców i pentesterów.
Wdrożeniowcy odnajdą tutaj zwięzły przewodnik po właściwych ustawieniach zabezpieczeń aplikacji na produkcji wraz z krótkim wyjaśnieniem działania każdego z mechanizmów. Pentesterzy, wykonujący przegląd konfiguracji aplikacji opartej o ASP.NET po lekturze tekstu będą w stanie stwierdzić, jakie słabości dana konfiguracja kryje.
Także programiści znajdą tutaj coś dla siebie – część z opisywanych zabezpieczeń można wprowadzić jedynie z poziomu kodu źródłowego aplikacji.
Wyłączenie ustawień deweloperskich
W ASP.NET wyróżniamy następujące ustawienia deweloperskie związane z bezpieczeństwem: trace, debug i customErrors.
1. trace
Mając włączony mechanizm trace aplikacja odkłada odwołania do kolejnych stron. Po wejściu w szczegóły pojedynczego żądania HTTP do konkretnego zasobu widać informacje dotyczące tego żądania – dane przychodzące od użytkowników (m.in. ciasteczka, wartości trzymane w sesji, wartości z pól formularza), oraz informacje po stronie serwera (zmienne serwerowe, ścieżkę na dysku do konkretnej strony), które potencjalnie są przydatne atakującemu.
Aby dostać się do informacji z trace wystarczy przejść do strony Trace.axd (np. http://localhost/MyApp/Trace.axd).
Schemat wpisu w pliku web.config:
<trace enabled="true|false" localOnly="true|false" pageOutput="true|false" requestLimit="integer" mostRecent="true|false" writeToDiagnosticsTrace="true|false" traceMode="SortByTime|SortByCategory"/>
Domyślna konfiguracja:
<trace enabled="false" localOnly="true" mostRecent="false" pageOutput="false" requestLimit="10" traceMode="SortByTime" writeToDiagnosticsTrace="false"/>
Możliwe jest całkowite wyłączenie mechanizmu trace poprzez ustawienie:
<trace enabled="false"/>
Poniższe ustawienie:
<trace enabled="true" localOnly="false"/>
pozwala na dostęp do informacji z trace z dowolnej maszyny. Aby pozostawić dostęp tylko z poziomu samego serwera wystarczy usunąć atrybut localOnly.
Więcej informacji na temat ustawień trace można znaleźć tutaj:
http://msdn.microsoft.com/en-us/library/ie/6915t83k.aspx
2. debug
Ustawienie flagi debug=true wpiera proces diagnozowania błędów w aplikacji. Takie ustawienie nie powinno się pojawić w produkcyjnej konfiguracji, głównie ze względu na negatywny wpływ na wydajność aplikacji. Jeżeli wystąpi i dodatkowo mechanizm custom errors jest wyłączony, to poza dokładnymi informacjami o wyjątku pojawią się też fragmenty kodu źródłowego.
Domyślna konfiguracja:
<compilation debug="false" ...> ... </compilation>
Poprawnie powinno być tak:
<system.web> <compilation debug="false" ...> ... </compilation> </system.web>
Więcej informacji na temat ustawienia debug jest dostępnych tutaj:
http://aspnetresources.com/articles/debug_code_in_production
3. custom errors
Custom errors to mechanizm, który pozwala przy wystąpieniu błędu po stronie aplikacji przekierować użytkownika na specjalnie w tym celu przygotowaną stronę. Jeżeli przekierowanie jest nieskonfigurowane, natomiast mechanizm włączony (mode=”On”), to użytkownikowi jest wyświetlana dość lakoniczna informacja o błędzie i wiadomość o tym, że mechanizm custom errors jest włączony i co należy zrobić, aby go wyłączyć.
Przykład wyjątku z włączonym:
i wyłączonym mechanizmem custom errors.
W przypadku gdy mechanizm jest wyłączony, są prezentowane szczegóły wyjątku: dokładny stack trace, ścieżka na dysku do aplikacji. Aby nie straszyć użytkownika błędami oraz nie ujawniać szczegółów o nich, wystarczy z poziomu pliku web.config ustawić przykładowo:
<customErrors mode="On" />
lub:
<customErrors mode="On" redirectMode="ResponseRewrite" defaultRedirect="~/Error.aspx" />
jeżeli mamy specjalną stronę Error.aspx w głównym katalogu aplikacji zawierającą jedynie komunikat o tym, że wystąpił problem z aplikacją i opcjonalnie prośbę o kontakt z administratorem.
Schemat wpisu w pliku web.config:
<system.web> <customErrors defaultRedirect="url" mode="On|Off|RemoteOnly"> <error. . ./> </customErrors> </system.web>
Opcja RemoteOnly powoduje, że szczegóły błędu dostępne są jedynie na lokalnej maszynie, poza tym działa analogicznie jak On.
Domyślna konfiguracja:
<customErrors mode="RemoteOnly" />
Istnieje możliwość przekierowania na różne strony błędu w zależności od kodu odpowiedzi HTTP, przykład można znaleźć tutaj:
http://msdn.microsoft.com/en-us/library/h0hfz6fc.aspx
Więcej na temat popularnych błędów w konfiguracji:
http://weblogs.asp.net/dotnetstories/archive/2009/10/24/five-common-mistakes-in-the-web-config-file.aspx
Konfiguracja ciasteczek
Schemat konfiguracji ciasteczek w pliku web.config:
<httpCookies domain="String" httpOnlyCookies="true|false" requireSSL="true|false" />
Domyślna konfiguracja:
<httpCookies httpOnlyCookies="false" requireSSL="false" domain="" />
Aby zabezpieczyć ciasteczka przesyłane przez naszą aplikację należy rozważyć nadanie im następujących flag: httpOnly i secure.
1. flaga httpOnly
Oznaczenie ciasteczka flagą httpOnly sprawia, że dane ciasteczko nie będzie dostępne poprzez odwołanie z poziomu skryptów po stronie klienta (Javascript, VBScript). Pozwala to zabezpieczyć aplikację np. przed przejęciem ciasteczka z identyfikatorem sesji użytkownika przez atakującego przy wykorzystaniu podatności typu XSS.
Więcej na ten temat tutaj: http://msdn.microsoft.com/en-us/library/ms533046.aspx
Aby to osiągnąć wystarczy odpowiedni wpis w pliku web.config:
<system.web> <httpCookies httpOnlyCookies="true"> </system.web>
Przykład nagłówka odpowiedzi HTTP ustawiający ciasteczko z flagą httpOnly (google.pl):
-------------------------------- .... Set-Cookie: NID=577=Pav5HNGaDlTGcMaWPMjA1WiBOQADwXtq4b4-S2yT0XtsIA2_OouKx-FhDRTGH2pLTyJwHo3UkihWROKrgEkDp9BqIospmq5GGeI60BkGBU9u2BsxSNOLWaL6Ml457D; expires=Tue, 14-Aug-2012 09:58:16 GMT; path=/; domain=.google.pl; HttpOnly .... --------------------------------
2. flaga secure
Atrybut secure w nagłówku Set-Cookie według specyfikacji oznacza, że klient (przeglądarka) powinien skorzystać z bezpiecznego sposobu przesyłania go z powrotem na serwer. W praktyce oznacza to, że wysyłany jest przez przeglądarkę tylko z wykorzystaniem SSL. Aby skonfigurować aplikację tak, żeby dodawała ten atrybut należy ustawić opcję requireSSL.
Część stron wykorzystuje https tylko do logowania użytkownika, natomiast po poprawnym zalogowaniu przechodzi na http.
W przypadku przesyłania cookie sesji takiej strony (nie ma atrybutu secure) poprez sieć niezaufaną, np. niezabezpieczone WIFI w kawiarni, istnieje możliwość, że zostanie ono wykradzione i wykorzystane do podszycia się pod użytkownika. Świetnym przykładem jak i przestrogą jest tutaj aplikacja FireSheep (http://en.wikipedia.org/wiki/Firesheep).
<system.web> <httpCookies requireSSL="true"> </system.web>
W przypadku korzystania z FormsAuthentication sytuacja wygląda analogicznie:
<authentication mode="Forms"> <forms requireSSL="true" ... /> </authentication>
Przykład nagłówka ustawiający ciasteczko z flagą secure (owasp.org):
-------------------------------- .... Set-Cookie: wiki_session=dkvtgi2ijsqd5lkee39r35ndhp0; path=/; secure; HttpOnly .... --------------------------------
Więcej informacji na temat ochrony ciasteczek można znaleźć tutaj:
https://www.owasp.org/index.php/HttpOnly
http://www.codinghorror.com/blog/2008/08/protecting-your-cookies-httponly.html
http://www.dotnetnoob.com/2010/11/how-to-secure-aspnet-cookies.html
http://www.ietf.org/rfc/rfc2109.txt
Zabezpieczenie ViewState
Protokół HTTP jest bezstanowy, w związku z czym powstał szereg mechanizmów, który pozwala w jakiś sposób zasymulować stan w komunikacji po HTTP – w przypadku ASP.NET są to m.in. mechanizm ViewState i mechanizm sesji. Mechanizm sesji pozwala przechowywać zmienne związane z danym użytkownikiem po stronie serwera. Obiekt sesji jest łączony z konkretnym użytkownikiem poprzez odpowiednie ciasteczko, w związku z czym dostęp do zmiennych w sesji jest możliwy z różnych stron naszej aplikacji.
ViewState pozwala natomiast na przechowanie stanu elementów danej strony aspx tylko pomiędzy kolejnymi jej postback’ami. Postback oznacza wysłanie formularza z przeglądarki na serwer, który jako wynik zwraca tą samą stronę. W odróżnieniu od sesji, wartość ViewState jest przesyłana cały czas pomiędzy przeglądarką a serwerem.
ViewState w uproszczeniu to kolekcja par klucz wartość, ViewState formularza jest sumą kolekcji ViewState kontrolek w hierarchii formularza.
Mechanizm ViewState realizowany jest domyślnie poprzez ukryte pole formularza o nazwie __VIEWSTATE, którego wartość jest po stronie serwera serializowana z wykorzystaniem klasy LosFormatter (limited object serialization) oraz kodowana z wykorzystaniem Base64.
Aby zobaczyć jak wygląda Viewstate, z poziomu prostej strony Default.aspx do Viewstate dodajemy jedną wartość (z poziomu code behind):
ViewState["secret"] = "my presious"
Po otwarciu strony w przeglądarce mamy w kodzie html strony:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUENTM4MQ8WAh4Gc2VjcmV0BQtteSBwcmVzaW91c2Rk" />
Po zrobieniu Base64 decode otrzymujemy:
Narzędzie Burp Suite pozwala zobaczyć sparsowany ViewState, gdzie widzimy dokładnie nasz element dodany z kodu. Dodatkowo mamy informację, że MAC nie jest włączone, ale to tym za moment.
Zabezpieczenie ViewState przed możliwością modyfikacji jest istotne wtedy, gdy znajdują się w nim wartości którymi użytkownik nie powinien manipulować. ViewState niezabezpieczony może zostać przeparsowany, a interesująca atakującego wartość łatwo zmodyfikowana. Aby zabezpieczyć się przed tym należy skorzystać z opcji enableViewStateMac, natomiast jeżeli chcemy dodatkowo ukryć samą treść ViewState przed możliwością odczytania, korzystamy z szyfrowania ViewState – ustawienia viewStateEncryptionMode.
Schemat konfiguracji :
<pages ... enableViewStateMac="[True|False]" viewStateEncryptionMode="[Always|Auto|Never]" ... >
Domyślna konfiguracja:
<pages ... enableViewStateMac="true" viewStateEncryptionMode="Auto" ... </pages>
1. enableViewStateMac
Opcja enableViewStateMac ustawiona na true powoduje, że dla ViewState’u wyliczany jest MAC (Machine Authentication Code), a następnie dołączany na jego końcu. Kiedy wartość ViewState jest przez przeglądarkę odsyłana na serwer, ASP.NET ponownie wylicza MAC i jeżeli MAC przysłany razem z ViewState i nowo wyliczony się różnią, otrzymana wartość ViewState nie jest brana pod uwagę. Pozwala to zapobiec próbom modyfikacji wartości ViewState przez potencjalnego atakującego.
Aby ASP.NET dołączał i sprawdzał MAC dla ViewState należy ustawić w pliku web.config:
<system.web> <pages ... enableViewStateMac="true" ... </pages> </system.web>
Przykład z początku paragrafu z enableViewStateMac=”true” :
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUENTM4MQ8WAh4Gc2VjcmV0BQtteSBwcmVzaW91c2Rk3tR+yg6/2maxiEtvzxcAidrUCWjUZRV2vx+uounE27g=" />
Zdekodowany:
Sprasowany:
Możemy co prawda nadal zobaczyć, co się znajduje w kolekcji ViewState, natomiast jego modyfikacja w sytuacji, gdy mamy np. formularz przy jego submicie spowoduje wystąpienie wyjątku typu ViewStateException.
2. viewStateEncryptionMode
Ustawienie viewStateEncryptionMode określa sytuacje kiedy ViewState jest szyfrowany:
# Always – zawsze
# Never – nigdy
# Auto – jest szyfrowany, jeżeli kontrolka tego zażąda (poprzez wywołanie metody Page.RegisterRequiresViewStateEncryption())
W celu włączenia szyfrowania należy w pliku web.config ustawić:
<system.web> <pages ... viewStateEncryptionMode="Always" ... </pages> </system.web>
Nasz przykład z włączonym viewStateEncryptionMode=”Always” :
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="lWAQeD9//qD9i/ux3HT2VKHxY2pchUPv0Aj5Kj9K25IZ1oO6EFISlot8xLcC3LA4VH+CDrFJrG8wqXjCdoUy/Xg2LF79ffn/lmv2+xApJyT5OwmbnqcXVPltYXVpwZ7u0gtWW6j+8tQSB44sDVT66Q==" />
Próba zrobienia Base64 decode:
Próba sparsowania:
Teraz mamy ViewState w formie całkowicie nieczytelnej.
ViewState jest szyfrowany z wykorzystaniem algorytmu skonfigurowanego w ustawieniu validation w sekcji machineKey, z wykorzystaniem klucza ustawionego we właściwości decryptionKey. Podczas liczenia MAC dla ViewState wykorzystywany jest klucz z opcji validationKey. Przykładowo, jeżeli jako validation będzie wybrany algorytm AES, to zostanie użyty do szyfrowania ViewState, natomiast do walidacji ViewState (MAC) zostanie wykorzystany HMACSHA1.
Fragment schematu konfiguracji:
<system.web> <machineKey ... validationKey="AutoGenerate|value[,IsolateApps]" decryptionKey="AutoGenerate|value[,IsolateApps]" validation="HMACSHA256" [SHA1 | MD5 | 3DES | AES | HMACSHA256 | HMACSHA384 | HMACSHA512 | alg:algorithm_name] ... /> </system.web>
Zarówno validationKey jak i decyptionKey mogą być generowane losowo, jak i możemy, bezpośrednio podać wartość. Dodatkowo, przy generacji podając opcję IsolateApps klucze będą generowane per aplikacja.
Istnieje jeszcze możliwość zabezpieczenia ViewState w taki sposób, aby był inaczej szyfrowany dla różnych użytkowników. Natomiast to jest do zrealizowania tylko z poziomu kodu aplikacji (wykorzystanie właściwości Page.ViewStateUserKey).
Ustawienie to pozwala zabezpieczyć aplikację przed atakami typu Cross Site Request Forgery. Problematyka ataku CSRF poruszona jest szerzej tutaj:
http://www.troyhunt.com/2010/11/owasp-top-10-for-net-developers-part-5.html
Więcej informacji na temat ViewState i konfiguracji machineKey:
http://msdn.microsoft.com/en-us/library/ms972976.aspx
http://msdn.microsoft.com/en-us/library/ms178199(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/950xf363.aspx
http://aspnetresources.com/articles/ViewState
http://msdn.microsoft.com/en-us/library/ff649308.aspx
http://msdn.microsoft.com/en-us/library/w8h3skw9.aspx
http://msdn.microsoft.com/en-us/library/bb386448.aspx
Ukrywanie nagłówków odpowiedzi HTTP charakterystycznych dla ASP.NET
Poniższy przykład przedstawia najczęściej występujące nagłówki odpowiedzi HTTP przesyłane przez aplikacje stworzone z wykorzystaniem ASP.NET:
---------------------------- HTTP/1.1 200 OK ... Server: Microsoft-IIS/7.5 X-AspNet-Version: 2.0.50727 X-Powered-By: ASP.NET X-AspNetMvc-Version: 1.0 -----------------------------
Są to nagłówki serwera IIS, oraz nagłówki związane z samym ASP.NET. Nagłówki te same w sobie stanowią bardziej narzut na ruch sieciowy niż zagrożenie jako takie, niemniej jednak ujawniają informacje na temat wykorzystywanych wersji serwera IIS czy ASP.NET, więc warto zadbać o to aby nasza aplikacja ich nie wysyłała. Poniżej są zaprezentowane sposoby usunięcia każdego z nich.
1. X-AspNet-Version
Aby pozbyć się tego nagłówka należy dodać w pliku web.config następujący wpis:
<system.web> <httpRuntime enableVersionHeader="false" /> </system.web>
2. X-AspNetMvc-Version
Ten nagłówek oznacza wersję ASP.NET MVC z jakiej korzysta nasza aplikacja. W przypadku tego nagłówka nie ma możliwości wycięcia go na poziomie konfiguracji, można to zrobić tylko z poziomu kodu aplikacji.
Aby tego dokonać należy z poziomu Global.asax dodać w Application_Start linijkę:
MvcHandler.DisableMvcResponseHeader = true;
3. X-Powered-By
Z poziomu IIS Managera wchodzimy we właściwości strony i znajdujemy ustawienie: Nagłówki odpowiedzi HTTP, a następnie usuwamy go z listy.
4. Server
Aby ten nagłówek usunąć konieczne są dwie zmiany. Pierwsza – zainstalowanie narzędzia UrlScan i skonfigurowanie w pliku UrlScan.ini opcji RemoveServerHeader=1. Więcej na temat narzędzia UrlScan nieco później.
W przypadku poprawnych żądań do strony to wystarczy aby ukryć nagłówek Server, natomiast gdy do aplikacji zostanie wysłane nieprawidłowe żądanie (zwracające w odpowiedzi 400 BadRequest), to pojawi się nam ponownie nagłówek Server, tym razem o wartości Microsoft-HTTPAPI/2.0.
--------------------------------------------- HTTP/1.1 400 Bad Request Content-Type: text/html; charset=us-ascii Server: Microsoft-HTTPAPI/2.0 Date: Mon, 13 Feb 2012 22:05:19 GMT Connection: close Content-Length: 300 ---------------------------------------------
Aby i ten nagłówek się nie pojawił trzeba wyedytować klucz w rejestrze:
HKLM\SYSTEM\CurrentControlSet\Services\HTTP\Parameters\DisableServerHeader
nadając mu wartość 1.
Fragment odpowiedzi zwracającej kod 400 przy modyfikacji rejestru:
------------------------------------------- HTTP/1.1 400 Bad Request Content-Type: text/html; charset=us-ascii Date: Mon, 13 Feb 2012 21:51:41 GMT Connection: close Content-Length: 300 ... ------------------------------------------
Ukrycie nagłówków można też osiągnąć w sposób programistyczny, opis dostępny tutaj:
http://consultingblogs.emc.com/howardvanrooijen/archive/2009/08/25/cloaking-your-asp-net-mvc-web-application-on-iis-7.aspx
Więcej informacji na temat tego, czemu warto je ukryć można znaleźć tutaj:
http://www.troyhunt.com/2012/02/shhh-dont-let-your-response-headers.html
Checklista
- Wyłącz ustawienia deweloperskie – trace (enabled=”true”), debug (debug=”false”)
- Włącz mechanizm custom errors (mode=”On” lub „RemoteOnly”)
- Sprawdź, czy:
- ciasteczka nie potrzebne z poziomu skryptów mają ustawione httpOnly (httpOnlyCookies=”true”),
- przy wykorzystaniu https mają ustawione requireSSL=”true”
- Włącz szyfrowanie ViewState (viewStateEncryptionMode=”Always”) i opcję dołączania do ViewState MAC (enableViewStateMac=”true”)
- Ukryj nagłówki charakterystyczne dla ASP.NET i serwera IIS
Podsumowanie
W artykule omówiliśmy ustawienia deweloperskie, które powinny być wyłączone w produkcyjnej konfiguracji aplikacji, później omówiliśmy zabezpieczenie danych trzymanych w ViewState, opisaliśmy także ciasteczka charakterystyczne dla ASP.NET i sposoby na ich wyłączenie. W kolejnej części przejdziemy m.in. do ukrycia informacji na temat usług sieciowych i serwisów WCF, poznamy także dokładniej możliwości narzędzia UrlScan.