Filtrowanie danych w PHP
Udostępniając formularz na stronie internetowej, bądź przykładowo API dajemy użytkownikom możliwość wysłania do nas dowolnych danych. Zresztą samo posiadanie strony jest już czynnikiem ryzyka związanym z atakami, bo różne skrypty na stronie wysyłają i przyjmują różne zapytania z zewnątrz.
Jedną z ważnych elementów tworzenia jakichkolwiek aplikacji jest walidacja, bądź sanityzacja danych. W skrócie polega ona na tym, że zanim zapiszemy cokolwiek w bazie, bądź wykonamy operacje na jakichkolwiek danych przekazanych pośrednio lub bezpośrednio przez użytkownika, powinniśmy sprawdzić czy aby na pewno ten wysłał to co założyliśmy.
Przykładowo – jeżeli udostępniamy na stronie formularz w którym użytkownik wpisuje adres e-mail to istotna jest weryfikacja czy aby na pewno użytkownik podał adres e-mail., a nie kod w SQL lub cokolwiek innego. Wyobraźmy sobie taki scenariusz.
- Udostępniamy użytkownikowi możliwość wpisania adresu e-mail w formularzu.
- Następnie zapisujemy adres email w taki sposób (w zmiennej
$email
mamy to co wpisał użytkownik)INSERT INTO users (email) VALUES ('$email')
- Dowcipny użytkownik jednak zamiast prawidłowego adresu e-mail wpisuje
'); DELETE FROM users; --
- To powoduje, że wykonujemy taką komendę:
INSERT INTO users (email) VALUES (''); DELETE FROM users; --')
--
to komentarz w SQL, więc to co jest po nim nie będzie brane pod uwagę, a cała operacje może skutkować usunięciem wszystkich użytkowników w bazie.
Łatwo sobie wyobrazić, że w ten sposób można bardzo dużo – dodać się do bazy, pobierać dane itd.
Filtry
PHP udostępnia zestaw dedykowanych funkcji do filtrowania danych. Dzielą się na dwa typy:
- walidujące, czyli sprawdzanie czy dane są takie jak być powinny
- sanityzujące, czyli sprawiające by dany były takie jakich oczekujemy (np. usunięcie niedozwolonych znaków itp.)
Do obsługi obu typów służy funkcja filter_var
.
filter_var(mixed $value, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed
$value
– wartość, którą sprawdzamy / poprawiamy
$filter
– jeden z kilkudziesięciu dostępnych filtrów np. e-mail, adres strony internetowej itd.
$options
– dodatkowe opcje lub flagi np. zezwól na polskie znaki w adresie e-mail. Przekazujemy je w tablicy z kluczem options
i flags
. Dodatkowo dostępna jest opcja default, która pozwala na zwrócenie dowolnej wartości zamiast false
.
Przykład użycia
$params = [
'options' => [
'default' => 0,
'min_range' => 1,
'max_range' => 1000
],
'flags' => FILTER_FLAG_ALLOW_HEX | FILTER_FLAG_ALLOW_OCTAL
];
filter_var ('555.44', FILTER_VALIDATE_INT, array|int $options = 0): mixed
Istnieje jeszcze tablicowa odmiana funkcji – filter_var_array
, która przyjmuje tablicę wartości, a nie pojedynczą wartość.
Filtry walidujące
Filtry walidujące sprawdzają wartość i zwracają true (prawidłowa), false (nieprawidłowa) lub null (jeżeli jest ustawiona flaga w opcjach FILTER_NULL_ON_FAILURE
. Idea z tą flagą jest taka, że sprawdzany jest format zwracanych danych np. jak oczekujemy wartości „true”, „1”, „on”, „false”, „0”, „off” to otrzymamy null, jeżeli żadna z tych wartości nie zostanie zwrócona. Bez flagi otrzymalibyśmy zawsze false
. Poza flagą niektóre filtry mają również dodatkowe opcje (np. możliwość ustawienia minimalnej i maksymalnej wartości) oraz opcję default
w której przekazujemy co ma zwrócić funkcja zamiast false
.
FILTER_VALIDATE_EMAIL
– sprawdza czy adres e-mail ma prawidłowy format. Dostępna jest flagaFILTER_FLAG_EMAIL_UNICODE
, która zezwala na używanie np. polskich znaków w adresie.FILTER_VALIDATE_URL
– sprawdza czy adres URL ma prawidłowy format. Dodatkowo możemy ustawić flagiFILTER_FLAG_PATH_REQUIRED
orazFILTER_FLAG_QUERY_REQUIRED
, które sprawdzają odpowiednio czy adres zwiera ścieżkę (znak / np.example.com/index.php
) oraz zapytanie (? np.index.php?query=test
).FILTER_VALIDATE_DOMAIN
– sprawdza czy adres domeny ma prawidłowy format. Włączenie flagiFILTER_FLAG_HOSTNAME
dodatkowo sprawdza czy zaczyna się od litery alfabetu lub cyfry oraz zawiera tylko takie znaki lub łączniki (np.-
)FILTER_VALIDATE_IP
– sprawdza czy wartość jest adresem IP. Przy użyciu flag możemy ograniczyć zakres sprawdzania adresu:FILTER_FLAG_IPV4
,FILTER_FLAG_IPV6
,FILTER_FLAG_NO_PRIV_RANGE
,FILTER_FLAG_NO_RES_RANGE
,FILTER_FLAG_GLOBAL_RANGE
,FILTER_VALIDATE_MAC
– sprawdza czy wartość jest adresem MAC.FILTER_VALIDATE_BOOL
– zwracatrue
dla wartości1
,"true"
lub"on"
– przydatne jak korzystamy np. z JavaScript do przesyłania danych typuboolean
FILTER_VALIDATE_INT
– sprawdza czy wartość jest typu int. W opcjach możemy ustawić minimalną (min_range
) i maksymalną (max_range
) wartość. Dodatkowe flagi pozwalają też na przekazanie wartości w systemie szesnastkowym (FILTER_FLAG_ALLOW_HEX
)lub ósemkowym (FILTER_FLAG_ALLOW_OCTAL
)FILTER_VALIDATE_FLOAT
– sprawdza czy wartość jest typu float. W opcjach możemy dodatkowo ustawić minimalną (min_range
) i maksymalną (max_range
) wartość oraz znak rozdzielający część ułamkową np. przecinek zamiast kropki (opcjadecimal
). Ponadto dostępna jest flagaFILTER_FLAG_ALLOW_THOUSAND
, która pozwala na rozdzielanie części tysięcznych np. możliwy wtedy jest taki zapis13,111,753.479
.FILTER_VALIDATE_REGEXP
– sprawdza czy adres pasuje do wyrażenia regularnego. Wyrażenie podajemy w opcjiregexp
.
Filtry sanityzujące
Filtry sanityzujące służą do poprawienia danych (np. usuwają zbędne znaki) i zwracają zmienione dane, które potem możemy np. poddać pod filtr walidujący. Korzysta się z nich tak samo jak z filtrów walidujących z tą różnicą, że nie ma tutaj dostępnych opcji – są tylko dodatkowe flagi. Na końcu wyjaśnimy niektóre flagi, które pojawiają się w wielu funkcjach.
FILTER_DEFAULT
,FILTER_UNSAFE_RAW
– domyślny filtr, który nie robi nic, ale umożliwia ustawienie dodatkowych flag. które omówimy na końcuFILTER_FLAG_STRIP_LOW
,FILTER_FLAG_STRIP_HIGH
,FILTER_FLAG_STRIP_BACKTICK
,FILTER_FLAG_ENCODE_LOW
,FILTER_FLAG_ENCODE_HIGH
,FILTER_FLAG_ENCODE_AMP
FILTER_SANITIZE_EMAIL
– usuwa wszystkie znaki specjalne oprócz dozwolonych w adresie e-mailFILTER_SANITIZE_ENCODED
– usuwa lub zamienia znaki w adresie url, działa podobnie dourlencode()
. Dostępne flagi toFILTER_FLAG_STRIP_LOW
,FILTER_FLAG_STRIP_HIGH
,FILTER_FLAG_STRIP_BACKTICK
,FILTER_FLAG_ENCODE_LOW
,FILTER_FLAG_ENCODE_HIGH
FILTER_SANITIZE_ADD_SLASHES
– dodaje ukośniki przed znakami'
,""
,\
,null
. Działa tak jakaddslashes
.FILTER_SANITIZE_NUMBER_FLOAT
– usuwa wszystkie znaki za wyjątkiem cyfr i znaków+-
. Dostępne flagi pozwalają na zwiększenie zakresu:FILTER_FLAG_ALLOW_FRACTION
– kropka jako znak rozdzielający część ułamkową,FILTER_FLAG_ALLOW_THOUSAND
– rozdzielenie części tysięcznych przecinkiem,FILTER_FLAG_ALLOW_SCIENTIFIC
– używaniee
lubE
jak naukowa notacjaFILTER_SANITIZE_NUMBER_INT
– usuwa wszystkie znaki za wyjątkiem cyfr i znaków plus i minus.FILTER_SANITIZE_SPECIAL_CHARS
– zamienia znaki'"<>&
i znaki sterujące na format HTML-a. Dostępne filtry:FILTER_FLAG_STRIP_LOW
,FILTER_FLAG_STRIP_HIGH
,FILTER_FLAG_STRIP_BACKTICK
,FILTER_FLAG_ENCODE_HIGH
FILTER_SANITIZE_FULL_SPECIAL_CHARS
– zamienia znaki'"<>&
i znaki sterujące na format HTML-a. Działa tak jak funkcjahtmlspecialchars
z włączoną flagąENT_QUOTES
. W przypadku wykrycia znaku spoza domyślnie ustawionego kodowania zwrócony zostanie pusty łańcuch. FiltrFILTER_FLAG_NO_ENCODE_QUOTES
powoduje, że znaki'
i""
nie będą kodowane. Na serwerach często ta flaga jest włączona domyślnie.FILTER_SANITIZE_URL
– usuwa wszystkie znaki oprócz liter, cyfr i używanych w adresie url.
Niektóre flagi są używane przez wiele funkcji. Flagi ze STRIP
w nazwie – usuwają, a te z ENCODE
w nazwie – zamieniają.
FILTER_FLAG_STRIP_LOW
,FILTER_FLAG_ENCODE_LOW
– usuwa / koduje znaki sterujące (ASCII < 32)FILTER_FLAG_STRIP_HIGH
,FILTER_FLAG_ENCODE_HIGH
– usuwa / koduje znaki z górnej połowy tabeli ASCI (ASCII > 127)FILTER_FLAG_STRIP_BACKTICK
,FILTER_FLAG_ENCODE_BACKTICK
– usuwa / koduje znak cofania.
Filtrowanie odpowiedzi z serwera
Do filtrowania danych przychodzących może posłużyć funkcja filter_input
, która w pierwszym parametrze przyjmuje typ danych, a w drugim nazwę pola. W kolejnych parametrach podajemy filtr i opcje (jak w poprzednich funkcjach).
INPUT_GET
– sprawdzana jest zmienna globalna http$_GET
INPUT_POST
– sprawdzana jest zmienna globalna http$_POST
INPUT_COOKIE
– sprawdzana jest zmienna globalna ciasteczek$_COOKIE
INPUT_SERVER
– sprawdzana jest zmienna globalna serwera$_SERVER
INPUT_ENV
– sprawdzana jest środowiskowa zmienna globalna$_ENV
Przykładowo dla adresu http://example.com?test=1
, filter_input(INPUT_GET, 'test')
zwróci domyślnie wartość true
. Oczywiście możemy stosować filtry i opcje tak jak w poprzedniej funkcji i uzyskać poprawiony łańcuch. Jeżeli zmienna nie istnieje zostanie zwrócona wartość null
.
Podobnie jak poprzednio, dostępny jest tablicowy odpowiednik funkcji – filter_input_array
, który przyjmuje tablicę w pierwszym argumencie.
Walidacja z użyciem tablicowego odpowiednika
Funkcja filter_var_array
pozwala na filtrowanie wielu danych jednocześnie.
Możemy ustawić jedną flagę dla wszystkich danych przekazanych w tablicy, albo ustawić tablicę z filtrami, która będzie odpowiadała konkretnym danym.
filter_var_array(
[ 'nick' => 'Eskim',
'age' => 199,
'email' => 'email@example.com',
],
[ 'nick' => [
'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
'flags' => FILTER_FLAG_STRIP_BACKTICK | FILTER_FLAG_ENCODE_HIGH,
],
'age' => [
'filter' => FILTER_VALIDATE_INT,
'flags' => FILTER_FLAG_STRIP_BACKTICK | FILTER_FLAG_ENCODE_HIGH,
'options' => ['min_range' => 3, 'max_range' => 150]
],
'email' => [
'filter' => FILTER_VALIDATE_EMAIL
],
]
]
);
Zapytania SQL
Przed wysłaniem zapytania do bazy powinniśmy również podać je sanityzacji. Do tego służy specjalna funkcja prepare
, która występuje chyba we wszystkich bibliotekach do obsługi zapytań w PHP (choć oczywiście może się nieco różnić składnią). Najpierw używamy prepare
, a potem dopiero wykonujemy zapytanie. Implementacja tej funkcjonalności różni się w zależności od biblioteki, ale cel pozostaje ten sam.
PDO
PDO jest najpopularniejszą biblioteką PHP używaną do operowania na bazie danych. Zanim wyślemy zapytanie zamieniamy je na obiekt i przed wysłaniem (lub w trakcie) wpisujemy parametry do obiektu.
$prep = $db->prepare (' SELECT id FROM users WHERE name = :username');
$prep->bindParam('username', $username, PDO::PARAM_STR);
$prep->execute();
W powyższym przykładzie tworzy zapytanie z parametrem username pod który później podpinamy zmienną typu string $username
. Robimy to za pomocą bindParam
. Oczywiście zmiennych może być więcej. Parametry można także przekazać bezpośrednio przy wywoływaniu metody execute
.
HTML Purifier
HTML Purifier to biblioteka filtrująca napisana w PHP, która służy do oczyszczania kodu HTML usuwając potencjalnie niebezpieczne treści i upewniając się, że wynikowy kod HTML jest poprawny z punktu widzenia norm i standardów. Biblioteka jest zaprojektowana w taki sposób, aby chronić przed różnego rodzaju atakami, takimi jak ataki XSS (Cross-site Scripting):
- oczyszcza kod, usuwając niebezpieczne tagi, atrybuty i skrypty,
- upewnia się, że wynikowy kod HTML jest zgodny ze standardami określonymi przez W3C
Możesz dostosować działanie biblioteki do swoich potrzeb wybierając, które tagi czy atrybuty chcesz zachować, a które usunąć. Potrafi również przekształcać niektóre tagi lub atrybuty w inne na przykład przekształcając wszystkie odnośniki target="_blank"
w rel="noopener"
, aby były bardziej bezpieczne. Biblioteka obsługuje nie tylko fragmenty kodu HTML, ale również całe dokumenty, w tym DOCTYPE, HTML i BODY.
Jeśli planujesz osadzać treści użytkowników na swojej stronie internetowej lub aplikacji, HTML Purifier to jedno z narzędzi, które warto rozważyć w celu zapewnienia bezpieczeństwa i zgodności wynikowego kodu HTML.
Co dalej?
Powyższe nie wyczerpuje tematu, który jest znacznie szerszy i obejmuje każdy rodzaj danych, który pobieramy z dowolnego zewnętrznego źródła – pliku, przez internet, itd. Najważniejsze, by zawsze o tym pamiętać i zakładać najgorszy scenariusz.
Podsumowanie
- Dane uzyskane od użytkownika powinny być filtrowane.
- Sanityzację i walidację danych można przeprowadzić wykorzystując funkcję
filter_var
lub jej tablicowego odpowiednikafilter_var_array
. - Filtry walidujące sprawdzają poprawność podanych danych np. adres e-mail.
- Filtry sanityzujące poprawiają dane na format, który nas interesuje np. usuwają znaki specjalne.
- Do filtrowania danych przychodzących możemy skorzystać z funkcji
filter_input
. - Biblioteki do obsługi baz danych oferują zwykle dedykowane funkcje do sanityzacji danych.