Każdy programista powinien wiedzieć, że wszelkie dane wejściowe powinny być filtrowane. Program ma otrzymać z zewnątrz to czego oczekuje i nic ponadto ! W kontekście PHP jest to wyjątkowo ważna kwestia gdyż, może to doprowadzić do nieoczekiwanych sytuacji nie tylko dla naszej strony ale także i dla samego serwera.
Brak filtrowania danych
Poniżej przedstawię w skrócie kilka przykładów, które naświetlą problem bezpieczeństwa przy braku filtrowania danych wejściowych. Zakładamy, że dane są przesyłane metodą GET tak więc potencjalny haker może zmienić wartość, która jest przekazywana do skryptu ! I to jest kluczowa kwestia przy filtrowaniu, usunąć wszystkie niebezpieczne znaki ze zmiennych $_GET a także zawęzić typ danych, np: liczba lub tekst. Poniżej kilka niebezpiecznych wariantów w których potencjalny haker może wstrzyknąć spreparowane dane – modyfikując zapytanie w linku URL:
- możliwość odczytanie dowolnego pliku na serwerze na przykład pliku z hasłami: /etc/passwd
- wstrzyknięcie kodu HTML lub języka skryptowego np. JavaScript
- uruchomić zdalnie program na serwerze, np. jeśli skrypt PHP korzysta z funkcji exec()
- spreparować zapytanie MYSQL
Zabezpieczenia i filtracja
1. Ograniczenie operacji na plikach do katalogu użytkownika
Aby uniemożliwić funkcjom PHP otwieranie plików poza zadeklarowanym katalogiem ( np. użytkownika ), należy skorzystać z dyrektywy: open_basedir. Dyrektywa ta znajduje się w pliku konfiguracyjnym php.ini. Dla przykładu:
open_basedir = /home/
UWAGA: pamiętaj o dodanie slasha „/” na końcu ścieżki określającej dostęp do plików dla modułu PHP. W przeciwnym wypadku, będziemy mieli do czynienia z wieloznacznością np.: home{inne znaki} będzie możliwy dostęp do wszystkich katalogów zaczynających się od „home„.
Jeśli chcemy ustawić ścieżkę dla dyrektywy open_basedir dla poszczególnych wirtualnych hostów to musimy dodać odpowiednią dyrektywę w pliku odpowiedzialnym za ustawienie VIRTUAL HOSTÓW. Dla przykładu:
<VirtualHost *:80> ServerName macsurf.com ServerAlias www.macsurf.com DocumentRoot "/Users/Macsurf/Documents/" php_admin_value open_basedir /Users/Macsurf/Documents/ </VirtualHost>
Teraz możemy sobie dla przetestowania dyrektywy: open_basedir sprawdzić czy istnieje możliwość odczytania pliku poza zadeklarowanym katalogiem. Nie ma znaczenia z jakiej funkcji PHP skorzystamy ( file(), file_get_contents(), fopen() ) moduł wykonawczy PHP zgodnie z powyższą dyrektywą odmówi otwarcie takowego pliku ! A więc w ten sposób mamy problem z głowy jeśli chodzi o odczytywanie newralgicznych danych, np. odczytanie pliku z hasłami na serwerze: /etc/passwd – nie powiedzie się.
2. Funkcje filtrujące w PHP
Poniżej lista funkcji, które pomogą nam podczas filtrowania/modyfikowania danych wejściowych:
- strip_tags() : usuwa znaczniki HTML i PHP
- str_replace() : zastępuje znak podany w pierwszym parametrze znakiem określonym w drugim parametrze
- utf8_decode() : sprowadza wszystkie znaki UTF-8 do postaci jedno-bajtowej ( single-byte ) ASCII
- substr() : wycina fragment ciągu tekstowego ( w naszym przypadku będzie ograniczać maksymalny rozmiar ciągu wejściowego )
- trim() : usuwa białe znaki przed i za ciągiem
Praktyczny przykład
Zakładamy, że mamy dwie zmienne, których wartość jest przekazywana metodą: GET: „number” i „user” do pliku index.php:
https://www.mor.pl/index.php?number=15&user=macsurf78
Nasze kryteria co do obu zmiennych są dość proste – zmienna number ma przyjmować wartość tylko i wyłącznie numeryczną a user alfanumeryczną.
// filtrowanie ogólne wszystkich zmiennych z $_GET: foreach($_GET as $key => $value) { $value = substr($value, 0, 20); $value = trim($value); $value = strip_tags($value); $value = utf8_decode($value); // filtrowanie indywidualne tylko dla $_GET['number'] if($key == 'number') { $value = intval($value); } // filtrowanie indywidualne tylko dla $_GET['user'] else if($key == 'user') { $value = str_replace(array('.', '/', '\\', '+', '-', '?', '=', '&', '@'), '', $value); } // aktualizacja zmiennej wejściowej przefiltrowaną wartością $_GET[$key] = $value; }