Podstawy PHP - Ochrona przed XSS

Bełdzio

Artykuł pochodzi ze strony: http://www.beldzio.com/blog/?p=8.

Czym jest XSS?

XSS czyli Cross Site Scripting to sposób na dostarczenie jednemu z klientów serwera, dowolnego kodu JavaScript. Dzieje się to przez nieodpowiednie/nieumiejętne posługiwanie się różnymi formatami danych, w szczególności łączeniem plain-tekstu z formatem HTML lub formatem JavaScript. Dostarczony w ten sposóbw kod JavaScript może być zupełnie nieszkodliwy, ale może być też tragiczny w skutkach. Np atak XSS w serwisie PayPal mógłby pozbawić nas pieniędzy. Odpowiednikiem XSS w świecie baz danych jest SQL Injection.

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 lub haseł, podszyciem się pod innych użytkowników czy nawet zablokowaniem strony dla atakowanego użytkownika.

Do uzyskania danych firmowych może doprowadzić nieumiejętne posługiwanie się z danymi przekazywanymi 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.

Niestety nie można bagatelizować sprawy nawet jeśli nasza strona umożliwia tylko i wyłącznie wprowadzenie kodu HTML. Bo co by było gdyby ktoś nagle po kilku minutach zabawy wpadł na pomysł stworzenia pływającej ramki, która przykryłaby całą właściwą stronę (to też nie jest problem, wstawia się kolorowego diva) i podlinkowanie jej do podróby naszego sajtu na jego serwerze. Co by było, gdyby nasi klienci otrzymali nagle wiadomości e-mail z prośbą zalogowania się na stronie? Większość z nich nie jest obeznana z metodami przechwycania haseł i z powodu "prawidłowej domeny i kłódeczki" zalogowała się na prawdziwo-fałszywej stronie narażając się na niebezpieczeństwo.

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

Jak działa wyszukiwanie podatności na XSS?

Jak się przed tym uchronić? Ochrona jest równie banalna jak i atak. Wystarczy pamiętać o tym, że znaki specjalne HTML, jak <, >, &, ", =, !, itp. wrenderowane bezpośrednio w wynik strony zostaną zinterpretowane jak HTML. Dane dostarczone przez użytkownika, jeśli wmieszane nieostrożnie w kod HTML staną się kodem HTML. Oczywistą konsekwencją jest więc to, że jeśli wrenderujemy bezpośrednio parametr od użytkownika, to cokolwiek od niego dostaniemy zostanie zinterpretowane jako HTML, a więc jako częsć strony. Ma to taki wynik, że użytkownik może dowolnie kształtować wygląd swojej wczytanej strony swoim parametrem. Jeśli parametry są jedynie zwracane, użytkownik zmodyfikuje jedynie "swoją" wersję strony, czyli nic czego nie byłby wstanie zrobić również bez takiej luki. Pamiętajmy jednak, że nie każdy kto wysyła parametry zawierające znaki < czy > ma niecne intencje. To może być zwykły użytkownik, który nie rozumie czemu jeśli wpisze "3<2", jego strona "rozpada się na kawałki", przez błąd składni HMTL.

Problem pojawia się, jeśli dane pochodzą od użytkownika który wie co robi oraz ma niecne intencje; i jednocześnie jego parametry nie są tylko zwracane do niego, ale również dodawane do wersji strony innych użytkowników. Np gdyby na 4programmers, forum było podane na XSS, ktoś umieszczając kod JavaScript w poście, każdy kto wyświetliłby ten post uruchomiłby złośliwy kod JavaScript, który mógłby być wykorzystany do wykradnięcia danych.

Poprawne łączenie danych HTML z danymi tekstowymi

  • Nieodpowiedni
    <?php $name = $_GET['name']; ?>
    <table>
      <tr><?= $name ?></tr>
    </table>
    
  • Odpowiedni, użycie funkcji htmlEntities()
    <?php $name = $_GET['name']; ?>
    <table>
        <tr><?= htmlEntities($name); ?></tr>
    </table>
    

