
Exploit PoC y análisis de causa raíz para una inyección crítica de objetos PHP sin autenticación en WordPress Database for Contact Form 7, que conduce a RCE mediante la eliminación arbitraria de archivos.
Plugin: Database for Contact Form 7 (contact-form-entries) ≤ 1.4.3
CVSS: 9.8 (Crítico)
CWE: CWE-502 — Deserialización de Datos No Confiables
Requisito de autenticación: Ninguno (No autenticado)
Impacto: Ejecución Remota de Código
El plugin "Database for Contact Form 7" (slug: contact-form-entries), en su versión 1.4.3 e inferiores, contiene una vulnerabilidad de Inyección de Objetos PHP. Cuando un administrador de WordPress visualiza un registro (entrada) de formulario dentro del panel de administración, el plugin llama a la función maybe_unserialize() directamente sobre los datos enviados por un usuario no autenticado a través de Contact Form 7, sin controlar la lista de clases permitidas para instanciar.
Un atacante no necesita iniciar sesión; solo necesita enviar un formulario de contacto normal insertando un objeto PHP serializado en cualquier campo del formulario. Estos datos se almacenan sin procesar en la base de datos. Cuando un administrador abre y ve esa entrada, la función de deserialización instancia un objeto elegido por el atacante, disparando métodos mágicos como __destruct() o __wakeup() → ejecutando comportamiento arbitrario dependiendo de los gadgets POP disponibles en el entorno de WordPress.
Nivel de severidad: Con un gadget POP adecuado (por ejemplo, una clase cuyo método
__destruct()llama aunlink()), un atacante puede eliminar el archivowp-config.php, devolviendo WordPress a su pantalla de instalación inicial → reinstalando con una cuenta de administrador controlada por el atacante → instalando un plugin que contiene un webshell → logrando la Ejecución Remota de Código (RCE) completa en el servidor.
| Atributo | Valor |
|---|---|
| CVE ID | CVE-2025-7384 |
| Puntuación CVSS | 9.8 (Crítico) |
| CWE | CWE-502 — Deserialización de Datos No Confiables |
| Plugin afectado | contact-form-entries (Database for Contact Form 7) ≤ 1.4.3 |
| Requisito de autenticación | Ninguno — cualquiera que envíe un formulario CF7 puede inyectar el payload |
| Condición de activación | El administrador ve la entrada inyectada en el panel de administración |
| Impacto máximo | Ejecución Remota de Código no autenticada |
| Versión parcheada | 1.4.4+ (reemplaza unserialize con json_decode o allowed_classes: false) |
PHP usa serialize() para convertir un objeto en una cadena de texto estructurada, y unserialize() para restaurar el objeto desde esa cadena. Cuando unserialize() recibe datos de una fuente no confiable (por ejemplo, entrada de usuario), un atacante puede construir un objeto arbitrario perteneciente a cualquier clase cargada en la memoria de PHP en ese momento.
Métodos especiales que PHP invoca automáticamente durante el ciclo de vida de un objeto. Los más importantes en este contexto:
__wakeup() — se invoca inmediatamente cuando se deserializa un objeto__destruct() — se invoca cuando un objeto se destruye (sale del ámbito, o la petición termina)__toString() — se invoca cuando un objeto se convierte a cadenaTécnica que encadena múltiples métodos mágicos de clases existentes dentro de la aplicación para construir una secuencia peligrosa de comportamientos. El atacante no escribe código nuevo; solo manipula las propiedades de objetos existentes para que, cuando se ejecuten los métodos mágicos, realicen acciones no previstas por los desarrolladores.
maybe_unserialize() en WordPressUna función envolvente del núcleo de WordPress. Llama a is_serialized() para comprobar si una cadena son datos serializados; si es así, llama a unserialize() para restaurar el objeto. Problema: esta función no pasa el parámetro allowed_classes (disponible desde PHP 7.0) para limitar qué clases se permite instanciar.
Comience ejecutando grep en todo el código fuente del plugin para localizar las funciones de deserialización — estas son las funciones más peligrosas en PHP, ya que pueden conducir a Inyección de Objetos:
grep -rn "unserialize" wp-src/wp-content/plugins/contact-form-entries/

El resultado revela múltiples puntos de llamada a maybe_unserialize(), especialmente dentro de includes/data.php, línea 545, en la función 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;
}
Pregunta clave: ¿De dónde proviene la variable $string? Si viene de entrada de usuario sin filtrado → esto es una vulnerabilidad.
Localice dónde se invoca verify_val(). Rastree hacia atrás en el mismo archivo 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 es precisamente $v['value'] — valores obtenidos de la tabla de la base de datos wp_vxcf_leads_detail. Esta función se llama cuando un administrador ve los detalles de una entrada de formulario.
Siguiente pregunta: ¿De dónde provienen los datos dentro de wp_vxcf_leads_detail? ¿Quién los escribe?
Del Paso 2 sabemos que los datos se obtienen de la base de datos. Siguiente pregunta: ¿quién escribe los datos en ella? Busque consultas INSERT dentro de data.php:
grep -rn "insert" wp-src/wp-content/plugins/contact-form-entries/includes/data.php

Abra el código de la función create_lead() (líneas 85-103) para más detalles:
