PHP opravil dvě kritické chyby jádra zneužitelné ke spuštění kódu bez přihlášení
Bezpečnostní tým PHP vydal 21. října 2026 opravné verze 8.2.35, 8.3.36, 8.4.27 a 8.5.12. Zavírají dvě chyby jádra jazyka, CVE-2026-109618 a CVE-2026-109619, obě se známkou 9,2. Vznikají tím, jak PHP uklízí paměť po přerušeném požadavku, a za určitých podmínek vedou ke spuštění kódu bez přihlášení.

Bezpečnostní tým PHP zveřejnil 21. října 2026 dvě chyby jádra jazyka: CVE-2026-109618 a CVE-2026-109619. Opravené jsou ve verzích 8.2.35, 8.3.36, 8.4.27 a 8.5.12, které vyšly týž den. Obě chyby dostaly podle CVSS 4.0 známku 9,2, tedy kritickou. Vektor té známky ale uvádí vysokou složitost útoku (AC:H) a podmínku na straně cíle (AT:P), takže nejde o jediný požadavek, který zabere na každém serveru. Jméno PHP2Shell nese exploit firmy Wiz, jejíž výzkumníci jsou mezi nálezci; v rozboru uvádí, že na objevu, koordinaci a ověření opravy pracovala i firma Calif a OpenAI.
Obě chyby mají společný kořen. Když PHP při zpracování požadavku narazí na fatální chybu, nejčastěji na vyčerpání paměti, nevrací se z rozdělané práce postupně, ale vyskočí z ní rovnou ven. Co v tu chvíli zůstalo rozpracované, pak interpret bere za hotové.
Když dojde paměť v nevhodné chvíli
První chybu php.net popisuje jako „RCE via OOM corruption“ a celé znění je v hlášení GHSA-w8hw-w9fx-j79w. PHP ukončuje požadavek při nedostatku paměti (OOM, out of memory) voláním longjmp(). V některých místech je paměť požadavku už dosažitelná, a přitom ještě není celá připravená. Když zrovna tehdy selže kterákoli alokace, nemusí s těmi daty vůbec souviset, zůstanou data v neplatném stavu. Úklid na konci požadavku je pak přečte nebo se podle nich zařídí.
Aby se k tomu stavu dalo dojít, musí paměť dojít a požadavek přitom projít limity, které PHP hlídá. Hlášení popisuje tři cesty, všechny při rozboru formuláře typu multipart: požadavek bez použitelně udané délky těla projde úvodní kontrolou post_max_size a dál posílá data; jedna část formuláře může nést neomezený počet hlaviček, které v paměti PHP zaberou víc než na drátě a vyčerpají memory_limit i v mezích post_max_size; prázdná pole pro soubor se nepočítají do max_file_uploads, ale vytvoří běžná pole s metadaty a vnořená jména tu práci násobí.
Záleží na tom, jak PHP běží. Apache s modulem mod_php volá úklid požadavku vždy, i když start požadavku selhal. Formulář se rozebírá už při startu, takže celá věc proběhne dřív, než se spustí kód aplikace. Proti mod_php hlášení potvrzuje spuštění kódu přes HTTP s výchozími limity a běžnou konfigurací prefork s více procesy. PHP-FPM při selhání startu podřízený proces ukončí, takže neplatný stav nikdo nepřečte; po úspěšném startu ale paměť může dojít i jinde. Rozbor zdrojového kódu podle hlášení ukazuje FPM jako zranitelné, hotový útok proti němu ale nikdo nepředvedl. U FrankenPHP rozbor naznačuje stejné chování jako u mod_php, u LSAPI jako u FPM, obojí bez ověření útokem.
Ukazatel z minulého požadavku přežije do dalšího
Druhá chyba, „RCE via stale global state“ v hlášení GHSA-6wmf-4rv5-p3rp, je obecnější. Proces PHP si mezi požadavky drží globální stav – stav chyb, session, objektů a rozšíření – a ten občas ukazuje do paměti jednoho konkrétního požadavku. Před uvolněním té paměti se má stav vynulovat. Když požadavek přeruší fatální chyba, nulování se nestihne, paměť se uvolní a globální ukazatel pak míří do prázdna. Stav se umí poškodit i obráceně: nezávažné varování z úklidu znovu zapíše informaci o chybě až poté, co ji nulování smazalo.
K potížím jsou potřeba dva kroky ve dvou požadavcích, které obslouží týž proces. V prvním zůstane stav ležet, například po vyčerpání paměti, po chybě při překladu, po nezachycené výjimce nebo po překročení max_execution_time. Druhý ten stav použije dřív, než ho běžná inicializace stihne přepsat. Hlášení jmenuje dvě taková místa. Jedno je varování z konfigurace: u mod_php se nastavení z .htaccess a direktivy php_value uplatní ještě před php_request_startup(), takže stačí zastaralá nebo neplatná volba, která vyvolá varování; to se nemusí nikam vypsat ani zalogovat a skrytí přes error_reporting, display_errors ani zavináč nepomůže. Druhé je průběh nahrávání (session.upload_progress), který sáhne na session dřív, než se připraví obvyklým způsobem. U FPM je první cesta dostupná jen tehdy, když frontend posílá parametry PHP_VALUE nebo PHP_ADMIN_VALUE, které vyvolají varování před inicializací.
Na PHP 8.5 je navíc potvrzené místo kolem výpisu zásobníku u fatální chyby: s fatal_error_backtraces se backtrace poslední fatální chyby uloží a nulování se při úklidu vždy mine. Podle php.net je to nejsilnější předvedený případ a stojí na něm potvrzené spuštění kódu proti mod_php.
Co oprava mění
Změnu nesou záznamy v changelogu všech čtyř větví jedinou položkou: „Fixed GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp (Avoid state corruption via bailout).“ U první chyby oprava podle hlášení ruší longjmp() při OOM. Místo okamžitého výskoku přijde měkké ukončení v nejbližším bezpečném bodě virtuálního stroje a tvrdé ukončení přes _exit(), až když spotřeba paměti dosáhne druhého, vyššího prahu. Úklid tak už neuvidí neplatný stav a PHP si zároveň udrží schopnost omezovat paměť. U druhé chyby oprava doplňuje nulování širšího stavu, aby nic nepřežilo přes hranici požadavku do operací, které běží před běžnou inicializací. Hlášení k druhé chybě dodává, že úplnou jistotu dává jen aktualizace a obecné nastavení, které by pokrylo všechny cesty, neexistuje.
Jedna drobnost mate: novinka na php.net nese datum 21. října 2026, kdežto v changelogu všech čtyř větví stojí u opravných verzí 22. října. Držíme se data z oznámení, tedy 21. října.
Rozsah je podle registru CVE jednotný: dotčené jsou větve 8.2 před 8.2.35, 8.3 před 8.3.36, 8.4 před 8.4.27 a 8.5 před 8.5.12. Záznamy spravuje jako CNA přímo projekt PHP. Firma Wiz ve svém rozboru píše o zranitelnosti „každé verze PHP vydané za poslední desetiletí, zpět až po 5.4“; to je širší tvrzení než rozsah v registru, který se drží dnes udržovaných větví. Pro starší, už nepodporované větve 7.4, 8.0 a 8.1 vydal projekt podle oznámení jen zdrojové opravy, bez hotových balíčků; verze 5.x a starší oprava nečeká.
Rozpor v číslování a otazník nad úplností opravy
Na dvou místech se prameny rozcházejí a stojí za to to napsat. Zaprvé: php.net i obě hlášení vážou CVE-2026-109618 na chybu kolem OOM a CVE-2026-109619 na přežívající stav včetně průběhu nahrávání a výpisu zásobníku. Tabulka v rozboru firmy Wiz přiřazuje čísla jinak – pod 109618 vede chybu v průběhu nahrávání a čtení paměti procesu, pod 109619 výpis zásobníku a záměnu typů v OPcache. Čísla CVE spravuje projekt PHP, takže v článku se držíme jeho přiřazení; rozdíl nejspíš plyne z toho, že obě strany seskupily víc dílčích nálezů pod dvě čísla jinak. Zadruhé: na konferenci oss-security někdo upozornil, že podle zpráv z Fediverse nemusí být oprava v 8.5.12 úplná. Autorka těch příspěvků sama píše, že nejspíš jde o jinou chybu, ne o tutéž. Potvrzené to zatím není.
Jak zjistit, že server opravdu běží na opravené verzi
Instalace balíčku sama o sobě nic neznamená, dokud běžící proces drží starou knihovnu. Po aktualizaci se musí restartovat PHP-FPM, případně Apache s mod_php, a teprve pak php -v (a hlavně výstup běžícího webu, například přes phpinfo() na dočasné stránce) ukáže opravené číslo. Na Linuxu lze doplnit kontrolu, že žádný proces nedrží smazanou starou knihovnu: grep -l "(deleted)" /proc/*/maps vypíše procesy, které běží nad souborem, jenž už na disku neexistuje – typický příznak toho, že se balíček vyměnil, ale služba se nerestartovala. Podle manuálové stránky proc_pid_maps(5) jádro právě takto značí mapování nad smazaným souborem.
Dočasná ochrana riziko sníží, opravu nenahradí
Kdo nemůže aktualizovat hned, má v hlášeních několik opatření. Jsou to zmírnění, ne opravy. U první chyby php.net radí nastavit a administrativně vynutit memory_limit=-1 (přes php_admin_value ve fondu FPM nebo v Apachi) a ověřit, že žádný fond, adresář ani aplikace nevrací konečný limit zpět; k tomu držet nízký post_max_size a omezit velikost a složitost požadavku na frontendu. Varuje přitom před RLIMIT_AS a RLIMIT_DATA, které samy spustí tu zranitelnou cestu. U druhé chyby radí vypnout průběh nahrávání (session.upload_progress.enabled=0) a restartovat všechny procesy, na PHP 8.5 k tomu nastavit fatal_error_backtraces=0. Žádné z toho nekryje všechny cesty a hlášení k druhé chybě výslovně píše, že spolehlivá je jedině aktualizace.
Na postižených serverech stojí za pozornost signály, které rozbor firmy Wiz jmenuje jako stopu po pokusu: neobvyklé požadavky typu multipart následované chybami o nedostatku paměti nebo opakovanými odpověďmi HTTP 500, nezvykle velké nebo poškozené cookie PHPSESSID a nevysvětlené střídání procesů. Samy o sobě nic nedokazují, ale dávají se podle nich hledat.
Jak PHP2Shell vznikl
Podle časové osy v rozboru firmy Wiz objevili výzkumníci první chybu 19. srpna 2026, spuštění kódu bez přihlášení předvedli proti Drupalu 20. srpna a proti WordPressu 21. srpna, projektu PHP to nahlásili 24. srpna. Projekt podle téhož zdroje reporty potvrdil 21. září a začal připravovat společnou opravu, zveřejnění přišlo 21. října. Firma u výzkumu uvádí vlastní nástroj a dva jazykové modely a tvrdí, že právě ony posunuly dávno existující slabinu z „těžko zneužitelné“ na prakticky zneužitelnou; na posouzení té teze nemáme nezávislý zdroj a bereme ji jako tvrzení nálezce. Záznam na konferenci oss-security podepsaný členem bezpečnostního týmu PHP potvrzuje jen to podstatné: dvě kritické chyby, známku 9,2, rozsah verzí a větu, že spolehlivá je jen aktualizace.
Prameny: oznámení a changelog na php.net, obě bezpečnostní hlášení v repozitáři php-src, záznamy CVE-2026-109618 a CVE-2026-109619, oznámení na konferenci oss-security a rozbor firmy Wiz.