Należy to robić z każdą zmienną typu string, którą chcemy wrenderować w HTML, którą dostaliśmy od użytkownika (albo której część pochodzi od użytkownika). Zmienne typu int lub bool nie są podane na 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>JavaScript:alert("Test XSS");</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>JavaScript:alert(\"Test XSS\");</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ą:

user image

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 ? loginy, hasła itp. 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 :

user image

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 ważne 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 NUL.

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 bardzo podobna, wynika z nieodpowiedniego połączenia formatu plain-tekst z formatem SQL. Podobnie jak znaki <, > i & mają specjalne znaczenie w HTML, znaki ', ", -, ; mają specjalne znaczenie w SQL. Jeśli połączymy bezceremonialnie format plain-tekst z formatem SQL, to server SQL zinterpretuje je jako SQL. To znaczy, że cokolwiek przekaże użytkownik, zostanie potraktowane jako prawdziwy SQL. Należy więc tak połączyć format SQL z formatem plain-tekst, żeby specjalne znaki z plain-tekstu straciły swoje specjalne znaczenie; możemy to zrobić escape'ując znaki. Należy to zrobić poprzedzając taki znak backslashem: \.

Oto przykłady nieodpowiedniego i odpowiedniego obchodzenia się z różnymi formatami:

  • Nieodpowiedni
    $name = $_GET['name'];
    $query = "SELECT * FROM users WHERE name = '$name';";
    
  • Odpowiedni, użycie funkcji mysql_real_escape_string()
    $name = $_GET['name'];
    $query = "SELECT * FROM users WHERE name = '" . mysql_real_escape_string($name) . "';";
    

Nie należy używać funkcji addslashes() ani innych podobnych. Tylko funkcja mysql_real_escape_string() zapewnia bezpieczeństwo danych. Alternatywą mogą być zmienne z PDO, lub używanie ORM, np Eloquent z frameworka Laravel.

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. Również wartości formularzy, które możemy odczytać z $_POST.

Tak na prawdę, podczas łączenia tekstu z formatem HTML:

<h2><?= $zmienna =></h2>

każdy taki kod jest podatny na XSS (chyba że $zmienna jest typu bool lub int). Nie ważne skąd pochodzi $zmienna, niebezpieczeństwo istnieje. Jeśli $zmienna pochodzi bezpośrednio z $_GET/$_POST, niebezpieczeństwo jest extra-duże.

Samozagłada, czyli register_globals()

register_globals() jest dyrektywą parsera PHP, umożliwiającą 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 ($user=== '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 zalecane jest wyłączenie dyrektywy register_globals().

PHP

14 komentarzy

Great information. Thank you.

nice! Love the content it'll really help me.

bardzo dobra informacja! Dodałem stronę do ulubionych dog grooming near me

Cholera MF się zabezpieczyło ;/

oczywiście administrator NFZ przeczytał dokładnie <ort>artykół</ort> :D i zabezpieczył się w połowie :D

http://www.mf.gov.pl/?const=1&dzial=25&wysw=[xss]
:) Niedlogo to sobie będziemy wypłacać pieniądze ze skarbu państwa :)

Ataki na tym polegające są dość proste. Ale mój brat (który w sumie zawodowo zajmuje się bezpieczeństwem stron internetowych) wyznaje jedną zasadę - nigdy nie nauczysz się zabezpieczać stron, póki sam się daną metodą na jakąś nie włamiesz. Efektem tego jest fakt, że jego koledzy zaniechali zwyczaj beztroskiego otwierania linków, które od niego dostaną :D

to się możesz zdziwić :) a czemu nie mogłem tego sam wykombinować ? bo nie widzę powodu :>

kto ci o tym powiedzial, bo sam na pewno nie wykombinowales tego :>

nie o to mi chodziło :) chodziło mi raczej o coś takiego : argesor wstrzykuje kod którego zadaniem jest odwołanie się do skryptu znajdującego się na jego serwerze z parametrem, będącym wartością pobraną z ciastek

nice, very nice:)

BTW: co znaczyło "dostęp do ciastek" .. rozumiem, że do ciastek naszych - czyli sprawdziło to by się w przypadku gdy z komputera korzysta wiele osób .. ?

moim zdaniem tytul powinien brzmiec "jak wlamac sie na strone przez XSS" :)

Będzie może w następnym odcinku :)

Extra artukuł, przydałby się jeszcze o SQL Injections...