Stručně: PHP nabízí pro nejčastější bezpečnostní úkoly hotové, prověřené funkce: password_hash() pro hesla, random_bytes() a random_int() pro tokeny, htmlspecialchars() proti cross-site scriptingu, připravené dotazy s PDO proti SQL injection, hash_equals() pro bezpečná porovnání a rozšíření Sodium pro kryptografii. Kdo tyto stavební prvky důsledně používá, vyhne se většině zranitelností aplikací. Proti chybám v samotném interpretu PHP, jako u PHP2Shell, nepomáhají; tam pomůže jen aktualizace.
Proč vestavěné bezpečnostní funkce PHP?
Psát bezpečnostní kód vlastními silami je náchylné k chybám. Vlastní hash hesla, svépomocně sestavený generátor tokenů nebo vlastní filtr pro výstupy HTML často vypadají správně a přesto správné nejsou. Vestavěné funkce jsou zdokumentované, otestované a udržují se s aktualizacemi PHP.
Pro rozhodovatele z toho plyne: nezeptejte se své agentury nebo vývojového týmu, zda byly bezpečnostní funkce napsány ručně, ale zda se používají standardní funkce jazyka. Přehled podává tabulka.
| Úkol | Funkce | Dostupné od | Důležitá poznámka |
|---|---|---|---|
| Ukládání hesel | password_hash(), password_verify() |
PHP 5.5 | sloupec až 255 znaků; bcrypt ořezává na 72 bajtů |
| Náhodné tokeny | random_bytes(), random_int() |
PHP 7.0 | nikdy rand() nebo mt_rand() pro tajné hodnoty |
| Náhoda objektově | Random\Randomizer |
PHP 8.2 | stejná bezpečnost, flexibilnější rozhraní |
| Escapování výstupu HTML | htmlspecialchars() |
odjakživa | výchozí příznaky od 8.1 escapují i jednoduché uvozovky |
| Porovnání v konstantním čase | hash_equals() |
PHP 5.6 | uživatelský vstup jako druhý argument |
| Moderní kryptografie | Sodium (sodium_*) |
PHP 7.2 | místo vlastního šifrování |
| Tajemství ve stack trace | #[\SensitiveParameter] |
PHP 8.2 | hodnota se v trace neobjeví |
| Kontrola vstupů | filter_var() |
PHP 5.2 | kontroluje formát, nenahrazuje escapování |
Hesla a tokeny
password_hash() vytvoří hash s přidanou solí, password_verify() jej ověří. S PASSWORD_DEFAULT PHP aktuálně používá bcrypt; od PHP 8.4 je výchozí nákladový faktor podle příručky 12 místo 10. Protože se výchozí algoritmus může změnit, příručka doporučuje sloupec v databázi s 255 znaky. U bcrypt se hesla po 72 bajtech ořezávají, což je třeba mít na paměti u velmi dlouhých hesel-vět.
<?php
// Registrace
$hash = password_hash($passwort, PASSWORD_DEFAULT);
// Přihlášení, včetně automatického vylepšení starých hashů
if (password_verify($eingabe, $hash)) {
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
$hash = password_hash($eingabe, PASSWORD_DEFAULT); // uložit znovu
}
}
// Token pro odkaz na obnovení hesla
$token = bin2hex(random_bytes(32));
Pro porovnání tajných hodnot, například podpisu, je třeba použít hash_equals(). Běžné porovnání pomocí === může podle příručky prostřednictvím doby běhu prozradit, od kterého místa se dvě hodnoty liší. Podrobněji: Bezpečné ukládání hesel v PHP a bezpečná náhodná čísla a tokeny.
Escapování výstupů a bezpečné dotazy do databáze
Cross-site scripting vzniká, když se uživatelské vstupy dostanou do HTML bez filtrování. htmlspecialchars() převádí speciální znaky na entity HTML. Od PHP 8.1 jsou výchozími příznaky ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401; dříve se jednoduché uvozovky nepřeváděly. Kdo musí podporovat starší verze, uvede příznaky výslovně.
SQL injection brání připravené dotazy. Hodnoty se přenášejí odděleně od textu SQL a dotaz nemohou změnit:
<?php
$stmt = $pdo->prepare('SELECT id, name FROM kunden WHERE email = :email');
$stmt->execute(['email' => $email]);
$kunde = $stmt->fetch();
echo htmlspecialchars($kunde['name'] ?? '', ENT_QUOTES, 'UTF-8');
Podrobnosti najdete v článku Prevence cross-site scriptingu v PHP a na stránce o SQL injection.
Kontrola vstupů je druhý, samostatný krok. filter_var() zjistí, zda má hodnota platný formát, například e-mailovou adresu nebo celé číslo v povoleném rozsahu. To ale nenahrazuje escapování při výstupu ani připravený dotaz, protože i formálně platné hodnoty mohou obsahovat speciální znaky.
Tajemství držet mimo chybová hlášení
Nedoceněným únikem dat jsou stack trace. Pokud ve funkci nastane chyba, PHP zaprotokoluje řetězec volání včetně argumentů. Pokud tam je heslo nebo klíč API, skončí v logu. Od PHP 8.2 takové parametry označuje atribut #[\SensitiveParameter]; jejich hodnota se pak v trace objeví jen jako objekt SensitiveParameterValue.
<?php
function anmelden(string $benutzer, #[\SensitiveParameter] string $passwort): bool
{
// ... kontrola ...
throw new RuntimeException('Datenbank nicht erreichbar');
}
Podle poznámky v příručce atribut působí jen u konkrétní implementace, ne u metody rozhraní. Nastavte jej proto tam, kde kód skutečně běží.
Relace: bezpečná nastavení a nové standardy
Relace jsou častým cílem útoků. Ústřední jsou tři nastavení: session.use_strict_mode=1 odmítá ID relací, které server sám nevytvořil; session.cookie_httponly=1 brání přístupu ke cookie relace přes JavaScript; session.cookie_samesite=Lax ztěžuje odesílání spolu s požadavky z cizích stránek. Podle přijatého RFC se právě tyto tři hodnoty stanou s PHP 8.6 výchozími. Do té doby je nastavte sami, a i potom výslovně, aby konfigurace nezávisela na výchozích hodnotách.
session.use_strict_mode = 1
session.cookie_httponly = 1
session.cookie_samesite = Lax
session.cookie_secure = 1
Více v článku Zabezpečení relací v PHP.
Co vestavěné funkce nezajistí
Projekt PHP je ve své bezpečnostní politice jasný: PHP není sandbox. Obejití open_basedir nebo disable_functions, škodlivé vstupy do unserialize() a nekontrolované argumenty funkcí jako mysqli_query() se nepovažují za bezpečnostní zranitelnosti PHP. Pro provozovatele to znamená: tato nastavení jsou užitečné zpevnění, ale ne bezpečnostní hranice, a unserialize() s cizími daty nemá nic společného.
Druhý bod se týká PHP2Shell. CVE-2026-109618 a CVE-2026-109619 podle php.net umožňují spuštění kódu dříve nebo během toho, než běží vaše aplikace; u mod_php byl předveden exploit ještě předtím, než se vůbec spustí aplikační kód. Žádná funkce ve vašem kódu to nezachytí. Bezpečné programování a aktuální verze PHP jsou dvě oddělené povinnosti a obě musí být splněny.
Jak konkrétně pomáhá PHP SOS?
V bezpečnostním auditu aplikací PHP cíleně kontrolujeme:
- Zacházení s hesly a tokeny: Používají se
password_hash()arandom_bytes(), nebo existují vlastní řešení? - Výstupy a dotazy: Kde se vstupy dostanou do HTML, SQL nebo příkazů shellu bez filtrování?
- Konfigurace relací: Jsou nastaveny Strict Mode, HttpOnly, SameSite a Secure a nelze je přepsat?
- Běh: Běží podporovaná verze PHP s opravami z 21. 10. 2026?
Časté dotazy
Jak se v PHP správně ukládají hesla?
Pomocí password_hash() a PASSWORD_DEFAULT, kontroluje se pomocí password_verify(). Sloupec v databázi by měl podle příručky pojmout až 255 znaků, protože se výchozí algoritmus může v budoucnu změnit.
Hodí se rand() pro tokeny?
Ne. Pro tokeny, klíče a odkazy pro obnovu hesla je třeba použít random_bytes() nebo random_int(). Podle příručky vracejí kryptograficky bezpečné náhodné hodnoty.
Od kdy je Sodium součástí PHP?
Rozšíření Sodium pro moderní kryptografii je součástí PHP od verze 7.2.0. Starší verze je mohly doplnit jen přes PECL.
Chrání tyto funkce před PHP2Shell?
Ne. Chrání před chybami ve vaší aplikaci. PHP2Shell sídlí v samotném interpretu a uzavře ho jen aktualizace PHP, následovaná restartem všech procesů PHP.
Mění se standardy relací v PHP 8.6?
Ano, podle přijatého RFC se session.use_strict_mode a session.cookie_httponly zapnou ve výchozím stavu a session.cookie_samesite se nastaví na Lax. Stávající konfigurace byste přesto měli nastavit sami výslovně.