Stručně: U bezpečnosti uploadu v PHP nerozhoduje jedna kontrola, ale jejich kombinace: typ souboru kontrolovat na serveru, soubory přejmenovat, ukládat mimo webroot a ve složce pro nahrávání nepovolit spouštění skriptů. Nezkontrolovaný formulář pro nahrávání je jednou z klasických cest, kterou útočníci dostanou zadní vrátka na webový server.
Proč jsou formuláře pro nahrávání oblíbenou branou
Každý formulář, který přijímá soubory, zapisuje na váš server cizí data. Patří k nim formuláře pro uchazeče o práci, zákaznické portály, galerie obrázků, oblasti podpory a mnohá rozšíření CMS a e-shopů. Pokud útočníkovi uspěje uložit tam soubor, který webový server následně provede jako PHP, má trvalá zadní vrátka.
Pro vedení to znamená: nevinně vypadající upload životopisů může v nejhorším případě vést k převzetí celého webového serveru. Ve složkách pro nahrávání navíc často leží osobní údaje, které jsou při chybné konfiguraci veřejně dostupné.
Pro správce a vývojáře to znamená: kód aplikace a konfigurace serveru musí do sebe zapadat. Slabinu jedné úrovně často zachytí druhá, pokud jsou obě čistě provedeny. Jak najít již podstrčené soubory, popisuje příručka Jak poznat a odstranit webshell.
Základní pravidla bezpečnosti uploadu v PHP
Následující pravidla odpovídají doporučením OWASP Cheat Sheets a v provozu se osvědčila:
- Seznam povolených místo seznamu zakázaných: Přijímat jen výslovně povolené typy souborů, například PDF, JPG a PNG. Seznamy zakázaných přípon lze obejít variantami a dvojitými příponami.
- Kontrolovat obsah, nejen příponu: Skutečný typ zjistit na serveru pomocí
finfo. Typ MIME v$_FILESje údaj odesílatele a není důvěryhodný. - Nový název souboru: Původní název nikdy nepoužívat jako název souboru. Vygenerovat náhodný název a připojit příponu ze seznamu povolených.
- Uložení mimo webroot: Soubory ukládat tak, aby nebyly přímo dostupné přes URL. Doručuje se skriptem s kontrolou oprávnění.
- Omezit velikost: V aplikaci, v PHP i ve webovém serveru.
- Žádné spouštění: Pokud uploady přece musí ležet ve webrootu, vypnout v tomto adresáři spouštění skriptů.
- Kontrola jen na serveru: Kontroly v JavaScriptu v prohlížeči zlepšují obsluhu, ale nechrání.
Bezpečný příklad s finfo a náhodným názvem
Následující příklad ukazuje jádro bezpečného zpracování. Je záměrně stručný a je nutné ho zasadit do vaší aplikace, tedy s přihlášením, kontrolou oprávnění, ochranou CSRF a protokolováním.
<?php
declare(strict_types=1);
const MAX_BYTES = 5 * 1024 * 1024; // 5 MB
const ERLAUBT = [
'application/pdf' => 'pdf',
'image/jpeg' => 'jpg',
'image/png' => 'png',
];
const ZIELORDNER = '/var/www/daten/uploads'; // mimo webroot
$datei = $_FILES['dokument'] ?? null;
if (!is_array($datei) || $datei['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
exit('Upload fehlgeschlagen.');
}
if ($datei['size'] > MAX_BYTES || !is_uploaded_file($datei['tmp_name'])) {
http_response_code(400);
exit('Datei ungültig.');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$typ = $finfo->file($datei['tmp_name']);
if (!isset(ERLAUBT[$typ])) {
http_response_code(415);
exit('Dateityp nicht erlaubt.');
}
$name = bin2hex(random_bytes(16)) . '.' . ERLAUBT[$typ];
$ziel = ZIELORDNER . '/' . $name;
if (!move_uploaded_file($datei['tmp_name'], $ziel)) {
http_response_code(500);
exit('Speichern fehlgeschlagen.');
}
chmod($ziel, 0640);
// Původní název ukládat do databáze jen jako text
// a při každém výstupu escapovat pomocí htmlspecialchars().
U obrázků se vyplatí další krok: obrázek znovu zakódovat pomocí GD nebo Imagick a přitom zahodit metadata. To ostatní kontroly nenahrazuje, ale snižuje zbytkové riziko způsobené vloženými cizími daty.
U dokumentů z neznámých zdrojů, například přihlášek nebo fotografií k reklamacím, je navíc před předáním užitečná antivirová kontrola. Chrání méně webový server než zaměstnance, kteří soubory později otevírají na svých pracovních počítačích. Každý upload také protokolujte s časem, účtem, velikostí souboru a rozpoznaným typem, aby šlo neobvyklé vzory později dohledat.
U CMS a e-shopů mají zabudované knihovny médií většinou vlastní kontroly. Rizikovější jsou rozšíření formulářů, funkce importu a vlastní programované doplňkové stránky, které ukládají soubory mimo knihovnu. Právě tato místa byste měli při kontrole prověřit jako první.
Soubory z chráněné složky doručujte s Content-Disposition: attachment a X-Content-Type-Options: nosniff. Prohlížeč pak problematický obsah nezobrazí přímo, ale nabídne ho ke stažení.
Vypnutí spouštění PHP ve složce pro nahrávání
I když soubor se špatným obsahem proklouzne, webový server ho nesmí spustit jako skript. Tato druhá ochranná vrstva patří na každý server, který ukládá uploady do webrootu.
Pro Nginx s PHP-FPM:
location ^~ /uploads/ {
location ~* \.(php|phtml|phar)$ {
deny all;
}
}
Pro Apache, nejlépe přímo ve VirtualHost místo v .htaccess, aby pravidlo nemohla aplikace přepsat:
<Directory "/var/www/html/uploads">
AllowOverride None
<FilesMatch "\.(php|phtml|phar)$">
Require all denied
</FilesMatch>
</Directory>
Pokud PHP běží jako modul Apache (mod_php), můžete ve stejném bloku navíc nastavit php_admin_flag engine off. Po změně ověřte neškodným testovacím souborem, který jen vypíše text, že server odpoví na požadavek na něj kódem 403.
Limity velikosti a souvislost s PHP2Shell
Uploady úzce souvisejí s nastavením PHP post_max_size a upload_max_filesize. V souvislosti s PHP2Shell doporučuje php.net jako přechodné opatření nastavit post_max_size na 8M, pokud nejsou uploady potřeba, nebo na 32M s uploady, a upload_max_filesize držet pod ním. Není to úplná ochrana a nenahrazuje to aktualizaci na opravenou verzi PHP.
; php.ini nebo pool FPM (php_admin_value), poté restartovat všechny workery
file_uploads = On
post_max_size = 32M
upload_max_filesize = 20M
max_file_uploads = 5
Pokud web uploady vůbec nepotřebuje, je file_uploads = Off nejčistším řešením. Jaká nastavení mají smysl do záplaty, najdete v článku Okamžitá opatření bez záplaty. Vhodná práva pro adresáře uploadů popisuje příručka Práva k souborům na webovém serveru.
Typické chyby ve funkcích pro nahrávání
Při procházení stávajících aplikací se pořád objevují stejné vzory. Zkontrolujte své formuláře cíleně na tyto body:
- Přípona se kontroluje jen částečně, takže názvy souborů s více příponami proklouznou.
- Původní název z formuláře skončí v cestě k uložení. To otevírá cesty k manipulaci s cestami a k přepsání existujících souborů.
- Uploady leží veřejně s předvídatelnými názvy. U přihlášek nebo smluv je to i problém ochrany osobních údajů.
- Složka pro nahrávání je pro webový server zapisovatelná a zároveň spustitelná.
- Staré doplňky pro nahrávání nebo pluginy formulářů se neaktualizují, přestože výrobce vydal bezpečnostní aktualizace.
- Chyby se neprotokolují. Odmítnuté uploady jsou přitom časným signálem cílených pokusů.
Jak konkrétně pomáhá PHP SOS?
- Kontrolujeme vaše funkce pro nahrávání v kódu i v konfiguraci serveru a pojmenujeme konkrétní slabiny s prioritou.
- Zákaz spouštění ve složce pro nahrávání, vhodná práva a limity velikosti nastavujeme v rámci zabezpečení serveru.
- Při podezření prohledáme adresáře uploadů na podstrčené skripty a vyčistíme je v rámci našeho odstranění malwaru.
- Na přání necháme provedení nezávisle ověřit penetračním testem.
Časté dotazy
Stačí při uploadu kontrolovat příponu souboru?
Ne. Přípona i typ MIME hlášený prohlížečem pocházejí od odesílatele a lze je libovolně zvolit. Kontrolujte obsah na serveru, přidělte nový název souboru a zakažte spouštění skriptů v adresáři pro nahrávání.
Kam by se měly ukládat nahrané soubory?
Nejlépe mimo veřejný webový adresář. Doručení pak zajišťuje skript, který předem ověří, zda smí uživatel soubor vidět. Soubory tak nejsou ani přímo dostupné, ani spustitelné.
Jsou soubory SVG jako obrázky neškodné?
Ne automaticky. SVG je XML a může obsahovat aktivní obsah. Povolte SVG jen tehdy, když soubory vyčistíte nebo je doručujete výhradně ke stažení s vhodnou hlavičkou Content-Disposition.
Souvisí velikost uploadu s PHP2Shell?
Ano, nepřímo. php.net doporučuje jako přechodné opatření nižší hodnoty post_max_size a upload_max_filesize. To snižuje riziko, ale nenahrazuje aktualizaci na opravenou verzi PHP a restart všech workerů.