Am 21. Juli 2026 hat die CVE-Nummernvergabestelle VulnCheck CVE-2026-65008 veröffentlicht — eine kritische Schwachstelle im PHP-CMS Grav in allen Versionen vor 2.0.7. Sie erlaubt die Ausführung beliebiger Shell-Kommandos auf dem Server (CWE-94, Codeausführung). Bewertet ist sie mit CVSS v4.0 9.3 und CVSS v3.1 9.8, beide im Bereich „kritisch". Behoben ist der Fehler in Grav 2.0.7.
CVE-ID: CVE-2026-65008
GHSA: GHSA-fj2p-qj2f-74v5 (getgrav)
CVSS v4.0: 9.3 (kritisch)
CVSS v3.1: 9.8 (kritisch)
CWE: CWE-94 (Code Injection)
Betroffen: Grav CMS in allen Versionen vor 2.0.7
Gefixt: Grav 2.0.7
Veröffentlicht: 21. Juli 2026 (VulnCheck)
Wir haben den Fall im isolierten Labor mit den echten Grav-Releases nachgestellt — vom Exploit über die im SIEM feuernde Erkennung bis zum Fix. Dieser Artikel erklärt, wo der Fehler sitzt, wie ein Angriff abläuft, wie er am Endpunkt sichtbar wird und welche Maßnahme greift; belegt mit dem Material aus dem Lauf.
Problem: eine Callable-Zeichenkette ohne Allowlist
Grav baut Formulare und Felder aus sogenannten Blueprints auf. Ein Blueprint kann Feldwerte dynamisch erzeugen, indem er eine Funktion aufruft — eine Direktive der Form data-*@. Die Methode Blueprint::dynamicData() nimmt dazu eine als Class::method notierte Callable-Zeichenkette samt Argumenten und reicht sie ohne Allowlist direkt an call_user_func_array() weiter. Es wird also nicht geprüft, welche Funktion dort landet.
Der zweite Baustein ist eine öffentliche Hilfsmethode, Grav\Common\Utils::arrayFilterRecursive($source, $fn). Sie ruft die übergebene Funktion $fn auf jedem Element eines Arrays auf. Wird sie als Callable in die dynamische Direktive gehängt und bekommt system als aufzurufende Funktion, dann führt sie ein Shell-Kommando aus, das im Array steht. Über diesen Umweg lässt sich die fehlende Prüfung in dynamicData() zu vollständiger Codeausführung ausbauen.
Entscheidend für die Tragweite: Das Form-Plugin verarbeitet das Frontmatter einer Seite über genau diesen Pfad. Die schädliche Direktive muss also nur im Frontmatter einer Seite stehen.
Szenarien: wer es platziert und wer es auslöst
Der Angriff hat zwei getrennte Rollen, und das macht ihn heimtückisch.
Platzieren. Ein Konto mit dem Recht admin.pages oder api.pages.write schreibt die Direktive in das Frontmatter einer Seite. Das ist ein Redakteurs- oder API-Schreibrecht, kein Administratorzugang zum Server.
Auslösen. Ausgeführt wird das Kommando, sobald irgendjemand die präparierte Seite öffnet — auch ein unauthentifizierter Besucher. Die Kommandos laufen mit den Rechten des Webserver-Nutzers.
Im Labor haben wir genau diesen Kern nachgestellt: das echte Grav 2.0.4 (Upstream-Release, per composer installiert), aufgerufen über die dokumentierte data-*@-Direktive. Der Original-Code führte das Kommando als www-data aus:

Wir haben den verwundbaren Code-Pfad (dynamicData() → call_user_func_array() → das system-Trampolin) direkt über die Blueprint-Schnittstelle des echten Grav-Releases ausgelöst — den Weg, den eine bösartige Frontmatter-Direktive nimmt. Die vorgelagerte HTTP- und Formular-Einreichung gegen eine laufende Website haben wir nicht mit aufgebaut; der ausgeführte Code ist derselbe.
Lösung: erkennen und patchen — beides im Labor gezeigt
Erkennung. Ein Angriff dieser Art fällt am Endpunkt auf: Der Webserver-Nutzer startet plötzlich eine Shell — für einen PHP-Webserver ein anomales Verhalten. Dafür haben wir eine eigene Erkennungsregel geschrieben und den Grav-Host an eine Wazuh-Instanz angebunden. Sobald das Konto www-data eine Shell startet, schlägt die Regel (ID 100710) an. Nach dem Auslösen des Exploits ist der Alarm im Wazuh-Dashboard sichtbar — mit dem auslösenden Prozess (/usr/bin/dash), dem Nutzer (uid 33, www-data) und der Zuordnung zu MITRE ATT&CK T1059.004:

Damit ist die Erkennung vorgeführt: Die Regel ist geschrieben, im Lab gefeuert und hier als Dashboard-Screenshot belegt. Ein rohes Log allein würde diesen Nachweis nicht ersetzen.
Patchen. Die primäre und wirksame Maßnahme bleibt das Update auf Grav 2.0.7. Die gepatchte Version prüft vor dem Aufruf, ob eine dynamische Direktive gefährlich ist: Eine direkt gefährliche Funktion (system, exec und ähnliche) wird abgelehnt, und jedes Argument wird geprüft, damit kein harmloser Helfer als Trampolin für eine gefährliche Funktion dienen kann. Genau das system-Trampolin über arrayFilterRecursive fällt damit weg. Denselben Payload haben wir gegen das echte Grav 2.0.7 laufen lassen — der Aufruf wird abgewiesen, kein Kommando läuft:

Für den Fall, dass ein Update nicht sofort möglich ist, ergeben sich aus dem Mechanismus zwei sinnvolle Zwischenschritte: die Zahl der Konten mit admin.pages- und api.pages.write-Recht klein halten und deren Aktivität nachvollziehbar machen, und die oben gezeigte Erkennung (Webserver-Nutzer startet Shell) scharf schalten. Beides ersetzt den Patch nicht, verkürzt aber das Zeitfenster.
Stand der Recherche ist CVE-2026-65008 nicht im CISA-KEV-Katalog gelistet, und eine aktive Ausnutzung in freier Wildbahn ist öffentlich nicht dokumentiert. Bei einer kritischen, gut dokumentierten Codeausführung ist das eher eine Frage des Zeitpunkts als der Wahrscheinlichkeit — die Priorisierung des Patches sollte davon nicht abhängen.
Einordnung für den Betrieb
Eine einzelne kritische Lücke in einer weit verbreiteten Web-Software ist ein klassischer Priorisierungsfall: Wo läuft eine verwundbare Grav-Instanz, ist sie erreichbar, und wie schnell kommt der Patch darauf? Genau diese Sicht liefert ein Threat Exposure Filter — exponierte Instanzen sichtbar machen und das Patchen nach Ausnutzbarkeit ordnen. Das geordnete Erkennen verwundbarer Versionen und das Einspielen des Fixes sind Aufgabe von Vulnerability- und Patch-Management; ein Schwachstellenscan findet die betroffenen Systeme im Bestand.
