Na kratko: Vsaka spletna stran na strežniku naj ima lasten pool PHP-FPM z lastnim sistemskim uporabnikom in lastnim socketom. Ločitev postane učinkovita šele s podrobnostmi: pravice socketa samo za spletni strežnik, security.limit_extensions na .php, brez javno dosegljivih statusnih strani, lastne meje za procese in čas izvajanja ter ločeni dnevniki. Pred samim PHP2Shell varuje samo posodobitev; pooli omejijo, kako daleč seže napad.
En strežnik, veliko spletnih strani, skupno tveganje
Na številnih strežnikih je skozi leta zraslo, česar nihče ni tako načrtoval. Podjetniška spletna stran, trgovina, kampanjska stran iz leta 2019 in testni sistem tečejo drug ob drugem, pogosto pod istim uporabnikom in v istem poolu. Če je pozabljena kampanjska stran ogrožena, so odprti tudi podatki strank trgovine.
PHP-FPM, upravljalnik procesov FastCGI za PHP, to rešuje s pooli: skupinami procesov delavcev z lastnimi nastavitvami. Kako deluje osnovno načelo, pojasnjuje PHP-FPM razloženo: upravljalnik procesov, pooli in delavci. Zgradbo ločenih poolov z lastnim uporabnikom, lastno začasno mapo in open_basedir opisuje vodnik open_basedir in izolacija po spletnih straneh. Ta prispevek gre korak dlje: kako preverite obstoječe poole in katere nastavitve naredijo ločitev vzdržljivo?
Pomembna umestitev: pri PHP2Shell ranljivosti CVE-2026-109618 in CVE-2026-109619 ležita v interpreterju PHP, ki si ga delijo vsi pooli ene različice PHP. Lasten pool izkoriščanja ne prepreči. Odloča pa, ali napadalec potem doseže eno spletno stran ali vse.
Pooli PHP-FPM z lastnim uporabnikom: preverite stanje
Preden karkoli spremenite, si ustvarite pregled. Naslednji ukazi samo berejo. Poti in številke različic se po distribuciji razlikujejo:
# Vsi pooli z uporabnikom in socketom
grep -HE '^\s*(\[|user|group|listen)\s*' /etc/php/*/fpm/pool.d/*.conf
# Tekoči pooli in njihovi uporabniki
ps -eo user,pid,cmd | grep '[p]hp-fpm: pool'
# Socketi in njihove pravice
ls -l /run/php/
# Preverite konfiguracijo za napake (ničesar ne spremeni)
sudo php-fpm8.3 -t
Bodite pozorni na tri vzorce: več spletnih strani v istem poolu, poole, ki tečejo kot www-data ali celo kot root, in socketi, v katere lahko piše vsak uporabnik. Vsak od njih spodkopava ločitev.
Če na strežniku teče več različic PHP, ima vsaka lastno storitev FPM z lastnimi imeniki poolov. Preverite vse, ne samo različice, ki ste jo nazadnje nastavili. Prav stare različice s pozabljenimi pooli so tveganje, ker se pogosto ne vzdržujejo in se po posodobitvah ne zaženejo znova. Namigi so pod Več različic PHP na enem strežniku. Rezultat zabeležite v majhno tabelo: spletna stran, pool, uporabnik, socket, različica PHP. Ta pregled je hkrati podlaga za vsako poznejšo spremembo.
Najpomembnejše nastavitve za vsak pool
Naslednji pregled temelji na konfiguraciji FPM v priročniku PHP. Vrednosti za procese in čase se ravnajo po vaši obremenitvi in aplikaciji:
| Nastavitev | Namen | Priporočilo |
|---|---|---|
user, group |
uporabnik delavcev | lasten uporabnik za vsako spletno stran, nikoli root |
listen.owner, listen.group, listen.mode |
pravice na socketu | samo skupina spletnega strežnika, način 0660 |
listen.acl_users |
dostop prek ACL namesto lastnika | vnesite samo uporabnika spletnega strežnika |
security.limit_extensions |
izvršljive končnice | omejite na .php (privzeto .php .phar) |
clear_env |
praznjenje okoljskih spremenljivk | pustite na privzetem yes |
pm.max_children |
največje število delavcev | omejite tako, da ena spletna stran ne obremeni strežnika |
request_terminate_timeout |
prekinitev obtičanih zahtev | nastavite glede na najdaljšo legitimno zahtevo |
pm.status_path, ping.path |
statusna stran in stran ping | nastavite samo, če je potrebno, in zaščitite notranje |
slowlog, request_slowlog_timeout |
zapisnik počasnih zahtev | za vsak pool v svojo datoteko |
Izsek za utrjen pool je videti takole:
; /etc/php/8.3/fpm/pool.d/shop.conf (izsek)
[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
Nastavitve, kot sta memory_limit ali session.upload_progress.enabled, določite za vsak pool z php_admin_value in php_admin_flag. Po priročniku teh vrednosti ni mogoče prepisati z ini_set(). Več o tem pod php_admin_value: zaklepanje nastavitev pred prepisovanjem.
Pravilna umestitev statusnih strani in menjave delavcev
pm.status_path in ping.path sta aktivna samo, če sta nastavljena. Statusna stran prikazuje tekoče zahteve, procese in obremenitev. Za spremljanje je to koristno, javno dosegljiva pa je nepotreben vir informacij za napadalce. Te poti v spletnem strežniku posredujte samo za notranje naslove. Kako Nginx in PHP-FPM varno sodelujeta, opisuje Zavarovanje Nginx s PHP-FPM.
pm.max_requests določa, po koliko zahtevah se delavec znova zažene; privzeto je 0, torej nikoli. Vrednost nad nič pomaga proti počasi naraščajoči porabi pomnilnika. Kot zaščita pred PHP2Shell ni mišljena: php.net je ne navaja med prehodnimi ukrepi in poudarja, da popolno zaščito daje samo posodobitev. Po posodobitvi velja tudi: vse delavce vseh poolov je treba znova zagnati, ker sicer stari procesi tečejo naprej z ranljivo kodo.
Vzdržujte predlogo za nove poole in z njo ustvarite vsako novo spletno stran. Tako ločitev nastane samodejno, namesto da bi jo čez leta dodatno vgrajevali.
Tipične napake pri prehodu
Najpogostejša napaka je nov pool, ki sicer teče pod lastnim uporabnikom, a njegove datoteke še vedno pripadajo www-data. Takrat pool ne sme pisati tam, kjer mora, nekdo pa pravice nastavi preveč radodarno. Vnaprej razjasnite, katere mape morajo biti zapisljive. Druga napaka so načrtovana opravila, ki tečejo naprej pod starim uporabnikom in ustvarjajo datoteke z napačnim lastnikom. Tretja je skupna mapa sej za vse poole, ki ločitev spet odpravi.
Po vsakem prehodu preizkusite prijavo, nalaganja, predpomnilnike in načrtovana opravila spletne strani ter spremljajte dnevnik napak poola.
Kako PHP SOS pomaga konkretno?
- Zajamemo vse poole, uporabnike, sockete in nastavitve ter pokažemo, kje si spletne strani danes delijo procese ali pravice.
- Za vsako spletno stran vzpostavimo utrjen pool, s pravicami socketa, mejo končnic, mejami virov in lastnimi dnevniki.
- Statusne strani zavarujemo, vse delavce nadzorovano znova zaženemo in preizkusimo vsako spletno stran.
- V okviru utrjevanja strežnikov ločitev dokumentiramo kot predlogo za prihodnje projekte.
Pogosta vprašanja
Zakaj naj ima vsaka spletna stran lasten pool PHP-FPM?
Lasten pool z lastnim sistemskim uporabnikom spletne strani loči na ravni procesov in pravic do datotek. Če je ena spletna stran ogrožena, koda datotek drugih ne more kar brati. Poleg tega je mogoče meje in nastavitve določiti ločeno za vsako spletno stran.
Ali lasten pool ščiti pred PHP2Shell?
Ne. Ranljivosti so v samem interpreterju PHP, ki ga vsi pooli uporabljajo skupaj. Zaščita je samo posodobitev na popravljeno različico s ponovnim zagonom vseh delavcev. Ločeni pooli pa omejijo, kako daleč seže uspešen napad.
Kaj povzroči security.limit_extensions?
Po priročniku PHP nastavitev omejuje, katere končnice datotek PHP-FPM izvede kot glavni skript. Privzeto sta .php in .phar. Kdor jo omeji na .php, prepreči, da bi se na primer naložene slikovne datoteke izvedle kot PHP, če je spletni strežnik napačno konfiguriran.
Ali je statusna stran PHP-FPM nevarna?
Razkriva podrobnosti o tekočih procesih in zahtevah. Če je javno dosegljiva brez zaščite, napadalcem olajša orientacijo. Po priročniku je aktivna samo, če je nastavljen pm.status_path. Takrat naj spletni strežnik pot omeji na notranje naslove.