AJAX i bezpieczeństwo serwerowych skryptów Grzegorz Wójcik

AJAX - proszek do prania

Jeśli Twoja publiczna aplikacja internetowa bardzo intensywnie korzysta z technologii AJAX powinieneś zadać sobie pytanie, czy serwerowe skrypty obsługujące takie requesty są należycie zabezpieczone – okazać się może bowiem, że z ich dobrodziejstw korzystasz nie tylko Ty…

Rozważmy następujący scenariusz: na stronie głównej naszego serwisu (index.php) wyświetlamy listę artykułów. Klikając w link „komentarze” przy danym artykule, za pośrednictwem technologii AJAX pytamy skrypt po stronie serwera (komentarze.php) o listę komentarzy dla danego artykułu.

Zakładając, że korzystamy np. z MooTools takie zapytanie może mieć np. taką formę:

  1. var myRequest = new Request({
  2.  url: 'komentarze.php',
  3.  method: 'get',
  4.  onSuccess: function(responseText) {}
  5. });
  6.  
  7. myRequest.send('artykul_id=7');

Rodzi się pytanie – jak zabezpieczyć skrypt komentarze.php przed niecnym wykorzystaniem przez osobę trzecią? Średnio rozgarnięty intruz odwiedzając naszą stronę i analizując requesty przeglądarki (np. za pomocą Firebuga) bez problemu dowie się o jego istnieniu i akceptowanych parametrach.

Intruz na swojej stronie nie będzie w stanie wykonywać AJAX-owych zapytań bezpośrednio do naszego skryptu ze względu na założenia bezpieczeństwa tej technologii (ang. same domain policy – żądania mogą być wykonywane tylko w obrębie tej samej domeny). Ale nic nie stoi na przeszkodzie, aby skorzystał z biblioteki cURL i napisał prosty skrypt pośredniczący (proxy), który będzie odpowiadał za interakcję z komentarze.php. Jak ustrzec się takiego zagrożenia?

Nagłówki HTTP

Wydawać by się mogło, że po stronie serwera wystarczy dokonać inspekcji nagłówków requesta i np. sprawdzić domenę w zmiennej $_SERVER["HTTP_REFERER"]. Nic bardziej mylnego – zabezpieczanie skryptów tylko w oparciu o nagłówki HTTP można porównać do antykoncepcji metodą kalendarzyka małżeńskiego (vel watykańskiej ruletki). Wynika to z prostego faktu, że wykazując odrobinę dobrej woli wartością nagłówków można dowolnie manipulować. Przykładowo ustawienie dowolnej wartości dla pola Referer: w cURL jest kwestią jednej linii kodu:

  1. curl_setopt($ch, CURLOPT_REFERER, "http://www.kminek.pl/");

Oczywiście nagłówki dla pewności można sprawdzić zawsze ale nigdy nie powinna być to jedyna metoda zabezpieczeń. Należy nadmienić, że różne biblioteki JavaScript często dodają do requestów AJAX-owych nagłówek X-Requested-With: XMLHttpRequest, który również nie zaszkodzi sprawdzić.

Baza danych

Skoro nagłówki HTTP nie gwarantują wystarczającego bezpieczeństwa potrzebna jest jakaś inna forma ochrony. Najlepszym wyjściem jest wygenerowanie dla AJAX-owego żądania tokena (np. losowego ciągu alfa-numerycznego), który następnie będzie sprawdzany przez skrypt po stronie serwera. Generowanie losowego ciągu znaków może wyglądać np. w ten sposób:

  1. $token = md5(time().'jakislosowyciagznakow');

Taki token może być zapisywany np. w tabeli w bazie danych. Dla naszego przykładu z artykułami kroki tej metody mogą wyglądać następująco:

  1. plik index.php generuje losowy token, zapisuje go w tabeli w bazie danych i „wypluwa” jako zmienną JavaScript, która jest „doklejana” do wszystkich AJAX-owych wywołań
  2. skrypt komentarze.php sprawdza, czy przesłany token znajduje się w tabeli w bazie danych – jeśli tak – skrypt zwraca wyniki zapytania i kasuje token z tabeli. Jeśli tokena nie ma w tabeli – następuje przerwanie działania (exit()).

Zmienne sesji

Token możemy też przekazać za pomocą zmiennych sesji. To najlepsze i sugerowane rozwiązanie. Przede wszystkim unikamy obciążania bazy danych kolejnymi zapytaniami. Przeglądarka, wysyłając AJAX-owe żądanie, wyśle przecież również ciasteczko z identyfikatorem sesji. Później wystarczy już tylko porównać zmienną sesji z przesłanym tokenem. Czyli – kontynuując nasz przykład z artykułami – w pliku index.php będziemy mieli np. coś takiego:

  1. <?php
  2. session_start();
  3. $token = md5(time().'jakislosowyciagznakow');
  4. $_SESSION['token'] = $token;
  5. ?>

