Aplikacje internetowe

Bełdziowe spojrzenie na aplikacje internetowe

Ochrona przed XSS – podstawy

Czym jest XSS ??

XSS czyli Cross Site Scripting to sposób na wstawienie swojego kodu na podatną stronę. Dzieje się to przez obdarzenie użytkownika zbyt dużym zaufaniem, czego skutkiem może być kompromitacja naszej strony.
Jakby zareagowali nasi klienci gdyby dowiedzieli się, że ich dane są w posiadaniu osób, które nie powinny mieć do nich dostępu, lub po wejściu na stronę ukazały by im się treści kompromitujące naszą firmę/stronę? Artykuł ten ma pokazać podstawowe sposoby zabezpieczania skryptów przed atakami typu XSS.

Co mamy do stracenia ?

Umiejętne wykorzystanie błędów programisty może skutkować wejściem w posiadanie ważnych danych firmowych, pobraniem loginów oraz haseł użytkowników czy nawet zablokowaniem strony.
Do uzyskanie danych firmowych może doprowadzić brak filtrowania danych przekazywanych przez użytkownika podczas zapisu ich do bazy danych. Do kradzieży danych użytkownika może doprowadzić wstrzyknięcie kodu JavaScript, zaś do modyfikacji strony kodu HTML/PHP.

W tym artykule pominiemy zaawansowane kwestię SQL Injection, a zajmiemy się wstrzykiwaniem kodu JavaScript i HTML. Spowodowane jest to głównie faktem, że artykuł ma nauczyć podstawowych metod zabezpieczania się, a nie sposobów ataku.

Czas na zabezpieczanie, czyli psucie czas zacząć

Jako, że uczyć się jest najlepiej na przykładach, dlatego w celu lepszego zobrazowania istoty błędów będę je opisywał na podstawie istniejących stron.

