Trumpai: kiekviena serverio svetainė turėtų turėti savo PHP-FPM baseiną su savo sistemos naudotoju ir savo lizdu. Atskyrimas veiksmingas tampa tik su detalėmis: lizdo teisės tik žiniatinklio serveriui, security.limit_extensions iki .php, jokių viešai pasiekiamų būsenos puslapių, savos procesų ir vykdymo laiko ribos bei atskiri žurnalai. Nuo paties PHP2Shell apsaugo tik atnaujinimas; baseinai riboja, kaip toli pasiekia ataka.
Vienas serveris, daug svetainių, bendra rizika
Daugelyje serverių per metus išaugo tai, ko niekas taip neplanavo. Įmonės svetainė, parduotuvė, 2019 m. kampanijos puslapis ir testinė sistema veikia greta, dažnai su tuo pačiu naudotoju ir tame pačiame baseine. Jei pažeidžiamas užmirštas kampanijos puslapis, atviri tampa ir parduotuvės klientų duomenys.
PHP-FPM, PHP FastCGI procesų valdytojas, tai sprendžia baseinais: darbinių procesų (worker) grupėmis su savais nustatymais. Kaip veikia pagrindinis principas, paaiškina PHP-FPM paaiškinimas: procesų valdytojas, baseinai ir darbiniai procesai. Atskirų baseinų su savu naudotoju, savu laikinuoju aplanku ir open_basedir sukūrimą aprašo gidas open_basedir ir izoliacija kiekvienai svetainei. Šis straipsnis žengia žingsnį toliau: kaip patikrinti esamus baseinus ir kurie nustatymai atskyrimą daro patikimą?
Svarbu įvertinti: PHP2Shell atveju pažeidžiamumai CVE-2026-109618 ir CVE-2026-109619 yra PHP interpretatoriuje, kurį dalijasi visi tos pačios PHP versijos baseinai. Atskiras baseinas neužkerta kelio išnaudojimui. Tačiau jis lemia, ar užpuolikas po to pasieks vieną svetainę, ar visas.
PHP-FPM baseinai su atskiru naudotoju: esamos būklės patikra
Prieš ką nors keisdami, susidarykite vaizdą. Šios komandos tik skaito. Keliai ir versijų numeriai skiriasi priklausomai nuo distribucijos:
# Visi baseinai su naudotoju ir lizdu
grep -HE '^\s*(\[|user|group|listen)\s*' /etc/php/*/fpm/pool.d/*.conf
# Veikiantys baseinai ir jų naudotojai
ps -eo user,pid,cmd | grep '[p]hp-fpm: pool'
# Lizdai ir jų teisės
ls -l /run/php/
# Patikrinti konfigūraciją dėl klaidų (nieko nekeičia)
sudo php-fpm8.3 -t
Atkreipkite dėmesį į tris šablonus: kelios svetainės tame pačiame baseine, baseinai, veikiantys kaip www-data ar net kaip root, ir lizdai, į kuriuos gali rašyti visi naudotojai. Kiekvienas jų paneigia atskyrimą.
Jei serveryje veikia kelios PHP versijos, kiekviena turi savo FPM paslaugą su savo baseinų katalogais. Patikrinkite visas, ne tik paskutinę sukonfigūruotą versiją. Būtent senos versijos su pamirštais baseinais yra rizika, nes jos dažnai nei prižiūrimos, nei po atnaujinimų paleidžiamos iš naujo. Nuorodų rasite skyriuje Kelios PHP versijos viename serveryje. Rezultatą užfiksuokite nedidelėje lentelėje: svetainė, baseinas, naudotojas, lizdas, PHP versija. Ši apžvalga kartu yra pagrindas bet kokiam vėlesniam pakeitimui.
Svarbiausi kiekvieno baseino nustatymai
Toliau pateikta apžvalga remiasi FPM konfigūracija PHP vadove. Procesų ir laikų reikšmes lemia jūsų apkrova ir programa:
| Nustatymas | Paskirtis | Rekomendacija |
|---|---|---|
user, group |
darbinių procesų naudotojas | atskiras naudotojas kiekvienai svetainei, niekada root |
listen.owner, listen.group, listen.mode |
teisės lizdui | tik žiniatinklio serverio grupė, režimas 0660 |
listen.acl_users |
prieiga per ACL užuot savininko | įrašyti tik žiniatinklio serverio naudotoją |
security.limit_extensions |
vykdomi plėtiniai | apriboti iki .php (numatytasis .php .phar) |
clear_env |
išvalyti aplinkos kintamuosius | palikti numatytąjį yes |
pm.max_children |
didžiausias darbinių procesų skaičius | apriboti taip, kad viena svetainė neapkrautų serverio |
request_terminate_timeout |
užstrigusių užklausų nutraukimas | nustatyti pagal ilgiausią teisėtą užklausą |
pm.status_path, ping.path |
būsenos ir ping puslapis | nustatyti tik jei reikia, ir apsaugoti viduje |
slowlog, request_slowlog_timeout |
lėtų užklausų žurnalas | kiekvienam baseinui į atskirą failą |
Sutvirtinto baseino ištrauka atrodo taip:
; /etc/php/8.3/fpm/pool.d/shop.conf (ištrauka)
[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
Tokius nustatymus kaip memory_limit ar session.upload_progress.enabled nustatykite kiekvienam baseinui su php_admin_value ir php_admin_flag. Pasak vadovo, šių reikšmių negalima perrašyti per ini_set(). Daugiau apie tai skyriuje php_admin_value: nustatymų užrakinimas nuo perrašymo.
Būsenos puslapiai ir darbinių procesų keitimas: teisingas vertinimas
pm.status_path ir ping.path aktyvūs tik tada, kai nustatyti. Būsenos puslapis rodo veikiančias užklausas, procesus ir apkrovą. Stebėsenai tai naudinga, bet viešai pasiekiamas tai nereikalingas informacijos šaltinis užpuolikams. Šiuos kelius žiniatinklio serveryje nukreipkite tik vidiniams adresams. Kaip saugiai kartu veikia Nginx ir PHP-FPM, aprašo Nginx su PHP-FPM apsauga.
pm.max_requests nustato, po kiek užklausų darbinis procesas paleidžiamas iš naujo; numatytasis yra 0, taigi niekada. Reikšmė didesnė už nulį padeda nuo lėtai augančio atminties naudojimo. Kaip apsauga nuo PHP2Shell jis nėra skirtas: php.net jo neįvardija tarp pereinamųjų priemonių ir pabrėžia, kad pilnai apsaugo tik atnaujinimas. Po atnaujinimo galioja ir tai: visi visų baseinų darbiniai procesai turi būti paleisti iš naujo, nes kitaip seni procesai toliau veikia su pažeidžiamu kodu.
Prižiūrėkite naujų baseinų šabloną ir kiekvieną naują svetainę kurkite su juo. Taip atskyrimas atsiranda automatiškai, užuot po metų buvęs papildomai pritaikomas.
Tipinės klaidos pereinant
Dažniausia klaida yra naujas baseinas, kuris veikia su atskiru naudotoju, bet kurio failai vis dar priklauso www-data. Tada baseinas negali rašyti ten, kur turi, ir kas nors nustato per plačias teises. Iš anksto išsiaiškinkite, kurie aplankai turi būti įrašomi. Antra klaida yra suplanuotos užduotys, kurios toliau veikia su senu naudotoju ir kuria failus su neteisingu savininku. Trečia yra bendras sesijų aplankas visiems baseinams, kuris vėl panaikina atskyrimą.
Po kiekvieno perėjimo išbandykite svetainės prisijungimą, įkėlimus, talpyklas ir suplanuotas užduotis ir stebėkite baseino klaidų žurnalą.
Kaip PHP SOS padeda konkrečiai?
- Surenkame visus baseinus, naudotojus, lizdus ir nustatymus ir parodome, kur svetainės šiandien dalijasi procesus ar teises.
- Kiekvienai svetainei įrengiame sutvirtintą baseiną su lizdo teisėmis, plėtinių riba, resursų ribomis ir savais žurnalais.
- Apsaugome būsenos puslapius, kontroliuojamai paleidžiame iš naujo visus darbinius procesus ir išbandome kiekvieną svetainę.
- Serverio sutvirtinimo rėmuose atskyrimą dokumentuojame kaip šabloną būsimiems projektams.
Dažni klausimai
Kodėl kiekviena svetainė turėtų turėti atskirą PHP-FPM baseiną?
Atskiras baseinas su atskiru sistemos naudotoju atskiria svetaines procesų ir failų teisių lygmeniu. Jei viena svetainė pažeidžiama, kodas negali be didesnių kliūčių skaityti kitų failų. Be to, ribas ir nustatymus galima nustatyti kiekvienai svetainei atskirai.
Ar atskiras baseinas apsaugo nuo PHP2Shell?
Ne. Pažeidžiamumai yra pačiame PHP interpretatoriuje, kurį bendrai naudoja visi baseinai. Apsaugo tik atnaujinimas į ištaisytą versiją su visų darbinių procesų (worker) perkrovimu. Tačiau atskiri baseinai riboja, kaip toli pasiekia sėkminga ataka.
Ką veikia security.limit_extensions?
Pasak PHP vadovo, nustatymas riboja, kokius failų plėtinius PHP-FPM vykdo kaip pagrindinį scenarijų. Numatytieji yra .php ir .phar. Apribojus iki .php, išvengiama, kad, pavyzdžiui, įkelti paveikslėlių failai būtų vykdomi kaip PHP, jei žiniatinklio serveris sukonfigūruotas neteisingai.
Ar PHP-FPM būsenos puslapis pavojingas?
Jis išduoda detales apie veikiančius procesus ir užklausas. Jei jis viešai pasiekiamas be apsaugos, užpuolikams lengviau susiorientuoti. Pasak vadovo, jis aktyvus tik nustačius pm.status_path. Tada žiniatinklio serveris turėtų apriboti kelią vidiniais adresais.