Przejdź do treści

PHP2Shell – jedna luka w PHP i państwa gaszą swoje strony

Są podatności, o których czytają tylko administratorzy. I są takie, po których rząd wydaje komunikat, a urzędy same wyłączają swoje strony. 22 października 2026 roku Ministerstwo Cyfryzacji opublikowało komunikat o lukach w PHP, a Centralny Ośrodek Informatyki czasowo zdjął z sieci strony Biuletynu Informacji Publicznej działające w systemie SSDIP. Tego samego dnia podobne decyzje zapadały w Holandii, Finlandii, Francji czy Danii.

Piszę o tym z mieszanymi uczuciami, bo na PHP się wychowałem – ten blog przez lata stał na WordPressie, mam tu kilkadziesiąt wpisów z tagiem php, od Symfony 6 po refaktoryzację tabliczki mnożenia. Dlatego zanim w kolejnych wpisach przejdę do tego, czego ta historia uczy o bezpieczeństwie i dlaczego sam dziś piszę głównie w innych językach, chcę rzetelnie opisać, co się właściwie stało. Bez paniki i bez „PHP umarł”.

Jeśli masz serwer z PHP, przeczytaj najpierw to. Poprawione wersje to 8.5.12, 8.4.27, 8.3.36 i 8.2.35. Podatne są wszystkie wersje od 5.4 wzwyż. Zgodnie z komunikatem CERT Polska jedyną skuteczną ochroną jest aktualizacja. Ustawienia w php.ini opisane niżej tylko zmniejszają ryzyko. Po aktualizacji przejrzyj logi, bo nowa wersja nie mówi nic o tym, czy ktoś nie wszedł wcześniej.

Co się zepsuło

Dwie luki dostały numery CVE-2026-109618 i CVE-2026-109619 i ocenę 9,2 w skali CVSS 4.0. W mediach funkcjonują razem jako PHP2Shell. Obie są błędami typu use-after-free w samym silniku PHP, nie w żadnym frameworku ani CMS-ie.

Mechanizm jest ciekawszy niż zwykle. Gdy PHP-owi kończy się pamięć przydzielona na żądanie (memory_limit), silnik nie zwija się elegancko przez kolejne funkcje, tylko przeskakuje do punktu awaryjnego (w C robi to longjmp). Taki skok w pół operacji potrafi zostawić wewnętrzne struktury w niespójnym stanie: wskaźnik dalej pokazuje na pamięć, która formalnie została już zwolniona. Jeśli napastnik potrafi doprowadzić do takiego skoku w odpowiednim momencie, a potem wpisać w zwolnione miejsce własne dane, przejmuje sterowanie procesem.

Najgorsze jest to, gdzie ten moment wypada. Według opisów badaczy ścieżka ataku prowadzi przez obsługę śledzenia postępu wysyłania plików (session.upload_progress), uruchamianą przy parsowaniu żądania multipart/form-data, zanim skrypt aplikacji w ogóle wystartuje. To odwraca zwykły porządek rzeczy. Nie ma znaczenia, czy kod strony jest napisany starannie, czy niedbale, czy używa frameworka, czy filtruje dane wejściowe – bo do tego kodu żądanie jeszcze nie dotarło. Wystarczy, że na serwerze działa podatne PHP, a strona przyjmuje żądania POST. Podatne są zarówno Apache z mod_php, jak i PHP-FPM.

Jest i drugi skutek, mniej widowiskowy, ale groźny: te same błędy pozwalają odczytać fragmenty pamięci procesu PHP. A tam bywa wszystko, co przechodziło przez poprzednie żądania – hasła, tokeny sesji, klucze API.

Oś czasu

Ta historia ma nietypowy przebieg, warto go więc rozpisać:

  • 2024 – podczas przeglądu kodu jeden z programistów PHP trafia na błąd w obsłudze braku pamięci. Nie zostaje on wtedy potraktowany jako luka bezpieczeństwa.
  • 11 sierpnia 2026 – firma Calif zgłasza zespołowi PHP pierwszą ścieżkę zdalnego wykonania kodu. Według opisów badacze pracowali z modelami AI w ramach inicjatywy OpenAI „Patch the Planet”. 24 sierpnia niezależnie zgłasza się zespół badawczy Wiz.
  • 17 października – honeypoty Amazona rejestrują próbę wykorzystania luki, cztery dni przed publikacją poprawek.
  • 20 października – szczegóły wyciekają przed planowaną, skoordynowaną publikacją. PHP przyspiesza wydanie.
  • 21 października, 14:00 UTC – PHP wydaje poprawione wersje. Cloudflare raportuje pierwsze ataki już o 7:43 UTC tego dnia, czyli kilka godzin przed poprawką. CERT Polska publikuje komunikat.
  • 22 października – komunikat Ministerstwa Cyfryzacji i wyłączenie BIP w SSDIP. Po południu COI zaczyna stopniowo przywracać strony.

