Je winkelwagen is leeg
Producten die je toevoegt, verschijnen hier.
PHP2Shell (CVE-2026-109618/109619) technisch uitgelegd: SAPI's, ini-modes, FPM-reloads, CageFS, Imunify360-regels, vendor-builds en hoe je controleert of een PHP-binary de fix bevat.
Samenvatting: de techniek achter onze aanpak van CVE-2026-109618 en -109619
Beide lekken zitten in de PHP-engine, nog vóór de code van een site draait. Hoe groot het risico is, hangt af van de SAPI: mod_php was het bevestigde doelwit, PHP-FPM en LiteSpeed draaien PHP in aparte processen. Wij zetten session.upload_progress uit op elke PHP-build, maten na dat ook de draaiende processen de instelling gebruikten, lieten Imunify360 het aanvalspatroon blokkeren en installeerden de vendor-builds zodra ze verschenen. Omdat het versienummer na de fix vaak gelijk blijft, controleerden we elke binary op de symbolen die de fix meebrengt.
Dit is het technische vervolg op onze terugblik op het PHP-lek van 21 oktober. Daar lees je wat we deden en waarom we online bleven. Hier gaan we de diepte in: wat er in de engine misgaat, waarom de SAPI uitmaakt, welke configuratie we op welke stack zetten, hoe de vendor-builds binnenkwamen en hoe je zelf controleert of een binary de fix bevat. Beheer je zelf PHP
KeurigOnline zegtPHPPHP (Hypertext Preprocessor) is een populaire open-source scripttaal die speciaal is ontworpen voor webontwikkeling en server-side programmering.-servers, dan is dit voor jou.
Te technisch? In onze terugblik lees je het verhaal zonder commando's. Alleen weten of je iets moet doen? Zie de status van het PHP-lek.
Het PHP-team kondigde op 20 oktober een beveiligingsrelease aan voor twee lekken met een CVSS v4.0-score van 9,2[1]. De beste openbare technische analyse is die van Daniele Berardinelli[2]. In het kort:
nNumUsed en nNumOfElements voordat de nieuwe key er echt staat. Faalt precies daar een geheugenallocatie, dan springt de memory-limit-afhandeling via longjmp() uit de engine en blijft een bucket achter die nooit is geïnitialiseerd. De opruimcode behandelt die bucket daarna als geldig. Een aanvaller stuurt de geheugenfout bewust naar die plek door de multipart-parser op te blazen met headers, lege file-velden en diep geneste veldnamen, en door de Content-Length-check op post_max_sizePS(id)-string als out-of-bounds heap read terug in een Set-Cookie-header, zonder dat het script iets hoeft te doen.Van zo'n lek naar code-executie is nog een flinke stap. ASLR moet eerst omzeild worden, met een arbitrary read, een tweede codepointer of brute force met een crash als signaal. Berardinelli noemt dat "conventional PHP memory corruption engineering" en waarschuwt dat de publieke bronnen de laatste stap niet volledig aantonen. Zijn conclusie vatten we graag samen met zijn eigen woorden: "Exploit unreliability is not a mitigation."
| CVE | Primitief | Trigger | Gerepareerd in |
|---|---|---|---|
| CVE-2026-109618 | Niet-geïnitialiseerde bucket na een longjmp() bij geheugengebrek | Opgeblazen multipart-body tot aan memory_limit | 8.5.12, 8.4.27, 8.3.36, 8.2.35; bronbackports voor 7.4, 8.0 en 8.1 |
| CVE-2026-109619 | Verouderde pointer naar request-heap in globals | Twee requests op dezelfde worker, via upload progress | Idem |
Dezelfde engine-bug is niet op elke manier van PHP draaien even goed te misbruiken. Berardinelli: "The startup time exploit path is not identical across SAPIs."
php_request_startup(). Na een startup-fout doet mod_php nog een gewone request-shutdown en kan het de beschadigde tabel in hetzelfde request gebruiken. Voor beide CVE's zijn hier routes bevestigd. De publieke PoC die op 21 oktober rondging, werkte volgens de eigen documentatie alleen op een afgestelde ApacheBG(unserialize) publiceert, gevolgd door upload progress op een later request naar dezelfde worker.Voor beide CVE's geldt dat de staat met de worker sterft: "When a worker dies, the stale state dies with it." Keep-alive helpt een aanvaller alleen als de frontend de verbinding aan dezelfde worker koppelt, en dat is geen garantie.
Op onze shared hosting
KeurigOnline zegtShared hostingShared hosting is een hostingvorm waarbij meerdere websites dezelfde serverresources delen. (cl01 tot en met cl09) draait PHP als lsphp onder LiteSpeed Enterprise 6.3.7 of onder Apache met mod_lsapi, binnen CloudLinux
KeurigOnline zegtCloudLinuxCloudLinux is een gespecialiseerd Linux-besturingssysteem voor webhostingproviders dat elke gebruiker isoleert in een eigen container. CageFS. Op onze VPS
KeurigOnline zegtVPSEen VPS is een eigen virtuele server met meer vrijheid en flexibiliteit dan shared hosting.'en en DirectAdmin
KeurigOnline zegtDirectAdminDirectAdmin is een webhosting control panel ontwikkeld door JBMC Software als kostenefficiënt en gebruiksvriendelijk alternatief voor cPanel.-servers draait Apache met PHP-FPM. mod_php gebruiken we vrijwel nergens. Daarmee viel het bevestigde aanvalspad voor het overgrote deel van onze servers af, en was de FPM-route via upload progress het pad om dicht te zetten.
Het advies tot de patch was session.upload_progress.enabled = 0, toegepast vóór het parsen van requests, en daarna elke worker herstarten[1][2]. Dat blokkeert de bevestigde routes van CVE-2026-109619, niet de geheugenroute van CVE-2026-109618. Op onze servers draaien PHP-versies van 5.2 tot en met 8.5 naast elkaar, verdeeld over drie configuratiemodellen.
Elke CustomBuild-versie leest een eigen php.conf.d. Een drop-in per versie, daarna een reload van de bijbehorende FPM-pool:
; /usr/local/php83/lib/php.conf.d/zz-mitigations.ini
session.use_strict_mode = 1
session.upload_progress.enabled = 0systemctl reload php-fpm83
php83 -r 'var_dump(ini_get("session.upload_progress.enabled"));'Voor alle alt-php-versies tegelijk staat de instelling in /etc/cl.selector/global_php.ini. CloudLinux verspreidt die met cagefsctl naar de ini's van elke versie en elk account:
; /etc/cl.selector/global_php.ini
; phpup-2026 begin
session.upload_progress.enabled = Off
; phpup-2026 end/usr/sbin/cagefsctl --setup-cl-selector
/usr/local/lsws/bin/lswsctrl restart # LiteSpeed; onder Apache + mod_lsapi: apachectl gracefulOp een CloudLinux-server kan een account ook de eigen CustomBuild-PHP van de server kiezen in plaats van alt-php. Binnen CageFS ziet dat account niet /usr/local/phpNN/lib/php.conf.d van de host, maar de kopie in het CageFS-skeleton. Een drop-in die je alleen op de host zet, geldt dan niet in de cage. We hebben dat gemeten met een check als de user zelf, en op een paar servers dezelfde ochtend het skeleton bijgewerkt:
cp zz-phpup-2026.ini /usr/share/cagefs-skeleton/usr/local/php83/lib/php.conf.d/
su -s /bin/bash <user> -c '/usr/local/php83/bin/php -r "echo ini_get(\"session.upload_progress.enabled\");"'De drop-in hoort daarbij ook in de echte map te staan, anders wist de nachtelijke CageFS-update hem weer uit het skeleton.
session.upload_progress.enabled is een PHP_INI_PERDIR-directive[6]. Een php_flag in een .htaccess (mod_php, LiteSpeed) of een regel in .user.ini (FPM) kan hem dus per map weer aanzetten. Oude installaties hebben zulke regels soms nog. Vraag je klanten daarom om dat niet te doen, of zet de waarde met php_admin_value in de pool-configuratie vast. Draai je PHP 8.5 en kun je nog niet patchen, zet dan ook fatal_error_backtraces = 0: dat sluit de tweede bevestigde producer van CVE-2026-109619[2].
De belangrijkste les van de ochtend. Een check op de opdrachtregel leest de ini vers in, maar de FPM-master leest zijn configuratie alleen bij het starten of bij een reload. Staat de drop-in er pas sinds vanochtend en is de pool sinds vannacht niet herladen, dan zegt php -r "0" terwijl elke worker die bezoekers bedient nog met upload progress aan draait. Wij hebben daarom niet de configuratie gemeten, maar het tijdstip van de laatste reload naast de mtime van de drop-in gelegd:
ini=/usr/local/php74/lib/php.conf.d/zz-mitigations.ini
stat -c %Y "$ini"
# laatste (her)start van de pool; op EL9 logt FPM naar de journal
grep -E 'Reloading in progress|ready to handle connections' /usr/local/php74/var/log/php-fpm.log | tail -1
journalctl -u php-fpm74 -o short-iso | grep -E 'Reloaded|ready to handle connections' | tail -1Is de reload ouder dan de drop-in, dan volgt systemctl reload php-fpmNN (een graceful SIGUSR2, bezoekers merken er niets van). Controleer daarna dat de laatste regel "ready to handle connections" is. Een master die blijft hangen op "Reloading in progress" bedient geen requests meer.
Bij LiteSpeed
KeurigOnline zegtLiteSpeedLiteSpeed is een snelle webserver met een efficiënte event-driven architectuur en ingebouwde caching, compatibel met Apache. zit de valkuil op een andere plek. Losse lsphp-ouderprocessen (ppid 1, soms dagen oud) overleven een lswsctrl restart en blijven op het oude binary draaien. Na een update zoek je ze op het verwijderde executable:
for p in $(pgrep -x lsphp); do
readlink /proc/$p/exe | grep -q '(deleted)' && echo "oud binary: $p"
doneOnder Apache met mod_lsapi ververst lsphp zich na een graceful vanzelf. Alleen idle processen, zoals die van webmail
KeurigOnline zegtWebmailE-mail bekijken en beheren via een webbrowser zonder aparte software, met toegang vanaf elk apparaat met internetverbinding., moesten we daar met de hand stoppen.
CloudLinux zag al op 21 oktober pogingen die op het rondgaande aanvalspatroon leken, en zette er een ModSecurity-regel tegen in de Imunify360
KeurigOnline zegtImunify360Imunify360 is een uitgebreide beveiligingssuite voor Linux webservers met AI-aangedreven malware scanning.-ruleset[3]. Ruleset 8.85 voegde daar regels aan toe voor de publieke aanvalstool, varianten daarvan en verdachte uploads. Op servers waar Imunify360
KeurigOnline zegtImunify360Imunify360 is een uitgebreide beveiligingssuite voor Linux webservers met AI-aangedreven malware scanning. nog ontbrak, hebben we het die ochtend geïnstalleerd (agent 8.14.1). Daarna hebben we per server gecontroleerd welke ruleset geladen was, en lokaal getest dat een normaal request 200 gaf en een request met het exploitkenmerk 403 (blokregel 77318849).
Een WAF is een extra laag, geen vervanging van de patch. De aanval zit vóór de applicatie, in de manier waarop PHP een multipart-body parseert, en elke handtekening is te omzeilen door wie de bug begrijpt. Daarom hielden we de andere lagen staan tot de patch erop zat.
Voor servers met een legacy OS-versie kwam de vendor-build het laatst. Daar plaatsten we op de avond van 21 oktober php_uploadprogress_guard van de Belgische hoster Hosted Power[4] (versie 3.1.0, MIT-licentie). De extensie koppelt de session-upload-progress-callback los op C-niveau. Dat is sterker dan een ini-waarde, want .user.ini of .htaccess kan hem niet terugzetten. Hij draagt per branch een gerepareerde kopie van de multipart-parser mee en bootst het nieuwe gedrag bij geheugengebrek na. Op een PHP
KeurigOnline zegtPHPPHP (Hypertext Preprocessor) is een populaire open-source scripttaal die speciaal is ontworpen voor webontwikkeling en server-side programmering.-build die de officiële fix al bevat, stapt hij vanzelf opzij.
; /usr/local/php74/lib/php.conf.d/20-hp_uploadprogress_guard.ini
extension = hp_uploadprogress_guard.soGebouwd hebben we hem per versie met de eigen phpize van die build. Eerst op een testserver, met een probe: een multipart-POST met een PHP_SESSION_UPLOAD_PROGRESS-veld en een sessiecookie tegen de ingebouwde webserver (php -S). Zonder guard stond er een upload-progress-key in de sessie, met guard bleef die leeg, en de upload zelf was byte-voor-byte gelijk. Daarna server voor server, met een vergelijking van alle sites tegen een nulmeting.
De echte fix komt van drie partijen, elk met een eigen kanaal.
Op 21 oktober om 14:00 UTC verschenen 8.5.12, 8.4.27, 8.3.36 en 8.2.35. Voor 7.4, 8.0 en 8.1 leverde het PHP-team minimale bronbackports. Oudere EOL-branches zijn aangewezen op vendors[1].
DirectAdmin nam de releases dezelfde middag op in CustomBuild, en kort na middernacht ook in het kanaal voor legacy OS-versies. Daar zitten de reparaties als patchbestanden per branch, van 5.6 tot en met 8.1:
da build update
ls /usr/local/directadmin/custombuild/patches/php-7.4/ | grep 109618
# 143-php-7.4-cve-2026-109618.patch
da build versions | grep -i 'php 7.4' # 7.4.33* -> 7.4.33: zelfde nummer, nieuwe build
da build php # per versie een paar minutenCloudLinux bracht de gerepareerde alt-php-pakketten eerst uit via het ELS-kanaal (pakket els-php-release) en later via de stabiele repository. Het versienummer bleef gelijk, alleen de release ging omhoog, bijvoorbeeld alt-php84-common-8.4.26-5 en alt-php74-common-7.4.33-81. De changelog laat zien wat de fix doet:
8.4.26-5
- CVE-2026-109618: do not bail out from inside the memory manager; allocation
failures are reported through a deferred error instead of unwinding through
half-updated engine state
- CVE-2026-109619: clear stale request globals at shutdown so a bailout cannot
carry them into the next request
7.4.33-81
- CVE-2026-109618, CVE-2026-109619: security hardening in the memory allocator
and the multipart request parser [...]
Also disables the session upload-progress callback, as upstream does on the
EOL branches - session.upload_progress.enabled no longer has any effect.Op de EOL-branches is de callback dus helemaal uitgeschakeld. Een aardig detail bij het uitrollen: elk alt-php-pakket draait in zijn scriptlet cagefsctl --rebuild-alt-php-ini. Op een server met bijna 1.900 CageFS-accounts kost dat ruim vier minuten per pakket, en een inhaalupdate van honderden pakketten loopt dan uren uit. Daar hebben we de rebuild tijdens de update tijdelijk overgeslagen en er na afloop één gedraaid. De update was toen in een kwartier klaar.
Bij de backports en de alt-php-pakketten blijft PHP_VERSION gelijk. "Draait 7.4.33" zegt dus niets over de fix. Wij controleerden daarom elke binary op de engine-symbolen die de fix meebrengt. Op een ongepatchte build ontbreken ze:
nm -D --defined-only /usr/local/php74/sbin/php-fpm \
| grep -E 'zend_mm_safe_error_ex|zend_handle_delayed_oom'| Branch (gemeten) | Symbool na de fix | Gedrag bij geheugengebrek |
|---|---|---|
| 5.6, 7.0, 7.3, 7.4, 8.0, 8.1 (backports) | zend_mm_safe_error_ex | Proces stopt direct |
| 8.3, 8.4, 8.5 | zend_handle_delayed_oom | Uitgestelde, nette fatal error |
Voor alt-php is de changelog de snelste check: rpm -q --changelog alt-php74-common | grep CVE-2026-109618. Deze controles hebben we na elke ronde over al onze servers gedraaid, per PHP-build, en niet alleen voor de versies die in een slot staan.
De minimale backports voor de oudere branches stoppen het proces bij elk geheugengebrek direct, in plaats van een fatal error terug te geven. Onder PHP-FPM sterft de worker dan ("child exited with code 1") en ziet Apache
KeurigOnline zegtApacheApache HTTP Server is een open source webserver ontwikkeld door de Apache Software Foundation. "Failed to read FastCGI header" of een connection reset. De bezoeker krijgt een 503 waar hij eerst een PHP-fatal 500 kreeg. Vanaf 8.2 blijft het een nette 500. Sites die altijd net onder hun memory_limit draaiden, vielen daardoor om. We hebben ze een ruimere limiet gegeven via .user.ini, zonder FPM-reload: PHP leest dat bestand binnen user_ini.cache_ttl (standaard 300 seconden) vanzelf opnieuw. De php-fpm.log per pool, gefilterd op "exited with code 1" sinds het moment van de build, gaf de lijst van sites die het raakte.
Na de release meldden onderzoekers nog enkele problemen. Het PHP-team bevestigde een regressie uit de fix zelf waardoor een proces in bepaalde gevallen crasht[5]. De belangrijkste andere melding vereist session.upload_progress aan. Die instelling laten we daarom ook na de patch overal uit.
Tijden in Nederlandse tijd (CEST, UTC+2).
| Moment | Gebeurtenis |
|---|---|
| di 20 okt | PHP kondigt de beveiligingsrelease aan voor 21 oktober 14:00 UTC |
| wo 21 okt, 08:30-10:00 | session.upload_progress uit op elke PHP-build |
| 10:00-11:00 | Runtime nagemeten (FPM-reload tegen ini-mtime, CageFS-check als user), pools herladen waar nodig |
| 11:30-12:00 | Imunify360 geïnstalleerd waar het ontbrak; ModSecurity-regel tegen het aanvalspatroon actief |
| 16:00 | php.net: 8.5.12, 8.4.27, 8.3.36, 8.2.35; vanaf 16:10 CustomBuild-builds op de VPS'en |
| 18:30 | Alle beschikbare CustomBuild-builds geïnstalleerd |
| ~19:30 | Imunify360 ruleset 8.85 op cl01 tot en met cl09 |
| avond | Guard-extensie op servers met een legacy OS-versie |
| do 22 okt, 00:10 | DirectAdmin: patches voor legacy OS-versies, 5.6 tot en met 8.1; eerst getest, dan uitgerold |
| 00:40 | CloudLinux: gerepareerde alt-php 5.2 tot en met 8.5 via ELS; om 01:35 op cl01 tot en met cl09 |
| 02:16 | Laatste server bijgewerkt, symbool-check over alle builds groen |
Zo'n nacht laat zien waarom we onze hosting zo inrichten. Van de release van PHP tot de laatste gecontroleerde build zat ruim tien uur, over tientallen PHP-builds en drie leveranciers heen. Heb je vragen over je eigen server of wil je met ons sparren over je PHP-stack? Neem contact met ons op.
Hooguit een kleine. De verouderde staat van CVE-2026-109619 sterft met de worker, dus vaker recyclen verkort het venster. Een aanvaller kan de keten in de nieuwe worker gewoon opnieuw opbouwen. Patchen en session.upload_progress uitzetten blijven de echte maatregelen.
Berardinelli noemt het een aparte, onvolledige mitigatie voor CVE-2026-109618[2]. Je ruilt een engine-bug in voor een server die zonder limiet vol kan lopen. Op shared hosting
KeurigOnline zegtShared hostingShared hosting is een hostingvorm waarbij meerdere websites dezelfde serverresources delen. is dat geen optie.
Dat hangt af van je vendor. Upstream levert niets meer voor die branches. DirectAdmin
KeurigOnline zegtDirectAdminDirectAdmin is een webhosting control panel ontwikkeld door JBMC Software als kostenefficiënt en gebruiksvriendelijk alternatief voor cPanel. CustomBuild heeft patches vanaf 5.6, CloudLinux
KeurigOnline zegtCloudLinuxCloudLinux is een gespecialiseerd Linux-besturingssysteem voor webhostingproviders dat elke gebruiker isoleert in een eigen container. alt-php vanaf 5.2. Controleer het met nm of de changelog, zoals hierboven.
Nee. Bijna geen applicatie gebruikt het, op de EOL-branches van alt-php
KeurigOnline zegtPHPPHP (Hypertext Preprocessor) is een populaire open-source scripttaal die speciaal is ontworpen voor webontwikkeling en server-side programmering. is de callback zelfs helemaal uitgeschakeld, en een deel van de restmeldingen werkt alleen als het aanstaat.