...ma warstwy. Pisałem nie raz i do znudzenia będę powtarzał o zasadzie defence in depth. Tym razem na przykładzie ASP.NET i cross-site scriptipng.
Bezpieczeństwo jest jak cebula
Walidacja wejścia, encoding wyjścia
Na zabezpieczenie aplikacji internetowej przed cross site-scripting składają się co najmniej dwa elementy:
- walidacja danych wejściowych,
- encoding danych wyjściowych,
Czasami walidacja danych wejściowych zastępowany/uzupełniany jest o funkcję "oczyszczania" danych z potencjalnie niebezpiecznych "fragmentów". Oczyszczanie danych wejściowych jest w zasadzie nieco zmodyfikowaną formą negatywnej walidacji, czyli podejścia opartego na znanej liście złych wartości. Osobiście uważam, że skuteczniejsze jest rozwiązanie polegające na określeniu dopuszczalnych wartości (format, zakres, typ) i odrzucaniu wszystkiego, co reguł tych nie spełnia.
Niezależnie od przyjętej strategii walidacji/oczyszczania danych wejściowych, rozwiązanie to nie jest w 100% skuteczne (samo w sobie). Dużo zależy choćby od tego w jakim kontekście wypisane (użyte) zostaną te dane. I dlatego tutaj istotna staje się "druga linia" zabezpieczeń, czyli odpowiedni do kontekstu encoding danych wyjściowych. Jeśli mowa o ASP.NET, to tu ponownie wypada wspomnieć o Anti-XSS Library. Dzięki tej bibliotece programista nie musi pamiętać jak ma kodować konkretne znaki, wystarczy jeśli zapamięta, że powinien użyć odpowiedniej funkcji, w praktyce jednej z:
- JavaScriptEncode,
- HtmlEncode,
- UrlEncode,
- HtmlAttributeEncode,
Właśnie w tym kontekście często dane wprowadzone przez użytkownika są wykorzystywane, a brak właściwego encodingu prowadzi do cross-site scripting.
Oddzielnie, łącznie... Zależne czy niezależne?
W zasadzie zarówno walidacja danych wejściowych jak i encoding danych wyjściowych mogą funkcjonować samodzielnie. To znaczy aplikacja może nie mieć encodingu lub nie mieć walidacji a nie być podatną na cross-site scripting, bo drugie z zabezpieczeń akurat w tych konkretnych przypadkach jest wystarczające. Co do zasady ani encoding ani walidacja nie daje 100% bezpieczeństwa. Dla uproszczenia przyjmijmy, że statystycznie walidacja daje 80% "pewności", encoding natomiast 90% (liczby są tylko przykładowe!). Oznacza to, że istnieje szansa 2/10, że walidacja nie zadziała i 1/10, że nie zadziała encoding. Jeśli przyjąć, że zdarzenia brak walidacji oraz brak encodingu są niezależne od siebie (przyznaję, czasami to dość ryzykowne założenie), prawdopodobieństwo zaistnienia zdarzenia brak walidacji i brak encodingu wynosi 2/10 * 1/10, czyli 2/100. Właśnie na tym polega zasada defence in depth, złożenie ze sobą wielu warstw zabezpieczeń powoduje, że przełamanie wszystkich warstw jest zdecydowanie mniej prawdopodobna (choć nie niemożliwa) niż przełamanie dowolnej z wykorzystanych warstw.
Z doświadczenia wiem, że zdarzenia brak encodingu oraz brak walidacji nie są zdarzeniami niezależnymi od siebie. Jeśli ktoś (programista) nie ma w zwyczaju stosować zabezpieczeń, to jest w tym bardzo konsekwentny - nie będzie stosował żadnego z nich.
Przykład z życia
W przypadku ASP.NET samo środowisko oferuje dodatkowe zabezpieczenie: Request Validation. Mechanizm ten ogranicza powierzchnię ataku, choć trzeba pamiętać, że były możliwości jego obejścia i prawdopodobnie jeszcze będą (a i obecnie wiele serwerów nie ma zainstalowanych wszystkich poprawek, co zmniejsza skuteczność tego mechanizmu).
Załóżmy, że pierwsza linia obrony (czyli request validation) jest nieskuteczna, ponieważ brak jest odpowiedniej poprawki. Jeśli jest to jedyna linia obrony - wystarczy znaleźć miejsce, w którym dane wprowadzone przez użytkownika są wypisywane na stronie.
Sytuacja komplikuje się, jeśli istnieje druga linia obrony. Nawet trywialne ograniczenie polegające na blokowaniu znaku < wprowadza dodatkowe utrudnienie. Nie wystarczy już znaleźć miejsca, w którym aplikacja wypisuje na wyjście otrzymane dane. Trzeba znaleźć takie miejsce, w którym istnieje możliwość podpięcia skryptu bez wykorzystania znaku <. W praktyce sprowadza się to często do znalezienia miejsca, gdzie otrzymane dane wykorzystywane są jako:
- atrybut tagu HTML,
- URL,
- element skryptu,
To, czy taka aplikacja podatna jest na cross-site scripting zależy od tego, czy takie "ryzykowne" konstrukcje są w niej wykorzystywane (obecnie, lub w przyszłości).
Trzecią linią obrony jest odpowiedni encoding. Programista nie musi pamiętać jak powinien to zrobić, musi natomiast pamiętać, by to zrobić. W przypadku trzech powyższych scenariuszy wystarczy, by zapamiętał, że ma użyć funkcji:
- HtmlAttributeEncode,
- UrlEncode,
- JavaScriptEncode,
Złożenie wszystkich trzech warstw powoduje, że prawdopodobieństwo wystąpienia podatności na cross-site scripting jest bliskie zeru. Niestety, tak napisane aplikacje prawie nie występują w naturze...