Stručně: Uživatel webserveru, pod kterým běží PHP, by měl podle principu nejmenších oprávnění (Least Privilege) číst jen to, co web potřebuje, a zapisovat jen tam, kam ukládá data. Nemá přihlašovací shell, práva sudo, vlastní cronjoby ani klíče SSH a jeho účet v databázi smí data jen číst a měnit. Volby systemd jako ProtectSystem a PrivateTmp PHP-FPM navíc omezují.
Otázka po průniku: co smí tento proces?
U PHP2Shell jde o spuštění škodlivého kódu na dálku bez přihlášení. Pokud se to podaří, kód neběží v prázdnu, ale pod konkrétním uživatelem: uživatelem procesu PHP. Vše, co tento uživatel smí, smí od té chvíle i útočník.
Na mnoha serverech je to překvapivě hodně. Uživatel vlastní veškerý programový kód, má shell, může zakládat vlastní cronjoby a databázový účet aplikace má všechna práva ke všem databázím. Pro útočníka je to ideální výchozí pozice, aby se na serveru trvale usadil.
Jak správně přidělit práva k souborům a složkám, popisuje příručka Oprávnění k souborům na webserveru. Tento článek se zaměřuje na samotný účet: přihlášení, zvláštní práva, plánované úlohy, databázi a hranice, které vymezuje systemd.
Inventura: kdo je uživatel webserveru?
Nejprve zjistěte, kteří uživatelé se vůbec podílejí. U PHP-FPM určuje každý pool pomocí user a group, pod kým běží jeho workery. U mod_php běží PHP jako uživatel Apache. Jak spolupracují master a workery u PHP-FPM, vysvětluje stránka PHP-FPM vysvětleno: správce procesů, pooly a workeři. Následující příkazy jen čtou:
# Pod jakými uživateli běží webserver a PHP?
ps -eo user,group,cmd | grep -E "[p]hp-fpm|[n]ginx|[a]pache2|[h]ttpd" | sort | uniq -c
# Údaje o účtu: domovský adresář a shell (uživatele upravte)
getent passwd www-data
# Má uživatel práva sudo?
sudo -l -U www-data
# Vlastní cronjoby?
sudo crontab -l -u www-data
# Klíče SSH v domovském adresáři (u Debianu/Ubuntu je to /var/www)
sudo ls -la /var/www/.ssh 2>/dev/null
Výsledky si poznamenejte pro každého uživatele poolu. Pokud všechny weby běží pod stejným uživatelem, je to první bod na seznamu; jak je oddělit, popisuje článek Izolace poolů PHP-FPM pro každý web.
Práva uživatele webserveru: co účet nepotřebuje
Následující přehled ukazuje, co uživatel webserveru typicky nepotřebuje a jak mu to odebrat.
| Právo | Proč je nebezpečné | Doporučení |
|---|---|---|
| Přihlašovací shell | Je možné přihlášení a interaktivní používání | Shell /usr/sbin/nologin |
| Záznamy sudo | Cesta k právům root | žádné záznamy, ani pro jednotlivé příkazy |
| Vlastní crontab | trvalá zadní vrátka, která přežijí každý restart | žádný, omezit pomocí /etc/cron.allow |
| Klíče SSH | Přihlášení zvenčí | žádné authorized_keys |
| Vlastnictví programového kódu | Kód lze měnit | Vlastníkem je uživatel pro nasazování |
| Práva zápisu mimo datové složky | Ukládání škodlivého kódu | jen uploady, cache, temp |
| Plná práva k databázi | Databáze jiných aplikací, přístup k souborům přes databázi | jen vlastní databáze, jen datová práva |
Shell nastavíte příkazem usermod -s /usr/sbin/nologin www-data. Pro cron platí: pokud existuje /etc/cron.allow, smějí zakládat vlastní cronjoby jen v něm uvedení uživatelé. Nově založené cronjoby nebo neznámé klíče SSH jsou typickým znamením, že se někdo v systému uchytil. Zvlášť výrazné varovné znamení popisuje článek Varovné znamení: webserver spouští shell.
Databáze: omezte práva na nezbytné
Databázový účet webu je fakticky druhým uživatelem webserveru. Jeho přístupové údaje leží v konfiguračním souboru aplikace a vložený kód je může přečíst. Proto by měl smět jen to, co aplikace v provozu potřebuje.
-- Příklad MySQL/MariaDB: vlastní účet pro každý web, jen datová práva
CREATE USER 'seite1'@'localhost' IDENTIFIED BY 'hier-ein-langes-Zufallspasswort';
GRANT SELECT, INSERT, UPDATE, DELETE ON seite1.* TO 'seite1'@'localhost';
-- Zkontrolovat práva
SHOW GRANTS FOR 'seite1'@'localhost';
Práva jako FILE, SUPER nebo GRANT OPTION a přístup k *.* aplikační účet nepotřebuje. Mnoho CMS potřebuje pro aktualizace navíc CREATE, ALTER, INDEX a DROP. Čistší je samostatný účet pro aktualizace a migrace, který se používá jen při údržbě. Požadavky ověřte v dokumentaci výrobce.
PHP-FPM navíc omezit pomocí systemd
systemd může službu omezit v jejím pohledu na systém. Pro PHP-FPM se obzvlášť hodí některé volby z dokumentace systemd. Působí i na master proces, který startuje jako root.
# sudo systemctl edit php8.3-fpm
[Service]
# /usr, /boot a /etc jen pro čtení
ProtectSystem=full
# /home, /root a /run/user neviditelné
ProtectHome=true
# vlastní /tmp a /var/tmp jen pro tuto službu
PrivateTmp=true
# žádné rozšíření práv přes setuid nebo capabilities souborů
NoNewPrivileges=true
Tyto volby otestujte před nasazením do ostrého provozu. Pokud leží weby pod /home, ProtectHome=true přístup zablokuje. NoNewPrivileges=true může rušit programy, které PHP volá a které při startu mění svá skupinová práva, například některé lokální poštovní programy pro funkci PHP mail(). Postupujte proto po krocích:
- Přidejte jednu volbu, restartujte službu, zkontrolujte
systemctl status php8.3-fpm. - Otestujte klíčové funkce webu: přihlášení, formuláře, odesílání pošty, uploady, cronjoby aplikace.
- Vyhodnoťte chybový log PHP-FPM a aplikace.
- Teprve pak přidejte další volbu.
Nakonec socket: Podle příručky PHP dostává ve výchozím stavu práva 0660 pro uživatele a skupinu poolu. Nastavte listen.owner a listen.group tak, aby se mohl připojit jen webserver, ne každý lokální uživatel.
Minimální práva jsou postoj, ne jednorázový projekt. Inventuru z tohoto článku opakujte po každé přestavbě a po každém novém webu, protože nové služby často přicházejí s velkorysými výchozími právy.
Závěr: útočník zdědí to, co povolíte
Least Privilege zní jako teorie, ale je to velmi praktická otázka: co může udělat ten, kdo spouští kód jako uživatel webserveru? Na serveru se shellem, záznamem sudo, vlastním crontabem a databázovým účtem se všemi právy zní odpověď: téměř vše. Na omezeném serveru zůstane spíše u incidentu v jednom webu, který lze rozpoznat a vyčistit.
Změna nevyžaduje žádný nový software, jen pečlivou inventuru a postupné testy. Začněte body, které téměř nic nerozbijí, tedy shellem, sudo a klíči SSH, a poté postupujte k právům k databázi a volbám systemd.
Jak konkrétně pomáhá PHP SOS?
- Kontrola účtů: Pro každý pool a webserver zaznamenáme uživatele, shell, záznamy sudo, cronjoby, klíče SSH a práva k databázi.
- Snížení práv: Odebereme, co není potřeba, a oddělíme účty pro nasazování, běh a údržbu.
- Omezení služeb: Ochranné volby systemd nastavujeme postupně a po každém kroku web testujeme.
- Udržení v čase: V rámci hardeningu serveru zdokumentujeme koncepci oprávnění pro budoucí změny.
Časté dotazy
Pod jakým uživatelem běží PHP na mém serveru?
Záleží na provozním režimu. U mod_php běží PHP jako uživatel Apache, například www-data nebo apache. U PHP-FPM určuje uživatele každý pool pomocí user a group; hlavní proces (master) většinou běží jako root, workery jako uživatel poolu. Příkaz ps to ukáže rychle.
Potřebuje uživatel webserveru přihlašovací shell?
Ne. Nginx, Apache ani PHP-FPM nepotřebují pro tohoto uživatele interaktivní přihlášení. Shell jako /usr/sbin/nologin brání tomu, aby se někdo pod tímto účtem přihlásil. Nasazování probíhá pod samostatným uživatelem.
Proč www-data nedávat žádná práva sudo, ani pro jediný příkaz?
Protože vložený kód zdědí přesně tato práva. I zdánlivě neškodný příkaz může být při chybném povolení cestou k právům root. Úlohy, které vyžadují vyšší oprávnění, patří do samostatných služeb nebo postupů mimo webserver.
Pomůže to proti PHP2Shell?
Zranitelnost to neuzavře, k tomu je potřeba aktualizace PHP a restart všech workerů. Minimální práva ale omezují, co může útočník po úspěšném zneužití udělat: měnit soubory, uchytit se v systému, dosáhnout na jiné weby.