Ukratko: PHP Request Lifecycle ima dvije razine. Jednom po procesu PHP učitava svoje ekstenzije (faza MINIT) i na kraju ih ponovno ruši (MSHUTDOWN). Za svaki pojedini zahtjev postoji zasebna faza pokretanja i čišćenja (RINIT i RSHUTDOWN). U web-radu jedan worker obrađuje mnogo zahtjeva jedan za drugim, pa PHP između dva zahtjeva mora pouzdano vratiti na početno sva stanja zahtjeva. Upravo na toj granici nalazila se CVE-2026-109619, druga ranjivost PHP2Shella.
Dvije razine: proces i zahtjev
Za ljude koji nisu tehničari pomaže slika: PHP worker je poput šaltera u uredu. Šalter se ujutro jednom opremi obrascima, pečatima i računalom. Nakon toga dolaze redom brojni posjetitelji. Nakon svakog posjetitelja stol mora biti prazan kako sljedeći ne bi vidio tuđe dokumente.
PHP radi isto tako. Pri pokretanju procesa učitava ekstenzije i postavke koje su jednake za sve zahtjeve. Svaki zahtjev zatim dobiva vlastitu memoriju, vlastite varijable i vlastite podatke zahtjeva poput $_GET, $_POST ili $_FILES. Na kraju zahtjeva PHP tu memoriju oslobađa. PHP Internals Book to sažima kratko: PHP na kraju svakog zahtjeva zaboravlja memoriju zahtjeva.
Faze PHP Request Lifecyclea u pregledu
Nazivi potječu iz unutrašnjosti PHP-a; svaka se ekstenzija može uključiti u te faze.
| Faza | Kada se izvršava | Koliko često | Tipična zadaća |
|---|---|---|---|
| MINIT (Module Init) | pri pokretanju procesa | jednom po procesu | registrirati ekstenzije, stvoriti ini unose, trajni podaci |
| GINIT | pri stvaranju globalnih varijabli | po procesu, kod threadova po threadu | pripremiti globalne varijable ekstenzije |
| RINIT (Request Init) | na početku svakog zahtjeva | po zahtjevu | pripremiti memoriju i stanje zahtjeva |
| Izvršavanje | između | po zahtjevu | izvodi se Vaš PHP kod |
| RSHUTDOWN | na kraju svakog zahtjeva | po zahtjevu | osloboditi resurse zahtjeva |
| PRSHUTDOWN | nakon RSHUTDOWN | po zahtjevu, rijetko korišteno | posljednji radovi čišćenja |
| MSHUTDOWN | pri završetku procesa | jednom po procesu | osloboditi trajne podatke |
Važna rečenica iz PHP Internals Booka za razvojne programere ekstenzija: globalne varijable ne prazne se automatski nakon svakog zahtjeva. Što mora biti svježe po zahtjevu, treba vratiti na početno u RINIT-u. Ovo pravilo pokazuje zašto je granica između dva zahtjeva sigurnosno kritično mjesto.
Na kraju zahtjeva redoslijed je također utvrđen: prvo se izvršavaju shutdown funkcije koje ste registrirali s register_shutdown_function(), zatim destruktori, potom se prazni međuspremnik izlaza. Tek nakon toga slijedi RSHUTDOWN.
Zašto se workeri ponovno koriste
U naredbenom retku jedan proces obrađuje točno jedan zahtjev i završava. U web-radu bi to bilo presporo. PHP-FPM, Apache s mod_php-om, LiteSpeed i FrankenPHP stoga drže workere koji poslužuju mnogo zahtjeva zaredom. MINIT se tada izvršava jednom, a RINIT i RSHUTDOWN tisućama puta. Kako FPM upravlja svojim workerima, objašnjava PHP-FPM objašnjen.
U PHP-FPM-u pm.max_requests određuje nakon koliko se zahtjeva worker ponovno pokreće. Zadano je 0, dakle neograničeno. Priručnik tu postavku navodi kao sredstvo protiv curenja memorije u bibliotekama trećih strana:
; FPM pool: ponovno pokrenuti worker nakon 500 zahtjeva (protiv curenja memorije)
pm = dynamic
pm.max_children = 20
pm.max_requests = 500
Još dalje ide worker mod FrankenPHP-a: ondje se cijela aplikacija pokrene jednom i ostaje u memoriji. Stanje aplikacije tada preživljava i na razini PHP-a. Primjer pokazuje razliku:
<?php
function anfrageZaehler(): int
{
static $zaehler = 0; // u klasičnom FPM radu po zahtjevu opet 0
return ++$zaehler; // u worker modu vrijednost nastavlja rasti kroz zahtjeve
}
echo anfrageZaehler();
U klasičnom radu poziv uvijek ispisuje 1 jer PHP odbacuje memoriju zahtjeva. U worker modu vrijednost raste dok worker živi. FrankenPHP dokumentacija zato savjetuje svjesno vraćanje stanja vezanog uz zahtjev na početno između zahtjeva.
Gdje CVE-2026-109619 cilja
CVE-2026-109619 ne tiče se koda Vaše aplikacije, nego samog PHP-a. Prema php.net interno stanje koje pripada zahtjevu moglo je preživjeti oslobađanje svoje memorije. Kasniji zahtjev u istom workeru mogao ga je upotrijebiti prije nego što ga inicijalizacija vrati na početno. U slici: na stolu šaltera ostao je listić koji je zapravo već trebao biti sklonjen. Pojedinosti iskorištavanja namjerno ne objavljujemo; razvrstavanje se nalazi na stranici CVE-2026-109619.
Prijelazne mjere php.net za ovu ranjivost ciljaju potvrđene putove i nisu potpuna zaštita:
session.upload_progress.enabled=0u php.ini, FPM poolu ili konfiguraciji poslužitelja, zatim ponovno pokrenuti sve workere; ne putemini_set()ili.user.ini.- Na PHP-u 8.5 dodatno
fatal_error_backtraces=0i ponovno pokretanje. - Ukloniti nevaljane ili zastarjele postavke po zahtjevu koje prije inicijalizacije stvaraju poruke. Samo sakrivanje poruka prema php.net ne pomaže.
- U aplikaciji otkloniti izvana izazvane fatalne pogreške i prekoračenja vremena, također u callbackovima te u obradi uploada i uvoza.
Pravi je lijek ažuriranje na PHP 8.5.12, 8.4.27, 8.3.36 ili 8.2.35. Budući da je pogreška u radnom workeru, djeluje tek nakon ponovnog pokretanja svih procesa; koraci su na stranici Ponovno pokretanje PHP-FPM-a i svih workera.
Nakon ponovnog pokretanja provjerite s ps -eo pid,lstart,cmd | grep php jesu li svi PHP procesi pokrenuti nakon trenutka ažuriranja.
Što životni ciklus znači za rad i razvoj
Iz modela se mogu izvesti jasna pravila. Za administratore: ažuriranje je djelotvorno tek kad više ne radi nijedan proces sa starim kodom. Svaki način rada s dugovječnim workerima treba dokumentirano ponovno pokretanje. Za programere: kod koji se izvodi u worker modovima ne smije ostavljati stanje vezano uz zahtjev u statičkim varijablama ili singletonima, primjerice podatke o korisnicima ili ovlasti. Za oboje vrijedi: sačuvati zapisnike od 20. 10. 2026. kako bi se kasnije moglo procijeniti je li se prije zakrpe nešto dogodilo.
Kako PHP SOS konkretno pomaže?
Pomoću PHP patch servisa model provodimo u praksi:
- Odrediti način rada: Utvrđujemo radi li PHP-FPM, mod_php, LiteSpeed, FrankenPHP ili php-cgi i kako se upravlja workerima.
- Ažuriranje i ponovno pokretanje: Instaliramo ispravljenu verziju i kontrolirano ponovno pokrećemo svakog workera.
- Provjera procesa: Na temelju vremena pokretanja i verzija dokazujemo da nije ostao nijedan stari proces.
- Provjeriti worker modove: Kod dugovječnih aplikacija pregledavamo vraća li se stanje vezano uz zahtjev ispravno na početno.
Česta pitanja
Pokreće li PHP novi proces za svaki zahtjev?
U naredbenom retku da, u web-radu uglavnom ne. PHP-FPM, Apache s mod_php-om, LiteSpeed ili FrankenPHP puštaju da jedan proces obrađuje mnogo zahtjeva jedan za drugim. Između zahtjeva PHP čisti stanje zahtjeva.
Koja je razlika između MINIT i RINIT?
MINIT se izvršava jednom, kad se PHP proces pokrene, i stvara trajne strukture. RINIT se izvršava na početku svakog pojedinog zahtjeva i priprema ono što treba samo tom zahtjevu.
Pomaže li pm.max_requests protiv CVE-2026-109619?
php.net češća ponovna pokretanja workera ne navodi kao prijelaznu mjeru. Postavka samo ograničava koliko zahtjeva worker obradi prije ponovnog pokretanja. Protiv ranjivosti pomaže ažuriranje uz naknadno ponovno pokretanje svih workera.
Što je Worker Mode u FrankenPHP-u?
U worker modu aplikacija se pokrene jednom i ostaje u memoriji. To štedi vrijeme, ali statičke varijable, globalne varijable i cacheovi ostaju sačuvani između zahtjeva. Prema FrankenPHP dokumentaciji stanje vezano uz zahtjev zato se mora svjesno vraćati na početno.