a w pliku komentarze.php sprawdzimy przesłany w żądaniu hash z tym, który jest przechowywany w sesji:

  1. <?php
  2. session_start();
  3. $token = $_GET['token'];
  4.  
  5. if (!empty($_SESSION['token']) && $_SESSION['token'] == $token) {
  6.  
  7.  // poprawny request
  8.  
  9. } else {
  10.  
  11.  // niepoprawny request
  12.  
  13. }
  14. ?>

Jeśli znacie inne ciekawe sposoby na zabezpieczanie skryptów będę wdzięczny za sugestie w komentarzach.

Czytaj więcej:
Artykuły » AJAX, PHP
Tagi:
, ,

Grzegorz Wójcik

Grzegorz Wójcik jest założycielem internetowego magazynu kminek.pl. Pasjonat i twórca lekkich, dostępnych i użytecznych stron internetowych budowanych w oparciu o standardy sieciowe i najlepsze praktyki. Prywatnie wielki miłośnik rocka z lat 90.

Zobacz wszystkie artykuły tego autora (14)

  1. Tomek 1

    A jaki problem przesłać taki token w żądaniu przy użyciu curla?
    Jakoś te zabezpieczenie wygląda na takiej jak z nagłówkami, tyle tylko że dodaje jeszcze jeden parametr do przesłania.

  2. Nie rozumiem tylko po co chcesz zabezpieczać komentarze? Co w tym niebezpiecznego, że te dane są dostępne poprzez bezpośrednie zapytanie? Przecież nawet bez ajax`a można wykonać podobną operację, przefiltrować nawet za pomocą cURL’a stronę z artykułem i będziemy mieli komentarze w czystej postaci. To nie jest problem. Ideologia ta przypomina mi blokowanie kopiowania tekstu w JS przez pseudodeveloperów (blokowanie kontekstowego menu, prawego klawisza myszy etc.). Nie widzę zagrożenia.

    • Wszystko zalezy od tego jak – jako tworca aplikacji internetowej – podchodzisz do kwestii bezpieczenstwa Twojej aplikacji. Ja np. lubie wiedziec, ze z informacji zgromadzonych w
      mojej bazie danych korzysta tylko moja aplikacja i nikt inny. Jesli mam kaprys udostepnienia
      pewnych danych na zewnatrz – wystawiam API. Tutaj chodzi po prostu o to, ze mam pelna kontrole jakie dane sa udostepniane i komu.

      Prosty przyklad – zalozmy, ze komentarze do tego bloga sa pobierane AJAXem i sa JS-only (czyli uzytkownik bez JS nie zobaczy komentarzy). Gdy requesty AJAXowe nie sa w zaden sposob zabezpieczone, ‘intruz’ moze bardzo latwo zaciagnac je na swojej stronie. A tak – gdy wprowadzisz powyzsze zabezpieczenia – nawet cURL Ci nie pomoze :)

      Natomiast budujac API sam moge np. okreslic, ze udostepnial bede przykladowo tylko pierwszych kilka slow kazdego komentarza – czyli decyduje co, w jakim zakresie i komu jest udostepniane.

  3. Nie myl bezpieczeństwa od sposobu prezentacji danych. Słusznie napisałeś “intruz” w cudzysłowach, gdyż żadne to niebezpieczeństwo. Wytłumacz mi co jest niebezpiecznego dla Ciebie w tym, że “intruz” korzysta z danych, które Ty PUBLICZNIE udostępniasz? Czy to ajaxem czy tradycyjnie? No i jaki pożytek ma ta osoba, skoro nie jest żadnym niebezpieczeństwem?
    Nie zapominaj, że zwykłe zapytania http i prezentacja danych to też API z którego korzysta przeglądarka w komunikacji z Twoją aplikacją. Choć nie definitywnie.
    Rozumiem gdyby komentarze były dostępne tylko dla uwierzytelnionych użytkowników. Ale w tym przypadku wydaje mi się logiczne skorzystanie z sesji.
    To tak jakbyś założył kilkanaście zamków na drzwi które są otwarte, a kiedy je zamkniesz nikt i tak nie próbowałby ich sforsować. To tylko komplikowanie sobie pracy ;]
    Nie zabezpiecza się request’ów, które nie mają wpływu na wewnętrzne działanie aplikacji, te prezentujące dane mogą być otwarte bo nie stanowią niebezpieczeństwa.
    Tyle.

    Pozdrawiam.

    • Okej. Napisze to jeszcze raz lopatologicznie, bo to co piszesz wynika chyba z niezrozumienia przez Ciebie istoty problemu.

      Przyklad. Zalozmy, ze nie tylko komentarze, ale caly moj blog jest AJAX – only. Czyli np artykuly tez pobierane sa w ten sposob. Na przyklad – ta strona to 2 requesty – jeden z pytaniem o artykul i drugi z pytaniem o liste komentarzy. Oba requesty zabezpieczone sa tokenami, jak w artykule powyzej.

      Mozesz mi powiedziec, jak za pomoca cURLa chcesz skorzystac z tych danych ? :) Widzisz – wlasnie w ten sposob mozna bardzo latwo zabezpieczyc sie przed scrapingiem.

      • DooB 7

        Ja mam pytanie odnośnie tego tokena.
        Nie jestem jakimś ekspertem piszę tylko co mi się wydaje i proszę o sprostowanie jeśli się mylę.

        Nie jest możliwe pobranie za pomocą cURL treści strony z tokenem, a potem wysłanie zapytania razem z tym tokenem spowrotem do skryptu php?

        I jeszcze inne pytanie bardziej o kwestie techniczne:
        Jeśli strona jest dynamiczna jakieś – przyciski do pobrania kolejnej porcji danych (np pobranie kolejnej podstrony komentarzy) to jak ponownie użyć tokena bez przeładowania strony jeśli został on skasowany przy pierwszym użyciu.

        Artykuł ciekawy i wiele mi rozjaśnił, dzięki :)

      • hmmm, co do pierwszej uwagi wydaje mi sie to mozliwe. przy czym token musialbys wyciagac jakis wyrazeniem regularnym z HTMLa strony, pozniej kolejne zadanie musialoby sie odbyc w tej samej sesji (czyli cURL musialby trzymac tez cookie podtrzymujace sesje ale to tez jest mozliwe) – wiec jak widzisz dla chcacego nic trudnego :)

        ad.2 – ja bym uzyl tego samego tokena dla kolejnych zadan.

  4. I to jest właśnie głupi stereotyp “ajax zły”. A to:

    http://pastebin.pl/18160

    jest bezpieczne? I gdzie pewność, “ze z informacji zgromadzonych w
    mojej bazie danych korzysta tylko moja aplikacja i nikt inny”?

    Kod mozna dopracowac, a ten jest tylko pięciominutowym przykładem.

    To, że AJAX przedstawia dane w CZYSTEJ postaci (bez tagów html, skryptów itp.), nie znaczy, że jest bardziej niebezpieczny niż zwykłe zapytania HTTP, które prezentują te same informacje obklejone różnym świństwem ;]. Pomijamy tylko proces parsowania.

  5. billy0o 10

    Poczytaj o page scraping’u, oraz HTML scraping`u. Nikt się przed tym nie zabezpiecza, bo nie jest to żadne niebezpieczeństwo. Poza tym curl też obsługuję sesje :) Więc marne to zabezpieczenie. Tak jak już mówiłem zabezpieczenia przed scraping`iem to jak JavaScript’owe skrypty próbujące uniemożliwić kopiewanie tekstu. Żenada.

    Pozdrawiam.

  6. MajareQ 11

    Biednyś billy, biednyś…
    Włącz myślenie…

    PS. Dzięki Ci, Grzegorzu :)

  7. Paweł 12

    A taki przykład na przykład kiedy istnieje plik checkuser.php do którego kierowane są requesty AJAXowe z zapytaniem czy dany użytkownik istnieje w bazie. Samo zabezpieczenie może być skonstruowane trochę wyżej niż na poziomie tego pliku. A wykonywanie samego tego pliku z parametrem np. “checkuser.php?user=zdzisiek” może być niebezpieczne. Tak więc dzięki za wskazówkę.

  8. billy0o 13

    @MajareQ Twoja troska jest nadzwyczaj urocza, poruszyłeś mnie ;). Skoro dla Ciebie myślenie to utrudnanie zycia i stwarzanie urojonych problemów to powodzenia życzę. Chciałbym mieć takie problemy z bezpieczeństwem.

    Ja się wycofuję. Pozdro dla kumatych, którzy wiedzą o co kaman ;] A Wy twórzcie sobie hasełka w javascripcie. ;)

    Grzegorz wiem, że tego nie opublikujesz ale zobacz, kod html Twojej strony jest widoczny. Ups :D Moze napisz poradnik jak to zabezpieczyć? Nara

  9. Może najlepiej zrobić stronę w Silver Lighcie (client side ofc) ? żeby nikt przypadkiem nie skopiował twojego tekstu, a fu ! Złodzieje komentarzy !@#!@#!

  10. and 15

    AJAX to nie tylko pobieranie danych. Ja go używam również do przesyłania informacji o dodawaniu, edycji, usuwaniu itd. Nie uważam więc, żeby rezygnacja z zabezpieczeń była dobrym pomysłem.

Dodaj komentarz | * pola obowiązkowe

 
 
 
 

Dozwolone tagi HTML: <a href="" title=""> <blockquote cite=""> <code> <em> <strong> <pre lang="" line="" escaped="">

Komentarze są moderowane. Mile widziane wpisy wnoszące nowe, ciekawe informacje do omawianego tematu
lub sygnalizujące ewentualne błędy merytoryczne. Wszelkie przejawy spamu lub nieetycznego zachowania bedą
karane blokadą adresu IP/domeny.