Krótko: Gdy przeglądarka żąda strony PHP, serwer WWW przyjmuje żądanie i przekazuje je przez interfejs (SAPI) do PHP. PHP tłumaczy skrypt na bajtkod, wykonuje go w Zend Engine i zwraca wynik, zwykle HTML, serwerowi WWW. Potem stan żądania jest sprzątany, aby następne zaczynało od czystego stanu.
Co się dzieje, gdy ktoś otwiera Państwa stronę?
Dla odwiedzającego to jedno kliknięcie. Na serwerze w ciągu milisekund przebiega wiele kroków. Kto zna ten przebieg, lepiej rozumie, dlaczego aktualizacje, restarty i konfiguracja są tak ważne i dlaczego luka w samym PHP waży więcej niż luka we wtyczce.
Podręcznik PHP wymienia trzy elementy potrzebne dla strony w PHP: interpreter PHP (jako CGI lub moduł serwera), serwer WWW i przeglądarkę. Interpreter to program, który czyta i wykonuje kod PHP. To, jak jest połączony z serwerem WWW, określa tryb pracy.
Jak działa PHP? Siedem kroków żądania
- Żądanie: Przeglądarka wysyła do serwera żądanie HTTP, na przykład
GET /kontakt.php. - Serwer WWW: Apache, Nginx lub LiteSpeed przyjmują żądanie. Pliki statyczne, takie jak obrazy, wydają samodzielnie. Dla plików
.phpwywołują PHP. - Przekazanie przez SAPI: Interfejs serwera (SAPI) przekazuje do PHP dane żądania, takie jak nagłówki, parametry i dane formularzy. W PHP-FPM odbywa się to przez protokół FastCGI, w mod_php bezpośrednio w procesie Apache. Szczegóły na stronie SAPI wyjaśnione.
- Przygotowanie żądania: PHP zapisuje dane żądania w zmiennych takich jak
$_GET,$_POST,$_COOKIEi$_FILESoraz rezerwuje pamięć dla żądania. - Tłumaczenie: Skrypt jest rozbijany na elementy (tokeny), z nich powstaje abstrakcyjne drzewo składni (AST), a z niego opcody, rodzaj języka maszynowego dla PHP. Jeśli OPcache jest aktywny, ten krok jest pomijany, o ile skrypt leży już w pamięci podręcznej.
- Wykonanie: Maszyna wirtualna Zend Engine przetwarza opcody, odpytuje bazy danych, czyta pliki i tworzy wyjście.
- Odpowiedź i sprzątanie: Wyjście wraca przez SAPI do serwera WWW, a stamtąd do przeglądarki. Następnie PHP zwalnia pamięć żądania i resetuje stan.
AST istnieje od PHP 7. Według powiązanego RFC parser tworzył wcześniej opcody bezpośrednio podczas wczytywania; podział na trzy fazy ułatwił utrzymanie kompilatora.
Przykład: co skrypt widzi z żądania
Poniższy nieszkodliwy skrypt pokazuje, jakie informacje PHP otrzymuje z żądania i w jakim trybie pracy działa:
<?php
header('Content-Type: text/plain; charset=utf-8');
echo 'Betriebsart (SAPI): ' . PHP_SAPI . "\n";
echo 'PHP-Version: ' . PHP_VERSION . "\n";
echo 'Methode: ' . ($_SERVER['REQUEST_METHOD'] ?? '-') . "\n";
echo 'Speicher in dieser Anfrage: ' . memory_get_usage() . " Bytes\n";
Na serwerze z PHP-FPM PHP_SAPI zwraca wartość fpm-fcgi, w Apache z mod_php apache2handler. Takie pliki kontrolne proszę umieszczać w katalogu publicznym tylko na krótko i nigdy na stałe, ponieważ informacje o wersji pomagają także atakującym w orientacji.
Gdzie wchodzą pamięć podręczna bajtkodu i JIT
Tłumaczenie w kroku 5 kosztuje czas. OPcache przechowuje według podręcznika prekompilowany bajtkod we wspólnej pamięci i oszczędza PHP wczytywania oraz rozbijania skryptów przy każdym żądaniu. Od PHP 8.0 dodatkowo kompilator JIT może tworzyć z opcodów prawdziwy kod maszynowy. Według RFC dotyczącego JIT jest to niemal samodzielna część OPcache.
| Faza | Wynik | Czy jest buforowana? |
|---|---|---|
| Analiza leksykalna | strumień tokenów | nie |
| Parsowanie | abstrakcyjne drzewo składni (AST) | nie |
| Kompilacja | opcody (tablice op dla pliku i funkcji) | tak, w OPcache |
| JIT (opcjonalnie) | kod maszynowy | tak, w pamięci OPcache |
| Wykonanie | wyjście strony | nie |
Pamięć podręczna dotyczy tylko przetłumaczonego kodu. Dane pojedynczego żądania, na przykład wpisy w formularzu czy wartości sesji, nie są współdzielone przez OPcache.
Dlaczego PHP2Shell uderza właśnie w ten przebieg
Obie luki PHP2Shell nie dotyczą jednej aplikacji, lecz samego przebiegu. W przypadku CVE-2026-109618 stan braku pamięci (out of memory) może przerwać PHP, gdy dane żądania są dopiero częściowo przygotowane, czyli w kroku 4. Przy późniejszym sprzątaniu nieprawidłowe dane mogą zostać potraktowane jako prawidłowe.
W przypadku CVE-2026-109619 wewnętrzny stan żądania może pozostać po zwolnieniu pamięci. Późniejsze żądanie w tym samym workerze może go użyć, zanim zostanie zresetowany. Dotyczy to kroku 7, czyli sprzątania. Tło wyjaśniamy na stronie CVE-2026-109619 oraz w artykule o cyklu życia żądania.
Wynika z tego: środki ochrony w kodzie aplikacji, na przykład wtyczki bezpieczeństwa, działają dopiero w kroku 6. W mod_php według zespołu PHP pokazano nawet exploit, zanim w ogóle zacznie działać kod aplikacji. Skuteczna jest tylko aktualizacja PHP do 8.5.12, 8.4.27, 8.3.36 lub 8.2.35 i restart wszystkich procesów PHP, ponieważ długo żyjące workery w przeciwnym razie nadal wykonują stary kod.
Jak konkretnie pomaga PHP SOS?
- Uwidocznienie przebiegu: Ustalamy, jaki tryb pracy, jaka konfiguracja i jaka pamięć podręczna są faktycznie aktywne na Państwa serwerze.
- Prawidłowa aktualizacja: Instalujemy aktualizację PHP i na podstawie czasów startu sprawdzamy, że nie działa już żaden stary proces.
- Zabezpieczenie warstw: W ramach utwardzania serwera ograniczamy rozmiary żądań, pamięć i uprawnienia tam, gdzie działają: przed kodem aplikacji.
- Sprawdzenie śladów: Na serwerach dostępnych bez zabezpieczeń szukamy oznak włamania.
Częste pytania
Czy kod PHP jest tłumaczony od nowa przy każdym wywołaniu?
Bez pamięci podręcznej tak: PHP rozbija skrypt, buduje drzewo składni i tworzy z niego opcody. Przy aktywnym OPcache opcody leżą we wspólnej pamięci i są używane ponownie, dopóki plik się nie zmieni.
Co pozostaje w pamięci po żądaniu?
Co do zasady stan żądania jest na końcu sprzątany, aby następne żądanie zaczynało od czystego stanu. Długo żyją tylko takie rzeczy jak OPcache czy trwałe połączenia. Właśnie to sprzątanie dotyczy CVE-2026-109619.
Dlaczego po aktualizacji trzeba zrestartować PHP?
W PHP-FPM i mod_php procesy działają nieprzerwanie i mają załadowany stary kod programu. Nowe pliki na dysku niczego tu nie zmieniają. Dopiero restart ładuje poprawiony kod.
Czy serwer WWW bez PHP jest bezpieczniejszy?
Ma mniejszą powierzchnię ataku, ponieważ żadna logika programu nie reaguje na żądania. Dla czysto statycznych treści to realna opcja. Dla sklepów, formularzy i logowania potrzebny jest jednak język po stronie serwera, a wtedy liczy się jego utrzymanie.