21 października 2026 roku PHP dostało poprawkę na dwie luki, które pozwalają uruchomić dowolny kod na serwerze bez logowania. Nie ma ich w WordPressie, w motywie ani w żadnej wtyczce. Siedzą w samym PHP, czyli w warstwie pod stroną, a to zmienia charakter zadania: to nie decyzja o rozwoju serwisu, tylko konserwacja z terminem.
Co ogłoszono 21 października
Dwie luki, CVE-2026-109618 i CVE-2026-109619, nazwane razem PHP2Shell. Obie mają 9,2 w CVSS w wersji 4.0. Tyle nadało im PHP w biuletynie, nie marketing producenta zabezpieczeń. Poprawki wyszły w czterech wydaniach: 8.5.12, 8.4.27, 8.3.36 i 8.2.35.
CERT-EU opisało sprawę jako Security Advisory 2026-018 i podało godzinę wydania, 13:30 UTC. CISA wydała alert tego samego dnia i napisała wprost, że do momentu publikacji nie zaobserwowała wykorzystania. Cloudflare w swoim wpisie z 21 października pisze coś innego: że widział ruch atakujący jego klientów i że atakujący modyfikowali payload, żeby obejść reguły. Jedno i drugie może być prawdą naraz, bo „nie zaobserwowała” znaczy tyle, co „nie mamy potwierdzonego przypadku”, a nie „nikt nie próbował”. Fundacja PHP dokłada trzeci fakt: w chwili wydania nie znała działającego exploita, natomiast koncepcje ataku krążyły już publicznie.
Numery CVE wyciekły 20 października po południu, a PHP przyspieszyło wydanie o dobę. Od wycieku do łatki minęło mniej niż dwadzieścia cztery godziny.
Biuletyn wymienia PHP-FPM, Apache z mod_php, php-cgi, FrankenPHP i LiteSpeed. Najwyższe ryzyko dotyczy mod_php, bo tam udało się pokazać atak dziejący się przed uruchomieniem kodu aplikacji.
Na czym polega błąd
PHP ogranicza pamięć żądania przez memory_limit. Kiedy limit zostaje przekroczony, PHP nie wraca z błędem po stosie wywołań, tylko przerywa wszystko mechanizmem longjmp(), czyli skokiem poza bieżący kontekst. To szybkie i sprawdzone rozwiązanie, dopóki nie trafi w moment, w którym jakaś struktura danych jest zbudowana do połowy. Wtedy PHP porzuca pracę bez sprzątania, a chwilę później uruchamia zwykłą procedurę zamykania żądania, która zakłada, że wszystko jest spójne.
CVE-2026-109618 to właśnie ta sytuacja: dane, których nikt nie zainicjalizował, są czytane jak poprawne. CVE-2026-109619 idzie o krok dalej. Stan należący do jednego żądania zostaje w pamięci workera po zwolnieniu tej pamięci, a kolejne żądanie obsłużone przez ten sam proces sięga po te bajty, zanim normalna inicjalizacja je nadpisze. Biuletyn wymienia konkretne ścieżki: stan błędów, sesji, obiektów i rozszerzeń. Jedna z nich to obsługa postępu uploadu (session.upload_progress), włączona domyślnie, bez potrzeby wywołania session_start() w kodzie.
Do pierwszego błędu wystarczy żądanie multipart, które na papierze mieści się w limitach. Biuletyn podaje trzy sposoby: żądanie bez użytego Content-Length przechodzi wstępne sprawdzenie post_max_size i dalej dosyła treść; część multipart może mieć nieograniczoną liczbę nagłówków, których reprezentacja w pamięci jest większa niż na łączu; puste pola plikowe nie liczą się do max_file_uploads, ale i tak tworzą tablice metadanych, a zagnieżdżone nazwy pól mnożą tę pracę.
Dlaczego wtyczka tego nie załata
I tu jest sedno: PHP parsuje żądanie przed inicjalizacją rozszerzeń i przed skompilowaniem pierwszej linii kodu aplikacji. Kiedy atak się dzieje, WordPress jeszcze nie istnieje w pamięci procesu. Nie ma wtyczki, nie ma motywu, nie ma functions.php. Jest interpreter, który przetwarza dane wejściowe.
Dlatego strona pisana przez bardzo dobrego programistę jest narażona dokładnie tak samo jak strona zaniedbana. Aktualizacja WordPressa, wtyczek i motywu, choć zawsze sensowna, w tym przypadku nic nie zmienia.
Jest jednak różnica między konfiguracjami, bo wpływa na to, co zobaczysz w logach. Apache z mod_php wywołuje procedurę zamykania żądania nawet wtedy, gdy start żądania się nie powiódł, i to otwiera drogę opisaną w biuletynie jako potwierdzoną. FPM kończy proces potomny, gdy start żądania zawiedzie, więc ta konkretna ścieżka nie przechodzi. Nie znaczy to, że FPM jest bezpieczny: biuletyn opisuje potwierdzoną ścieżkę na FPM, w której stan publikowany jest w trakcie działania aplikacji, a konsumentem jest obsługa postępu uploadu przy kolejnym żądaniu. FrankenPHP i LSAPI są prawdopodobnie podatne, choć bez potwierdzonego ataku.
Gdzie w tym jest twoja strona
W3Techs, w odczycie z 22 października 2026, podaje, że PHP używa 69,3% stron, których język po stronie serwera da się rozpoznać. Podział na wersje wśród stron na PHP wygląda tak: 8.x to 65,3%, 7.x to 26,9%, 5.x to 7,7%, 4.x to 0,1%.
Arytmetyka, którą możesz powtórzyć: 26,9 + 7,7 + 0,1 = 34,7. Ponad jedna trzecia stron na PHP działa na gałęzi, dla której projekt nie wyda już łatki. Dla 7.4, 8.0 i 8.1 zespół bezpieczeństwa opublikował minimalne backporty źródeł, ale to kod do samodzielnego zbudowania, nie pakiet od hostingu.
Druga rzecz, której właściciele stron często nie widzą: PHP siedzi też w sklepach, w panelach hostingowych, w interfejsach urządzeń sieciowych i w oprogramowaniu, które dostarcza firma zewnętrzna razem ze sprzętem. CERT-EU odsyła w tym miejscu do repozytorium NCSC-NL, które zbiera produkty z wbudowanym PHP. Jeśli w firmie jest kamerka z panelem albo stare urządzenie do backupu, to też jest PHP i też ma wersję.
Jak wygląda łatka od strony serwera
Najczęstszy błąd przy sprawdzaniu: php -v w SSH pokazuje wersję interpretera linii poleceń, a strona odpowiada przez inny pakiet, zwykle php-fpm albo moduł Apache. Sprawdzać trzeba to, co realnie obsługuje żądania HTTP: przez phpinfo(), panel hostingu albo polecenie, które pyta usługę, a nie powłokę.
Sama podmiana pakietu nie wystarczy bez restartu. Działające procesy robocze trzymają w pamięci stary kod i będą go wykonywać do końca życia procesu, więc po aktualizacji restartuje się FPM albo Apache. W kontenerach podmiana pakietu wewnątrz działającego kontenera jest obejściem, poprawna droga to nowy obraz bazowy i nowe kontenery.
Ile to zajmuje i ile kosztuje
Liczby w tym akapicie są przykładem, nie benchmarkiem. Jedna strona: sprawdzenie wersji, zrzut, aktualizacja pakietu, restart, test formularza kontaktowego. Przy wprawie dwadzieścia pięć minut. Osiemnaście stron, czyli portfel małej agencji: 18 × 25 = 450 minut, czyli 7,5 godziny. Jeden dzień pracy jednej osoby, którą trzeba mieć.
Koszt zaniechania trudniej policzyć, więc policzmy jedną jego część. Sklep, osiem godzin niedostępności, trzy zamówienia na godzinę, średnia wartość zamówienia 240 złotych: 8 × 3 × 240 = 5760 złotych. To strata widoczna w raporcie. Niewidoczna jest inna: jeśli atakujący dostanie powłokę z uprawnieniami użytkownika serwera WWW, ma dostęp do pliku konfiguracyjnego z danymi bazy, do katalogu z kopiami zapasowymi, jeśli ktoś je tam zostawił, i do wszystkiego, co aplikacja może przeczytać.
Nie napiszę, że twoje dane wyciekły, bo tego nie wiem i nie wiem tego bez twoich logów. Wiem, co się robi po takim zdarzeniu i w jakiej kolejności, a to część, którą można zaplanować zawczasu: odcięcie serwera od sieci z zachowaniem kopii, zabezpieczenie logów serwera WWW i PHP, przegląd katalogu pod kątem nowych plików, przegląd zadań cron i kont, zmiana wszystkich poświadczeń, które leżały na tej maszynie.
Co zrobić, jeśli nie możesz załatać dziś
Zacznij od tego, co biuletyn nazywa środkami tymczasowymi, i przyjmij do wiadomości, że żaden z nich nie jest rozwiązaniem. Dla CVE-2026-109618 pierwszy krok to memory_limit=-1 ustawione tak, żeby aplikacja nie mogła go nadpisać, przez php_admin_value w puli FPM albo w konfiguracji Apache, z restartem workerów i sprawdzeniem, że żadna pula ani katalog nie przywraca skończonego limitu. Do tego post_max_size 8M, jeśli nie ma uploadów, 32M, jeśli są, z upload_max_filesize ustawionym niżej. Na froncie dorzuć limity liczby pól, części multipart, nagłówków i głębokości zagnieżdżenia, także dla żądań bez Content-Length. Kontener albo cgroup jako granica dostępności, ale nie RLIMIT_AS ani RLIMIT_DATA, bo te wywołują dokładnie ten błąd alokacji, którego szukasz.
Dla CVE-2026-109619 wyłącz session.upload_progress.enabled w php.ini, w puli FPM albo w konfiguracji serwera, czyli tam, gdzie zadziała przed parsowaniem żądania, a nie przez ini_set(). Na PHP 8.5 dołóż fatal_error_backtraces=0. Biuletyn mówi wprost, że dla tej luki nie ma kompletnego środka konfiguracyjnego, bo stan może publikować aplikacja, rozszerzenie albo serwer, i nie zostawi po tym przewidywalnego wpisu w logu.
Jest też koszt, o którym trzeba powiedzieć głośno: memory_limit=-1 przenosi problem z bezpieczeństwa na dostępność. Skrypt, który wcześniej padał po wyczerpaniu limitu, teraz zje pamięć maszyny. To trade-off, nie magiczne rozwiązanie.
Czego to nie rozwiązuje
Firewall aplikacyjny nie łata PHP. Cloudflare wdrożył ochronę 21 października od 03:02 UTC, dla wszystkich planów z WAF, także darmowego, i przyznaje, że dane żądania nie wystarczały do odróżnienia ataku od normalnego ruchu, dlatego powstała funkcja łącząca sygnały z żądania i z odpowiedzi serwera źródłowego. Kto ma domenę za Cloudflarem, z regułami w trybie blokowania, ten ma ochronę. Kto trzyma tam tylko DNS, bez proxy, ten nie ma nic, bo ruch nie przechodzi przez ich sieć.
Monitoring nie cofa włamania. Łatka nie usuwa tego, co już leży na dysku. Backport na gałąź po końcu wsparcia jest obejściem, które trzeba będzie powtórzyć przy następnej luce, a przy okazji zwykle okazuje się, że nikt nie pamięta, jak się go budowało.
Jest jeszcze kwestia, którą Fundacja PHP opisała sama, bez wygładzania. Pierwsze oznaki problemu z pamięcią znalazł programista PHP w 2024 roku, przy naprawianiu innego zgłoszenia z audytu Quarkslab. Zespół bezpieczeństwa przez wiele miesięcy nie umiał wybrać między rozwiązaniami, bo każde łamało coś innego, a decyzje podejmowano konsensusem. Fundacja nazywa to porażką strukturalną. Wersja, którą masz na serwerze, powstała w projekcie, w którym naprawa poważnego błędu potrafi czekać dwa lata. Argumentem za zmianą technologii to nie jest. Argumentem za własnym terminem reakcji jest.
Lista kontrolna na dziś
- Sprawdź wersję PHP, którą widzi serwer WWW, nie tę z SSH.
- Jeśli twoja gałąź to 8.2, 8.3, 8.4 albo 8.5 i wersja jest niższa niż 8.2.35, 8.3.36, 8.4.27 albo 8.5.12, załatuj.
- Zrestartuj workery FPM albo Apache po aktualizacji.
- Kontenery przebuduj z nowego obrazu bazowego, nie restartuj starych.
- Na 8.1 i starszych podejmij decyzję: migracja albo izolacja od internetu, plus backport, jeśli budujesz ze źródeł.
- Sprawdź, czy domena jest proxowana przez WAF i czy reguły są w trybie blokowania, a nie tylko logowania.
- Zapisz, kto to zrobił, kiedy i na jakiej wersji skończył, bo następny biuletyn przyjdzie i ta kartka będzie jedyną odpowiedzią na pytanie „jak było ostatnio”.
Siódma pozycja jest zwykle tą, której nikt nie robi, bo po restarcie wszystko działa i temat znika z głowy do następnego razu. Cała reszta zależy od osoby, która ma w obowiązkach czytanie biuletynu, sprawdzenie wersji na każdym serwerze i wykonanie tego w oknie doby, a nie wtedy, gdy znajdzie się chwila. W dydlak.cloud robię dokładnie to: monitoring wersji na stronach, których pilnuję, wdrożenie łatki w ciągu doby od publikacji biuletynu i raport dla właściciela firmy napisany tak, żeby dał się przeczytać bez słownika. Jeśli u ciebie ta osoba nie ma imienia, mogę nią być.
