← Alle berichten

PHP2Shell, drie dagen later: welke sporen je op je webserver zoekt

24 oktober 2026 Security-onderzoek 9 min lezen

Op 21 oktober schreef ik over PHP2Shell, de twee kritieke PHP-kwetsbaarheden die uitlekten voordat er een patch was. Toen ging het om voorbereiden en patchen. Drie dagen later is er meer bekend, en één ding verandert de opdracht: het misbruik begon al vóór de patch. Wie netjes heeft bijgewerkt, is daarmee nog niet klaar. De vraag is ook of er in de dagen daarvoor iets is gebeurd.

Dit artikel gaat over die tweede vraag. Ik zet de waarnemingen van de afgelopen dagen op een rij en laat daarna zien welke sporen zo’n aanval achterlaat en hoe je er op een Linux-webserver naar zoekt. Ik beschrijf geen exploit; de focus ligt op detectie en onderzoek. Paden en adressen in de voorbeelden zijn fictief of komen uit de genoemde bronnen.

Wat er sinds de patch is gebeurd

Een overzicht uit de bronnen, met de stand van 23 en 24 oktober:

  • Misbruik vóór de patch. Amazon zag de eerste exploitpoging op een honeypot al op 17 oktober, vier dagen voor de publieke release, vanaf een commercieel anonimiseringsnetwerk (SecurityWeek). Hoe die partij vooraf aan de informatie kwam, is onbekend.
  • Volume. Cloudflare meldde tienduizenden pogingen in de eerste 45 minuten na de release, honderdduizenden binnen twee uur, en op 22 oktober miljoenen (The Hacker News).
  • Wat aanvallers proberen. Waargenomen zijn onder meer pogingen om een Mirai-variant (“Potassium”) af te leveren voor DDoS, een poging om via een sessiebestand een webshell te plaatsen, en pogingen om secrets uit omgevingsvariabelen te lezen en een tweede payload op te halen.
  • Lukt het? Tot nu toe meldt geen van de grote partijen een bevestigde geslaagde compromittering. Sophos trok een eerdere melding van succesvol misbruik weer in en ziet vooral scanning en mislukte pogingen (Sophos). Volgens watchTowr haalt geen van de rondgaande PoC’s betrouwbaar code-uitvoering; sommige testen alleen op timing, andere laten het doel crashen.
  • Nep-PoC’s. Op GitHub zijn nep-exploits en als-PoC-vermomde malware verschenen. Dat hoort bij elk groot lek en is de reden om zulke code nooit op een eigen machine te draaien.
  • Een derde kwetsbaarheid. Op 23 oktober publiceerde PHP een informatieve advisory over CVE-2026-115560, opnieuw rond session upload progress (php.net). Die speelt alleen als een applicatie session.serialize_handler in de configuratie op php_binary of php_serialize zet en die tijdens runtime met ini_set() naar php omzet vóór session_start(). PHP noemt dat een onwaarschijnlijke combinatie en lost het op in de volgende reguliere release. Tot die tijd blijft het advies: session.upload_progress.enabled=0.

Mijn interpretatie: dat er nog geen bevestigde inbraak is, zegt vooral dat het moeilijk is, niet dat het onmogelijk is. Een aanval die afhangt van geheugenindeling, het hergebruik van workers en de precieze configuratie werkt slecht op grote schaal, maar kan wel slagen tegen één goed bestudeerd doelwit. Ga er dus niet van uit dat “nog niemand is gehackt” ook voor jouw server geldt, zeker niet als die vóór 21 oktober aan het internet hing.

Waarom je verder moet kijken dan de access-log

Het vervelende aan deze kwetsbaarheden is dat een geslaagde aanval weinig zichtbaars hoeft achter te laten. Volgens de analyse van Datadog kan een worker na misbruik gewoon blijven draaien en een nette HTTP 200 teruggeven. Er hoeft niets te crashen. Je zoekt dus niet naar één duidelijk alarm, maar naar een combinatie van kleine signalen in verschillende bronnen: de access-log, de PHP-errorlog, het procesgedrag en het bestandssysteem.

Een tweede punt: standaard logt een webserver niet alles wat je nu nodig hebt. De grootte van de request-body en de cookie-headers staan meestal niet in het access-logformaat. Voor onderzoek achteraf heb je daarom deels geluk nodig, en voor de toekomst is dit een goede reden om je logging uit te breiden.

Sporen in de logs

Access-log: het soort verzoek

De verzoeken die bij deze aanval horen, vallen op door hun vorm, niet door een vast pad. Let volgens Datadog op:

  • POST-verzoeken, vaak multipart/form-data, naar endpoints die normaal geen uploads verwerken.
  • Velden als PHP_SESSION_UPLOAD_PROGRESS of een PHPSESSID die op ongebruikelijke plekken opduikt.
  • Verzoeken met heel veel onderdelen, diep geneste veldnamen (zoals files[a][a][a]...), of één veld van vele megabytes.
  • Grote uploads met een hoge Content-Length die traag blijven of nooit afronden.

Sophos noemt daarnaast twee concrete kenmerken uit waargenomen scans: de User-Agent php2shell-scan/109618 en de multipart-boundary B7kQpoc618. Dat zijn indicatoren van bekende scanners, geen bewijs van een geslaagde aanval, en ze zijn triviaal te veranderen. Gebruik ze om scanning te herkennen, niet om hem uit te sluiten.

Heb je de requestgrootte niet in je log? Dan is dit het moment om die toe te voegen. In Apache met een eigen logformaat:

LogFormat "%h %l %u %t \"%r\" %>s %O %{Content-Length}i \"%{User-Agent}i\"" withlen

PHP-errorlog: geheugenfouten tijdens de start

Dit is het bruikbaarste spoor, omdat het bij de aard van de kwetsbaarheid hoort. Datadog noemt als voorbeeld uit een echte poging:

PHP Fatal error:  Allowed memory size of 16777216 bytes exhausted ... Unknown on line 0

Het deel Unknown on line 0 is het interessante: de fout trad op tijdens het initialiseren van de request, nog voordat er een script draaide. Een enkele geheugenfout zegt weinig, want ook een legitieme zware pagina kan geheugen opmaken. Maar een kort reeksje van zulke fouten op dezelfde worker, zonder dat een echt script de oorzaak is, is verdacht, ook als er niets crasht.

Zorg dus eerst dat je deze log hebt. In een onderzoeksopstelling: log_errors=On, een werkende error_log en error_reporting=E_ALL. PHP waarschuwt expliciet dat lege logs niets bewijzen als logging uit stond.

Worker-crashes

Lukt het raden van een geheugenadres niet, dan kan een worker crashen. In de Apache-errorlog ziet dat er zo uit:

AH00052: child pid 12345 exit signal Segmentation fault (11)

Bij PHP-FPM is het equivalent een regel over een child die op een signaal afsluit, in het FPM-log. Correleer zulke crashes met grote POST-verzoeken en met upstream-resets in je reverse proxy. Nogmaals: een crash is níét vereist voor een geslaagde aanval, maar een reeks segfaults op een PHP-worker is wél altijd reden om te kijken.

Sporen op het systeem

De logs vertellen of er is geprobeerd. Het bestandssysteem en de processen vertellen of het is gelukt. Dit zijn de plekken die er na een geslaagde RCE toe doen.

Procesgedrag: de sterkste aanwijzing

Een PHP-worker die een shell of een downloadtool start, is het duidelijkste teken van code-uitvoering. Normaal doet php-fpm of een Apache-worker dat nooit. Heb je Microsoft Defender for Endpoint of een andere EDR op je Linux-servers, dan vang je dit met een regel op procesafstamming. In KQL voor Defender:

DeviceProcessEvents
| where Timestamp > datetime(2026-10-17)
| where InitiatingProcessFileName in~ ("php-fpm", "php", "php-cgi", "httpd", "apache2")
     or InitiatingProcessFileName matches regex @"^php-fpm\d"
| where FileName in~ ("sh", "bash", "dash", "curl", "wget", "python", "python3",
                      "perl", "nc", "ncat", "id", "whoami", "uname")
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName,
          ProcessCommandLine, InitiatingProcessCommandLine, AccountName
| order by Timestamp asc

Let op de startdatum: 17 oktober, niet 21 oktober. Het misbruik begon vóór de patch, dus je telemetrie moet minstens tot die datum terug.

Heb je geen EDR, dan is auditd het alternatief. Deze regel legt elke execve vast die onder de webserveruser (www-data) wordt uitgevoerd:

# /etc/audit/rules.d/php-exec.rules — pas de uid aan je webserveruser aan
-a always,exit -F arch=b64 -F euid=33 -S execve -k php_exec

Daarna zoek je met ausearch -k php_exec naar wat die user heeft uitgevoerd. In een schone situatie hoort dat een korte, voorspelbare lijst te zijn. Een sh, curl of wget die je niet kunt verklaren, is een startpunt voor onderzoek.

Bestandssysteem: webshells en nieuwe bestanden

Zoek naar .php-bestanden die recent zijn aangemaakt of gewijzigd in mappen die vanaf het web bereikbaar zijn. Een webshell is de meest voorkomende vorm van persistentie:

