Aktualna sytuacja: PHP2Shell. Dwie krytyczne luki w PHP (CVSS 9,2) umożliwiają wykonanie złośliwego kodu bez logowania. Poprawki od 21.10.2026.Co należy teraz zrobić →

Walidacja i filtrowanie danych wejściowych w PHP za pomocą filter_var

Jak programiści sprawdzają dane wejściowe w PHP za pomocą filter_var, filter_input_array i list dozwolonych, które filtry są przestarzałe i dlaczego walidacja nie zastępuje prepared statements ani kodowania wyjścia.

Stan 5 min czytaniaBezpieczne programowanie

Krótko: Dane wejściowe w PHP walidujesz za pomocą filter_var() lub filter_input_array() i jasnych reguł dla każdego pola: typ, zakres, długość i dozwolone wartości. To, co nie pasuje, jest odrzucane, a nie po cichu przerabiane. Walidacja jest ważna dla czystych danych i mniejszej powierzchni ataku, ale nie zastępuje ani prepared statements przeciw SQL injection, ani kodowania zależnego od kontekstu przeciw XSS.

Dlaczego „filtrować wszystko” nie jest koncepcją

W wielu starszych projektach PHP na początku każdego pliku znajduje się pętla, która przepuszcza wszystkie wartości z $_GET i $_POST przez funkcję oczyszczającą. Daje to poczucie bezpieczeństwa. W praktyce raz usuwa za dużo, na przykład apostrofy w nazwiskach, a raz za mało, ponieważ ta sama funkcja ma jednocześnie obowiązywać dla SQL, HTML i nazw plików.

Sensowna walidacja działa inaczej: dla każdego pola pyta, co jest tam dozwolone. Ilość w zamówieniu to liczba całkowita od 1 do górnej granicy. Pole sortowania to jedna z trzech stałych wartości. Data występuje w określonym formacie i jest po innej dacie. Wszystko inne jest odrzucane.

OWASP rozróżnia tu dwa poziomy: kontrolę składniową, czy wartość ma właściwą formę, oraz kontrolę semantyczną, czy w danym kontekście ma sens. Obie należą do serwera; kontrole w przeglądarce służą wyłącznie wygodzie obsługi.

Jakie filtry oferuje PHP

Rozszerzenie filter jest w PHP domyślnie dołączone. Najważniejsze filtry walidacyjne według podręcznika PHP:

Filtr Sprawdza Ważne opcje i wskazówki
FILTER_VALIDATE_INT liczba całkowita min_range, max_range
FILTER_VALIDATE_FLOAT liczba z przecinkiem decimal dla separatora
FILTER_VALIDATE_BOOL wartość logiczna z FILTER_NULL_ON_FAILURE można odróżnić od „fałsz”
FILTER_VALIDATE_EMAIL forma adresu e-mail nie dowodzi, że skrzynka istnieje
FILTER_VALIDATE_URL forma adresu URL schemat sprawdzić samodzielnie, brak internacjonalizowanych domen
FILTER_VALIDATE_IP adres IP flagi dla zakresów prywatnych i zarezerwowanych
FILTER_VALIDATE_REGEXP własny wzorzec zakotwiczyć wzorzec za pomocą ^ i $

Filtr oczyszczający FILTER_SANITIZE_STRING jest od PHP 8.1 przestarzały. Kto jeszcze go używa, powinien przestawić walidację na odpowiednie filtry, a przy wyświetlaniu stosować htmlspecialchars().

filter_var w PHP: poprawne sprawdzanie formularza

Za pomocą filter_input_array() można sprawdzić cały formularz w jednym kroku względem definicji. Nieznane pola nie są przy tym przejmowane:

<?php
declare(strict_types=1);

$regeln = [
    'menge'   => ['filter' => FILTER_VALIDATE_INT,
                  'options' => ['min_range' => 1, 'max_range' => 50]],
    'email'   => FILTER_VALIDATE_EMAIL,
    'agb'     => ['filter' => FILTER_VALIDATE_BOOL, 'flags' => FILTER_NULL_ON_FAILURE],
    'versand' => ['filter' => FILTER_VALIDATE_REGEXP,
                  'options' => ['regexp' => '/^(standard|express)$/']],
];