Wyciek przed poprawką to najgorszy możliwy scenariusz w koordynowanym ujawnianiu luk. Normalnie administratorzy dostają łatkę i opis jednocześnie – tu przez kilkanaście godzin wiedzieli o problemie wszyscy, a naprawić nie mógł go nikt.

Co wyłączono w Polsce

Najbardziej widoczny ruch to decyzja COI: czasowe wyłączenie stron BIP działających w Systemie Scentralizowanego Dostępu do Informacji Publicznej, czyli serwisów pod ssdip.bip.gov.pl. Ministerstwo podkreśla, że to nie wszystkie strony BIP w Polsce – wiele urzędów prowadzi Biuletyn na własnych systemach i hostingach. Tego samego dnia po 16:00 przywrócono przeglądanie opublikowanych informacji, a wyszukiwarka i funkcje redakcyjne miały wracać później.

Według doniesień prasowych Orange Polska tymczasowo wstrzymało dostęp do części swoich serwisów i aplikacji, a podobne decyzje zapadały w firmach i na uczelniach. Konkretnych nazw i liczb nikt jednak nie podał.

Za granicą skala była podobna albo większa. W Holandii zniknął portal Overheid.nl, w Finlandii publiczne serwisy rządowe, we Francji strony ministerstwa spraw wewnętrznych, w Danii krajowa platforma komunikacji szkolnej. Brytyjskie NCSC ostrzegało przed tylnymi furtkami, amerykańska NSA i CERT-EU zalecały wyłączenie serwisów, których nie da się od razu zaktualizować.

Ile tego jest

Tu trzeba bardzo uważać na liczby, bo w obiegu krążą trzy różne wielkości mierzone w trzech różnych jednostkach:

Co mierzonoWynikŹródło
Udział PHP wśród stron WWW (z rozpoznawalnym językiem serwera)ok. 70–75%W3Techs, cytowane przez media
Serwery wystawione do internetu, które ujawniają podatną wersję PHPok. 2 mlnOX Security, skan Shodana z 21.10
Szacunek wszystkich podatnych serwerówponad 22,6 mlnPalo Alto Networks, cytowane przez prasę

Pierwsza liczba nie mówi o podatności niczego – to po prostu popularność języka. Druga jest twarda, ale zaniżona: liczy tylko serwery, które w nagłówkach chwalą się wersją PHP, a porządnie skonfigurowane tego nie robią. Trzecia to szacunek, którego metodologii nie znam. Polskich liczb na dziś nikt nie opublikował.

Ale z tego samego skanu OX Security pochodzą dwie liczby, które uważam za najważniejsze w całej historii:

  • dzień po wydaniu poprawek 245 serwerów pokazywało poprawioną wersję – na około 2 miliony podatnych;
  • 1,37 mln, czyli 69% podatnych serwerów, działa na wersjach PHP, które nigdy nie dostaną oficjalnej poprawki, bo dawno wyszły ze wsparcia.

Pierwsza liczba wyrówna się w ciągu tygodni. Druga – nie. Dla gałęzi 7.4, 8.0 i 8.1 zespół PHP udostępnił wprawdzie łatki w kodzie źródłowym, ale bez gotowych wydań; resztę muszą zrobić dystrybucje Linuksa i firmy hostingowe. Dla PHP 5 nie ma nic. Oficjalny obraz Dockera php dla wersji 8.1 i starszych też poprawki nie dostanie. Te serwery będą podatne, dopóki ktoś ich nie przeniesie na nowszą wersję – a skoro nie przeniósł przez ostatnie lata, to raczej nie zrobi tego jutro.

A wycieki?

Stan na wieczór 22 października: nie znam żadnego potwierdzonego wycieku danych ani policzonej listy przejętych serwerów – ani w Polsce, ani na świecie. Są za to potwierdzone próby ataku (honeypoty Amazona od 17 października, Cloudflare i watchTowr od 21 października) i są sprzeczne oceny skali zagrożenia. Część badaczy pisze, że atak nie wymaga żadnej niestandardowej konfiguracji. Firma Sansec, która zajmuje się bezpieczeństwem sklepów internetowych, po dniu obserwacji uznała, że publiczne exploity nie działają na domyślnej instalacji, a masowe włamania są mniej prawdopodobne, niż zakładała.

