Pierwsze miejsce na OWASP TOP10 zajmuje brak walidacji danych wejściowych. Wydawać by się mogło, że sprawa jest prosta do zrozumienia, ale jak się często okazuje - nie do końca. Ponieważ już z niejednym dostawcą na temat walidacji danych wejściowych gorące dyskusje miałem, mogę coś na ten temat powiedzieć. Uwaga - zaczynam :)
O walidacji parametrów słów kilka
Milczeniem pominę (na razie?) fakt, iż wymagania odnośnie bezpieczeństwa są często zupełnie pomijane na etapie analizy tematu, a to jest właśnie ten moment, kiedy taką analizę należy zacząć. Przykładowo w chwili, gdy definiowane są formatki systemu do interakcji z użytkownikiem, należy jednocześnie dla każdego pola określić zakres dozwolonych danych. Zakres ten powinien definiować:
- dopuszczalny zakres znaków,
- długość pola,
- wymagalność pola,
- format danych wpisywanych w pole,
Tak, jest to dodatkowa praca, ale lepiej wykonać ją na tym etapie, a nie dopiero wówczas, gdy upierdliwa osoba mojego pokroju zacznie się, w często niezbyt delikatnych słowach, wypowiadać na temat dostarczonego rozwiązania. W tego typu analizie należy uwzględnić również pola, które na formatce będą miały postać pól jednokrotnego bądź wielokrotnego wyboru, lub opcji wybranych z listy. Jeśli ktoś uważa, że takie pole nie musi być walidowane, bo użytkownik ma możliwość wyboru tylko z dostarczonych opcji, powinien bardzo mocno zastanowić się nad tym, co mówi. O ile takie podejście jest poniekąd usprawiedliwione w przypadku "ciężkich" klientów, o tyle w przypadku aplikacji internetowych, gdzie użytkownik ma całkowitą kontrolę nad danymi wymienianymi między przeglądarką i serwerem, zakrawa ono na ignorancję, by nie powiedzieć wprost - głupotę. W późniejszym etapie należy należy uwzględnić również wszelkie inne parametry, które mogą być manipulowane przez użytkownika, nie tylko pola formularzy, gdzie użytkownik może coś wpisać, ale również wszelkie pola ukryte formularzy, parametry wywołania strony, informacje zawarte w cookies, itd... Efektem tych (przyznaję, wcale nie prostych prac) powinna być specyfikacja wszystkich parametrów aplikacji, zawierająca między innymi informację o nazwie parametru, oczekiwanych danych, miejscu gdzie dane są "wprowadzane".
Na podstawie zebranych danych należy stworzyć odpowiednie walidatory. I tutaj popełniany jest często pierwszy bardzo poważny błąd. Weryfikacja parametrów jest często realizowana po stronie przeglądarki za pomocą skryptu. Skrypt ten wykonywany jest w środowisku kontrolowanym przez użytkownika, tak więc jego przydatność jest w praktyce pomijalna. Można to potraktować jedynie jako mechanizm zmniejszający obciążenie serwera walidacją, ale nie jako skuteczny mechanizm przed wprowadzeniem niedozwolonych danych. Walidacja ta MUSI zostać powtórzona po stronie serwera.
Jak weryfikować dane? Nie chodzi mi tutaj o konkretne fragmenty kodu, lecz o zasadę. Walidacja musi być "pozytywna". Oznacza to, że definiowany jest dopuszczalny zakres danych, a nie identyfikowane znaki niedozwolone. Innymi słowy jeśli chodzi o bezpieczną aplikację, nie stosujemy zasady "co nie jest zabronione, jest dozwolone", lecz zasadę odwrotną "co nie jest dozwolone jest zabronione". Inne podejście jest z góry skazane na niepowodzenie. Co należy zrobić w przypadku, gdy walidator stwierdzi "manipulowanie" danymi? NIE WOLNO ich używać wewnątrz aplikacji. Walidator nie powinien "poprawiać" danych (no, może później, po weryfikacjach związanych z bezpieczeństwem), powinien jedynie akceptować lub odrzucać dane. Jeśli ta sama walidacja jest stosowana po stronie serwera i klienta, można, w przypadku nieudanej walidacji danych przez serwer, "postraszyć" klienta. Dlaczego? Dlatego, że oznacza to z prawie 100% prawdopodobieństwem, że klient ten "coś kombinuje", skoro do serwera dotarły dane, które nie miały prawa zostać przepuszczone przez działające po stronie przeglądarki walidatory.
Zauważyłem, że w wielu przypadkach parametry "id" są weryfikowane przez wyrażenie regularne typu ^(\d+).*$ lub jeszcze bardziej ogólne .*(\d+).*. Jest to podejście z jednej strony poprawne - oczekiwaną wartością jest jakaś liczba, ja bym jednak dążył do większego doprecyzowania wzorca, czyli do postaci typu ^(\d+)$. Ot, taki drobny zamordyzm :) Ale spójny z tym, co wcześniej pisałem. Rolą walidatora jest stwierdzenie, czy dane dostarczone zgodne są z danymi dopuszczalnymi, a nie wyszukiwanie w stosie siana fragmentu, który akurat do wzorca może pasować. Ma to też swoje uzasadnienie wydajnościowe, choć może nie zawsze bardzo widoczne. Ot, coś na zasadzie tego, czy lepiej jest robić wyrażenie typu if BardzoZlozonaFunkcja() and i czy może lepiej if i and BardzoZlozonaFunkcja().
Jeśli przy tworzeniu aplikacji zastosowane zostaną powyższe reguły, nie będzie to oznaczało, że aplikacja jest na pewno bezpieczna, ale zbudowana została pierwsza linia obrony przed atakiem. Kolejna, to odpowiednie "przetworzenie" wprowadzonych danych przed ich wykorzystaniem, tak, by zminimalizować szansę przeprowadzenia udanego ataku interpreter injection. Ale o tym następnym razem :)