V októbri 2026 bola zverejnená kritická zraniteľnosť PHP s označením CVE-2026-109618 a CVE-2026-109619. Neautentifikovaný útočník mohol cez upravenú HTTP požiadavku spustiť na serveri vlastný kód. Bezpečnostná aktualizácia PHP vyšla 21. októbra a v ten istý deň som opravil servery, ktoré spravujem: weby verejnej správy aj komerčné WordPress weby.
Opíšem, ako som postupoval od inventúry po stanovisko pre zadávateľa. Mená klientov, domény ani interné detaily neuvádzam. Postup je však reálny a väčšina krokov sa dá použiť na každom serveri s WordPressom, Drupalom alebo inou PHP aplikáciou.
Čo presne bolo zraniteľné
Chyba bola v jadre PHP, konkrétne v spracovaní multipart požiadaviek (formuláre a nahrávanie súborov) a v module session. Hodnotenie CVSS 9,2 a podľa upozornenia SK-CERT sa týkala všetkých verzií PHP od 5.0. Opravené vydania:
| Vetva PHP | Opravená verzia | Stav podpory |
|---|---|---|
| 8.5 | 8.5.12 | podporovaná |
| 8.4 | 8.4.27 | podporovaná |
| 8.3 | 8.3.36 | bezpečnostné opravy |
| 8.2 | 8.2.35 | bezpečnostné opravy |
| 7.4 a staršie | bez vydania, iba zdrojový backport pre 7.4 | nepodporované |
Pre WordPress je dôležité, že aktualizácia WordPressu ani pluginov zraniteľnosť neopraví. Problém nebol v aplikácii, ale v interprete, na ktorom aplikácia beží. Útok nepotreboval prihlásenie ani konkrétny plugin. Stačilo, aby web prijímal požiadavky, a to robí každý web.
Krok 1: inventúra, kde všade beží PHP
Prvá chyba, ktorú pri takýchto incidentoch vídam, je kontrola iba hlavného webu. PHP však býva aj na miestach, na ktoré si nikto nespomenie: v administrácii nasadzovacej platformy, v phpMyAdmine, v starom archíve webu, ktorý „len tak beží“, alebo v testovacej inštancii.
Moje servery bežia na Dockeri a spravujem ich cez Coolify. Inventúru som preto urobil priamo v bežiacich kontajneroch. Na každom serveri som prešiel všetky kontajnery a v tých, kde PHP existuje, som zistil jeho skutočnú verziu:
for c in $(docker ps --format '{{.Names}}'); do
v=$(docker exec "$c" php -r 'echo PHP_VERSION;' 2>/dev/null)
[ -n "$v" ] && echo "$c $v"
done Verziu v názve image (napríklad wordpress:php8.3-apache) som nebral ako dôkaz. Tag nehovorí, ktorá podverzia v kontajneri naozaj beží, a obraz mohol byť zostavený pred mesiacmi. Rovnako som skontroloval aj samotný hostiteľský systém, či na ňom PHP nie je nainštalované mimo Dockera.
Výsledok bol typický. Väčšina webov bežala na PHP 8.2 alebo 8.3, administrácia Coolify na PHP 8.4. Okrem toho sa našli dva problémové komponenty: produkčný web na PHP 7.4, ktorý sa nedá jednoducho aktualizovať, a starý archív na Drupale s PHP 5.6. Zraniteľné bolo všetko.
Krok 2: aktualizácia PHP tam, kde to ide
Pri moderných weboch bola oprava priamočiara. Oficiálne Docker image wordpress a php dostali opravené verzie v priebehu dňa, takže stačilo kontajnery znovu zostaviť z aktuálneho základného obrazu. Pred každým zásahom som skontroloval, že v Coolify nebeží iné nasadenie, a až potom spustil nasadenie s vynúteným prebudovaním (force rebuild). Bez neho by Docker mohol použiť starú vrstvu z cache.
- Weby na PHP 8.3: prebudované na 8.3.36.
- Web na PHP 8.2: nový kontajner na 8.2.35.
- phpMyAdmin a ďalšie pomocné kontajnery: aktualizované na opravený obraz.
- Coolify: aktualizované na verziu s PHP 8.4.27. Na administráciu sa ľahko zabudne, pritom má najvyššie oprávnenia na celom serveri.
- Projekty s pripnutým digestom image: ak má Dockerfile obraz pripnutý na konkrétny hash, nový obraz sa nestiahne sám. Digest treba v repozitári zmeniť a commitnúť.
Po každom nasadení som v kontajneri znovu overil verziu PHP a skontroloval, že web vracia HTTP 200 a v logoch nie sú fatálne chyby.
Nechcete pri ďalšej zraniteľnosti riešiť servery sami?
V rámci správy webu sledujem bezpečnostné upozornenia, nasadzujem opravy a overujem, že web po nich funguje.
Krok 3: starý web na PHP 7.4, ktorý sa nedá aktualizovať
Najťažšia časť. Jeden produkčný web bežal na PHP 7.4 so staršou témou a pluginmi, ktoré na PHP 8 nikto neotestoval. Prechod na novú verziu by v ten deň znamenal riziko výpadku verejného webu. Zároveň PHP 7.4 je od novembra 2022 bez podpory a opravená verzia preň nevyšla.
Postupoval som v dvoch fázach.
Dočasné zmiernenie hneď
Kým som pripravoval skutočnú opravu, zmenšil som útočnú plochu nastaveniami v Apache cez php_admin_flag a php_admin_value. Tieto hodnoty nemôže prepísať ani .htaccess, ani ini_set() v kóde aplikácie:
<IfModule php7_module>
php_admin_flag session.upload_progress.enabled off
php_admin_value post_max_size 32M
php_admin_value upload_max_filesize 30M
</IfModule> Pamäť procesu som obmedzil na úrovni kontajnera (docker update --memory), nie cez memory_limit. Testovacím skriptom som overil, že aplikácia nastavenia naozaj nedokáže zmeniť.
Oprava jadra PHP z oficiálneho backportu
Projekt PHP spolu s opravou zverejnil v repozitári php-src aj bezpečnostnú vetvu pre 7.4. Obsahuje iba dve zmeny nad poslednou verziou 7.4.33 a dotýka sa jadra Zend, spracovania multipart požiadaviek a modulu session. Z nej som v samostatnom Docker builde zostavil nový interpret php a modul Apache libphp7.so.
- Rovnaké prostredie: build bežal na rovnakej verzii Debianu ako produkčný kontajner. Rozšírenia PHP sa nemenili, pretože API rozšírení zostáva rovnaké. Vymenil som iba dva binárne súbory.
- Debian bez podpory: repozitár bezpečnostných aktualizácií pre starý Debian už vracal 404, takže závislosti pre build bolo treba brať z
archive.debian.org. - Test na kópii: opravený obraz som spustil vedľa produkcie nad kópiou dát webu. Porovnal som titulnú stránku, podstránky, vyhľadávanie, prihlásenie a REST API. Stavové kódy aj veľkosti odpovedí sa zhodovali s produkciou.
- Nasadenie s možnosťou návratu: pôvodné binárky som zálohoval, nové som nakopíroval do kontajnera a webový server reštartoval. Kontajner som zámerne nevytváral nanovo: kód webu ležal v anonymnom Docker volume a nové vytvorenie by web nahradilo prázdnou inštaláciou.
Výsledkom je PHP 7.4 s opravou zraniteľnosti, bez zmeny správania webu. Treba však povedať otvorene: ide o záplatu, nie o riešenie. PHP 7.4 nebude dostávať opravy ďalších zraniteľností, a preto je prechod na podporovanú verziu stále najdôležitejšou úlohou do budúcna. Ak riešite podobný web, pozrite si aj článok WordPress po zmene PHP nefunguje.
Krok 4: čo sa opraviť nedá, treba vypnúť
Archív starého webu na Drupale s PHP 5.6 a nepodporovanou databázou sa opraviť nedal. Backport pre takú starú vetvu neexistuje a náklady na migráciu neboli v ten deň na mieste. Po dohode s klientom som kontajner zastavil a vypol jeho automatický štart. Dáta zostali zachované pre prípadnú migráciu. Nedostupný archív je menší problém než verejne dostupný server so známou kritickou zraniteľnosťou.
Krok 5: sprísnenie siete
Pri inventúre som našiel aj porty, ktoré nemali byť dostupné z internetu: priamy prístup k administrácii, realtime služby nasadzovacej platformy, databázu na nestandardnom porte. S PHP zraniteľnosťou priamo nesúvisia, no zväčšujú útočnú plochu.
Pozor na jednu vec: UFW porty publikované Dockerom nefiltruje, pretože Docker si zapisuje vlastné pravidlá do iptables. Pravidlá preto patria do reťazca DOCKER-USER. Aby prežili reštart, spúšťa ich malá systemd služba naviazaná na Docker. Prístup ostal povolený iba z internej siete a zo správcovského servera.
Krok 6: kontrola, či niekto zraniteľnosť nezneužil
Medzi zverejnením informácií o zraniteľnosti a nasadením opravy uplynulo niekoľko hodín. Oprava zastaví budúce útoky, ale neodstráni následky tých, ktoré by už prebehli. Pri každom WordPress webe som preto preveril:
- prístupové logy od dňa pred zverejnením, najmä POST požiadavky, chyby 5xx a pády procesov,
- používateľov v databáze: nové účty, zmeny administrátorov, zapnutú registráciu a predvolenú rolu,
- súbory PHP v adresári uploads a súbory PHP zmenené od dátumu zverejnenia,
- súbory šablóny porovnané cez kontrolný súčet s verziou v gite,
- zmeny obsahu: kto a kedy upravoval stránky a či obsah neobsahuje podozrivý kód.
Pri zmenách treba hľadať vysvetlenie, nie len počítať. Napríklad desiatka článkov zmenená naraz v jednej sekunde vyzerala na prvý pohľad podozrivo. Ukázalo sa, že išlo o plánovanú synchronizáciu článkov z externého zdroja, ktorú spúšťa téma. Ani na jednom webe sa známky zneužitia nenašli.
Krok 7: stanovisko pre zadávateľa a SK-CERT
Organizácie verejnej správy po upozornení SK-CERT žiadajú od prevádzkovateľov formálne stanovisko. Napísal som ho podľa štyroch bodov, ktoré sa v takých žiadostiach opakujú:
- Využívanie PHP: zoznam všetkých komponentov s PHP vrátane verzií pred a po oprave.
- Posúdenie zraniteľnosti: ktoré komponenty boli zasiahnuté a s akým rizikom.
- Prijaté opatrenia: čo sa urobilo pri každom komponente a kedy, vrátane plánovaných krokov s termínom.
- Potvrdenie a overenie účinnosti: ako bola oprava overená, výsledok kontroly zneužitia a zostatkové riziko.
V stanovisku som nič nezakrýval. Ak niečo beží na nepodporovanej verzii, patrí to do časti o zostatkovom riziku spolu s plánom nápravy. Úprimné stanovisko s plánom je pre bezpečnostný tím užitočnejšie než tvrdenie, že je všetko v poriadku.
Ako som pri tom využil AI agenta
Väčšinu práce v termináli robil AI agent pod mojím dohľadom: prechádzal kontajnery na serveroch, porovnával verzie, pripravil Dockerfile pre build PHP 7.4, spúšťal testy a kontrolné dotazy do databázy. O tom, čo sa vypne, čo sa nesmie aktualizovať, kedy reštartovať server a čo pôjde do stanoviska, som rozhodoval ja. Reštart servera po aktualizácii jadra som si nechal výslovne pre seba. Agent zrýchľuje rutinu, zodpovednosť za produkciu však zostáva na správcovi. Viac o tomto spôsobe práce píšem v článku Codex CLI pre začiatočníkov.
Čo si z incidentu odniesť
- Majte inventúru dopredu. Kto vie, kde všade beží PHP, opraví servery za hodiny, nie za dni.
- Staré verzie PHP sú najdrahšie práve v deň incidentu. Moderné weby stačilo prebudovať, starý web zabral väčšinu času.
- Pripravte si cestu návratu. Zálohy a overený postup obnovy dávajú istotu zasiahnuť rýchlo.
- Nepoužívané služby vypnite. Zabudnutý archív je ideálny vstup pre útočníka.
- Bezpečnosť nie je jednorazová úloha. Pravidelné aktualizácie, monitoring a správne nastavený hosting znižujú počet takých nocí.
Časté otázky
Týka sa zraniteľnosť CVE-2026-109618 aj môjho WordPress webu?
Takmer určite áno, ak web beží na PHP vo verzii staršej ako 8.5.12, 8.4.27, 8.3.36 alebo 8.2.35. Zraniteľnosť je v jadre PHP, nie vo WordPresse ani v plugine, preto ju aktualizácia WordPressu neopraví. Verziu PHP overíte v administrácii hostingu alebo v časti Nástroje, Zdravie webu, Informácie, Server.
Stačí vypnúť session.upload_progress?
Nie ako trvalé riešenie. Vypnutie session.upload_progress.enabled a zníženie limitov požiadaviek je dočasné zmiernenie, ktoré zmenší útočnú plochu, kým nenasadíte opravené PHP. Skutočnou opravou je aktualizácia PHP, prípadne oficiálny bezpečnostný backport.
Čo ak web beží na PHP 7.4 a nedá sa aktualizovať?
Pre PHP 7.4 nevyšla štandardná opravená verzia. Projekt PHP však zverejnil zdrojový backport opravy, z ktorého sa dá zostaviť opravený interpret a nahradiť ním zraniteľné binárky. Ide o technicky náročnejší zásah, ktorý treba otestovať na kópii webu. Dlhodobo je jediným bezpečným riešením prechod na podporovanú verziu PHP.
Ako zistím, či už niekto zraniteľnosť na mojom webe zneužil?
Prejdite prístupové logy od dátumu zverejnenia zraniteľnosti, skontrolujte nových používateľov a zmeny administrátorských účtov, nové súbory PHP v adresári uploads a nečakané zmeny obsahu. Súbory šablóny porovnajte s verziou v gite alebo so zálohou. Pri akejkoľvek pochybnosti prizvite správcu.