{"id":119,"date":"2026-10-22T11:24:57","date_gmt":"2026-10-22T11:24:57","guid":{"rendered":"https:\/\/dydlak.cloud\/blog\/?p=119"},"modified":"2026-10-22T11:24:58","modified_gmt":"2026-10-22T11:24:58","slug":"php2shell","status":"publish","type":"post","link":"https:\/\/dydlak.cloud\/blog\/php2shell\/","title":{"rendered":"PHP2Shell: dziura siedzi w PHP, nie w twojej stronie"},"content":{"rendered":"<p><em>21 pa\u017adziernika 2026 roku PHP dosta\u0142o poprawk\u0119 na dwie luki, kt\u00f3re pozwalaj\u0105 uruchomi\u0107 dowolny kod na serwerze bez logowania. Nie ma ich w WordPressie, w motywie ani w \u017cadnej wtyczce. Siedz\u0105 w samym PHP, czyli w warstwie pod stron\u0105, a to zmienia charakter zadania: to nie decyzja o rozwoju serwisu, tylko konserwacja z terminem.<\/em><\/p>\n<hr \/>\n<h2>Co og\u0142oszono 21 pa\u017adziernika<\/h2>\n<p>Dwie luki, CVE-2026-109618 i CVE-2026-109619, nazwane razem PHP2Shell. Obie maj\u0105 9,2 w CVSS w wersji 4.0. Tyle nada\u0142o im PHP w biuletynie, nie marketing producenta zabezpiecze\u0144. Poprawki wysz\u0142y w czterech wydaniach: 8.5.12, 8.4.27, 8.3.36 i 8.2.35.<\/p>\n<p>CERT-EU opisa\u0142o spraw\u0119 jako Security Advisory 2026-018 i poda\u0142o godzin\u0119 wydania, 13:30 UTC. CISA wyda\u0142a alert tego samego dnia i napisa\u0142a wprost, \u017ce do momentu publikacji nie zaobserwowa\u0142a wykorzystania. Cloudflare w swoim wpisie z 21 pa\u017adziernika pisze co\u015b innego: \u017ce widzia\u0142 ruch atakuj\u0105cy jego klient\u00f3w i \u017ce atakuj\u0105cy modyfikowali payload, \u017ceby obej\u015b\u0107 regu\u0142y. Jedno i drugie mo\u017ce by\u0107 prawd\u0105 naraz, bo \u201enie zaobserwowa\u0142a\u201d znaczy tyle, co \u201enie mamy potwierdzonego przypadku\u201d, a nie \u201enikt nie pr\u00f3bowa\u0142\u201d. Fundacja PHP dok\u0142ada trzeci fakt: w chwili wydania nie zna\u0142a dzia\u0142aj\u0105cego exploita, natomiast koncepcje ataku kr\u0105\u017cy\u0142y ju\u017c publicznie.<\/p>\n<p>Numery CVE wyciek\u0142y 20 pa\u017adziernika po po\u0142udniu, a PHP przyspieszy\u0142o wydanie o dob\u0119. Od wycieku do \u0142atki min\u0119\u0142o mniej ni\u017c dwadzie\u015bcia cztery godziny.<\/p>\n<p>Biuletyn wymienia PHP-FPM, Apache z mod_php, php-cgi, FrankenPHP i LiteSpeed. Najwy\u017csze ryzyko dotyczy mod_php, bo tam uda\u0142o si\u0119 pokaza\u0107 atak dziej\u0105cy si\u0119 przed uruchomieniem kodu aplikacji.<\/p>\n<h2>Na czym polega b\u0142\u0105d<\/h2>\n<p>PHP ogranicza pami\u0119\u0107 \u017c\u0105dania przez <code>memory_limit<\/code>. Kiedy limit zostaje przekroczony, PHP nie wraca z b\u0142\u0119dem po stosie wywo\u0142a\u0144, tylko przerywa wszystko mechanizmem <code>longjmp()<\/code>, czyli skokiem poza bie\u017c\u0105cy kontekst. To szybkie i sprawdzone rozwi\u0105zanie, dop\u00f3ki nie trafi w moment, w kt\u00f3rym jaka\u015b struktura danych jest zbudowana do po\u0142owy. Wtedy PHP porzuca prac\u0119 bez sprz\u0105tania, a chwil\u0119 p\u00f3\u017aniej uruchamia zwyk\u0142\u0105 procedur\u0119 zamykania \u017c\u0105dania, kt\u00f3ra zak\u0142ada, \u017ce wszystko jest sp\u00f3jne.<\/p>\n<p>CVE-2026-109618 to w\u0142a\u015bnie ta sytuacja: dane, kt\u00f3rych nikt nie zainicjalizowa\u0142, s\u0105 czytane jak poprawne. CVE-2026-109619 idzie o krok dalej. Stan nale\u017c\u0105cy do jednego \u017c\u0105dania zostaje w pami\u0119ci workera po zwolnieniu tej pami\u0119ci, a kolejne \u017c\u0105danie obs\u0142u\u017cone przez ten sam proces si\u0119ga po te bajty, zanim normalna inicjalizacja je nadpisze. Biuletyn wymienia konkretne \u015bcie\u017cki: stan b\u0142\u0119d\u00f3w, sesji, obiekt\u00f3w i rozszerze\u0144. Jedna z nich to obs\u0142uga post\u0119pu uploadu (<code>session.upload_progress<\/code>), w\u0142\u0105czona domy\u015blnie, bez potrzeby wywo\u0142ania <code>session_start()<\/code> w kodzie.<\/p>\n<p>Do pierwszego b\u0142\u0119du wystarczy \u017c\u0105danie multipart, kt\u00f3re na papierze mie\u015bci si\u0119 w limitach. Biuletyn podaje trzy sposoby: \u017c\u0105danie bez u\u017cytego <code>Content-Length<\/code> przechodzi wst\u0119pne sprawdzenie <code>post_max_size<\/code> i dalej dosy\u0142a tre\u015b\u0107; cz\u0119\u015b\u0107 multipart mo\u017ce mie\u0107 nieograniczon\u0105 liczb\u0119 nag\u0142\u00f3wk\u00f3w, kt\u00f3rych reprezentacja w pami\u0119ci jest wi\u0119ksza ni\u017c na \u0142\u0105czu; puste pola plikowe nie licz\u0105 si\u0119 do <code>max_file_uploads<\/code>, ale i tak tworz\u0105 tablice metadanych, a zagnie\u017cd\u017cone nazwy p\u00f3l mno\u017c\u0105 t\u0119 prac\u0119.<\/p>\n<h2>Dlaczego wtyczka tego nie za\u0142ata<\/h2>\n<p>I tu jest sedno: PHP parsuje \u017c\u0105danie przed inicjalizacj\u0105 rozszerze\u0144 i przed skompilowaniem pierwszej linii kodu aplikacji. Kiedy atak si\u0119 dzieje, WordPress jeszcze nie istnieje w pami\u0119ci procesu. Nie ma wtyczki, nie ma motywu, nie ma <code>functions.php<\/code>. Jest interpreter, kt\u00f3ry przetwarza dane wej\u015bciowe.<\/p>\n<p>Dlatego strona pisana przez bardzo dobrego programist\u0119 jest nara\u017cona dok\u0142adnie tak samo jak strona zaniedbana. Aktualizacja WordPressa, wtyczek i motywu, cho\u0107 zawsze sensowna, w tym przypadku nic nie zmienia.<\/p>\n<p>Jest jednak r\u00f3\u017cnica mi\u0119dzy konfiguracjami, bo wp\u0142ywa na to, co zobaczysz w logach. Apache z mod_php wywo\u0142uje procedur\u0119 zamykania \u017c\u0105dania nawet wtedy, gdy start \u017c\u0105dania si\u0119 nie powi\u00f3d\u0142, i to otwiera drog\u0119 opisan\u0105 w biuletynie jako potwierdzon\u0105. FPM ko\u0144czy proces potomny, gdy start \u017c\u0105dania zawiedzie, wi\u0119c ta konkretna \u015bcie\u017cka nie przechodzi. Nie znaczy to, \u017ce FPM jest bezpieczny: biuletyn opisuje potwierdzon\u0105 \u015bcie\u017ck\u0119 na FPM, w kt\u00f3rej stan publikowany jest w trakcie dzia\u0142ania aplikacji, a konsumentem jest obs\u0142uga post\u0119pu uploadu przy kolejnym \u017c\u0105daniu. FrankenPHP i LSAPI s\u0105 prawdopodobnie podatne, cho\u0107 bez potwierdzonego ataku.<\/p>\n<h2>Gdzie w tym jest twoja strona<\/h2>\n<p>W3Techs, w odczycie z 22 pa\u017adziernika 2026, podaje, \u017ce PHP u\u017cywa 69,3% stron, kt\u00f3rych j\u0119zyk po stronie serwera da si\u0119 rozpozna\u0107. Podzia\u0142 na wersje w\u015br\u00f3d stron na PHP wygl\u0105da tak: 8.x to 65,3%, 7.x to 26,9%, 5.x to 7,7%, 4.x to 0,1%.<\/p>\n<p>Arytmetyka, kt\u00f3r\u0105 mo\u017cesz powt\u00f3rzy\u0107: 26,9 + 7,7 + 0,1 = 34,7. Ponad jedna trzecia stron na PHP dzia\u0142a na ga\u0142\u0119zi, dla kt\u00f3rej projekt nie wyda ju\u017c \u0142atki. Dla 7.4, 8.0 i 8.1 zesp\u00f3\u0142 bezpiecze\u0144stwa opublikowa\u0142 minimalne backporty \u017ar\u00f3de\u0142, ale to kod do samodzielnego zbudowania, nie pakiet od hostingu.<\/p>\n<p>Druga rzecz, kt\u00f3rej w\u0142a\u015bciciele stron cz\u0119sto nie widz\u0105: PHP siedzi te\u017c w sklepach, w panelach hostingowych, w interfejsach urz\u0105dze\u0144 sieciowych i w oprogramowaniu, kt\u00f3re dostarcza firma zewn\u0119trzna razem ze sprz\u0119tem. CERT-EU odsy\u0142a w tym miejscu do repozytorium NCSC-NL, kt\u00f3re zbiera produkty z wbudowanym PHP. Je\u015bli w firmie jest kamerka z panelem albo stare urz\u0105dzenie do backupu, to te\u017c jest PHP i te\u017c ma wersj\u0119.<\/p>\n<h2>Jak wygl\u0105da \u0142atka od strony serwera<\/h2>\n<p>Najcz\u0119stszy b\u0142\u0105d przy sprawdzaniu: <code>php -v<\/code> w SSH pokazuje wersj\u0119 interpretera linii polece\u0144, a strona odpowiada przez inny pakiet, zwykle <code>php-fpm<\/code> albo modu\u0142 Apache. Sprawdza\u0107 trzeba to, co realnie obs\u0142uguje \u017c\u0105dania HTTP: przez <code>phpinfo()<\/code>, panel hostingu albo polecenie, kt\u00f3re pyta us\u0142ug\u0119, a nie pow\u0142ok\u0119.<\/p>\n<p>Sama podmiana pakietu nie wystarczy bez restartu. Dzia\u0142aj\u0105ce procesy robocze trzymaj\u0105 w pami\u0119ci stary kod i b\u0119d\u0105 go wykonywa\u0107 do ko\u0144ca \u017cycia procesu, wi\u0119c po aktualizacji restartuje si\u0119 FPM albo Apache. W kontenerach podmiana pakietu wewn\u0105trz dzia\u0142aj\u0105cego kontenera jest obej\u015bciem, poprawna droga to nowy obraz bazowy i nowe kontenery.<\/p>\n<h2>Ile to zajmuje i ile kosztuje<\/h2>\n<p>Liczby w tym akapicie s\u0105 przyk\u0142adem, nie benchmarkiem. Jedna strona: sprawdzenie wersji, zrzut, aktualizacja pakietu, restart, test formularza kontaktowego. Przy wprawie dwadzie\u015bcia pi\u0119\u0107 minut. Osiemna\u015bcie stron, czyli portfel ma\u0142ej agencji: 18 \u00d7 25 = 450 minut, czyli 7,5 godziny. Jeden dzie\u0144 pracy jednej osoby, kt\u00f3r\u0105 trzeba mie\u0107.<\/p>\n<p>Koszt zaniechania trudniej policzy\u0107, wi\u0119c policzmy jedn\u0105 jego cz\u0119\u015b\u0107. Sklep, osiem godzin niedost\u0119pno\u015bci, trzy zam\u00f3wienia na godzin\u0119, \u015brednia warto\u015b\u0107 zam\u00f3wienia 240 z\u0142otych: 8 \u00d7 3 \u00d7 240 = 5760 z\u0142otych. To strata widoczna w raporcie. Niewidoczna jest inna: je\u015bli atakuj\u0105cy dostanie pow\u0142ok\u0119 z uprawnieniami u\u017cytkownika serwera WWW, ma dost\u0119p do pliku konfiguracyjnego z danymi bazy, do katalogu z kopiami zapasowymi, je\u015bli kto\u015b je tam zostawi\u0142, i do wszystkiego, co aplikacja mo\u017ce przeczyta\u0107.<\/p>\n<p>Nie napisz\u0119, \u017ce twoje dane wyciek\u0142y, bo tego nie wiem i nie wiem tego bez twoich log\u00f3w. Wiem, co si\u0119 robi po takim zdarzeniu i w jakiej kolejno\u015bci, a to cz\u0119\u015b\u0107, kt\u00f3r\u0105 mo\u017cna zaplanowa\u0107 zawczasu: odci\u0119cie serwera od sieci z zachowaniem kopii, zabezpieczenie log\u00f3w serwera WWW i PHP, przegl\u0105d katalogu pod k\u0105tem nowych plik\u00f3w, przegl\u0105d zada\u0144 cron i kont, zmiana wszystkich po\u015bwiadcze\u0144, kt\u00f3re le\u017ca\u0142y na tej maszynie.<\/p>\n<h2>Co zrobi\u0107, je\u015bli nie mo\u017cesz za\u0142ata\u0107 dzi\u015b<\/h2>\n<p>Zacznij od tego, co biuletyn nazywa \u015brodkami tymczasowymi, i przyjmij do wiadomo\u015bci, \u017ce \u017caden z nich nie jest rozwi\u0105zaniem. Dla CVE-2026-109618 pierwszy krok to <code>memory_limit=-1<\/code> ustawione tak, \u017ceby aplikacja nie mog\u0142a go nadpisa\u0107, przez <code>php_admin_value<\/code> w puli FPM albo w konfiguracji Apache, z restartem worker\u00f3w i sprawdzeniem, \u017ce \u017cadna pula ani katalog nie przywraca sko\u0144czonego limitu. Do tego <code>post_max_size<\/code> 8M, je\u015bli nie ma upload\u00f3w, 32M, je\u015bli s\u0105, z <code>upload_max_filesize<\/code> ustawionym ni\u017cej. Na froncie dorzu\u0107 limity liczby p\u00f3l, cz\u0119\u015bci multipart, nag\u0142\u00f3wk\u00f3w i g\u0142\u0119boko\u015bci zagnie\u017cd\u017cenia, tak\u017ce dla \u017c\u0105da\u0144 bez <code>Content-Length<\/code>. Kontener albo cgroup jako granica dost\u0119pno\u015bci, ale nie <code>RLIMIT_AS<\/code> ani <code>RLIMIT_DATA<\/code>, bo te wywo\u0142uj\u0105 dok\u0142adnie ten b\u0142\u0105d alokacji, kt\u00f3rego szukasz.<\/p>\n<p>Dla CVE-2026-109619 wy\u0142\u0105cz <code>session.upload_progress.enabled<\/code> w <code>php.ini<\/code>, w puli FPM albo w konfiguracji serwera, czyli tam, gdzie zadzia\u0142a przed parsowaniem \u017c\u0105dania, a nie przez <code>ini_set()<\/code>. Na PHP 8.5 do\u0142\u00f3\u017c <code>fatal_error_backtraces=0<\/code>. Biuletyn m\u00f3wi wprost, \u017ce dla tej luki nie ma kompletnego \u015brodka konfiguracyjnego, bo stan mo\u017ce publikowa\u0107 aplikacja, rozszerzenie albo serwer, i nie zostawi po tym przewidywalnego wpisu w logu.<\/p>\n<p>Jest te\u017c koszt, o kt\u00f3rym trzeba powiedzie\u0107 g\u0142o\u015bno: <code>memory_limit=-1<\/code> przenosi problem z bezpiecze\u0144stwa na dost\u0119pno\u015b\u0107. Skrypt, kt\u00f3ry wcze\u015bniej pada\u0142 po wyczerpaniu limitu, teraz zje pami\u0119\u0107 maszyny. To trade-off, nie magiczne rozwi\u0105zanie.<\/p>\n<h2>Czego to nie rozwi\u0105zuje<\/h2>\n<p>Firewall aplikacyjny nie \u0142ata PHP. Cloudflare wdro\u017cy\u0142 ochron\u0119 21 pa\u017adziernika od 03:02 UTC, dla wszystkich plan\u00f3w z WAF, tak\u017ce darmowego, i przyznaje, \u017ce dane \u017c\u0105dania nie wystarcza\u0142y do odr\u00f3\u017cnienia ataku od normalnego ruchu, dlatego powsta\u0142a funkcja \u0142\u0105cz\u0105ca sygna\u0142y z \u017c\u0105dania i z odpowiedzi serwera \u017ar\u00f3d\u0142owego. Kto ma domen\u0119 za Cloudflarem, z regu\u0142ami w trybie blokowania, ten ma ochron\u0119. Kto trzyma tam tylko DNS, bez proxy, ten nie ma nic, bo ruch nie przechodzi przez ich sie\u0107.<\/p>\n<p>Monitoring nie cofa w\u0142amania. \u0141atka nie usuwa tego, co ju\u017c le\u017cy na dysku. Backport na ga\u0142\u0105\u017a po ko\u0144cu wsparcia jest obej\u015bciem, kt\u00f3re trzeba b\u0119dzie powt\u00f3rzy\u0107 przy nast\u0119pnej luce, a przy okazji zwykle okazuje si\u0119, \u017ce nikt nie pami\u0119ta, jak si\u0119 go budowa\u0142o.<\/p>\n<p>Jest jeszcze kwestia, kt\u00f3r\u0105 Fundacja PHP opisa\u0142a sama, bez wyg\u0142adzania. Pierwsze oznaki problemu z pami\u0119ci\u0105 znalaz\u0142 programista PHP w 2024 roku, przy naprawianiu innego zg\u0142oszenia z audytu Quarkslab. Zesp\u00f3\u0142 bezpiecze\u0144stwa przez wiele miesi\u0119cy nie umia\u0142 wybra\u0107 mi\u0119dzy rozwi\u0105zaniami, bo ka\u017cde \u0142ama\u0142o co\u015b innego, a decyzje podejmowano konsensusem. Fundacja nazywa to pora\u017ck\u0105 strukturaln\u0105. Wersja, kt\u00f3r\u0105 masz na serwerze, powsta\u0142a w projekcie, w kt\u00f3rym naprawa powa\u017cnego b\u0142\u0119du potrafi czeka\u0107 dwa lata. Argumentem za zmian\u0105 technologii to nie jest. Argumentem za w\u0142asnym terminem reakcji jest.<\/p>\n<h2>Lista kontrolna na dzi\u015b<\/h2>\n<ol>\n<li>Sprawd\u017a wersj\u0119 PHP, kt\u00f3r\u0105 widzi serwer WWW, nie t\u0119 z SSH.<\/li>\n<li>Je\u015bli twoja ga\u0142\u0105\u017a to 8.2, 8.3, 8.4 albo 8.5 i wersja jest ni\u017csza ni\u017c 8.2.35, 8.3.36, 8.4.27 albo 8.5.12, za\u0142atuj.<\/li>\n<li>Zrestartuj workery FPM albo Apache po aktualizacji.<\/li>\n<li>Kontenery przebuduj z nowego obrazu bazowego, nie restartuj starych.<\/li>\n<li>Na 8.1 i starszych podejmij decyzj\u0119: migracja albo izolacja od internetu, plus backport, je\u015bli budujesz ze \u017ar\u00f3de\u0142.<\/li>\n<li>Sprawd\u017a, czy domena jest proxowana przez WAF i czy regu\u0142y s\u0105 w trybie blokowania, a nie tylko logowania.<\/li>\n<li>Zapisz, kto to zrobi\u0142, kiedy i na jakiej wersji sko\u0144czy\u0142, bo nast\u0119pny biuletyn przyjdzie i ta kartka b\u0119dzie jedyn\u0105 odpowiedzi\u0105 na pytanie \u201ejak by\u0142o ostatnio\u201d.<\/li>\n<\/ol>\n<p>Si\u00f3dma pozycja jest zwykle t\u0105, kt\u00f3rej nikt nie robi, bo po restarcie wszystko dzia\u0142a i temat znika z g\u0142owy do nast\u0119pnego razu. Ca\u0142a reszta zale\u017cy od osoby, kt\u00f3ra ma w obowi\u0105zkach czytanie biuletynu, sprawdzenie wersji na ka\u017cdym serwerze i wykonanie tego w oknie doby, a nie wtedy, gdy znajdzie si\u0119 chwila. W <a href=\"https:\/\/dydlak.cloud\">dydlak.cloud<\/a> robi\u0119 dok\u0142adnie to: monitoring wersji na stronach, kt\u00f3rych pilnuj\u0119, wdro\u017cenie \u0142atki w ci\u0105gu doby od publikacji biuletynu i raport dla w\u0142a\u015bciciela firmy napisany tak, \u017ceby da\u0142 si\u0119 przeczyta\u0107 bez s\u0142ownika. Je\u015bli u ciebie ta osoba nie ma imienia, mog\u0119 ni\u0105 by\u0107.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>PHP2Shell to dwie luki w samym PHP, nie w kodzie strony. Wyja\u015bniam, czemu wtyczka tego nie za\u0142ata, jak sprawdzi\u0107 wersj\u0119 na serwerze i co zrobi\u0107 w ci\u0105gu doby.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"wpai_meta_description":"Luki CVE-2026-109618 i CVE-2026-109619 siedz\u0105 w PHP, nie w kodzie strony. Jak sprawdzi\u0107 wersj\u0119, za\u0142ata\u0107 j\u0105 i zamkn\u0105\u0107 temat w ci\u0105gu doby.","footnotes":""},"categories":[28],"tags":[32,26,23],"class_list":["post-119","post","type-post","status-publish","format-standard","hentry","category-strony-internetowe","tag-bezpieczenstwo","tag-male-firmy","tag-strona-internetowa"],"_links":{"self":[{"href":"https:\/\/dydlak.cloud\/blog\/wp-json\/wp\/v2\/posts\/119","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dydlak.cloud\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dydlak.cloud\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dydlak.cloud\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dydlak.cloud\/blog\/wp-json\/wp\/v2\/comments?post=119"}],"version-history":[{"count":3,"href":"https:\/\/dydlak.cloud\/blog\/wp-json\/wp\/v2\/posts\/119\/revisions"}],"predecessor-version":[{"id":122,"href":"https:\/\/dydlak.cloud\/blog\/wp-json\/wp\/v2\/posts\/119\/revisions\/122"}],"wp:attachment":[{"href":"https:\/\/dydlak.cloud\/blog\/wp-json\/wp\/v2\/media?parent=119"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dydlak.cloud\/blog\/wp-json\/wp\/v2\/categories?post=119"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dydlak.cloud\/blog\/wp-json\/wp\/v2\/tags?post=119"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}