
PoC di exploit e analisi della causa principale di un'iniezione di oggetti PHP critica e non autenticata in WordPress Database for Contact Form 7, che conduce a RCE tramite eliminazione arbitraria di file.
Plugin: Database for Contact Form 7 (contact-form-entries) ≤ 1.4.3
CVSS: 9.8 (Critico)
CWE: CWE-502 — Deserializzazione di dati non attendibili
Requisito di autenticazione: Nessuno (non autenticato)
Impatto: Esecuzione di codice in remoto
Il plugin "Database for Contact Form 7" (slug: contact-form-entries) versione 1.4.3 e precedenti contiene una vulnerabilità di Iniezione di Oggetti PHP. Quando un amministratore WordPress visualizza un record (entry) di un modulo all'interno del pannello di amministrazione, il plugin chiama la funzione maybe_unserialize() direttamente sui dati inviati da un utente non autenticato tramite Contact Form 7, senza controllare l'elenco delle classi consentite da istanziare.
Un attaccante non ha bisogno di effettuare il login — deve solo inviare un normale modulo di contatto inserendo un oggetto PHP serializzato in un qualsiasi campo del modulo. Questi dati vengono salvati grezzi nel database. Quando un amministratore apre la visualizzazione di quell'entry, la funzione di deserializzazione istanzierà un oggetto scelto dall'attaccante, attivando metodi magici come __destruct() o __wakeup() → eseguendo comportamenti arbitrari a seconda dei gadget POP disponibili nell'ambiente WordPress.
Livello di gravità: Con un gadget POP adatto (ad esempio, una classe il cui metodo
__destruct()chiamaunlink()), un attaccante può eliminare il filewp-config.php, riportando WordPress alla schermata di installazione iniziale → reinstallando con un account amministratore controllato dall'attaccante → installando un plugin contenente una webshell → ottenendo la piena Esecuzione di Codice in Remoto (RCE) sul server.
| Attributo | Valore |
|---|---|
| ID CVE | CVE-2025-7384 |
| Punteggio CVSS | 9.8 (Critico) |
| CWE | CWE-502 — Deserializzazione di dati non attendibili |
| Plugin interessato | contact-form-entries (Database for Contact Form 7) ≤ 1.4.3 |
| Requisito di autenticazione | Nessuno — chiunque invii un modulo CF7 può iniettare il payload |
| Condizione di attivazione | L'amministratore visualizza l'entry iniettata nel pannello di amministrazione |
| Impatto massimo | Esecuzione di codice in remoto non autenticata |
| Versione corretta | 1.4.4+ (sostituisce unserialize con json_decode o allowed_classes: false) |
PHP usa serialize() per convertire un oggetto in una stringa di testo strutturata, e unserialize() per ripristinare l'oggetto da quella stringa. Quando unserialize() riceve dati da una fonte non attendibile (es. input utente), un attaccante può costruire un oggetto arbitrario appartenente a qualsiasi classe attualmente caricata nella memoria PHP in quel momento.
Metodi speciali che PHP invoca automaticamente durante il ciclo di vita di un oggetto. I più importanti in questo contesto:
__wakeup() — invocato immediatamente quando un oggetto viene unserializzato__destruct() — invocato quando un oggetto viene distrutto (esce dallo scope, o la richiesta termina)__toString() — invocato quando un oggetto viene convertito in stringaUna tecnica che collega più metodi magici di classi esistenti all'interno dell'applicazione per costruire una sequenza pericolosa di comportamenti. L'attaccante non scrive nuovo codice — manipola solo le proprietà degli oggetti esistenti così che, quando i metodi magici vengono eseguiti, compiano azioni non volute dagli sviluppatori.
maybe_unserialize() in WordPressUna funzione wrapper del WordPress Core. Chiama is_serialized() per verificare se una stringa è dati serializzati — se vero, chiama unserialize() per ripristinare l'oggetto. Problema: questa funzione non passa il parametro allowed_classes (disponibile da PHP 7.0) per limitare quali classi possono essere istanziate.
Inizia cercando in tutto il codice sorgente del plugin le funzioni di deserializzazione — queste sono le funzioni più pericolose in PHP poiché possono portare a Object Injection:
grep -rn "unserialize" wp-src/wp-content/plugins/contact-form-entries/

L'output rivela molteplici punti di chiamata di maybe_unserialize(), in particolare dentro includes/data.php alla riga 545 nella funzione 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;
}
Domanda chiave: Da dove ha origine la variabile $string? Se proviene da input utente senza filtro → questa è una vulnerabilità.
Trova dove viene invocata verify_val(). Traccia all'indietro nello stesso file data.php:

// 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 è esattamente $v['value'] — i valori recuperati dalla tabella del database wp_vxcf_leads_detail. Questa funzione viene chiamata quando un amministratore visualizza i dettagli di un'entry del modulo.
Domanda successiva: Da dove provengono i dati dentro wp_vxcf_leads_detail? Chi li scrive?
Dal Passo 2 sappiamo che i dati vengono letti dal database. Prossima domanda: chi scrive i dati al suo interno? Cerca query INSERT dentro data.php:
grep -rn "insert" wp-src/wp-content/plugins/contact-form-entries/includes/data.php

Apri il codice della funzione create_lead() (righe 85-103) per i dettagli:
