Stručně: Chcete-li zakázat spouštění PHP v uploadech, zjistěte nejprve všechny složky, do kterých smí PHP zapisovat, a poté v každé z nich zakažte spouštění skriptů v konfiguraci serveru, ne pomocí .htaccess. V Nginx musí zákaz zabrat před obecným blokem PHP, v Apache patří do VirtualHost s AllowOverride None. Práci uzavře security.limit_extensions = .php v poolu FPM a test s neškodným souborem.
Proč právě teď záleží na složkách s nahranými soubory
Po kritické zranitelnosti jako PHP2Shell chtějí útočníci jen zřídka spustit kód jen jednou. Chtějí se vracet, i když bude server později záplatován. Nejjednodušší cestou je webshell: malý soubor PHP, umístěný v zapisovatelné složce a později volaný přes prohlížeč.
Záplata takový soubor neodstraní. Kdo byl od 20. října 2026 online bez záplaty, měl by proto nejen aktualizovat, ale také zkontrolovat, zda někde neleží nové soubory PHP, a do budoucna zabránit jejich spuštění. Jak nálezy posoudit, popisuje Soubory PHP ve složce s nahranými soubory: správně posoudit nálezy.
Zákaz spouštění míří na druhou podmínku. Nebrání tomu, aby byl soubor uložen, ale tomu, aby jej server zacházel jako s programem.
Nejprve inventura: kam smí PHP zapisovat?
Většina návodů zmiňuje jen složku pro nahrávání. V praxi aplikace PHP zapisuje na mnohem víc míst. Každé z nich, které je dosažitelné přes webový server, potřebuje zákaz.
| Typ složky | Typický obsah | Proč zapisovatelná |
|---|---|---|
| Uploads, média | obrázky, PDF, přílohy | uživatelé a redakce nahrávají soubory |
| Cache | vygenerované stránky, náhledy | aplikace zrychluje doručování |
| Temp, import, export | soubory CSV, mezistavy | rozhraní a výměna dat |
| Zálohy ve webrootu | .zip, .sql |
rozšíření zálohují „na místě“ |
| Logy ve webrootu | chybové a ladicí logy | špatně nakonfigurované aplikace |
Které složky to na vašem serveru jsou, ukáže hledání s právy uživatele běhového prostředí. Nic nemění:
# Všechny složky pod /var/www, do kterých smí uživatel PHP zapisovat
# (upravte uživatele: www-data, nginx, apache nebo vlastní uživatel poolu)
sudo -u www-data find /var/www -type d -writable 2>/dev/null | sort
# Leží už v těchto složkách soubory PHP?
sudo -u www-data find /var/www -type d -writable -print0 2>/dev/null \
| xargs -0 -I{} find {} -maxdepth 1 -type f -iname "*.ph*" 2>/dev/null
Pokud první hledání vrátí celý webroot, patří programový kód uživateli běhového prostředí. To byste měli předem změnit, jak popisuje průvodce Oprávnění k souborům na webovém serveru. Jinak neexistuje složka, kterou by nebylo třeba blokovat.
Zákaz spouštění PHP v uploadech: Nginx
U Nginx rozhoduje pořadí. Podle dokumentace Nginx se regulární výrazy kontrolují v pořadí konfiguračního souboru a vyhrává první shoda. Pokud obecný blok location ~ \.php$ stojí před vaším zákazem, soubor se spustí. Jednoduchá prefixová location bez ^~ ze stejného důvodu nechrání.
Bezpečný je regulární výraz pro všechny blokované složky, který stojí před blokem PHP. Pokrývá i zřídka používané přípony:
server {
# ... základní konfigurace ...
# 1. Zákaz pro zapisovatelné složky, MUSÍ stát před blokem PHP
location ~* ^/(uploads|media|cache|tmp|export)/.*\.(php\d?|phtml|phar|pht|phps)$ {
deny all;
}
# 2. Obecný blok PHP: předávat jen existující soubory
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/seite1.sock;
}
}
Názvy složek upravte podle své inventury. U aplikací s jediným vstupním bodem je cesta přes seznam povolených ještě silnější; vzor ukazuje Bezpečné nastavení Nginx pro PHP.
Apache a PHP-FPM: zákaz na správném místě
V Apache patří zákaz do konfigurace serveru nebo VirtualHost. Soubor .htaccess ve složce s nahranými soubory se nehodí, protože ho může změnit každý, kdo tam smí zapisovat. Pro celý webroot proto platí AllowOverride None. Podle dokumentace Apache je AllowOverride povoleno jen v blocích <Directory> bez regulárních výrazů, nastavte ho tedy na webrootu a zákaz pomocí <DirectoryMatch> pod ním:
<Directory /var/www/seite1/public>
AllowOverride None
Require all granted
</Directory>
<DirectoryMatch "^/var/www/seite1/public/(uploads|media|cache|tmp|export)/">
<FilesMatch "\.(php\d?|phtml|phar|pht|phps)$">
Require all denied
</FilesMatch>
</DirectoryMatch>
Pokud ještě běží mod_php, doplňte do téhož bloku php_admin_flag engine off. U PHP-FPM působí druhá zábrana v poolu: security.limit_extensions určuje, které přípony FPM vůbec spouští. Podle příručky PHP je výchozí .php .phar; doporučuje se omezení na .php.
; v poolu, např. /etc/php/8.3/fpm/pool.d/seite1.conf
security.limit_extensions = .php
Test neškodným souborem
Zda zákaz funguje, ukáže jen test. Do každé blokované složky krátce umístěte soubor, který jen vypíše text, zavolejte jej přes doménu a poté smažte.
- Zkontrolujte konfiguraci:
nginx -tpopř.apachectl configtest, poté znovu načtěte. - Vytvořte testovací soubor, například
uploads/sperrtest.phps obsahem<?php echo 'ausgefuehrt';. - Zavolejte pomocí
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/uploads/sperrtest.php. Očekává se403. - Pokud se místo toho objeví
200a text „ausgefuehrt“, zákaz nezabírá. Zkontrolujte konfiguraci a pořadí. - Testovací soubor smažte a test zopakujte pro každou složku a každý web.
Pokud při inventuře najdete v zapisovatelných složkách neznámé soubory PHP, nemažte nic ukvapeně. Nejprve zajistěte stav a logy, protože tyto soubory jsou důkazním materiálem. Jak dále postupovat, popisuje Rozpoznání a odstranění webshellů.
Jak se funkce nahrávání v kódu staví bezpečně, s kontrolou typu a uložením mimo webroot, je v průvodci Bezpečné nahrávání souborů v PHP.
Závěr: zapisovat ano, spouštět ne
Zákaz spouštění v zapisovatelných složkách je jedním z nejúčinnějších jednotlivých opatření proti trvalým zadním vrátkům. Stojí pár řádků konfigurace a u čistě postavených aplikací nic nerozbije. Rozhodující jsou tři věci: úplná inventura místo jen zjevné složky pro nahrávání, správné pořadí v Nginx popř. správná úroveň v Apache a skutečný test s neškodným souborem.
Doplněno o oddělené vlastníky programového kódu, security.limit_extensions v poolu a pravidelné hledání nových souborů PHP vznikne stav, v němž je uložený soubor sice nepříjemný, ale sotva použitelný. Nenahrazuje to aktualizaci ani hledání stop, útočníkům to ale bere jeden z jejich nejdůležitějších nástrojů.
Jak konkrétně pomáhá PHP SOS?
- Inventura: Zjistíme všechny zapisovatelné složky každého webu a zkontrolujeme je na přítomné soubory PHP.
- Nastavit zákazy: Zákazy spouštění zapíšeme do Nginx, Apache a PHP-FPM, ve správném pořadí a bez závislosti na
.htaccess. - Otestovat: Každý zákaz zkontrolujeme neškodným souborem a výsledek zdokumentujeme.
- Vyčistit: Při nálezech převezmeme zajištění stop a odstranění malwaru.
Časté dotazy
Stačí .htaccess ve složce s nahranými soubory k zákazu PHP?
Ne spolehlivě. Kdo může do složky zapisovat, může soubor .htaccess také změnit nebo nahradit. Zákaz patří do konfigurace serveru nebo VirtualHost a pro webroot by mělo platit AllowOverride None.
Které složky musím zablokovat?
Všechny složky, do kterých smí uživatel PHP zapisovat a které jsou dosažitelné přes webový server. Vedle složek s nahranými soubory to bývají často adresáře cache, temp, import, export a záloh. Zjistěte je hledáním pod uživatelem běhového prostředí.
Rozbije zákaz můj web?
Zpravidla ne, protože ve složkách s nahranými soubory a cache u čistě napsaných aplikací nejsou skripty, které by se volaly přímo. Jednotlivá rozšíření tam ale přesto soubory PHP ukládají. Po přechodu proto otestujte nejdůležitější funkce.
Chrání zákaz před PHP2Shell?
Ne. PHP2Shell je zranitelnost v interpretu PHP, kterou uzavře jen aktualizace a restart všech workerů. Zákaz ale brání tomu, aby se uložený soubor později volal přes prohlížeč a používal jako trvalá zadní vrátka.