Przykładem ufności do użytkowników może być strona Narodowego Funduszu Zdrowia (http://www.nfz.gov.pl/). Najbardziej podatnym elementem stron są wszelkie formularze, na stronie NFZ rzuca nam się od razu w oczy wyszukiwarka. Wyszukiwarka ta nie filtruje danych, które otrzymuje od użytkownika co skutkuje możliwością wstrzyknięcia kodu HTML oraz JavaScript.

Jak działa wyszukiwanie podatności na XSS?

W polu input wyszukiwarki widoczne jest na bieżąco nasze zapytanie, dzięki czemu możemy bardzo prosto ingerować w wygląd strony. Aby tego dokonać wystarczy zamknąć ostatni tag HTML poprzedzający nasze zapytanie, czyli w tym przypadku jest to

"><h1>Test XSS</h1>

dzięki temu input zostanie zamknięty, a treść nagłówka zostanie umieszczona poza formularzem. Efekt tego pokazuje poniższy obrazek:

Jak się przed tym uchronić? Ochrona jest równie banalna jak i atak. Wystarczy użyć jednej funkcji PHP, a dokładnie strip_tags.
Zadaniem funkcji strip_tags jest filtracja podanego jako parametr ciągu i usunięcie z niego tagów HTML oraz PHP.
Gdyby programista piszący powyższą wyszukiwarkę użył tej funkcji efektem wpisania groźnego kodu byłaby następująca informacja:

Niestety, nie znaleziono wyników pasujących do zapytania Test XSS.

Kradzież danych użytkownika? Nic prostszego.

Wcześniejszy przykład ukazywał‚ idee wstrzyknięcia kodu HTML, co jednak gdy agresor użyje JavaScript, który ma zdecydowanie większe możliwości? Powróćmy do wyszukiwarka NFZ, gdy dokonamy modyfikacji
poprzedniego kodu, który modyfikował wygląd strony tak aby zawierał on w sobie kod JavaScript otworzą się przed nami ogromne możliwości. Aby sprawdzić, czy próba wstrzykniścia kodu JavaScript powiedzie się
wystarczy w wyszukiwarkę wpisać

<script>
	alert("test");
</script>

I tutaj zaczynają się schody. Parser PHP serwera NFZ ma włączoną dyrektywę magic_quotes_gpc, która jest złym zwyczajem. Dlaczego przecież zapewnia ona podstawowe bezpieczeństwo skryptów?
Tak jest, lecz gdy programista przyzwyczai się do zrzucania bezpieczeństwa swoich skryptów na parser PHP to stracą one na swojej przenośności, wzrośnie obciążenie serwera oraz nie uchroni go to przed wszystkimi
niebezpieczeństwami. W tym przypadku uniemożliwia nam to pokazanie napisu ponieważ parser PHP przerabia nasz kod na

<script>
	alert("test");
</script>

co uniemożliwia jego wykonanie. Nie blokuje to jednak wszystkich naszych możliwości. Możemy np. zmusić stronę do pokazania nam komunikatu z aktualną godziną:

Skoro już wiemy, że wstrzyknięcie kodu JavaScript jest możliwe pomyślmy jak atakujący może ukraść dane użytkownika. Wiele stron w plikach cookies umieszcza dane o użytkownikach takie jak loginy czy hasła.
Za pomocą XSS agresor bez problemu może osiągnąć dostęp do tych danych. JavaScript posiada funkcję odczytującą dane z plików cookies, poprzez napisanie odpowiedniego skryptu PHP, dane te mogą być przekazywane
do skryptu na serwerze agresora. Z badań przeprowadzonych na stronie NFZ wynika, że ich strona również zapisuje ciastko na naszym komputerze, co prawda nie zawiera ono danych o loginach czy hasłach lecz :


Podatność na XSS

Załóżmy, że dane te to poziom uprawnień użytkownika, jeśli agresor zmieniłby wartość na 0 to mógłby uzyskać dodatkowe prawa na serwerze. Dlatego właśnie dane należy trzymać na serwerze, korzystając z sesji,
a nie ciastek.
Mogłoby się wydawać, że skoro JavaScript ma większe możliwości to zabezpieczania też będą poważniejsze. Tak jednak nie jest, wystarczy, że prócz funkcji strip_tags
przydadzą się nam dwie dodatkowe funkcje addslashes oraz stripslashes
ich zadaniem jest dodawanie ukośnika przed znakami, które mogą być niebezpieczne czyli

 ', `, , i NULL

Bezpieczne dołączanie plików.

Wiele osób nie widzi nic niebezpiecznego w bezpośrednim dołączaniu plików ze zmiennych superglobalnych. Co może być w tym niebezpiecznego. Wyobraźmy sobie sytuację, gdy nazwa pliku przesyłana jest w zmiennej
z tablicy GET. Odpowiednie zmodyfikowanie adresu strony przez agresora może doprowadzić do uzyskania dostępu do pliku passwd oraz shadow, które zawierają dane o użytkownikach w systemie oraz innych plików np
konfiguracyjnych. Jak się przed tym zabezpieczyć Wg mnie dobrym sposobem jest utworzenie tablicy ze zmiennymi, których odpowiedniki plikowe mogą być includowane i sprawdzać za pomocą funkcji
in_array czy dany plik ma zezwolenie na dołączenie.

Bezpieczne dodawanie danych do bazy danych.

Tematyka SQL Injection jest bardziej rozległa niż poprzednich wstrzykniść, dlatego przedstawiś tutaj tylko podstawowe metody zabezpieczeń.
Pierwszą zasadą jest niewstawianie zmiennych przekazywanych przez użytkownika do zapytania. Należy wcześniej je przefiltrować. I tu ponownie PHP ułatwia nam zadanie udostępniając szereg funkcji.
Należą do nich strip_tags, addslashes, stripslashes oraz
htmlspecialchars. Wszystkie prócz ostatniej zostały już omówione więc skupimy się tylko na
htmlspecialchars. Umożliwia ona zamianę specjalnych znaczników HTML na odpowiednie im encje, dzięki czemu możemy bez
poważnych konsekwencji przyjmować od użytkownika kod HTML.

Nie tylko formularze są podatne.

Najbardziej podatnymi na XSS elementami stron są formularze oraz dane przekazywane do skryptów za pośrednictwem adresu, czyli zmienne z tablicy GET. Brak filtracji tablicy GET może doprowadzić do tych samych
konsekwencji co brak filtracji w formularzach.

Przykładem strony, a raczej stron podatnych na „adresowy XSS” są strony, na których zainstalowany jest system zarządzania treścią Mambo. CMS ten komunikuje się z użytkownikiem za pośrednictwem zmiennej

mosmsg

znajdującej się w tablicy superglobalnej GET. Poprzez odpowiednie zmodyfikowanie adresu strony możemy wstawić na stronę dowolny tekst. Jest to jedyne uchybienie Mambo, ponieważ dane przekazywane przez tą
zmienną są czyszczone do postaci czystego tekstu, a więc wstrzykniście kodu HTML/PHP jest niemożliwe. Przykładem takiej modyfikacji adresu może być

http://www.mamboserver.com/?mosmsg= Witamy+jesteśmy+kolejnÄ…+stronÄ…+podatnÄ…+na+XSS+.

Samozagłada, czyli register_globals.

Register_globals jest dyrektywą parsera PHP, umożliwiającą automatyczne tworzenie, krótkich nazw zmiennych, dzięki temu zamiast pisać

$_POST['nasza_zmienna']

możemy napisać

$nasza_zmienna

.

Początkowo może się wydawać, że dyrektywa ta jest bardzo pomocna, ponieważ pozwala zaoszczędzić czas związany z wpisywaniem nazwy tablicy, jednak stanowi ogromne niebezpieczeństwo dla naszej strony. Dlaczego?
Załóżmy, że nasz strona zawiera moduł logowania. Poziom użytkownika sprawdzany jest poprzez instrukcję warunkową

if ( $poziom === 'admin' )
	echo 'Jesteś adminem';

Agresor poprzez modyfikację adresu strony

http://www.nasz.serwer.pl/logowanie.php?poziom=admin

uzyska dostęp do praw administratora, bez konieczności logowania się. Tak więc mimo swojej przydatności zalecane jest wyłączenie dyrektywy register_globals.

Zakończenie, czyli to jeszcze nie jest koniec.

W tym artykule starałem przekazać Wam podstawową wiedzę z zakresu obrony przed XSS. Uważam, że wiedza na ten temat jest bardzo potrzebna webmasterom, ponieważ ogromna ilość stron jest podatna na ten atak.
Mimo, że PHP udostępnia nam wiele przydatnych funkcji pomagających w zabezpieczeniu przed wstrzykiwaniem złośliwego kodu, należy pomyśleć też o tworzeniu własnych funkcji, które zapewnią większe bezpieczeństwo
pisanych przez nas skryptów. I pamiętajcie o najważniejszej zasadzie, podczas pisania kodu, należy postępować tak jakby każdy użytkownik był agresorem i próbował znaleźć każdy nasz błąd.

PS. Administrator strony NFZ został poinformowany o błędzie na stronie.


Tagi:
Kategoria: Bezpieczeństwo, HTML Injection


36 komentarzy

  1. Ktos napisał(a):

    Dobry artykuł. Wspominałem już na IM o kilku kwestiach, jakie jeszce by się przydało poruszyć, ale ogólnie jest bardzo dobry. I oparty o bardzo ciekawy przykład :)

  2. Przecież to z NFZ to żadna luka, przecież samym JS i html”em nic się na stronie nie zrobi poza tym, że można sobie zobaczyć (tylko przez samego siebie) dodatkowe teksty na stronie i okienko z alert. I co z tego, że sobie sprawdzisz cookies używając tej „luki”, skoro można to zrobić w Firefox”ie – łącznie z wpisaniem do cookies czego się chce.Co innego gdyby taka luka była np. na forum przy pisaniu postów… więc marny przykład z tym NFS

  3. Bełdzio napisał(a):

    No i właśnie o to chodzi. Artykuł ma pokazać istotę niebezpieczeństwa i jak się przed nią bronić, a nie jak atakować. Powiem Ci, że testuje prawie każdy skrypt dawany do oceny na forum KS Ekspert ze względu podatności na XSS i jak narazie tylko jeden był odporny. Reszta stron po moich testach prowadziła do mojego profilu na forum poprzez wstrzyknięcie kilku znaków JavaScript.Chyba nie liczyłeś na to, że dostaniesz instrukcje jak krok po kroku wstrzyknąć swój kod do bazy danych, a jeśli tak to niestety nie jest to art, którego szukałeś.

  4. Spoko, rozumiem. Mam swoje sposoby i za sobią kilka stron, które miały luki i je wykorzystałem.BTW. chyba datę na serwerze masz trochę przestawioną ;)

  5. Bełdzio napisał(a):

    Oj Ty niedobry ;) nom, coś data szwankuje jak znajdę czas to oblookam co jest nie takPS wypadałoby się przedstawić ;)

  6. eee nieważne kim jestem ;)

  7. Ktos napisał(a):

    Hmm, czy to ten lol o którym ja myślę? :)

  8. Ktos: „lol” wpisałem tak sobie ;)Bełdzio: jak byś potrzebował przykład do art”a związany z MYSQL, to możesz sobie looknąć to (ostatnie na co się natknąłem):http://www.free-codecs.com/download_soft.php?s=lolJeszcze się tym nie bawiłem, ale pewnie nic niedobrego się z tym nie zrobi :Dpozdro

  9. Dziadziuś napisał(a):

    Taaaaak.no cóż. zaczynam się zastanawiać czy Bełdzio weźmiesz się porządnie za tego arta ;) . może trochę więcej o sql injections.po za tym chciłbym dodać że no tego. register_globals jest wskazane. co nie zmienia faktu że jest to trochę niebezpieczne. ale dobry koder sobie poradzi :)po za tym hmmmm. wszystko da się w php ominąć  jeżeli w skrypcie wszystkie zmienne są inicjalizowane :) wtedy to co wpisał user nie będzie brane pod uwagę :) tylko to co my wprowadzimy :)

  10. Bełdzio napisał(a):

    1. Tak jak pisałem o SQL Injection w tym arcie nie będę się rozpisywać, bo nie takie było jego przeznaczenie.2. Skoro debeloperzy PHP postanowili od wersji 4.2.0 ustawić domyślnie register_globals na OFF, oraz stwierdzili, że aktywna dyrektywa niesie za sobą duże niebezpieczeństwo, to dla mnie to jest wyznacznikiem wskazaności.

  11. Dziadziuś napisał(a):

    po pierwsze developerzy a nie debeloperzy ;P
    co do register globals. no cóż. ja akurat stosuję tą technikę i jeszcze jakoś na tym nie ucierpiałem ;P
    a tak ogólnie. no cóż. moze nie znam się na bezpieczeństwie ale samo php nie jest bezpieczne pomimo działania server-side. dlaczego? bo na każdy skrypt jest uruchamiany osobny proces :D a co za tym idzie może dojść do przekłamania danych :D.
    przykład
    kierując się danymi z bazy danych klient może podejmować finansowe decyzje. wystarczy że ułamek sekundy wcześniej wywoła stronę niż dodanie danych :D
    znasz sposób na ominięcie tego?

  12. Bełdzio napisał(a):

    1. mea culpa ;)2. jeszcze wszystko przed Tobą ;)3. z tymi ułamkami to przesadzasz ;) zawsze da się jakoś przyblokować dane, tak aby wyeliminować prawdopodobieństwo takiego błędu, ale zgodzę się z Tobą w tej kwestii.

  13. Dziadziuś napisał(a):

    Bełdziu Dziadziuś zna rozwiązanie. :D
    W sytuacji w której ułamki sekund mogą grać taką rolę stosuje się jsp ;)
    dlaczego? ponieważ każda strona (servlet) jest odpalana raz i jeden proces obsługuje 10 jak nie dziesiątki tysięcy wątków. wtedy przy starcie strony wystarczy sprawdzić czy któryś wątek nie wykonuje obliczeń na bazie. jak się wykryje to przyblokować ładowanie strony :P
    a po za tym servlet w trakcie swojego życia moze badać czy są jakieś nowe wpisy w bazie i następnie wysyłać do przeglądarki użytkownika informację o tym :) i przeładować stronę
    czy w php możesz przeładować stronę w momencie zmiany danych w bazie danych? nie. tam musisz przeładować sam automatycznie i nie wiadomo czy dane się zmienią. :)
    i co to daje? pobieranie danych które się nie zmieniają czyli zwiększa się obciążenie serwera ;)

  14. Bełdzio napisał(a):

    Nom, tak właśnie jest tylko art jest o PHP, a nie o JSP ;) dlatego skupiam się na PHP, a o wyższości JSP nad PHP i vice versa nie ma co gadać, bo każdy zainteresowany wie jak jest. ;)

  15. lofix napisał(a):

    Poczytałeś jakiś manual jakich pełno jest w sieci i teraz udajeszcz cffaniaczka :rotfl:

  16. Bełdzio napisał(a):

    ktoś musi ;) lepiej daj mi gdzieś kojota, żeby sformatować text :) bo jak wrzucam na 4p to się strona rozwala ;D

  17. Phomerus napisał(a):

    Bełdzio co do register_globals to mogles napisac ze mozna polaczyc ze tak powiem dwie rzeczy – i bezpieczenstwo i krotkie nazwy zmiennych, napoczatku pliku wystarczy zdefiniowac odpowiednio zmienne np. :$kupa = $_POST[kupa];$zenek = $_POST[zenek];po tym mozna juz w skrypcie bezpieczniue uzywac zmiennych bez niepotrzebnych postow i getow :)

  18. Bełdzio napisał(a):

    Mylisz pojęcia :) to co napisałeś czyli „ręczne” przypisywanie zmiennej wartości z tablicy jest czymś naturalnym i nijak ma się do register_globals. To po pierwsze, a po drugie mylisz się :) bo jeśli w zmiennej znajdującej się w tablicy $_GET znajduje się wartość, która posypie Twoją stronę, to przepisanie jej wartości do zwykłej zmiennej nic nam nie da :) efekt będzie taki sam :). Działanie powinno wyglądać np tak $login = strip_tags($_GET[”login”])

  19. omg napisał(a):

    Mozecie mi powiedziec KIEDY przypisanie wartosci do geta czy posta popsuje strone? A no wtedy gdy w zmiennaj znajdzie sie to co nie powinno, a takie pytanie… Czy jak mam funkje ala_ma_kota() i w niej zrobie echo $ala (przy WLACZONYM register_globals), to cos mi wyswietli?NIE, wiec teraz mi powiedzcie, jak zepsuc strone ktora jest napisana obiektowo i jedynymi przypisaniami NIE w funkji jest przypisanie np obiektu zarzadzajacego i obiektu bazy? :PPiszac obiektowo odcinasz sie od wiekszosci problemow w tym register_global!!!Co nie zmienia faktu ze i tak jak bedzie chcial to nieudacznie napisze kod i i tak bedzie podatny na wlamy :D

  20. Bełdzio napisał(a):

    A czy każdy skrypt pisany jest w oparciu o obiekty? :> Co do włączonego RG dałem bardzo prosty, który ukazuję zagrożenie wynikające z tego zagadnienia

  21. omg napisał(a):

    Bełdzio: „A czy każdy skrypt pisany jest w oparciu o obiekty?”Ja tam kazdy pisze obiektowo :PWygoda, szybkosc, niezawodnosc. A jak w chce cos dosdac/zmienic nie ma problemu :D

  22. Bełdzio napisał(a):

    Ja też prawie zawsz piszę obiektowo, jednak nie każdy postępuje tak samo jak my ;-)

  23. Michał‚ Fikus napisał(a):

    Przy SQL Injection wypadałoby wspomnieć o natywnych funkcjach dla baz danych, które przed SQL Injection bronią. Np. mysql_real_escape_string().

  24. Bełdzio napisał(a):

    Oczywiście :-) Jak znajdę chwilkę czasu to dopiszę to. Dzięki za zainteresowanie.

  25. MatheW napisał(a):

    Przy tym pierwszym XSS :”>Test XSSchyba nie wystarczy samo strip_tags(. Bo wtedy ciąg będzie się prezentować: „>Test XSSwięc pole input i tak się zamknie i napis Text XSS będzie widoczny ;p Trzeba dać htmlspecialchars(). Mógłbyś o tym napisać już przy tym przykładzie, bo ktoś nierozważny mógłby to przeoczyć.

  26. Bełdzio napisał(a):

    @MatheW postaram się w przyszłym tygodniu uaktualnić arta. Troszkę trzeba poprawić, troszkę dopisać i takie tam ;-)

  27. sxs napisał(a):

    "><h1>Test XSS</h1>

  28. Tomek napisał(a):

    Artykuł zgodnie z nazwą jest skierowany raczej do bardzo początkujących webmasterów.
    Co ro register_globals to jest za tym by wyłączyć, a przypisać sobie do zmiennych przefiltrowane dane z formularza.
    Co do pisania obiektowo to sam sie przyznam ze pisze strukturalnie, a idea obiektowa jest dla mnie poki co nie naturalna. Dopiero raczkuje ;)

  29. Michał `Bełdzio` Ławicki napisał(a):

    @Tomek temu artukułowi sporo brakuje ;-) pisałem go 2 lata temu, a przez te dwa lata moja wiedza się troszkę zwiększyła ;-) Zostawiłem go tu z kwestii sentymentalnej ;-) Teraz na bieżąco będę pisał o podobnych rzeczach, ale już troszkę konkretniej (mam nadzieję) :)

    btw pisanie obiektowo jest bardziej naturalne :) np


    class Ptak
    {
    public function lec(){}
    public function cwirkaj(){}
    public function zjedzRobaka(){}
    }
    $oPtak -> lec( ) etc :)

  30. Świetnie!

  31. Kokoss napisał(a):

    Dobry artykuł. Jak już jechać po nfz to jechać :) Register globals też mają włącznone :) http://www.nfz.gov.pl/new/szukaj.php?szukana=%22%3EO%20tak!%20Mamy%20w%B3%B9czone%20register%20globals! :)) Tylko mi jakoś nie udało się użyć tagu h1 :p Ale fakt faktem – XSS jest :) Super art.

  32. Kokoss napisał(a):

    Dobry artykuł. Jak już jechać po nfz to jechać :) Register globals też mają włącznone :) http://www.nfz.gov.pl/new/szukaj.php?szukana=%22%3EO%20tak!%20Mamy%20w%B3%B9czone%20register%20globals! :)) Tylko mi jakoś nie udało się użyć tagu h1 :p Ale fakt faktem – XSS jest :) Super art.

  33. Jeszcze było coś takiego co przerabiało znnaki na liczby i potem mozna bylo przy filtrowaniu addslashes lub magic_quotes_gpc dalej walic XSS-a.

  34. @Kokoss: przecież to zwykły GET – do każdego GETa coś możesz dokleić ;)

  35. Witam, a ja proponuje całą tablice $_GET zapisywać jako jedna zmieną typu string a nie tablice.
    Bardzo prosto to wykonac podam na przykładzie obiektu: mamy plik router.php

    class routers {
    static public function _url() {
    if($_GET[‚myurl’]) {
    $exp=explode(‚/’, utf8::_mb_strtolower(str_replace(array(‚ ‚,’.html’), array(”,”), trim(htmlentities($_GET[‚myurl’])))));
    unset($_GET);
    }
    $result->get->$exp;

    z Post robimy podobnie… .
    $result->post->$filtrpost
    return($result);
    w htaccess dodajemy dyrektywe:
    RewriteRule ^(.+)$ index.php?myurl=$1 [QSA]
    Przykładowy adres http://www.panisher.pl/start/start.html –>.html jest usuwany w $_GET ale widoczny w url na stronie. Znaki specjalne zastępują encje, a znak / dzieli na tablice naszą przefiltrowaną, ponieważ $exp[0] to nazwa pliku,a $exp[1] nazwa metody odpalanej.

    Jest to kawałek mojego frameworka jaki zastosowałem na panisher.pl, jak ktoś znajdzie lukę niech napiszę.
    Dostęp odbywa się przez $url = routers::_url();

  36. kl napisał(a):

    Strip_tags nie jest właściwe. Odpowiednie jest htmlspecialchars(), które równie dobrze unieszkodliwia tagi, ale nie rujnuje przy tym tekstu i pozwala napisać „1 mniejsze od 2” bez wycinania znaku mniejszości.

Dodaj komentarz