20 października zespół bezpieczeństwa PHP zapowiedział na następny dzień, na 14:00 UTC, wydanie z poprawkami dwóch krytycznych luk. Każda dostała 9,2 w skali CVSS 4.0. 21 października wyszły PHP 8.5.12, 8.4.27, 8.3.36 i 8.2.35. Obie luki mogą pozwolić na zdalne wykonanie kodu bez logowania, czyli obcy człowiek wysyła spreparowane żądanie i uruchamia na serwerze własny kod z uprawnieniami użytkownika, na którym działa PHP.
Według ogłoszenia zagrożona jest każda instalacja, która obsługuje żądania WWW: PHP-FPM, Apache z mod_php, php-cgi, FrankenPHP i LiteSpeed. Największe ryzyko dotyczy mod_php. Tam pokazano działający atak: żądanie bez logowania może go uruchomić, zanim w ogóle ruszy kod aplikacji.
Poniżej obie luki po kolei, na podstawie advisory opublikowanych przez projekt PHP.
Luka 1: brak pamięci w złym momencie (CVE-2026-109618)
Każde żądanie ma w PHP limit pamięci (memory_limit). W wersjach sprzed poprawki przekroczenie limitu kończyło się wyskokiem z bieżącego miejsca w kodzie (mechanizm longjmp() z języka C) i przerwaniem żądania. Na koniec PHP i tak uruchamiał sprzątanie po żądaniu.
W kilku miejscach pamięć żądania jest już widoczna dla reszty PHP, ale jeszcze nie do końca wypełniona. Jeśli akurat wtedy jakakolwiek alokacja się nie uda, nawet zupełnie niezwiązana, PHP zostawia te dane w połowie. Sprzątanie traktuje je potem jak poprawne i na nich pracuje. Przy odpowiednio ułożonej pamięci da się z tego zrobić wykonanie kodu.
Atakujący musi więc doprowadzić do braku pamięci w odpowiedniej chwili. Advisory wymienia trzy sposoby przez formularze wysyłane jako multipart, czyli tak samo jak pliki, i zaznacza, że w konkretnej aplikacji da się to zrobić także inaczej, jeśli dane od atakującego wywołują duże alokacje:
- żądanie bez użytecznego nagłówka
Content-Lengthprzechodzi wstępny testpost_max_sizei dalej dosyła dane, - jedna część formularza może mieć dowolnie dużo nagłówków, a w pamięci PHP zajmują one dużo więcej niż w samym żądaniu,
- puste pola plików nie liczą się do limitu
max_file_uploads, ale i tak tworzą tablice z opisem pliku, a zagnieżdżone nazwy pól mnożą tę pracę.
W mod_php sprzątanie po żądaniu odpala się zawsze, nawet gdy start żądania się nie udał. Parsowanie ciała żądania dzieje się przy starcie, więc cały atak mieści się przed kodem aplikacji. Potwierdzono go przez HTTP na mod_php z domyślnymi limitami. FPM przy nieudanym starcie kończy cały proces roboczy, więc ta konkretna ścieżka tam nie przechodzi, ale po udanym starcie brak pamięci wciąż może trafić w złe miejsce. Twórcy PHP piszą, że analiza kodu pokazuje podatność FPM, choć pełnego ataku na FPM nie pokazano.
Poprawka usuwa wyskok przy braku pamięci. memory_limit stał się limitem miękkim: alokacja zwykle się kończy, a błąd krytyczny PHP zgłasza w najbliższym bezpiecznym punkcie. Gdy zużycie dojdzie do drugiego, wyższego progu, PHP natychmiast kończy proces (_exit()). Ten próg ustawia nowa opcja memory_hard_limit_percent. Dokumentacja PHP podaje przy niej prosty test: jeśli Twoja wersja nie rozpoznaje tej opcji, jest podatna.
Luka 2: stan po poprzednim żądaniu (CVE-2026-109619)
PHP-FPM i Apache nie uruchamiają PHP od zera dla każdego żądania. Jeden proces roboczy obsługuje wiele żądań po kolei. Ma przy tym zmienne globalne, na przykład stan błędów, sesji czy rozszerzeń, które w trakcie żądania wskazują na pamięć tego żądania. Przed zwolnieniem tej pamięci trzeba je wyzerować.
Czasem tego zerowania nie ma. Błąd krytyczny (w tym brak pamięci, przekroczony czas wykonania albo nieudany require) przerywa operację, zanim ta po sobie posprząta. Albo ostrzeżenie zgłoszone już w trakcie zamykania żądania znowu wpisuje coś do wyczyszczonego stanu. Pamięć żądania jest zwalniana, a zmienna globalna dalej na nią wskazuje. Gdy coś z tego wskaźnika skorzysta, mamy klasyczny błąd użycia zwolnionej pamięci (use after free).
Do ataku potrzebne są dwa żądania trafiające do tego samego procesu roboczego. Pierwsze zostawia nieaktualny wskaźnik. Drugie uruchamia kod, który go odczytuje, zanim PHP zdąży normalnie przygotować nowe żądanie. Advisory opisuje dwa potwierdzone miejsca, w których tak się dzieje:
- Ostrzeżenia z ustawień PHP stosowanych przy każdym żądaniu. Przestarzałe albo błędne ustawienie zgłasza ostrzeżenie jeszcze przed inicjalizacją żądania, a samo zgłoszenie dotyka nieaktualnego stanu błędów. Nie pomaga ukrywanie błędów przez
error_reporting,@czydisplay_errors, bo stan jest odczytywany, zanim PHP zdecyduje, czy cokolwiek pokazać. W mod_php takie ustawienia przychodzą z.htaccessi zphp_valuew konfiguracji Apache. W FPM tylko wtedy, gdy serwer WWW przekazuje ustawienia przezPHP_VALUEalboPHP_ADMIN_VALUE. Ustawienia z puli FPM i z.user.initej ścieżki nie otwierają. - Postęp wysyłania plików w sesji (
session.upload_progress). Ta funkcja dotyka danych sesji jeszcze przed ich zwykłą inicjalizacją. Jest domyślnie włączona i działa nawet wtedy, gdy aplikacja w ogóle nie wołasession_start(). Ta ścieżka została potwierdzona na mod_php i na FPM, przy czym na FPM zależy od odpowiedniej operacji w samej aplikacji.
Najmocniejszy pokazany przypadek dotyczy PHP 8.5, w którym opcja fatal_error_backtraces jest domyślnie włączona. Ślad wywołań zapisany przy błędzie krytycznym nigdy nie był zerowany przy zamykaniu żądania i na tym zbudowano potwierdzony atak na mod_php.
Co z tym zrobić
Jedyna pełna poprawka to aktualizacja do 8.5.12, 8.4.27, 8.3.36 albo 8.2.35. PHP 8.1 i starsze nie dostają już poprawek od projektu, choć tym razem zespół wydał minimalne łatki źródłowe dla 7.4, 8.0 i 8.1 dla osób, które kompilują PHP same. PHP 8.2 kończy wsparcie 31 grudnia 2026.
Na Ubuntu sam numer z php -v nie odpowiada na pytanie, czy serwer jest załatany. Ubuntu przenosi poprawkę do własnego pakietu i nie zmienia wersji PHP. Na Ubuntu 24.04 poprawiony pakiet PHP 8.3 to 8.3.6-0ubuntu0.24.04.13, więc php -v dalej pokazuje 8.3.6. Sprawdzać trzeba wersję pakietu, którego używa strona, czyli FPM albo moduł Apache:
dpkg-query -W -f='${Package} ${Version}\n' 'php8.3-*' 'libapache2-mod-php*'
Na Ubuntu 26.04 poprawiony pakiet PHP 8.5 to 8.5.4-0ubuntu1.5. Pakiety z popularnego repozytorium PPA Ondřeja Surý'ego mają inną numerację i pokazują już wersje 8.4.27 czy 8.5.12.
Warto też pamiętać, że php -v w terminalu pokazuje wersję CLI. Strona może działać na innym pakiecie FPM albo w kontenerze z własnym PHP.
Jeśli aktualizacja musi poczekać, ogłoszenie podaje środki, które zmniejszają ryzyko, ale nie czynią instalacji bezpieczną:
memory_limit=-1ustawione tak, żeby aplikacja nie mogła go zmienić, na przykład przezphp_admin_valuew puli FPM, z restartem procesów roboczych. Usuwa przewidywalny limit, na którym opiera się potwierdzony atak, ale awaria alokacji na poziomie systemu dalej może trafić w podatną ścieżkę. Bez limitu jeden ciężki skrypt może też zająć całą pamięć serwera.- niskie
post_max_size: 8M, jeśli aplikacja nie przyjmuje plików, 32M, jeśli przyjmuje, iupload_max_filesizeponiżej tej wartości; do tego limity na serwerze WWW albo proxy (rozmiar żądania, liczba pól, części formularza i nagłówków), które obejmują też żądania bezContent-Length, session.upload_progress.enabled=0wphp.inialbo w puli FPM, potem restart wszystkich procesów roboczych (nie przezini_set()ani.user.ini),- na PHP 8.5 dodatkowo
fatal_error_backtraces=0i restart, - usunięcie przestarzałych i błędnych ustawień PHP stosowanych przy każdym żądaniu,
- usunięcie błędów krytycznych i przekroczeń czasu wykonania, do których atakujący może doprowadzić, na przykład przy wysyłce i imporcie plików,
- jeśli wydajność nie gra roli,
pm.max_requests=1, czyli nowy proces roboczy na każde żądanie.
Advisory odradza ograniczanie pamięci przez RLIMIT_AS albo RLIMIT_DATA, bo właśnie to wywołuje podatną ścieżkę. Zaznacza też, że brak błędów w logach niczego nie dowodzi: część ścieżek może nie zostawić żadnego wpisu, a przegląd logów obejmuje tylko te trasy, które akurat były używane.
Źródła: ogłoszenie na php.net, advisory GHSA-w8hw-w9fx-j79w i GHSA-6wmf-4rv5-p3rp, strona CVE Ubuntu.