Bezpieczeństwo PHP

18.04.2006 Michał Prysłopski

Aby ułatwić tworzenie bezpiecznych modułów PHP, zebrałem swoisty „dekalog projektanta”. Języki programowania nie są ze swojej natury niebezpieczne. To projektanci konkretnych systemów popełniają błędy narażające użytkowników i szkodzące wizerunkowi sieci

W zestawieniu celowo pomijam problem niebezpieczeństwa podsłuchania komunikacji naszego serwera z komputerem użytkownika. Nie jest to bowiem kwestia PHP i rozwiązaniem jest użycie szyfrowanego protokołu SSL/HTTPS.

1. Nie wierz w ciemno żadnym danym przesyłanym z komputera użytkownika

Wszystkie dane, nie tylko przesyłane przez formularze, można łatwo sfałszować.

Standard HTTP zakłada, że przeglądarka WWW użytkownika sama i niesprawdzana tworzy pełne zapytanie do twojego serwera. Może w nim podać cokolwiek. Np. że nazywa się NinjaExplorer 3.14 i działa na systemie ZX Spectrum, a w ogóle to numer IP komputera należy do papieża.

Parametry ukryte formularzy są tak samo widoczne i zmienialne, jak te jawnie wpisywane przez użytkownika, nie zakładaj więc, że wartości, które wpiszesz do formularza, dostaniesz z powrotem.

2. Sprawdzaj poprawność wszystkich danych i czyść w nich starannie znaki specjalne

Dotyczy to zwłaszcza informacji przesyłanych do bazy danych, ale też np. tekstów przetwarzanych przez wyrażenia regularne. Przydatna skądinąd funkcja pregreplace_ może być zmanipulowana do uruchamiania skryptów.

Podobnie znak nowej linii w nazwie nadawcy może zamienić funkcję mail w maszynkę do wysyłania spamu za pomocą Twojego serwera. Dzieje się tak dlatego, że \n jest znakiem specjalnym w formacie nagłówka e-maila i można po nim umieścić dodatkowe instrukcje.

Zaprzyjaźnij się z~funkcjami typu ctypealnum, mysqlrealescapestring, intval czy htmlentities.

3. Dawaj jak najmniej uprawnień

Zawsze im mniej, tym bezpieczniej. Np. czy użytkownik musi mieć możliwość wstawiania kodu HTML do swojego podpisu na forum? Z takimi uprawnieniami, wstawiając kod javascript, mógłby np. wykradać konta innym użytkownikom. W tym konkretnym przypadku alternatywą jest zastosowanie czegoś w rodzaju BBCode.

4. Sprawdzaj wszystko wielokrotnie

Potwierdzaj powtórnie czystość danych przed ważnymi operacjami. Np. pliki inkludowane powinny zakładać, że mogą być uruchamiane podstępnie poza systemem, powinny więc same sprawdzać np. uprawnienia użytkownika do czynności, którą wykonują.

Nie bój się prosić użytkownika o powtórzenie hasła przed ważnymi operacjami. Zmieniaj wtedy też identyfikator sesji funkcją sessionregenerateid.

Żeby ułatwić użytkownikowi życie, proś go w takiej sytuacji tylko o wpisanie hasła, a nie o pełne powtórne zalogowanie.

5. Nie przechowuj niczego wartościowego w ciasteczkach

Ciasteczka (cookies) przeglądarki użytkownika można podejrzeć lub ukraść. Przechowywanie tam haseł to proszenie się o kłopoty. Używaj danych sesji lub wpisów do bazy danych – informacji przechowywanych na twoim serwerze.

6. Przy przekazywaniu przez użytkownika ważnych informacji wymuszaj metodę POST

Metoda ta nie daje bezpieczniejszych wartości danych, ale znacznie utrudnia phishing i inne ataki polegające na podszywaniu się albo podkładaniu zmanipulowanych linków.

Czytając wartości pól formularza, używaj tabeli $POST a nie $REQUEST.

Metoda GET powinna być używana tylko wtedy, gdy użytkownik pyta o informację i to pytanie może bezpiecznie powtarzać, np. przy wyszukiwaniu. Efektywnie ustawienie formularza z metodą GET jest tym samym co umieszczenie na stałe linku na stronie.

Ponieważ nazwy metod GET i POST nie zostały wybrane przypadkowo, większość przeglądarek inaczej je obsługuje i np. pyta o dodatkowe potwierdzenie przed powtórnym wysłaniem formularza POST.

Jeżeli chcesz dodatkowo zapewnić unikalność operacji POST, umieść w nim dynamicznie unikalny „token” przechowywany równolegle w sesji lub bazie danych. Zazwyczaj takie zabezpieczenie utrudnia użytkownikowi edycję danych w serwisie WWW (np. ustawień jego konta), więc powinno być ograniczone do rzeczywiście unikalnych operacji.

7. Opóźniaj logowanie

Metodą „brutalnej siły”, testując tysiące albo miliony haseł, można w końcu odgadnąć to właściwe. Łatwo można sprawić, by takie podejście było niepraktyczne, jeżeli wymusi się choćby 15-sekundową przerwę między kolejnymi próbami zalogowania się na jedno konto użytkownika.

8. Pliki PHP nie muszą się znajdować w drzewku dokumentów serwera

Pliki z hasłami do bazy danych i innymi ważnymi danymi można umieścić w katalogu niedostępnym przez HTTP. Np. zamiast w /home/me/www/include//home/me/secrets/

9. Przed udostępnieniem aplikacji włącz cichą i restrykcyjną kontrolę błędów PHP

10. Utrudniaj przechwycenie sesji

Pomijając podsłuchiwanie komunikacji, przechwycenie numeru sesji jest możliwe albo przez wykorzystanie luk w przeglądarce wykorzystywanej przez użytkownika, albo przez wprowadzenie na twoją stronę kodu, który przez javascript prześle odpowiednie dane na serwer napastnika.

Jeżeli dobrze czyścisz wszystkie wyświetlane dane, ryzyko jest niewielkie. Możesz je jednak dodatkowo ograniczyć używając dodatkowych parametrów do weryfikacji sesji poza samym jej identyfikatorem.

Taki dodatkowy „token” najlepiej przekazywać przez parametry zaszyte w URL, dzięki czemu napastnik musiałby przechwycić jednocześnie identyfikator sesji ofiary jak i jego komunikację z naszym serwisem, aby przejąć sesję.

Możesz w ten sposób zwiększyć też bezpieczeństwo formularzy przez przekazywanie tokena o krótkim czasie życia przez pole ukryte oraz przez zmienne sesji. W ten sposób będziemy mieli pewność, że użytkownik odsyła nam formularz, który przed chwilą mu wysłaliśmy.