Na ostatnim spotkaniu OWASP w Krakowie jedna z prezentacji dotyczyła zapobiegania XSS w aplikacjach tworzonych w ASP.NET. Z prezentacją można zapoznać się tutaj: Defending ASP.Net apps against XSS. Ja chciałem z kolei zwrócić uwagę na pewną niekonsekwencję w zachowaniu platformy, która może doprowadzić do niepożądanych skutków, czyli do XSS.
Niekonsekwencje w ASP.NET
W ramach przykładu popatrzmy na trzy kontrolki:
Kontrolka TextBox
Zacznijmy od kontrolki TextBox, czyli normalnego pola tekstowego, w które użytkownik może wpisać dane. Jest ona również wykorzystywana do prezentacji danych, zarówno w celu ich edycji, jak i w trybie tylko do odczytu. Spróbujmy w tym przypadku:
- przypisać jej wartość,
- przypisać jej atrybut,
Przykładowy fragment kodu:
TextBox2.Text = TextBox1.Text; TextBox2.Attributes.Add("attr1", TextBox1.Text); TextBox2.Attributes.Add(TextBox1.Text, "val1");
W ramach payloadu testowego wykorzystam tym razem coś takiego:
pmq'"<>
Jak wygląda kod HTML zwrócony do klienta? Tak:
<input name="ctl00$MainContent$TextBox2" type="text" value="pmq'"<>" id="MainContent_TextBox2" attr1="pmq'"<>" pmq'"<>="val1" />
Rozbijmy go na poszczególne fragmenty.
Po pierwsze wartość przypisana do Text jest prawidłowo kodowana na wyjściu, fragment kodu HTML wygląda następująco:
value="pmq'"<>"
W tym przypadku programista nie musi troszczyć się o właściwy encoding danych na wyjściu. Analogicznie sytuacja wygląda w przypadku, gdy dodaje on atrybut. Wartość atrybutu jest kodowana, co pokazuje ten fragment kodu:
attr1="pmq'"<>"
W tym samym miejscu jest jednak pewna niespodzianka, o oczyszczenie/zakodowanie nazwy atrybutu programista musi zadbać sam, bo inaczej może spotkać go coś takiego:
pmq'"<>="val1"
Można argumentować, że sytuacja, w której atakujący kontroluje nazwę atrybutu nie jest powszechna. Zgoda, ale tym większe prawdopodobieństwo, że jeśli już taka konstrukcja zostanie zastosowana, to programista przyzwyczajony do tego, że o encoding troszczyć się nie musi, nie zastosuje go również w tym przypadku. A kontekst jest o tyle przyjemny (dla atakującego), że mechanizm ValidateRequest na niewiele się tutaj przyda (przy okazji polecam: Bypassing ValidateRequest in ASP.NET przy czym w tym kontekście bypass nie jest potrzebny).
Kontrolka Label
Co jest nie tak z kontrolką Label? Służy często do wypisania tekstu na stronę, w szczególności jest ona używana jako zamiennik kontrolki TextBox w sytuacjach, gdy dane mają być wyświetlone w trybie tylko do odczytu i gdy zastosowanie TextBox z wyłączoną edycją nie jest najlepszym pomysłem (na przykład stronę do wydruku).
W przypadku tej kontrolki zróbmy tylko jeden test, spróbujmy wypisać dane przy pomocy takiego kodu:
Label1.Text = TextBox1.Text;
Kod jest identyczny, jak w przypadku kontrolki TextBox. A rezultat? Trochę inny:
<span id="MainContent_Label1">pmq'"<></span>
Jak widać w tym przypadku to programista musi zadbać o odpowiednie zakodowanie znaków na wyjściu. Pojawia się pewna niekonsekwencja między różnymi kontrolkami. Ten sam sposób wypisania danych może być zarówno bezpieczny, jak i niebezpieczny, w zależności od wykorzystanej kontrolki.
W tym przypadku warto ponownie zwrócić uwagę na kontekst. Akurat tutaj mechanizm ValidateRequest może okazać się wystarczającym zabezpieczeniem, ale oczywiście nie musi. Ponownie nie chodzi mi tutaj o możliwość jego obejścia (np. w sposób opisywany we wcześniej wspominanym artykule), ale o to, że nie zawsze dane wchodzą do aplikacji tylko jedną ścieżką i nie na każdej ścieżce działa ValidateRequest. Przykładowo może istnieć XSS, jeśli:
- dane są wprowadzane przez WebService,
- dane są pobierane z importowanego pliku (np. import z XML),
- wypisywane dane są "składane" z kilku pól,
Jeśli zajrzeć w dokumentację, znajdzie się tam następujący fragment:
This control can be used to display user input, which is a potential security threat. By default, ASP.NET Web pages validate that user input does not include script or HTML elements.
Nie lubię tego typu zagmatwanych komunikatów, bo na dobrą sprawę po jego przeczytaniu można mieć poważne wątpliwości, czy i w jakich przypadkach problem istnieje. Z moich doświadczeń wynika, że w 100% przypadków, w których byłem w stanie w pełni kontrolować dane przekazywane do kontrolki Label, miałem tam XSS.
Kontrolka HyperLink
Na koniec kontrolka HyperLink. W tym przypadku kod wygląda następująco:
HyperLink1.Text = TextBox1.Text; HyperLink1.Target = TextBox1.Text; HyperLink1.NavigateUrl = TextBox1.Text; HyperLink1.Attributes.Add("attr1", TextBox1.Text);
Wykonuje on kolejno:
- ustawienie tekstu odnośnika,
- ustawienie atrybutu target (okna, w którym ma otworzyć się odnośnik),
- ustawienie adresu (atrybutu href),
- dodanie wartości atrybutu o nazwie attr1
Wygenerowany kod wygląda następująco:
<a id="MainContent_HyperLink1" attr1="pmq'"<>" href="pmq'"<>" target="pmq'"<>">pmq'"<></a>
Przypisanie wartości (tekstu) zachowuje się dokładnie tak samo, jak w przypadku kontrolki Label, czyli programista sam musi zadbać o właściwy encoding. Ciekawsze rzeczy dzieją się w przypadku atrybutów. Ustawiane były trzy atrybuty:
- href,
- target,
- attr1,
Oczekiwać można, że we wszystkich trzech przypadkach zachowanie kontrolki będzie takie samo. Jest nieco inaczej:
attr1="pmq'"<>" href="pmq'"<>" target="pmq'"<>"
Jak widać wartość atrybutu jest prawidłowo kodowana w przypadku atrybutu attr1 oraz href, w przypadku atrybutu target natomiast żaden encoding nie ma miejsca. WTF? Co więcej w dokumentacji dla HyperLink.Target nie widzę nawet jednego zdania, które mogłoby sugerować takie zachowanie.
Przy okazji trafiłem na ciekawy wątek związany ogólnie z atrybutami: .Net 4.0 Attributes.Add encoding bug. Wynika z niego, że encoding atrybutów dodawanych przez Attributes.Add pojawił się w ASP.NET 4.0 i, niespodzianka, spowodowało to trochę problemów.
Swoją drogą programiści wypowiadający się w tym wątku to jakieś mięczaki. Przecież problem można rozwiązać w trywialny sposób, wystarczy dać następującą nazwę atrybutu oraz jego wartość:
String name = "onclick=\\\"alert('Works!')\\\" foo"; String value = ""; (...).Attributes.Add(name, value);
Co się ślicznie przetłumaczy do postaci (przykład z testowego kodu, istotny pogrubiony fragment, wyciąłem trochę nieistotnej zawartości):
<input value="onclick="alert('Works!')" foo" onclick="alert('Works!')" foo="val1" />
Twardym trzeba być :)
Podsumowanie
To, co pokazałem, to oczywiście tylko mały wycinek tematu. Chodziło mi o pokazanie pewnego braku konsekwencji w ASP.NET, który może okazać się bolesny w skutkach. Gdyby przyjrzeć się dokładniej poszczególnym kontrolkom, może okazać się, że występują bardziej realistyczne przypadki. Rozumiem przez to takie sytuacje, w których istnieje większe prawdopodobieństwo, że wypisane zostaną dane pochodzące od klienta, bo przyznaję, że zarówno w przypadku nazwy atrybutu czy atrybutu target dla kontrolki HyperLink taka sytuacja jest umiarkowanie prawdopodobna.
Istniejący w ASP.NET mechanizm ValidateRequest czasami jest zabezpieczeniem wystarczająco skutecznym. Powtarzam: czasami, ponieważ:
- payload może nie wymagać wychwytywanych przez ValidateRequest ciągów znaków,
- może istnieć bypass tego mechanizmu,
- dane wchodzą do systemu różnymi ścieżkami, w szczególności niechronionymi przez ValidateRequest,
- dane wchodzą do systemu w postaci zakodowanej,
Porównując ilość podatności typu XSS w aplikacjach tworzonych w oparciu o ASP.NET i w tych tworzonych z wykorzystaniem innych technologii, ASP.NET prezentuje się całkiem nieźle. To nie znaczy jednak, że istniejące w platformie mechanizmy ochrony zwalniają od myślenia o ochronie przed XSS.
Jak szybko testować? Po raz kolejny z pomocą przychodzi mój "ulubiony" payload (tym razem właściwie dwa):
pmq'"<> pmq'"<A>
Jeśli na wyjściu pojawi się pierwszy payload, to mamy problem, ale prawdopodobnie ValidateRequest nas ocali. Jeśli na wyjściu pojawi się drugi payload, mamy dziurę. Różnica jest taka, że drugi payload nie ma prawa wejść do aplikacji z aktywnym ValidateRequest. Jeśli wejdzie, mechanizm ten dla aplikacji, lub tej strony, został wyłączony. Oczywiście pomijam tutaj inne możliwe ścieżki wprowadzenia tego typu payloadu do aplikacji (patrz wyżej).
Swoją drogą myślę, że ciekawym eksperymentem mogłoby być zastosowanie podejścia secure by default. W tym kontekście mogłoby to oznaczać domyślne stosowanie encodingu w każdym przypadku, oraz dostarczenie programistom wygodnej metody wypisania danych "surowych", jeśli rzeczywiście jest taka potrzeba. Tu aż prosi się by wspomnieć o ctemplate i Auto Escape (tak, mało związane z ASP.NET).
Ten temat po części już poruszałem we wpisie Niekonsekwencje w ASP.NET, ale postanowiłem do niego wrócić. Część rzeczy się powtórzy, więc ewentualne uczucie Deja vu jest uzasadnione. Chodzi o to, w jakich przypadkach ASP.NET sam zastosuje odpowiedni encod
Przesłany: May 12, 11:26