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 encoding, a w jakim programista musi sam o to zadbać.
Potencjalne XSSy w ASP.NET
Tym razem sprawdziłem zachowanie ASP.NET dla następujących kontrolek:
- Button
- CheckBox
- DropDownList
- HiddenField
- HyperLink
- Image
- ImageButton
- Label
- LinkButton
- ListBox
- Literal
- TextBox
- RadioButton
- Table (i pośrednio TableRow i TableCell)
Ograniczę się tutaj do pokazania przykładów, w których mamy do czynienia z potencjalnym XSS. Nie twierdzę, że przetestowałem każdy potencjalny scenariusz użycia tych kontrolek, kilka podstawowych scenariuszy to rzucenie okiem jednak chyba objęło.
Kontrolki
Najpierw kilka przypadków, w których wymienione kontrolki mają potencjalny problem z XSS.
Control.ID
Pierwszy potencjalny XSS to Control.ID (i oczywiście inne kontrolki, które po Control dziedziczą). Wygląda to tak (najpierw w kodzie strony):
Button1.ID = value;
A potem w generowanym HTML (fragment):
<input type="submit" name="pmq'"<>" value="pmq'"<>" id="pmq'"<>"
Jak widać wartości atrybutów name oraz id, które są w ten sposób ustawiane, nie są prawidłowo encodowane. Skutkiem jest XSS.
W tym przypadku sytuacja jest o tyle śmieszna, że zgodnie z dokumentacją ta wartość jest nieprawidłowa:
Only combinations of alphanumeric characters and the underscore character ( _ ) are valid values for this property. Including spaces or other invalid characters will cause an ASP.NET page parser error.
AttributeCollection.Add
Ten problem występuje w przypadku wielu elementów/kontrolek. Jeśli dodajemy atrybuty korzystając z funkcji Add, to mamy potencjalny problem:
public void Add( string key, string value )
Jak się okazuje, wartość value jest kodowana automatycznie, wartość key już nie. Właściwie w tym miejscu trudno mówić o tym, że nazwa atrybutu powinna być kodowana, lepszym rozwiązaniem byłoby chyba odrzucanie nieprawidłowych nazw. Ponownie fragmenty kodu:
Button1.Attributes.Clear(); Button1.Attributes.Add(value, "foo"); Button1.Attributes.Add("foo", value);
I wygenerowany kod HTML (fragment):
pmq'"<>="foo" foo="pmq'"<>" />
Moim zdaniem takie zachowanie należy uznać za błąd w ASP.NET. W specyfikacji HTML istnieją ograniczenia odnośnie tego, jakie nazwy są prawidłowe dla atrybutów, w kodzie powinno być więc sprawdzane, czy przekazana w parametrze key wartość spełnia te reguły, a jeśli nie - nie powinna ona być wykorzystana.
CheckBox.ID oraz CheckBox.Text i RadioButton.ID oraz RadioButton.Text
W tym przypadku problem ujawnia się w następującym fragmencie wygenerowanego kodu HTML:
<label for="pmq'"<>">pmq'"<></label>
W tym przypadku chodzi o dwie rzeczy. Po pierwsze kodowana nie jest wartość atrybutu for, po drugie wartość label (która przychodzi z CheckBox.Text) nie jest kodowana. W efekcie dwa dodatkowe potencjalne miejsca na XSS.
Pierwsze miejsce, atrybut for jest, moim zdaniem, ewidentnym błędem w ASP.NET, który wynika z wcześniej pokazywanego problemu z atrybutami id oraz name. W przypadku tagu label w atrybucie for podaje się identyfikator/nazwę tagu, dla którego ten opis jest przeznaczony. Prezentowany tutaj payload nie jest właściwą wartością id/name, więc nie powinna się w tym kontekście pojawić. A jeśli już, to powinna być zakodowana w sposób właściwy dla kontekstu, czyli dla atrybutu HTML.
Drugie miejsce to wartość (treść) opisu (labela) dla kontrolki. Jest ona wypisywana bez encodingu. Można dyskutować, czy to dobrze, czy źle. Teoretycznie ktoś mógłby chcieć wypisać w tym miejscu HTML po to, by konkretny label rzucał się bardziej w oczy. Z drugiej strony pojawia się niekonsekwencja w zachowaniu w porównaniu choćby do TextBox.Text, gdzie dane w(y)pisywane w ten sposób są kodowane w sposób prawidłowy dla kontekstu i siłą rzeczy XSS nie ma.
Identycznie jak CheckBox zachowuje się również RadioButton.
HyperLink.Target
To też jest sprawa, o której pisałem poprzednio. Jeśli z poziomu kodu ustawimy atrybut target w sposób następujący:
HyperLink1.Target = value;
To możemy skończyć z następującym fragmentem kodu HTML:
target="pmq'"<>">
Dane są wypisywane w kontekście atrybutu i nie są one kodowane w sposób prawidłowy. Szczerze mówiąc nie rozumiem dlaczego akurat jest to zrobione w ten sposób. Z mojego punktu widzenia jest to błąd w ASP.NET.
HyperLink.NavigateUrl
W tym przypadku dane wypisywane są z zastosowaniem odpowiedniego encodingu, ale jeśli atakujący kontroluje całość tego parametru, może on wstawić tam na przykład taką wartość:
javascript:alert('xss');
Tu jednak trudno mówić o błędzie w samym ASP.NET, jest to sprawa, o której pamiętać powinien programista. Jeśli w takiej kontrolce wypisujemy dane pochodzące od użytkownika, trzeba bardzo dokładnie sprawdzać, czy są to dane dozwolone. Nie jest to zadanie trywialne, z góry ostrzegam.
Label.Text, LinkButton.Text, HyperLink.Text, LinkButton.Text, Literal.Text
We wszystkich wymienionych tutaj przypadkach dane w(y)pisane przez Text pojawiają się w kontekście HTML bez encodingu.
Można dyskutować, czy jest to błąd, czy raczej daje to programiście elastyczność w zakresie tego, co będzie w stanie wypisać. Moim zdaniem w przypadku kontrolek:
- Label
- LinkButton
- HyperLink
- LinkButton
można byłoby z powodzeniem stosować domyślnie właściwe kodowanie.
Kontrolka Literal natomiast z powodzeniem mogłaby być wykorzystywana w sytuacji, w której ktoś musiałby wypisać bezpośrednio jakiś kod HTML. Ta kontrolka poza tym, co ma ustawione w Text nie wypisuje do HTML nic innego. Tu domyślny brak encodingu jestem w stanie zrozumieć.
Table.Caption, TableCell.Text
W tym przypadku również dane wypisywane są w kontekście HTML bez jakiegokolwiek encodingu. Znów można dyskutować, czy jest to zachowanie pożądane, czy raczej domyślnie powinien być stosowany encoding.
Table.BackImageUrl
Na koniec ciekawostka. Istnieje możliwość ustawienia obrazu tła dla tabeli. W kodzie HTML wygląda to tak (tym razem payload wyglądał następująco:
pmq'"<>()
Kod wstawiający tło:
Table1.BackImageUrl = value;
I wynikowy kod HTML:
style="background-image:url(pmq'"<>());">
Co to powoduje? Można modyfikować tą drogą styl tabeli, a i być może jeszcze jakiś wektor na XSS przez CSS zadziała.
Co z ValidateRequest?
Jak wiadomo istnieje coś takiego, jak ValidateRequest. Jest to podejście oparte na czarnej liście, sprawdza czy po znaku < nie pojawia się litera, lub jeden z kilku innych zakazanych znaków. W efekcie nawet jeśli dane są wypisywane w kontekście HTML bez encodingu, to prawdopodobnie ValidateRequest powstrzyma atak.
Z wymienionych wcześniej miejsc, ValidateRequest prawdopodobnie (prawdopodobnie, bo są sytuacje, w których ValidateRequest można obejść) powstrzyma XSS w przypadku:
- CheckBox.Text
- RadioButton.Text
- Label.Text
- LinkButton.Text
- HyperLink.Text
- LinkButton.Text
- Literal.Text
- Table.Caption
- TableCell.Text
Mechanizm ValidateRequest jest jednak kompletnie bezradny/bezużyteczny w przypadkach:
- Control.ID
- AttributeCollection.Add
- HyperLink.Target
- HyperLink.NavigateUrl
- Table.BackImageUrl
Jak się bronić
Ponownie odsyłam do Microsoft Web Protection Library i funkcji:
- Encoder.HtmlAttributeEncode
- Encoder.HtmlEncode
- Sanitizer.GetSafeHtmlFragment
Jeśli wypisujesz dane przez Kontrolka.Text możesz wykorzystać Sanitizer.GetSafeHtmlFragment. Nawet jeśli znajdzie się tam kod użytkownika, to zostanie on oczyszczony z potencjalnie niebezpiecznych fragmentów. Lepszym rozwiązaniem jest użycie Encoder.HtmlEncode, ale wówczas należy pamiętać, by nie używać tej funkcji w przypadku, gdy Kontrolka automatycznie realizuje pożądany encoding.
Jeśli korzystasz z:
- HyperLink.Target
Wykorzystaj Encoder.HtmlAttributeEncode.
Jeśli korzystasz z:
- Control.ID
- AttributeCollection.Add
Sprawdź czy:
- wartość ID jest zgodna ze specyfikacją HTML (dopuszczalna wartość atrybutów id i name),
- wartość key jest zgodna ze specyfikacją HTML (dopuszczalne nazwy atrybutów),
Jeśli korzystasz z:
- HyperLink.NavigateUrl
- Table.BackImageUrl
To potencjalnie masz problem. Zastanów się, czy można dokładnie walidować poprawność danych, które przekazuje użytkownik.
Czy to wszystko
Nie, to nie wszystko. Pokazałem tu tylko kilka specyficznych przypadków, w których w aplikacji ASP.NET może pojawić się XSS. Nie są to oczywiście wszystkie możliwe przypadki.
Dodatkowo chciałem po raz kolejny pokazać, że ASP.NET jest do pewnego stopnia niekonsekwentne, w części przypadków właściwy encoding jest realizowany automatycznie, w innych - musi o niego zadbać programista. Problem w tym, że nie zawsze wiadomo, z którym przypadkiem mamy do czynienia.