$daten  = filter_input_array(INPUT_POST, $regeln) ?? [];
$fehler = [];

foreach ($regeln as $feld => $_) {
    if (!array_key_exists($feld, $daten) || $daten[$feld] === false || $daten[$feld] === null) {
        $fehler[] = $feld;
    }
}

if ($fehler !== []) {
    http_response_code(422);
    // Wyświetlić komunikat o błędzie dla każdego pola, nie przetwarzać danych wejściowych
}

Zwróć uwagę na ścisłe porównania za pomocą ===: prawidłowa wartość 0 lub pusty ciąg nie mogą być mylone z niepowodzeniem. Dlaczego luźne porównania są tu niebezpieczne, wyjaśnia artykuł Type juggling: luźne porównania jako ryzyko bezpieczeństwa. Jak PHP w ogóle udostępnia dane żądania, opisuje artykuł Superglobals: $_GET, $_POST, $_FILES i inne.

Pułapki przy e-mailu, URL i tekstach swobodnych

Niektóre filtry robią mniej, niż sugeruje ich nazwa:

  1. Adresy URL: FILTER_VALIDATE_URL akceptuje także inne schematy niż http i https. Sprawdź potem schemat za pomocą parse_url() względem stałej listy. Jeśli adres URL jest pobierany przez serwer, obowiązują dodatkowe reguły, zob. Zapobieganie Server-Side Request Forgery w PHP.
  2. Adresy e-mail: Filtr sprawdza formę, a nie posiadanie. Kto musi mieć pewność, wysyła link potwierdzający.
  3. Tekst swobodny: Imion, wiadomości i adresów prawie nie da się sensownie ograniczyć do znaków. Sprawdzaj długość i kodowanie znaków, na przykład za pomocą mb_check_encoding($wert, 'UTF-8'), a przy wyświetlaniu polegaj na kodowaniu.
  4. Wyrażenia regularne: Bez ^ i $ wzorzec sprawdza tylko, czy występuje gdzieś w tekście, a nie czy pasuje cała wartość.
  5. Tablice zamiast ciągów: Atakujący może zamiast tekstu wysłać tablicę. Bez kontroli typu prowadzi to do ostrzeżeń lub nieoczekiwanego zachowania.
Wskazówka

Dla stałych wartości wyboru, takich jak sortowanie, język czy sposób wysyłki, lista dozwolonych z in_array($wert, $erlaubt, true) lub wyrażeniem match jest prostsza i ostrzejsza niż każde wyrażenie regularne.

Kolejny częsty błąd dotyczy liczb o znaczeniu, takich jak ceny, ilości czy rabaty. To, że wartość jest prawidłową liczbą, nic nie mówi o tym, czy jest dozwolona w danym kontekście. Ujemna ilość, rabat przewyższający wartość towaru albo cena przejęta z formularza zamiast z bazy danych to klasyczne błędy logiki. Ceny, uprawnienia i wartości statusów należą więc zawsze na serwer, a nigdy do ukrytych pól formularza.

JSON, webhooki i importy: walidacja poza formularzami

Wiele wejść to dziś nie formularze, lecz żądania JSON z aplikacji, webhooki z usług płatniczych albo pliki importowe od dostawców. Także tam obowiązuje: najpierw bezpiecznie wczytać, potem sprawdzić. OWASP radzi ograniczać rozmiar i głębokość zagnieżdżenia, zanim dane w ogóle zostaną wczytane. W PHP część tego przejmuje json_decode() z maksymalną głębokością i JSON_THROW_ON_ERROR:

<?php
$roh = file_get_contents('php://input', false, null, 0, 65536); // najwyżej 64 KB
try {
    $daten = json_decode($roh, true, 16, JSON_THROW_ON_ERROR);
} catch (JsonException) {
    http_response_code(400);
    exit;
}
if (!is_array($daten) || !is_string($daten['bestellnummer'] ?? null)) {
    http_response_code(422);
    exit;
}

W przypadku webhooków przed kontrolą treści stoi pytanie, czy wiadomość rzeczywiście pochodzi od wskazanej usługi. Jeśli usługa oferuje podpis, sprawdź go i porównaj za pomocą hash_equals(). W przypadku plików importowych sprawdzaj każdy wiersz jak formularz i przy błędach przerywaj w czysty sposób, zamiast przejmować połowiczne rekordy.

Komunikaty o błędach powinny wskazywać pole, którego dotyczą, ale nie zwracać odrzuconej wartości bez filtrowania. W przeciwnym razie w samym komunikacie o błędzie powstaje nowe miejsce wyjścia, które znowu trzeba zakodować.

Czego walidacja nie jest w stanie zapewnić

Walidacja zmniejsza powierzchnię ataku, ale według OWASP nie jest główną obroną przed injection. Nazwisko z apostrofem jest prawidłowe i mimo to musi bezpiecznie trafić do bazy danych. Udaje się to tylko za pomocą prepared statements, jak pokazuje poradnik Zapobieganie SQL injection w PHP. A prawidłowy tekst komentarza staje się bezpieczny dopiero dzięki kodowaniu zależnemu od kontekstu przy wyświetlaniu.

Właściwa kolejność brzmi więc: przy odbiorze walidować, przy przetwarzaniu parametryzować, przy wyświetlaniu kodować. Dodatkowo przed tym stoją granice rozmiaru: żądań, które są znacznie za duże, serwer WWW i PHP w ogóle nie powinny przetwarzać w całości. Służą do tego ustawienia takie jak post_max_size, które po PHP2Shell i tak należy sprawdzić.

Jak konkretnie pomaga PHP SOS?

  • Spisujemy wszystkie wejścia Państwa aplikacji: formularze, interfejsy, uploady, importy i webhooki.
  • Ustalamy dla każdego pola, co jest dozwolone, i zastępujemy ogólne funkcje oczyszczające jasnymi kontrolami.
  • Sprawdzamy, czy dostęp do bazy danych i wyświetlanie są zabezpieczone niezależnie od walidacji.
  • Dokumentujemy reguły tak, aby Państwa zespół mógł je kontynuować przy nowych funkcjach.

Systematyczną kontrolę opisuje usługa audyt bezpieczeństwa i test penetracyjny.

Częste pytania

Jaka jest różnica między walidacją a oczyszczaniem?

Walidacja sprawdza, czy wartość odpowiada oczekiwaniom, a w przeciwnym razie ją odrzuca. Oczyszczanie zmienia wartość, na przykład usuwając znaki. W decyzjach dotyczących bezpieczeństwa odrzucenie jest prawie zawsze lepsze, ponieważ po cichu zmieniona wartość może powodować nowe błędy.

Czy FILTER_SANITIZE_STRING można jeszcze używać?

Filtr jest od PHP 8.1 oznaczony jako przestarzały. Podręcznik PHP zaleca zamiast niego htmlspecialchars() przy wyświetlaniu. Istniejący kod należy zmienić, ponieważ przestarzałe funkcje mogą zniknąć w przyszłych wersjach.

Czy FILTER_VALIDATE_URL wystarczy, aby zabezpieczyć linki?

Nie. Filtr sprawdza tylko formę adresu. Według podręcznika prawidłowy adres URL nie musi zaczynać się od http, dlatego schemat trzeba samodzielnie sprawdzić względem listy dozwolonych, takiej jak https. Internacjonalizowane nazwy domen filtr odrzuca.

Czy muszę walidować także dane z własnej bazy danych?

Przy wyświetlaniu uznaje się je za niezaufane i odpowiednio koduje. Ponowna walidacja ma sens, jeśli dane pierwotnie pochodziły z zewnątrz i są teraz używane w nowym kontekście, na przykład jako nazwa pliku lub część polecenia.

Czy walidacja pomaga przy PHP2Shell?

Nie. PHP2Shell dotyczy przetwarzania żądań w interpreterze PHP, zanim kod Państwa aplikacji w ogóle zacznie działać. Pomaga na to wyłącznie aktualizacja PHP z następującym po niej restartem wszystkich procesów PHP.

Źródła

Kontakt natychmiast

Liczy się każda godzina. Proszę napisać do nas teraz.

Proszę krótko opisać system, wersję PHP i obserwacje. Skontaktujemy się z Państwem, ustalimy sytuację i będziemy pracować zdalnie, aż incydent zostanie zamknięty i udokumentowany.

Natychmiastowa pomoc