Īsi sakot: Katrai vietnei serverī vajadzētu būt savam PHP-FPM pūlam ar savu sistēmas lietotāju un savu socket. Nošķīrums kļūst efektīvs tikai ar detaļām: socket tiesības tikai tīmekļa serverim, security.limit_extensions uz .php, nekādu publiski sasniedzamu statusa lapu, savas robežas procesiem un darbības laikam, kā arī atsevišķi žurnāli. No paša PHP2Shell pasargā tikai atjauninājums; pūli ierobežo, cik tālu uzbrukums sniedzas.
Viens serveris, daudz vietņu, kopīgs risks
Daudzos serveros gadu gaitā ir izaugis tas, ko neviens tā nav plānojis. Uzņēmuma vietne, veikals, 2019. gada kampaņas lapa un testa sistēma darbojas blakus, bieži zem tā paša lietotāja un tajā pašā pūlā. Ja aizmirstā kampaņas lapa tiek kompromitēta, atklāti ir arī veikala klientu dati.
PHP-FPM, PHP FastCGI procesu pārvaldnieks, to risina ar pūliem: worker procesu grupām ar saviem iestatījumiem. Kā darbojas pamatprincips, izskaidro PHP-FPM skaidrojums: procesu pārvaldnieks, pūli un worker procesi. Atsevišķu pūlu izveidi ar savu lietotāju, savu pagaidu mapi un open_basedir apraksta ceļvedis open_basedir un izolācija katrai vietnei. Šis raksts iet vienu soli tālāk: kā pārbaudīt esošos pūlus un kuri iestatījumi nošķīrumu dara izturīgu?
Svarīgs ierindojums: PHP2Shell gadījumā ievainojamības CVE-2026-109618 un CVE-2026-109619 atrodas PHP interpretatorā, ko dala visi vienas PHP versijas pūli. Savs pūls neliedz izmantošanu. Taču tas izšķir, vai uzbrucējs pēc tam sasniedz vienu vietni vai visas.
PHP-FPM pūli ar savu lietotāju: esošā stāvokļa pārbaude
Pirms kaut ko mainiet, gūstiet pārskatu. Turpmākās komandas tikai lasa. Ceļi un versiju numuri atšķiras pa distributīviem:
# Visi pūli ar lietotāju un socket
grep -HE '^\s*(\[|user|group|listen)\s*' /etc/php/*/fpm/pool.d/*.conf
# Darbojošies pūli un to lietotāji
ps -eo user,pid,cmd | grep '[p]hp-fpm: pool'
# Sockets un to tiesības
ls -l /run/php/
# Pārbaudīt konfigurāciju uz kļūdām (neko nemaina)
sudo php-fpm8.3 -t
Pievērsiet uzmanību trim paraugiem: vairākas vietnes vienā pūlā, pūli, kas darbojas kā www-data vai pat kā root, un sockets, kuros rakstīt var visi lietotāji. Katrs no tiem grauj nošķīrumu.
Ja serverī darbojas vairākas PHP versijas, katrai ir savs FPM pakalpojums ar savām pūlu direktorijām. Pārbaudiet visas, ne tikai to versiju, ko iestatījāt pēdējo. Tieši vecas versijas ar aizmirstiem pūliem ir risks, jo tās bieži netiek ne uzturētas, ne pārstartētas pēc atjauninājumiem. Norādes par to atrodamas sadaļā Vairākas PHP versijas vienā serverī. Rezultātu fiksējiet nelielā tabulā: vietne, pūls, lietotājs, socket, PHP versija. Šis pārskats vienlaikus ir pamats katrai vēlākai izmaiņai.
Svarīgākie iestatījumi katram pūlam
Turpmākais pārskats balstās uz FPM konfigurāciju PHP rokasgrāmatā. Vērtības procesiem un laikiem atkarīgas no jūsu slodzes un lietojumprogrammas:
| Iestatījums | Mērķis | Ieteikums |
|---|---|---|
user, group |
worker procesu lietotājs | savs lietotājs katrai vietnei, nekad root |
listen.owner, listen.group, listen.mode |
tiesības uz socket | tikai tīmekļa servera grupa, režīms 0660 |
listen.acl_users |
piekļuve ar ACL īpašnieka vietā | ierakstīt tikai tīmekļa servera lietotāju |
security.limit_extensions |
izpildāmie paplašinājumi | ierobežot līdz .php (noklusējums .php .phar) |
clear_env |
tukšot vides mainīgos | atstāt noklusējumu yes |
pm.max_children |
maksimālais worker skaits | ierobežot tā, lai viena vietne neaizņemtu visu serveri |
request_terminate_timeout |
uzkārušos pieprasījumu pārtraukšana | iestatīt atbilstoši garākajam leģitīmajam pieprasījumam |
pm.status_path, ping.path |
statusa un ping lapa | iestatīt tikai vajadzības gadījumā un aizsargāt iekšēji |
slowlog, request_slowlog_timeout |
lēno pieprasījumu protokols | katram pūlam savā failā |
Izvilkums nostiprinātam pūlam izskatās šādi:
; /etc/php/8.3/fpm/pool.d/shop.conf (izvilkums)
[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
Iestatījumus, piemēram, memory_limit vai session.upload_progress.enabled, iestatiet katram pūlam ar php_admin_value un php_admin_flag. Saskaņā ar rokasgrāmatu šīs vērtības nevar pārrakstīt ar ini_set(). Vairāk sadaļā php_admin_value: iestatījumu bloķēšana pret pārrakstīšanu.
Statusa lapas un worker maiņa pareizā skatījumā
pm.status_path un ping.path ir aktīvi tikai tad, ja tie ir iestatīti. Statusa lapa rāda darbojošos pieprasījumus, procesus un noslodzi. Uzraudzībai tas ir noderīgi, bet publiski sasniedzams tas ir nevajadzīgs informācijas avots uzbrucējiem. Šos ceļus tīmekļa serverī pārsūtiet tikai iekšējām adresēm. Kā Nginx un PHP-FPM droši sadarbojas, apraksta Nginx ar PHP-FPM nostiprināšana.
pm.max_requests nosaka, pēc cik pieprasījumiem worker tiek startēts no jauna; noklusējums ir 0, tātad nekad. Vērtība, kas lielāka par nulli, palīdz pret lēni augošu atmiņas patēriņu. Kā aizsardzība pret PHP2Shell tā nav domāta: php.net to nenosauc starp pagaidu pasākumiem un uzsver, ka pilnīgi pasargā tikai atjauninājums. Pēc atjauninājuma turklāt spēkā: visi worker procesi visos pūlos jāpārstartē, jo vecie procesi citādi turpina darboties ar ievainojamo kodu.
Uzturiet veidni jauniem pūliem un katru jaunu vietni izveidojiet ar to. Tā nošķīrums rodas automātiski, nevis tiek pēc gadiem pielāgots.
Tipiskas kļūdas pārejā
Biežākā kļūda ir jauns pūls, kas gan darbojas zem sava lietotāja, bet kura faili joprojām pieder www-data. Tad pūls nedrīkst rakstīt tur, kur tam vajag, un kāds tiesības iestata pārāk brīvi. Vispirms noskaidrojiet, kurām mapēm jābūt rakstāmām. Otrā kļūda ir ieplānotie uzdevumi, kas turpina darboties zem vecā lietotāja un izveido failus ar nepareizu īpašnieku. Trešā ir kopīga sesiju mape visiem pūliem, kas nošķīrumu atkal atceļ.
Pēc katras pārejas pārbaudiet vietnes pieteikšanos, augšupielādes, kešatmiņas un ieplānotos uzdevumus un novērojiet pūla kļūdu žurnālu.
Kā PHP SOS palīdz konkrēti?
- Mēs uzskaitām visus pūlus, lietotājus, sockets un iestatījumus un parādām, kur vietnes šodien dala procesus vai tiesības.
- Mēs katrai vietnei ieviešam nostiprinātu pūlu ar socket tiesībām, paplašinājumu robežu, resursu robežām un savu žurnālu.
- Mēs aizsargājam statusa lapas, kontrolēti pārstartējam visus worker procesus un testējam katru vietni.
- Serveru nostiprināšanas ietvaros mēs dokumentējam nošķīrumu kā veidni nākamajiem projektiem.
Biežāk uzdotie jautājumi
Kāpēc katrai vietnei jābūt savam PHP-FPM pūlam?
Savs pūls ar savu sistēmas lietotāju nošķir vietnes procesu un failu tiesību līmenī. Ja viena vietne tiek kompromitēta, kods nevar bez grūtībām nolasīt citu vietņu failus. Turklāt robežas un iestatījumus var noteikt katrai vietnei atsevišķi.
Vai savs pūls pasargā no PHP2Shell?
Nē. Ievainojamības atrodas pašā PHP interpretatorā, ko visi pūli izmanto kopīgi. Aizsardzību dod tikai atjauninājums uz labotu versiju ar visu worker procesu pārstartēšanu. Atsevišķi pūli tomēr ierobežo, cik tālu sniedzas sekmīgs uzbrukums.
Ko dara security.limit_extensions?
Saskaņā ar PHP rokasgrāmatu iestatījums ierobežo, kādus failu paplašinājumus PHP-FPM izpilda kā galveno skriptu. Noklusējums ir .php un .phar. Kas to ierobežo līdz .php, tas novērš, ka, piemēram, augšupielādēti attēlu faili tiek izpildīti kā PHP, ja tīmekļa serveris ir nepareizi konfigurēts.
Vai PHP-FPM statusa lapa ir bīstama?
Tā atklāj detaļas par darbojošajiem procesiem un pieprasījumiem. Ja tā ir publiski sasniedzama bez aizsardzības, tā atvieglo uzbrucējiem orientēšanos. Saskaņā ar rokasgrāmatu tā ir aktīva tikai tad, ja iestatīts pm.status_path. Tad tīmekļa serverim ceļš jāierobežo līdz iekšējām adresēm.