# Recent gewijzigde PHP-bestanden in de webroot, sinds 16 oktober
find /var/www -name '*.php' -newermt '2026-10-16' -printf '%TY-%Tm-%Td %TH:%TM  %p\n' | sort

# PHP-bestanden met functies die typisch in webshells zitten
grep -rlE '\b(eval|assert|system|passthru|shell_exec|base64_decode)\s*\(' /var/www --include='*.php'

De tweede zoekopdracht geeft valse positieven, want legitieme code gebruikt deze functies ook. Het gaat om bestanden die er niet thuishoren of die kort geleden zijn verschenen. Vergelijk bij twijfel met een back-up of met de Git-historie van je applicatie.

Datadog noemt daarnaast dat exploitpogingen fragmenten in sessiebestanden kunnen achterlaten. De sessiebestanden staan standaard in de map uit session.save_path (vaak /var/lib/php/sessions of /tmp). Daar kun je zoeken naar PHP-code in wat alleen data zou moeten zijn:

# Vind 'php' uit session.save_path
php -i | grep -i session.save_path
# Zoek naar PHP-openingstags of geserialiseerde objecten in sessiebestanden
grep -rlE '<\?php|\|O:[0-9]' /var/lib/php/sessions 2>/dev/null

Persistentie en uitgaande verbindingen

Heeft een aanvaller code kunnen uitvoeren, dan wil hij meestal blijven. Loop daarom ook de bekende persistentieplekken na:

  • Cronjobs: crontab -l voor elke user, en /etc/cron.* plus /var/spool/cron.
  • Systemd: nieuwe of gewijzigde unit-bestanden, met systemctl list-timers en een blik in /etc/systemd/system.
  • Nieuwe of gewijzigde accounts en SSH-sleutels: /etc/passwd, en authorized_keys-bestanden in home-directories.
  • Uitgaande verbindingen: een webserver die uit zichzelf verbinding maakt met een onbekende host is verdacht. In je firewall- of netflow-logs let je op uitgaand verkeer vanaf de webserver naar adressen die er niet horen.

Een werkbare volgorde

Op basis van de operationele richtlijn van NVISO en de bovenstaande sporen houd ik deze volgorde aan:

  1. Inventariseer waar PHP draait: internet-facing sites, maar ook containers, staging, hostingomgevingen en leveranciersproducten. Noteer versie en eigenaar.
  2. Patch naar 8.5.12, 8.4.27, 8.3.36 of 8.2.35, internet-facing eerst. Controleer daarna dat de draaiende workers de nieuwe versie gebruiken, niet alleen het bestand op schijf. Een update op schijf zonder herstart van PHP-FPM of Apache verandert niets aan het draaiende proces.
  3. Kijk terug vanaf 17 oktober. Loop de logs en het systeem langs zoals hierboven. Dit is de stap die het makkelijkst wordt overgeslagen, en juist de belangrijkste nu bekend is dat het misbruik vóór de patch begon.
  4. Bewaar bewijs voordat je opruimt. Vind je iets, maak dan eerst kopieën van de web- en PHP-logs, de verdachte bestanden en zo mogelijk een geheugendump, voordat je de server herstelt.
  5. Behandel een treffer als een volledige compromittering. Code die onder de webserveruser draaide, betekent dat de secrets van die applicatie gelekt kunnen zijn. Roteer databasewachtwoorden, API-sleutels en tokens, net als bij de NetScaler-casus eerder deze maand.

Valkuilen

  • Een CMS-update patcht PHP niet. Een bijgewerkte WordPress of Drupal kan nog steeds op een kwetsbare PHP-runtime draaien. Het gaat om de PHP-versie, niet om de applicatie.
  • Een gepatcht bestand is geen gepatcht proces. Zonder herstart draait de oude runtime door.
  • Lege logs bewijzen niets als logging uit stond. Afwezigheid van sporen is geen bewijs van afwezigheid van misbruik, zeker bij een aanval die zonder crash kan verlopen.
  • Een WAF vervangt de patch niet. De technische kenmerken liggen nog niet vast, en handtekeningregels missen geheugenaanvallen vaak.

Kort samengevat

De acute fase, patchen, zou nu achter de rug moeten zijn. De fase die veel organisaties overslaan, begint nu pas: terugkijken of er in het venster vóór de patch iets is gebeurd. Omdat een geslaagde aanval weinig lawaai maakt, zit het bewijs in de combinatie van een rare reeks geheugenfouten, een onverwacht kindproces van een PHP-worker, en een bestand of verbinding die er niet hoort. Wie die drie bronnen naast elkaar legt en terugkijkt tot 17 oktober, heeft een reële kans om een stille inbraak alsnog te vinden.

Bronnen