Īsi: PHP Request Lifecycle ir divos līmeņos. Vienreiz katram procesam PHP ielādē savus paplašinājumus (fāze MINIT) un beigās tos atkal nojauc (MSHUTDOWN). Katram atsevišķam pieprasījumam ir sava starta un uzkopšanas fāze (RINIT un RSHUTDOWN). Tīmekļa darbībā viens darbprocess secīgi apstrādā daudzus pieprasījumus, tāpēc PHP starp diviem pieprasījumiem uzticami jāatiestata viss pieprasījuma stāvoklis. Tieši uz šīs robežas atradās CVE-2026-109619, otra PHP2Shell ievainojamība.
Divi līmeņi: process un pieprasījums
Netehniskiem lasītājiem palīdz attēls: PHP darbprocess ir kā lodziņš iestādē. Lodziņš no rīta tiek iekārtots vienreiz, ar veidlapām, zīmogiem un datoru. Pēc tam secīgi nāk daudzi apmeklētāji. Pēc katra apmeklētāja galdam jābūt tukšam, lai nākamais neredzētu svešus dokumentus.
PHP darbojas tāpat. Procesa startā tas ielādē paplašinājumus un iestatījumus, kas visiem pieprasījumiem ir vienādi. Katrs pieprasījums tad saņem savu atmiņu, savus mainīgos un savus pieprasījuma datus, piemēram, $_GET, $_POST vai $_FILES. Pieprasījuma beigās PHP šo atmiņu atbrīvo. PHP Internals Book to īsi apkopo: PHP katra pieprasījuma beigās aizmirst pieprasījuma atmiņu.
PHP Request Lifecycle fāzu pārskats
Nosaukumi nāk no PHP iekšienes; katrs paplašinājums var pieslēgties šīm fāzēm.
| Fāze | Kad darbojas | Cik bieži | Tipisks uzdevums |
|---|---|---|---|
| MINIT (Module Init) | procesa startā | vienreiz katram procesam | reģistrēt paplašinājumus, izveidot ini ierakstus, ilglaicīgi dati |
| GINIT | globālo mainīgo izveidē | katram procesam, pavedienu gadījumā katram pavedienam | sagatavot paplašinājuma globālos mainīgos |
| RINIT (Request Init) | katra pieprasījuma sākumā | katram pieprasījumam | sagatavot pieprasījuma atmiņu un pieprasījuma stāvokli |
| Izpilde | pa vidu | katram pieprasījumam | darbojas jūsu PHP kods |
| RSHUTDOWN | katra pieprasījuma beigās | katram pieprasījumam | atbrīvot pieprasījuma resursus |
| PRSHUTDOWN | pēc RSHUTDOWN | katram pieprasījumam, reti izmanto | pēdējie uzkopšanas darbi |
| MSHUTDOWN | procesa beigšanās brīdī | vienreiz katram procesam | atbrīvot ilglaicīgos datus |
Svarīgs teikums no PHP Internals Book paplašinājumu izstrādātājiem: globālie mainīgie pēc katra pieprasījuma netiek automātiski iztukšoti. Kam katrā pieprasījumā jābūt svaigam, tas jāatiestata RINIT. Šis noteikums parāda, kāpēc robeža starp diviem pieprasījumiem ir drošībai kritiska vieta.
Pieprasījuma beigās ir noteikta arī secība: vispirms darbojas shutdown funkcijas, ko reģistrējāt ar register_shutdown_function(), tad destruktori, tad tiek iztukšots izvades buferis. Tikai pēc tam seko RSHUTDOWN.
Kāpēc darbprocesi tiek izmantoti atkārtoti
Komandrindā process apstrādā tieši vienu pieprasījumu un beidzas. Tīmekļa darbībā tas būtu pārāk lēni. PHP-FPM, Apache ar mod_php, LiteSpeed un FrankenPHP tāpēc uztur darbprocesus, kas apkalpo daudzus pieprasījumus pēc kārtas. MINIT tad darbojas vienreiz, RINIT un RSHUTDOWN tūkstošiem reižu. Kā FPM vada savus darbprocesus, izskaidro PHP-FPM izskaidrots.
PHP-FPM pm.max_requests nosaka, pēc cik pieprasījumiem darbprocess tiek pārstartēts. Noklusējums ir 0, tātad neierobežoti. Rokasgrāmata iestatījumu min kā līdzekli pret atmiņas noplūdēm trešo pušu bibliotēkās:
; FPM pūls: darbprocesu pārstartēt pēc 500 pieprasījumiem (pret atmiņas noplūdēm)
pm = dynamic
pm.max_children = 20
pm.max_requests = 500
Vēl tālāk iet FrankenPHP worker režīms: tur visa lietojumprogramma tiek ielādēta vienreiz un paliek atmiņā. Lietojumprogrammas stāvoklis tad pārdzīvo arī PHP līmenī. Piemērs rāda atšķirību:
<?php
function anfrageZaehler(): int
{
static $zaehler = 0; // klasiskajā FPM darbībā katram pieprasījumam atkal 0
return ++$zaehler; // worker režīmā vērtība turpina skaitīt pāri pieprasījumiem
}
echo anfrageZaehler();
Klasiskajā darbībā izsaukums vienmēr izvada 1, jo PHP pieprasījuma atmiņu atmet. Worker režīmā vērtība aug, kamēr darbprocess dzīvo. FrankenPHP dokumentācija tāpēc iesaka ar pieprasījumu saistīto stāvokli starp pieprasījumiem atiestatīt apzināti.
Kur iedarbojas CVE-2026-109619
CVE-2026-109619 skar nevis jūsu lietojumprogrammas kodu, bet pašu PHP. Saskaņā ar php.net pieprasījumam piederošs iekšējais stāvoklis varēja pārdzīvot savas atmiņas atbrīvošanu. Vēlāks pieprasījums tajā pašā darbprocesā to varēja izmantot, pirms inicializācija to atiestatīja. Attēlā: uz lodziņa galda palika zīmīte, kas jau bija jānoņem. Izmantošanas detaļas mēs apzināti nepublicējam; klasifikācija atrodama sadaļā CVE-2026-109619.
php.net pagaidu pasākumi attiecībā uz šo ievainojamību vērsti pret apstiprinātiem ceļiem, tie nav pilnīga aizsardzība:
session.upload_progress.enabled=0php.ini, FPM pūlā vai servera konfigurācijā, pēc tam pārstartēt visus darbprocesus; ne caurini_set()vai.user.ini.- PHP 8.5 papildus
fatal_error_backtraces=0un pārstartēšana. - Katram pieprasījumam noņemt nederīgus vai novecojušus iestatījumus, kas pirms inicializācijas rada ziņojumus. Ziņojumu vienkārša paslēpšana saskaņā ar php.net nepalīdz.
- Lietojumprogrammā novērst no ārpuses izraisāmas fatālas kļūdas un taimautus, arī atzvanos (callbacks), augšupielāžu un importa apstrādē.
Īstā palīdzība ir atjauninājums uz PHP 8.5.12, 8.4.27, 8.3.36 vai 8.2.35. Tā kā kļūda atrodas darbojošamies darbprocesā, tas iedarbojas tikai pēc visu procesu pārstartēšanas; soļi aprakstīti sadaļā PHP-FPM un visu darbprocesu pārstartēšana.
Pēc pārstartēšanas ar ps -eo pid,lstart,cmd | grep php pārbaudiet, vai visi PHP procesi startēti pēc atjauninājuma brīža.
Ko dzīves cikls nozīmē darbībai un izstrādei
No modeļa izriet skaidri noteikumi. Administratoriem: atjauninājums ir efektīvs tikai tad, kad vairs nedarbojas neviens process ar veco kodu. Katrai darbības režīma versijai ar ilgdzīvojošiem darbprocesiem vajadzīga dokumentēta pārstartēšana. Izstrādātājiem: kods, kas darbojas worker režīmos, nedrīkst statiskos mainīgos vai singleton objektos atstāt ar pieprasījumu saistītu stāvokli, piemēram, lietotāja datus vai tiesības. Abiem ir spēkā: saglabāt žurnālus no 20.10.2026, lai vēlāk varētu novērtēt, vai pirms ielāpa kaut kas noticis.
Kā PHP SOS palīdz konkrēti?
Ar PHP ielāpu servisu mēs šo modeli īstenojam praksē:
- Noteikt darbības režīmu: mēs noskaidrojam, vai darbojas PHP-FPM, mod_php, LiteSpeed, FrankenPHP vai php-cgi un kā tiek pārvaldīti darbprocesi.
- Atjauninājums un pārstartēšana: mēs ieviešam labotu versiju un kontrolēti pārstartējam katru darbprocesu.
- Procesu pārbaude: mēs ar startēšanas laikiem un versijām pierādām, ka nav palicis neviens vecs process.
- Pārbaudīt worker režīmus: ilgdzīvojošām lietojumprogrammām mēs apskatām, vai ar pieprasījumu saistītais stāvoklis tiek tīri atiestatīts.
Biežāk uzdotie jautājumi
Vai PHP katram pieprasījumam startē jaunu procesu?
Komandrindā jā, tīmekļa darbībā parasti nē. PHP-FPM, Apache ar mod_php, LiteSpeed vai FrankenPHP ļauj vienam procesam secīgi apstrādāt daudzus pieprasījumus. Starp pieprasījumiem PHP uzkopj pieprasījuma stāvokli.
Kāda ir atšķirība starp MINIT un RINIT?
MINIT darbojas vienreiz, kad startē PHP process, un izveido ilglaicīgas struktūras. RINIT darbojas katra atsevišķā pieprasījuma sākumā un sagatavo to, kas vajadzīgs tikai šim pieprasījumam.
Vai pm.max_requests palīdz pret CVE-2026-109619?
php.net biežāku darbprocesu pārstartēšanu kā pagaidu pasākumu nemin. Iestatījums ierobežo tikai to, cik pieprasījumus darbprocess apstrādā pirms pārstartēšanas. Pret ievainojamību palīdz atjauninājums ar sekojošu visu darbprocesu pārstartēšanu.
Kas ir FrankenPHP worker režīms?
Worker režīmā lietojumprogramma tiek ielādēta vienreiz un paliek atmiņā. Tas ietaupa laiku, bet statiskie mainīgie, globālie mainīgie un kešatmiņas starp pieprasījumiem saglabājas. Saskaņā ar FrankenPHP dokumentāciju ar pieprasījumu saistīts stāvoklis tāpēc jāatiestata apzināti.