Stručně: Každý web na serveru by měl mít vlastní pool PHP-FPM s vlastním systémovým uživatelem a vlastním socketem. Oddělení se však stane účinným až s detaily: oprávnění k socketu jen pro webový server, security.limit_extensions na .php, žádné veřejně dostupné stavové stránky, vlastní limity pro procesy a dobu běhu a oddělené logy. Před samotným PHP2Shell chrání jen aktualizace; pooly omezují, jak daleko útok sáhne.
Jeden server, mnoho webů, společné riziko
Na mnoha serverech se za léta nashromáždilo to, co nikdo takto neplánoval. Firemní web, e-shop, kampaňová stránka z roku 2019 a testovací systém běží vedle sebe, často pod stejným uživatelem a ve stejném poolu. Pokud je napadena zapomenutá kampaňová stránka, jsou odkryta i data zákazníků e-shopu.
PHP-FPM, správce procesů FastCGI pro PHP, to řeší pooly: skupinami procesů worker s vlastním nastavením. Jak základní princip funguje, vysvětluje stránka PHP-FPM vysvětleno: správce procesů, pooly a workeři. Sestavení oddělených poolů s vlastním uživatelem, vlastní dočasnou složkou a open_basedir popisuje průvodce open_basedir a izolace pro každý web. Tento článek jde o krok dál: Jak zkontrolovat stávající pooly a která nastavení činí oddělení odolným?
Důležité zařazení: u PHP2Shell leží zranitelnosti CVE-2026-109618 a CVE-2026-109619 v interpretu PHP, který sdílejí všechny pooly dané verze PHP. Vlastní pool zneužití nezabrání. Rozhodne ale o tom, zda útočník poté dosáhne jednoho webu, nebo všech.
Pooly PHP-FPM s vlastním uživatelem: zkontrolujte stav
Než cokoli změníte, udělejte si přehled. Následující příkazy jen čtou. Cesty a čísla verzí se podle distribuce liší:
# Všechny pooly s uživatelem a socketem
grep -HE '^\s*(\[|user|group|listen)\s*' /etc/php/*/fpm/pool.d/*.conf
# Běžící pooly a jejich uživatelé
ps -eo user,pid,cmd | grep '[p]hp-fpm: pool'
# Sockety a jejich oprávnění
ls -l /run/php/
# Zkontrolovat konfiguraci na chyby (nic nemění)
sudo php-fpm8.3 -t
Všímejte si tří vzorců: několik webů ve stejném poolu, pooly běžící jako www-data nebo dokonce jako root a sockety zapisovatelné pro všechny uživatele. Každý z nich oddělení podkopává.
Pokud na serveru běží několik verzí PHP, má každá vlastní službu FPM s vlastními adresáři poolů. Zkontrolujte všechny, nejen verzi, kterou jste nastavovali naposledy. Právě staré verze se zapomenutými pooly jsou riziko, protože se často neudržují ani po aktualizacích nerestartují. Upozornění najdete v článku Více verzí PHP na jednom serveru. Výsledek zaznamenejte do malé tabulky: web, pool, uživatel, socket, verze PHP. Tento přehled je zároveň základem každé pozdější změny.
Nejdůležitější nastavení pro každý pool
Následující přehled vychází z konfigurace FPM v příručce PHP. Hodnoty pro procesy a časy se řídí vaší zátěží a aplikací:
| Nastavení | Účel | Doporučení |
|---|---|---|
user, group |
uživatel workerů | vlastní uživatel pro každý web, nikdy root |
listen.owner, listen.group, listen.mode |
oprávnění k socketu | jen skupina webového serveru, režim 0660 |
listen.acl_users |
přístup přes ACL místo vlastníka | zapsat jen uživatele webového serveru |
security.limit_extensions |
spustitelné přípony | omezit na .php (výchozí .php .phar) |
clear_env |
vyprázdnění proměnných prostředí | ponechat na výchozím yes |
pm.max_children |
nejvyšší počet workerů | omezit tak, aby jeden web nezatížil server |
request_terminate_timeout |
ukončení zaseknutých požadavků | nastavit podle nejdelšího legitimního požadavku |
pm.status_path, ping.path |
stavová a ping stránka | nastavit jen v případě potřeby a zabezpečit interně |
slowlog, request_slowlog_timeout |
protokol pomalých požadavků | pro každý pool do vlastního souboru |
Výňatek pro zpevněný pool vypadá takto:
; /etc/php/8.3/fpm/pool.d/shop.conf (výňatek)
[shop]
user = shop
group = shop
listen = /run/php/shop.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
request_terminate_timeout = 60s
security.limit_extensions = .php
clear_env = yes
slowlog = /var/log/php/shop.slow.log
request_slowlog_timeout = 10s
php_admin_value[error_log] = /var/log/php/shop.error.log
Nastavení jako memory_limit nebo session.upload_progress.enabled zadáváte pro každý pool pomocí php_admin_value a php_admin_flag. Podle příručky nelze tyto hodnoty přepsat pomocí ini_set(). Více v článku php_admin_value: uzamknutí nastavení proti přepsání.
Správně zařadit stavové stránky a obměnu workerů
pm.status_path a ping.path jsou aktivní jen tehdy, když jsou nastaveny. Stavová stránka ukazuje běžící požadavky, procesy a vytížení. Pro monitoring je to užitečné, veřejně dostupná je však zbytečným zdrojem informací pro útočníky. Tyto cesty ve webovém serveru předávejte jen interním adresám. Jak bezpečně spolupracují Nginx a PHP-FPM, popisuje Zabezpečení Nginx s PHP-FPM.
pm.max_requests určuje, po kolika požadavcích se worker restartuje; výchozí hodnota je 0, tedy nikdy. Hodnota větší než nula pomáhá proti pomalu rostoucí spotřebě paměti. Jako ochrana proti PHP2Shell není určena: php.net ji mezi přechodnými opatřeními neuvádí a zdůrazňuje, že plně chrání jen aktualizace. Po aktualizaci navíc platí: všichni workeři všech poolů se musí restartovat, protože staré procesy by jinak běžely dál se zranitelným kódem.
Udržujte šablonu pro nové pooly a každý nový web zakládejte podle ní. Oddělení pak vzniká automaticky, místo aby se po létech dodatečně dopracovávalo.
Typické chyby při přechodu
Nejčastější chybou je nový pool, který sice běží pod vlastním uživatelem, ale jeho soubory dál patří www-data. Pool pak nesmí zapisovat tam, kde musí, a někdo nastaví oprávnění příliš velkoryse. Předem vyjasněte, které složky musí být zapisovatelné. Druhou chybou jsou plánované úlohy, které dál běží pod starým uživatelem a vytvářejí soubory se špatným vlastníkem. Třetí je společná složka session pro všechny pooly, která oddělení opět ruší.
Po každém přechodu otestujte přihlášení, nahrávání, cache a plánované úlohy webu a sledujte chybový log poolu.
Jak konkrétně pomáhá PHP SOS?
- Zmapujeme všechny pooly, uživatele, sockety a nastavení a ukážeme, kde si weby dnes sdílejí procesy nebo oprávnění.
- Pro každý web nastavíme zpevněný pool s oprávněními k socketu, omezením přípon, limity zdrojů a vlastními logy.
- Zabezpečíme stavové stránky, kontrolovaně restartujeme všechny workery a otestujeme každý web.
- V rámci zpevnění serveru oddělení zdokumentujeme jako šablonu pro budoucí projekty.
Časté dotazy
Proč by měl mít každý web vlastní pool PHP-FPM?
Vlastní pool s vlastním systémovým uživatelem odděluje weby na úrovni procesů a oprávnění k souborům. Když je jeden web napaden, kód nemůže bez dalšího číst soubory ostatních. Navíc lze limity a nastavení určit pro každý web zvlášť.
Chrání vlastní pool před PHP2Shell?
Ne. Zranitelnosti jsou v samotném interpretu PHP, který sdílejí všechny pooly. Ochranu poskytuje jen aktualizace na opravenou verzi s restartem všech workerů. Oddělené pooly ale omezují, jak daleko úspěšný útok sáhne.
Co způsobuje security.limit_extensions?
Podle příručky PHP nastavení omezuje, které přípony souborů PHP-FPM spouští jako hlavní skript. Výchozí jsou .php a .phar. Kdo je omezí na .php, zabrání tomu, aby se například nahrané obrazové soubory spouštěly jako PHP, pokud je webový server nesprávně nakonfigurován.
Je stavová stránka PHP-FPM nebezpečná?
Prozrazuje podrobnosti o běžících procesech a požadavcích. Pokud je veřejně dostupná bez ochrany, usnadňuje útočníkům orientaci. Podle příručky je aktivní jen tehdy, když je nastaveno pm.status_path. Webový server by pak měl cestu omezit na interní adresy.