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 ambitnego kina sci-fi oraz grunge-rocka z lat 90.

Zobacz wszystkie artykuły tego autora (15)

  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.

    • chyba troche nie zrozumiales tego artykulu – a jak chcesz wygenerowac token nie znajac losowego ciagu znakow ???

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

      przeciez nie masz dostepu do kodu zrodlowego aplikacji…

  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.

  11. curl 16

    Przypominam, że cURL ciasteczka też obsługuje, spece od bezpieczeństwa.

  12. jakub 17

    WYdaje mi sie, że w tej dyskusji potrzebny jest jeszcze jeden głos. Wszelkie zabezpieczenia kluczowe ze względu na bezpieczeństwo skryptu, wszelakie, należy stosować po stronie serwera. Reszta jest nieskuteczna. Także posiadanie tokenu (który rozdajemy za darmo przy każdej wygenerowanej stronie) przy zapytaniu nie świadczy o tym, że zapytanie to jest dobre, właściwe i wykonane zgodnie z naszą wolą. Reszta to jak wspomniał billy utrudnianie rzeczy, które i tak da się zrobić, bez sensu.

  13. tcd 18

    Przypominam, ze istnieje mozliwosc modyfikacji przeslanych kodów JS. I to przy pomocy np. narzedzi developera Chrome’a. Mozna wiec bez problemu wiekszego odczytac tokeny i zaszczepic je w kodzie js z zapytaniem bezposrednio. Tez sklaniam sie ku zdaniu ze albo wysylamy cos uzytkownikowi (czy xhrem czy normalnie) i nie liczymy na poufnosc “tylko dla przegladarki” przeslanych danych albo zabezpieczamy nasza aplikacje od strony php i wysylamy userowi tylko to do czego on ma prawo miec dostep. Pozdrawiam

  14. Brudka 19

    Dobry temat do analizy.

    Rozumiem blog jest tylko przykładem.
    W sumie problemem jest właśnie bezpieczeństwo technologii wykonywanych po stronie klienta albo wykonywanych częściowo po stronie klienta.
    Z racji samej ich natury (kompilacja przy wykonaniu) kod bez obfuskacji jest praktycznie do pobrania od ręki i do analizy z marszu.
    Można stosować rozwiązania typu md5 i dzielić obsługę tak by przykładowo dane-klucze były dodatkowo generowane przez skrypty serwerowe. No ale to takie obchodzenie problemu dookoła. ;)

    Temat analizuję bardziej pod kątem bezpieczeństwa danych (pomijając szyfrowanie https) aniżeli pod kątem autoryzacji wpisów w jakieś blogi. Bo prosta capcha zrobi swoje. ;)

Dodaj komentarz * pola obowiązkowe

 
 

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

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.