Co lepsze do obsługi zapytań HTTP, filter_var czy filter_input?

Wielokrotnie początkujący programiści PHP zastanawiają się, która z tych funkcji jest lepsza. Mają one dużo wspólnych elementów, jednakże w różnych środowiskach sprawdzają się one raz lepiej, raz gorzej.

Zacznijmy od tego, co właściwie te funkcje nam dają. Otóż obie pozwalają nam na odkażanie i sprawdzanie poprawności zmiennych, w tym danych przychodzących od użytkowników. I tu kłania się podstawowa zasada: „Nigdy nie ufaj użytkownikom”, a właściwie danym, które Ci użytkownicy przesyłają do naszych aplikacji PHP.

Ale czy nie można tych czynności, jak na przykład sprawdzanie poprawności, robić z poziomu HTLM5 i JavaScript? Można, a nawet trzeba, jednakże na tym poziomie ma to na celu wspomóc użytkownika przy wprowadzaniu danych, a nie zabezpieczyć serwer.

No dobrze, ale jak użyć tych funkcji? Wyobraźmy sobie taki kod:

<?php

/*
  fragment tworzący połączenie do bazy danych
*/

$sql = 'SELECT author, content FROM posts WHERE id = ' . $_GET['id'] . ';';

/*
  fragment wykonujący zapytanie do bazy danych
*/

Nie będzie żadnego problemu, jeżeli użytkownik odwiedzi stronę:

https://nasz_serwer/?id=44

Ponieważ zapytanie, które się wykona, będzie wyglądać tak:

SELECT author, content FROM posts WHERE id = 44;

Jednakże, gdyby ktoś wysłał zapytanie takiej treści:

https://nasz_serwer/?id=44%3B+DROP+TABLE+posts

Wtedy do bazy danych poleci zapytanie:

SELECT author, content FROM posts WHERE id = 44; DROP TABLE posts;

I tak oto staliśmy się ofiarą ataku SQL Injection, co może przysporzyć nam niemałego problemu. I tu uwaga, specjalnie pomijam tutaj możliwość weryfikacji danych i powiązywania wartości z poziomu pdo czy mysqli. Na potrzeby tego wpisu, zapytania SQL będziemy wykonywać bezpośrednio, czego absolutnie nie wolno robić w aplikacjach „produkcyjnych”.

Aby ustrzec się przed atakiem w tej formie, użyjemy funkcji filter_input, zatem nasz kod będzie wyglądał tak:

<?php

/*
  fragment tworzący połączenie do bazy danych
*/

$id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);

if ($id != NULL) {
  $sql = 'SELECT author, content FROM posts WHERE id = ' . $id . ';';

  /*
    fragment wykonujący zapytanie do bazy danych
  */
} else {
  /*
    fragment z obsługą błędu (wyświetlający błąd lub podejmujący inne działania)
  */
}

Oczywiście poza „INPUT_GET, 'id'”, odpowiadającemu $_GET[’id’], mamy również opcje INPUT_POST, INPUT_COOKIE, INPUT_SERVER oraz INPUT_ENV. Jako trzeci parametr zaś został podany filtr. Dzielą się one na trzy grupy: sprawdzające poprawność, odkażające oraz inne. Pełny ich spis dostępny jest w dokumentacji. W zaprezentowanym przykładzie został użyty filtr, którego zadaniem jest usunięcie wszystkich znaków, które nie są cyfrą.

No dobrze, a czy nie możemy do zabezpieczenia tego zapytania użyć filter_var? Oczywiście, że możemy. Przyjmuje on te same filtry, które przyjmuje filter_input, zatem przykładowy kod wyglądałby tak:

<?php

/*
  fragment tworzący połączenie do bazy danych
*/

$id = filter_var($_GET['id'], FILTER_SANITIZE_NUMBER_INT);

if ($id != "") {
  $sql = 'SELECT author, content FROM posts WHERE id = ' . $id . ';';

  /*
    fragment wykonujący zapytanie do bazy danych
  */
} else {
  /*
    fragment z obsługą błędu (wyświetlający błąd lub podejmujący inne działania)
  */
}

W momencie, gdy parametr „id” w zapytaniu występuje i ma on przynajmniej jedną cyfrę, to wynik działania obu tych funkcji jest ten sam. Różnice zaczynają się pojawiać, gdy w zapytaniu tego parametru nie ma.

Gdy parametru id w zapytaniu nie ma, funkcja filter_input zwróci NULL, zaś funkcja filter_var zwróci pusty string, dodatkowo wywołując ostrzeżenie, gdyż próbujemy dostać się w tablicy $_GET do elementu z nieistniejącym indeksem. Możemy się oczywiście przed tym ostatnim zabezpieczyć używając funkcji isset lub filter_has_var, jednakże jedynie dostosowujemy swój kod do funkcji, która nie jest stworzona do tego, do czego chcemy jej użyć.

I w tym momencie dochodzimy do odpowiedzi na pytanie z tytułu. Do obsługi zapytań HTTP powinno się używać filter_input, bo do tego ta funkcja została stworzona. A filter_var? Tę funkcję powinniśmy stosować do sprawdzania zmiennych istniejących w naszym kodzie, lub otrzymywanych w wyniku wcześniejszego przetwarzania danych wejściowych.

Bibliografia:

Leave a Comment

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *