Stručně: Vstupy v PHP validujete pomocí filter_var() nebo filter_input_array() a jasných pravidel pro každé pole: typ, rozsah, délka a povolené hodnoty. Co nesedí, se odmítne, ne mlčky přetvoří. Validace je důležitá pro čistá data a menší plochu pro útok, ale nenahrazuje ani prepared statements proti SQL injection, ani kódování podle kontextu proti XSS.
Proč „filtrovat všechno“ není koncepce
V mnoha starších projektech v PHP se na začátku každého souboru nachází smyčka, která všechny hodnoty z $_GET a $_POST pošle přes čisticí funkci. Působí to bezpečně. V praxi to jednou odstraní příliš mnoho, například apostrofy v příjmeních, a jindy příliš málo, protože tatáž funkce má platit pro SQL, HTML i názvy souborů současně.
Smysluplná validace funguje jinak: u každého pole se ptá, co je tam povoleno. Objednané množství je celé číslo mezi 1 a horní hranicí. Pole pro řazení je jedna ze tří pevných hodnot. Datum má určitý formát a leží po jiném datu. Všechno ostatní se odmítne.
OWASP přitom rozlišuje dvě úrovně: syntaktickou kontrolu, zda má hodnota správný tvar, a sémantickou kontrolu, zda dává smysl v kontextu. Obě patří na server; kontroly v prohlížeči slouží jen k pohodlí obsluhy.
Jaké filtry PHP obsahuje
Rozšíření Filter je v PHP standardně součástí. Nejdůležitější validační filtry podle příručky PHP:
| Filtr | Kontroluje | Důležité volby a poznámky |
|---|---|---|
FILTER_VALIDATE_INT |
celé číslo | min_range, max_range |
FILTER_VALIDATE_FLOAT |
desetinné číslo | decimal pro oddělovač |
FILTER_VALIDATE_BOOL |
pravdivostní hodnota | s FILTER_NULL_ON_FAILURE odlišitelné od „nepravda“ |
FILTER_VALIDATE_EMAIL |
tvar e-mailové adresy | nedokazuje, že schránka existuje |
FILTER_VALIDATE_URL |
tvar URL | schéma ověřit sám, žádné internacionalizované domény |
FILTER_VALIDATE_IP |
adresa IP | příznaky pro privátní a rezervované rozsahy |
FILTER_VALIDATE_REGEXP |
vlastní vzor | vzor ukotvit pomocí ^ a $ |
Čisticí filtr FILTER_SANITIZE_STRING je od PHP 8.1 zastaralý. Kdo ho ještě používá, měl by validaci převést na vhodné filtry a pro výstup použít htmlspecialchars().
filter_var v PHP: čistá kontrola formuláře
Pomocí filter_input_array() lze zkontrolovat celý formulář najednou proti definici. Neznámá pole se přitom nepřebírají:
<?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);
// vypsat chybové hlášení pro každé pole, vstupy nezpracovávat
}
Všimněte si striktních porovnání pomocí ===: platná hodnota 0 nebo prázdný řetězec se nesmí zaměnit s neúspěchem. Proč jsou zde volná porovnání nebezpečná, vysvětluje článek Type juggling: volná porovnání jako bezpečnostní riziko. Jak PHP data požadavku vůbec poskytuje, popisuje stránka Superglobals: $_GET, $_POST, $_FILES a spol..
Úskalí u e-mailu, URL a volných textů
Některé filtry dělají méně, než by název naznačoval:
- URL:
FILTER_VALIDATE_URLpřijímá i jiná schémata než http a https. Schéma poté zkontrolujte pomocíparse_url()proti pevnému seznamu. Pokud server URL stahuje, platí další pravidla, viz Prevence Server-Side Request Forgery v PHP. - E-mailové adresy: Filtr kontroluje tvar, ne vlastnictví. Kdo si potřebuje být jistý, pošle potvrzovací odkaz.
- Volný text: Jména, zprávy a adresy lze jen stěží smysluplně omezit na znaky. Kontrolujte délku a kódování znaků, například pomocí
mb_check_encoding($wert, 'UTF-8'), a při výstupu se spolehněte na kódování. - Regulární výrazy: Bez
^a$vzor kontroluje jen to, zda se vyskytuje někde v textu, ne zda sedí celá hodnota. - Pole místo řetězců: Útočník může místo textu poslat pole. Bez kontroly typu to vede k varováním nebo neočekávanému chování.
Pro pevné hodnoty výběru, jako je řazení, jazyk nebo způsob dopravy, je seznam povolených hodnot pomocí in_array($wert, $erlaubt, true) nebo výrazu match jednodušší a přísnější než jakýkoli regulární výraz.
Další častá chyba se týká čísel s významem, například cen, množství nebo slev. To, že je hodnota platné číslo, neříká nic o tom, zda je v kontextu povolená. Záporné množství, sleva nad hodnotou zboží nebo cena převzatá z formuláře místo z databáze jsou klasické logické chyby. Ceny, oprávnění a stavové hodnoty proto vždy patří na server a nikdy do skrytých polí formuláře.
JSON, webhooky a importy: validace mimo formuláře
Mnoho vstupů dnes už nejsou formuláře, ale požadavky JSON z aplikací, webhooky platebních služeb nebo importní soubory od dodavatelů. I tam platí: nejdřív bezpečně načíst, pak zkontrolovat. OWASP radí omezit velikost a hloubku vnoření dříve, než se data vůbec načtou. V PHP část toho zajistí json_decode() s maximální hloubkou a JSON_THROW_ON_ERROR:
<?php
$roh = file_get_contents('php://input', false, null, 0, 65536); // nejvýše 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;
}
U webhooků stojí před kontrolou obsahu otázka, zda zpráva skutečně pochází z uvedené služby. Pokud služba nabízí podpis, ověřte ho a porovnejte pomocí hash_equals(). U importních souborů kontrolujte každý řádek jako formulář a při chybách čistě přerušte, místo abyste přebírali poloviční záznamy.
Chybová hlášení by měla jmenovat dotčené pole, odmítnutou hodnotu by ale neměla vracet nefiltrovanou. Jinak vznikne u samotného chybového hlášení nové místo výstupu, které je opět nutné kódovat.
Co validace nedokáže
Validace zmenšuje plochu pro útok, ale podle OWASP není hlavní obranou proti injection. Příjmení s apostrofem je platné a přesto se musí bezpečně dostat do databáze. To se podaří jen pomocí prepared statements, jak ukazuje příručka Prevence SQL injection v PHP. A platný text komentáře se stane neškodným teprve kódováním podle kontextu při výstupu.
Správné pořadí tedy zní: při příjmu validovat, při zpracování parametrizovat, při výstupu kódovat. Navíc před to patří limity velikosti: požadavky, které jsou příliš velké, by webserver a PHP vůbec neměly zpracovávat celé. K tomu slouží nastavení jako post_max_size, které by se po PHP2Shell mělo stejně zkontrolovat.
Jak konkrétně pomáhá PHP SOS?
- Zaznamenáme všechny vstupy vaší aplikace: formuláře, rozhraní, uploady, importy a webhooky.
- U každého pole stanovíme, co je povoleno, a plošné čisticí funkce nahradíme jasnými kontrolami.
- Zkontrolujeme, zda jsou přístupy do databáze a výstupy zabezpečeny nezávisle na validaci.
- Pravidla zdokumentujeme tak, aby je váš tým mohl u nových funkcí dále uplatňovat.
Systematickou kontrolu popisuje služba Bezpečnostní audit a penetrační test.
Časté dotazy
Jaký je rozdíl mezi validací a čištěním?
Validace ověřuje, zda hodnota odpovídá očekávání, a jinak ji odmítne. Čištění hodnotu mění, například odstraněním znaků. Pro bezpečnostní rozhodnutí je odmítnutí téměř vždy lepší, protože mlčky změněná hodnota může způsobit nové chyby.
Lze FILTER_SANITIZE_STRING ještě používat?
Filtr je od PHP 8.1 označen jako zastaralý. Příručka PHP místo něj doporučuje htmlspecialchars() při výstupu. Stávající kód byste měli upravit, protože zastaralé funkce mohou v budoucích verzích zaniknout.
Stačí FILTER_VALIDATE_URL k zabezpečení odkazů?
Ne. Filtr kontroluje jen tvar adresy. Podle příručky nemusí platná URL začínat http, proto musíte schéma sami ověřit proti seznamu povolených hodnot, například https. Internacionalizované doménové názvy filtr odmítá.
Musím validovat i data z vlastní databáze?
Při výstupu se považují za nedůvěryhodná a vhodně se kódují. Opětovná validace dává smysl, pokud data původně přišla zvenčí a nyní se používají v novém kontextu, například jako název souboru nebo část příkazu.
Pomáhá validace proti PHP2Shell?
Ne. PHP2Shell se týká zpracování požadavků v interpretu PHP dříve, než vůbec začne běžet kód vaší aplikace. Pomáhá jen aktualizace PHP s následným restartem všech procesů PHP.