Ataki polegające na modyfikowaniu żądań przesyłanych przez przeglądarkę do programu na serwerze to chyba najpopularniejsza metoda próby popsucia humoru programistom. Sam testując nowy serwis / portal internetowy, pierwsze co robię to podmiana wartości zmiennych przesyłanych w URL - dodaję apostrofy lub cudzysłowy, zwiększam zmienne liczbowe ponad wartości graniczne, cuduję ile wlezie. Jak mi się uda - udając skromność i chłodny profesjonalizm wspominam o tym “przy okazji” komu się da. Jak sie nie uda, nie wspominam, że w ogóle próbowałem, “bo po co?”. Mina mi szybko rzednie, gdy się okazuje, że po raz kolejny pod latarnią najciemniej.
Podstawowe zasady bezpieczeństwa pisania skryptów po stronie serwera zabraniają ufania danym przesyłanym przez użytkowników, słusznie, lecz dotyczy to nie tylko danych pochodzących z żądań POST i GET oraz zapisanych w cookies, lecz także zmiennych ustawianych i wysyłanych przez przeglądarkę internetową. Co więcej, użytkownik nawet nieświadomie może spowodować błędne działanie skryptów, które nie zostały napisane zgodnie z zasadą braku zaufania.
A teraz ostrzeżenie na żywym przykładzie. Za pomocą zmiennej środowiskowej $_SERVER można pisać skrypty PHP bardziej elastyczne - $_SERVER[’DOCUMENT_ROOT’] uwalnia nasz system od instalowania go zawsze w tym samym miejscu na serwerze, zmienna $_SERVER[’PHP_SELF’] uprości nam zmianę nazwy pliku, w którym się ona mieści, gdy kiedyś dojdziemy do wniosku, że nazwa client_edit.php jest wyjątkowo nie trendy w odróżnieniu od clientEditGiveMeABeerOrTwo.php. Zmienna $_SERVER[’HTTP_REFERER’] likwiduje problem “SkOnt kLiAaSh”… i właśnie zaufanie do tej zmiennej, a dokładnie jej brak spowodował, że mój skrypt kręcił się w kółko próbując powrócić na stronę, z której został wywołany.
A tak do tego doszło (na zmyślonym przykładzie artykułów):
Jako że można usunąć artykuł z kilku miejsc w systemie (edycja artykułu, wyszukiwanie artykułu, lista artykułów w grupie artykułów) napisałem skrypt usuwający artykuł i przenoszący użytkownika z powrotem na stronę, na której był wcześniej. W erze Ajax takie rozwiązanie wydaje sie prehistoryczne ale jeszcze niedawno byłem dumny jak paw z takiego pseudo-ajaxowego-z-przeładowaniem-strony zachowania mojego systemu. Budowa jego była bardzo prosta:
// usuwam artykuł
$obj->deleteArticle($_GET[‘article_id’]);
…
// przekierowuje na stronę, z której żądanie usunięcia zostało wysłane
header(‘Location:’ . $_SERVER[‘HTTP_REFERER’]);
…
Wszystko działało, dopóki nie zmieniłem sposobu wywołania tego skryptu - zamiast wpisywać adres skryptu kasowania artykułu w atrybut “href” znacznika <a>, wpisywałem go w atrybut “onclick” i wywoływałem za pomocą zmiany lokacji przez JavaScript:
<a href=“article_delete.php?article_id=1″></a>
<!– zmieniłem na: –>
<a onclick=“document.location=’article_delete.php?article_id=1′”>…</a>
<!– aby móc wywoływać także po kliknięciu na obrazki / inne obiekty na stronie –>
<img onclick=“document.location=’article_delete.php?article_id=1′” src=“…” />
<td onclick=“document.location=’article_delete.php?article_id=1′”></td>
W czym problem? Internet Explorer po wykonaniu zmiany lokacji za pomocą document.location “zapomina” wysłać w nagłówku adresu strony, w zasadzie wysyła go jedynie wtedy, gdy klikniemy na link z atrybutem “href”, prześlemy formularz POST lub GET używając do tego przycisku <input> typu “submit” lub “image” albo wywołując funkcję .submit() na formularzu (a i to się może zmienić przy nowszych wersjach przeglądarki). Bezpieczniejszym sposobem jest przebudowanie skryptu, aby sam wiedział, na którą stronę ma wrócić, dodanie zmiennych do sesji przechowujących ostatnią odwiedzaną stronę, bądź bardziej elastyczne lecz mniej bezpieczne przekazywanie za pomocą zmiennej POST lub GET pełnego adresu strony, na którą chcielibyśmy, aby nasz program powrócił po wykonaniu wszystkich operacji. To tylko trzy z najpopularniejszych sposobów rozwiązania tego problemu.
Puenta: nastała era Ajaxa, rozwiązania takie jak powyższe już nie powstają lecz przyzwyczajenie do zmiennych przesyłanych przez przeglądarkę pozostało i w wielu skryptach jeszcze są one używane. Nie jest to błąd i znając sposoby działania przeglądarek możemy się odpowiednio zabezpieczyć, ale co się stanie, gdy nowa przeglądarka ze stajni Microsoft lub Mozilla zdobędzie 80% rynku i nagle okaże się, że nie wysyła ona w ogóle żadnych “zbędnych” nagłówków? Albo gdy zechcesz tak jak ja przebudować interfejs użytkownika i nagle przeglądarka odmówi współpracy? Podpowiem: Twój skrypt zacznie kręcić się w kółko, Ty stracisz kilka cennych minut / godzin (w zależności od skomplikowania kodu i ilości browara wypitego dzień wcześniej) na szukanie źródła błędu a Twoja klawiatura - klawisz enter, 6 i tabulator. Nie ufaj użytkownikom, nie ufaj przeglądarkom, nie ufaj swojemu staremu kodowi i nie pal papierosów.