Wykorzystanie mod_rewrite do wykrywania ataków XSS

Łukasz Pilorz, 30 January 2007

Moduł mod_security serwera Apache jest znanym i szeroko stosowanym uzupełnieniem zabezpieczeń aplikacji internetowych - zaporą ogniową działającą na poziomie protokołu HTTP (Web Application Firewall). Można go użyć między innymi do wykrywania potencjalnych ataków Cross Site Scripting. Niestety mniejsze serwisy, wykorzystujące usługi hostingowe z dolnej półki, pozbawione są często możliwości wykorzystania tego modułu. W takim przypadku fragment funkcjonalności mod_security można próbować przenieść do reguł mod_rewrite. Nie jest to optymalne rozwiązanie, ale pozwala utworzyć prowizoryczną warstwę ochronną bez modyfikacji samej aplikacji.

Załóżmy, że naszym celem jest otrzymywanie na bieżąco informacji o próbach dokonywania ataków XSS w niewielkim serwisie. Ustawiamy (w pliku .htaccess) następujące reguły mod_rewrite:

RewriteEngine On
RewriteCond %{QUERY_STRING} (\"|%22|>|%3E|<|%3C|\'|%27|script|alert|eval|document).* [NC,OR]
RewriteCond %{PATH_INFO} (\"|%22|>|%3E|<|%3C|\'|%27|script|alert|eval|document).* [NC,OR]
RewriteCond %{HTTP_USER_AGENT} (\"|%22|>|%3E|<|%3C|\'|%27|script|alert|eval|document).* [NC,OR]
RewriteCond %{HTTP_COOKIE} (\"|%22|>|%3E|<|%3C|\'|%27|script|alert|eval|document).* [NC,OR]
RewriteCond %{HTTP_FORWARDED} (\"|%22|>|%3E|<|%3C|\'|%27|script|alert|eval|document).* [NC,OR]
RewriteCond %{HTTP:Host} (\"|%22|>|%3E|<|%3C|\'|%27|script|alert|eval|document).* [NC,OR]
RewriteCond %{HTTP_REFERER} (\"|%22|>|%3E|<|%3C|\'|%27).* [NC]
RewriteCond %{REQUEST_FILENAME} (.php|.html)
RewriteRule ^(.*)$ lpv.php [NC,E=LPV_FILENAME:%{REQUEST_FILENAME},E=LPV_PATH:%{PATH_INFO}]

Następnie tworzymy skrypt lpv.php, do którego przekierowane będą podejrzane żądania. Jego działanie będzie następujące:

  • sprawdzi, czy został wywołany przez mod_rewrite, czy bezpośrednio;
  • wyśle do nas list z danymi podejrzanego żądania (nie polecam takiego rozwiązania w praktyce);
  • odtworzy pierwotne żądanie i “posprząta” po mod_rewrite w $_SERVER;
  • zmieni ustawienie include_path, zamieniając aktualny katalog na ścieżkę pierwotnego żądania;
  • dołączy plik, którego dotyczyło pierwotne żądanie.

W ten sposób z punktu widzenia użytkownika (oraz aplikacji) obsługa żądania przebiegnie bez zmian, natomiast w międzyczasie informacja o potencjalnym ataku trafi do nas. Przykładowy skrypt do testów można znaleźć tutaj.

Uwaga: powyższy przykład to czysto testowy kod, pisany w środku nocy - nie polecam wykorzystywania metodą “kopiuj-wklej”, a szczególnie w serwisie o stałym ruchu użytkowników.

Ochrona serwisu przed atakami XSS - dziesięć przykazań programisty PHP

Łukasz Pilorz, 25 January 2007
  1. Dane, którym nie możesz ufać: parametry URL, dane pochodzące z formularzy, ciasteczka, nagłówki HTTP, pliki przesyłane przez użytkowników (tablice $_GET, $_POST, $_COOKIE, $_REQUEST, $_SERVER, $_FILES oraz ich odpowiedniki).
  2. Błędy poziomu “notice” to też błędy - wykorzystaj error_reporting(E_ALL).
  3. Jeżeli Twój serwis przetwarza dane o niejednolitym kodowaniu, zamień je na kodowanie wewnętrzne przed walidacją/filtrowaniem, oraz na kodowanie wyjściowe przed przesłaniem do przeglądarki (mb_convert_encoding() lub iconv()). Jeśli stosujesz wielobajtowe kodowanie, upewnij się, że wewnątrz danych nie ma znaków nieprawidłowych w tym kodowaniu. Nie zapomnij o zdefiniowaniu kodowania w wyjściowym dokumencie HTML (header(’Content-Type: text/html; charset=utf-8′); oraz <meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />).
  4. Możesz zastanowić się nad metodą oznaczania zmiennych - które z nich pochodzą od użytkownika, które przeszły filtrowanie HTML, a które są w postaci przeznaczonej do zapisu w bazie danych? Takie oznaczanie za pomocą nazw zmiennych nie zawsze jest uzasadnione (i nie zawsze możliwe do wykonania), ale warto o tym pomyśleć.
  5. Przygotuj osobne metody filtrowania przeznaczone dla typów danych oraz miejsc, w których znajdą się w wynikowym dokumencie HTML. Prawdopodobnie będziesz potrzebował filtrów dla: zwykłego tekstu (htmlspecialchars() + ENT_QUOTES), kodu HTML generowanego przez użytkownika (być może przyda Ci się HTML Purifier), fragmentów atrybutów HTML (htmlspecialchars() + ENT_QUOTES, czasami urlencode()) oraz wartości wstawianych wewnątrz skryptów JavaScript (tutaj poza <>"'& należy uważać na ;.%\{}()+ oraz \r i \n).
  6. Jeśli z jakiegoś powodu nie możesz zastosować do kodu HTML tworzonego przez użytkowników filtra typu “whitelist” (np. HTML Purifier), czeka Cię naprawdę ciężki orzech do zgryzienia. Zweryfikuj swój filtr za pomocą XSS Cheat Sheet. Strona ta nie zawiera wszystkich możliwych metod obejścia filtrów XSS, ale daje ogólne pojęcie o tym, co Cię czeka. Proponuję też lekturę artykułu Łukasza Lacha “PHP - Bezpieczne programowanie - Cross-Site Scripting”.
  7. Jeśli Twój filtr usuwa lub zmienia jakiś fragment danych, wstaw go w pętlę. Zmiana mogła spowodować zamianę szkodliwego kodu na inny, również niebezpieczny. Niech filtrowanie trwa tak długo, aż kolejny obrót pętli nie wykryje żadnych niebezpiecznych elementów. Dość dobrą praktyką jest zamiana szkodliwego kodu na jakiś tekst nie zawierający znaków specjalnych.
  8. Nie używaj strip_tags(), jeśli efekt działania tej funkcji nie jest naprawdę zgodny z Twoimi oczekiwaniami. Pamiętaj, by po strip_tags() użyć dodatkowo htmlspecialchars() + ENT_QUOTES.
  9. Przetestuj cały serwis. Pojedyncza luka XSS powoduje podatność całej domeny na ataki tego typu. Jeśli robisz to ręcznie, przydatne narzędzia to między innymi rozszerzenia do Firefoksa: Web Developer, Greasemonkey, ModifyHeaders, TamperData i wiele innych. Większy serwis można testować komercyjnym skanerem lub własnym skryptem. Jeśli nie jesteś pewien, czy Twoje testy są wystarczające, poszukaj specjalisty.
  10. Śledź na bieżąco nowości w dziedzinie bezpieczeństwa aplikacji WWW. Wciąż pojawiają się nowe metody ataków i omijania filtrów. Jest też bardzo prawdopodobne, że Twój serwis będzie narażony na ataki w wyniku luk w oprogramowaniu po stronie użytkownika (np. mhtml-redirect w IE lub UXSS w Acrobat Reader).

Demonstracja błędu w funkcji unset()

Łukasz Pilorz, 17 January 2007

Błąd w PHP umożliwiający między innymi obejście funkcji unset() jest znany od roku (publicznie nieco krócej). Jest szczególnie groźny przy włączonej dyrektywie register_globals, ale poza tablicą $_GLOBALS umożliwia też ataki przy pomocy $_FILES, $_GET itd.

Problem bierze się stąd, że PHP przed wersjami 4.4.3 i 5.1.4 przy usuwaniu elementu tablicy nie odróżniało indeksu liczbowego od skrótu klucza tekstowego (ma on również formę liczby całkowitej). Dlatego tworząc element tablicy o odpowiednim indeksie liczbowym można było spowodować, że zostanie on usunięty zamiast właściwego argumentu funkcji unset(). Szczegółowe wyjaśnienie można znaleźć na stronie Hardened-PHP, natomiast pod adresem http://lukasz.pilorz.net/testy/zend_hash/ przygotowałem prostą demonstrację błędu. Praktyczny przykład ataku (w tym przypadku na Wordpress < 2.0.7) stworzył rgod.

Swój serwis można chronić aktualizując wersję PHP, wyłączając register_globals (częściowa ochrona) lub upewniając się, że bezpieczeństwo kodu aplikacji nie jest zależne od poprawnego działania funkcji unset(). Przykładowe zabezpieczenie (niekoniecznie wzorowe, ale wystarczające): changeset 4717 w Wordpress.

Anti-DNS Pinning + Flash

Łukasz Pilorz, 13 January 2007

Kanatoko stworzył demo zastosowania Flash Player 9 do obejścia DNS-pinningu i skanowania portów. Od dwóch tygodni zajmował się tematyką anty-DNS-pinningu (może ktoś ma propozycję polskiego tłumaczenia?) na forum sla.ckers.org - wyniki jego testów są coraz ciekawsze.

DNS-pinning to metoda zabezpieczenia przed atakami cross-site wykorzystującymi DNS, polegająca na zapamiętywaniu przez przeglądarkę adresu IP domeny. Obejście tego zabezpieczenia pozwala między innymi uzyskać dostęp do zasobów WWW sieci lokalnej, w której znajduje się ofiara. Żeby atak był możliwy, ofiara musi otworzyć stronę internetową w domenie, dla której wpisy DNS są kontrolowane przez atakującego.

Wcześniej Kanatoko rozszerzył metodę anty-DNS-pinningu Martina Johnsa - zaproponował, żeby w celu zmuszenia przeglądarki do ponownego odpytania DNS sprowokować ją do próby połączenia z zamkniętym portem serwera.

Problemy z kodowaniem UTF-7

Łukasz Pilorz, 11 January 2007

Jedna z ostatnich luk SQL-injection w Wordpress zainspirowała mnie do przeprowadzenia krótkiego testu kilku polskich serwisów pocztowych. W tego typu aplikacjach naturalna jest konwersja treści listów z dowolnego kodowania. Jeżeli listy w postaci HTML są filtrowane z niebezpiecznego kodu przed konwersją kodowania, wówczas może wystąpić w serwisie luka XSS (a w skrajnym przypadku także SQL-injection).

Wyniki są pozornie pozytywne - podczas testu (obejmującego tylko UTF-7, i naprawdę bardzo pobieżnego) nie znalazłem luki spowodowanej błędną kolejnością filtrowania/konwersji. Dlaczego “pozornie pozytywne”?

  • Większość testowanych serwisów w ogóle nie konwertuje listów w kodowaniu UTF-7 (test obejmował wyłącznie HTML, bez alternatywy tekstowej). Efektem są “krzaczki”.
  • Jeden z wyjątków (Wirtualna Polska) zamiast konwersji stosuje ramkę z kodowaniem UTF-7, przy czym filtry nie obejmują znaków specjalnych HTML w tym kodowaniu. Powoduje to podatność na XSS.

Na marginesie: wszystkie testowane serwisy pocztowe wymagają włączenia obsługi JavaScript do poprawnego funkcjonowania.

Aktualizacja (11 stycznia): Więcej informacji na temat luk XSS w serwisach pocztowych (i nie tylko) można znaleźć na blogach Michała Majchrowicza i Michała Ławickiego.

Ponad rok temu podobna luka występowała w Google. Prostą próbkę błędnego kodu można znaleźć na stronie Chrisa Shifletta. W podanym tam przykładzie problem jest dokładnie odwrotny - ponieważ “obce” kodowanie dotyczy danych wyjściowych, a nie wejściowych, więc prawidłowa kolejność to filtrowanie-konwersja (w szczególnych przypadkach mogą wystąpić wyjątki od tej reguły).

Next Page »