Brak doniesień o wyciekach nie oznacza jednak, że wycieków nie ma. Włamanie przez lukę w silniku nie zostawia śladu w logach aplikacji, a odczyt pamięci procesu może nie zostawić żadnego śladu. O skutkach zwykle dowiadujemy się tygodnie albo miesiące później. CERT Polska prosi, by ślady wykorzystania luki zgłaszać przez incydent.cert.pl.

Co zrobić, jeśli nie możesz zaktualizować od razu

CERT Polska podaje tymczasowe ustawienia php.ini, z wyraźnym zastrzeżeniem, że nie wykluczają one ataku:

; php.ini – środki doraźne do czasu aktualizacji (wg CERT Polska)
session.upload_progress.enabled = Off
post_max_size = 8M
max_input_vars = 1000
; -1, jeśli serwer ma zapas pamięci; w przeciwnym razie 128M lub więcej
memory_limit = -1

Logika jest prosta: wyłączamy śledzenie postępu uploadu, przez które prowadzi ścieżka ataku, i odsuwamy granicę pamięci, której przekroczenie uruchamia wadliwy skok. Ma to swoją cenę: memory_limit = -1 oznacza, że jedno złośliwe żądanie może zająć całą pamięć serwera. Zamieniamy więc zdalne wykonanie kodu na możliwość odmowy usługi. To lepiej, ale wciąż źle. Przy PHP-FPM te same ustawienia trzeba nadać w konfiguracji puli (php_admin_flag, php_admin_value), a nie w globalnym php.ini.

Dlaczego to dotknęło akurat urzędy

Patrząc na listę wyłączonych serwisów, łatwo dojść do wniosku „PHP jest dziurawe”. To byłby zły wniosek. Poważne błędy w interpreterach zdarzają się wszystkim i nie jest to pierwszy taki rok ani pierwszy język. Lepsze pytanie brzmi: dlaczego państwa, zamiast zaktualizować, wyłączały?

Moim zdaniem odpowiedź siedzi w tamtych 69 procentach. Wyłącza się to, czego nie da się szybko zaktualizować, a szybko nie da się zaktualizować tego, czego nikt nie aktualizował od lat. Strona urzędu na PHP 7.2 nie jest wynikiem błędu w PHP 7.2, tylko tego, że ktoś kiedyś ją wdrożył, a potem nikt nie miał w budżecie jej utrzymania. To ta sama sytuacja, którą opisywałem przy legacy w COBOL-u: system działa, więc nikt go nie rusza, aż do dnia, w którym trzeba go ruszyć w ciągu godziny.

PHP ma tu jednak swoją specyfikę, która dobrze tłumaczy, czemu właśnie w nim ten ogon jest tak długi. I od razu uprzedzam: to nie jest zarzut wobec języka. Przez dwie dekady PHP był domyślnym językiem sieci, i to zasłużenie. Jest przyjemny, łatwy na start, a do uruchomienia strony wystarczy najtańszy hosting. Wrzucasz pliki przez FTP i działa, bez serwera aplikacji, kontenerów i konfiguracji. Siedzi w WordPressie, Joomli i Drupalu, w panelach cPanel i Plesk, wpięty w Apache jako moduł. Właśnie dzięki temu tak wielu z nas – ja też – w ogóle zaczęło pisać aplikacje internetowe.

Tyle że ta sama łatwość ma drugą stronę. To, co łatwo wdrożyć bez wiedzy, równie łatwo porzucić bez wiedzy. Nikt nie pamięta, że na serwerze w ogóle jest PHP, bo „przecież to tylko strona”. A model, w którym interpreter siedzi wewnątrz serwera WWW i obsługuje każde żądanie z internetu, zanim aplikacja w ogóle się odezwie, daje napastnikowi bardzo krótką drogę do celu – co PHP2Shell pokazał aż za dobrze.

Na tym chcę oprzeć kolejne wpisy. Nie na tezie, że PHP jest zły, bo nie jest. Raczej na tym, co widać dopiero po latach utrzymywania aplikacji: sposób wdrażania, aktualizowania i oddzielania kodu od internetu ma dla bezpieczeństwa większe znaczenie niż sam język. I właśnie dlatego, choć to na PHP nauczyłem się pisać aplikacje internetowe, dziś sięgam częściej po inne rozwiązania.