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 — PoC pour CVE-2026-63030 + CVE-2026-60137, alias WP2Shell | Kitploit
Outils/GitHubGitHub/crypto-cat/wp2shell
Scanners de VulnérabilitésAnalyse de CodeExploitationSécurité WebApprentissage et Éducation
GitHubcrypto-cat/wp2shell

wp2shell

PoC pour CVE-2026-63030 + CVE-2026-60137, alias WP2Shell

Voir le dépôt
3il y a 1 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

wp2shell

Exécution de code à distance sans authentification pour WordPress 6.9.0–6.9.4 et 7.0.0–7.0.1.

Enchaîne CVE-2026-63030 (SQLi par confusion de routes dans le batch) avec CVE-2026-60137 (ré-entrée de changeset du customizer) pour parvenir à la création d'un administrateur sans authentification et à l'exécution de commandes système. Aucun craquage de mot de passe requis.

wp2shell demo

Merci à hashkitten pour la découverte, consultez l'analyse technique complète de SLCyber ici.

La vulnérabilité

Le processeur de lots de l'API REST de WordPress (serve_batch_request_v1) contient un bug d'indexation off-by-one : lorsque wp_parse_url() échoue sur le chemin d'une sous-requête, le WP_Error résultant est poussé dans $validation[] mais pas dans $matches[]. Cela désynchronise les deux tableaux — chaque requête suivante est acheminée vers le mauvais handler.

En imbriquant un lot soigneusement structuré dans un autre lot, un attaquant peut :

  1. Router une requête validée par le schéma d'un endpoint vers le callback d'un endpoint complètement différent
  2. Injecter du SQL non assaini via author__not_in (le cast chaîne→tableau ignore absint())
  3. Utiliser UNION SELECT pour empoisonner le cache d'objets de WordPress avec de faux objets de publication
  4. Déclencher une auto-publication de changeset qui élève les privilèges, puis ré-entrer dans l'API REST avec le contexte admin

Une fois la configuration terminée (découverte du préfixe de table et de l'ID admin), la charge utile d'escalade se déclenche en une seule requête HTTP — l'empoisonnement du cache, l'élévation de privilèges et la création d'utilisateur se produisent tous côté serveur en un seul aller-retour.

Comment fonctionne la chaîne

root@kitploit:~
HTTP POST /batch/v1
    │
    ▼
┌─ Outer Batch ───────────────────────────────────────────────────────┐
│                                                                     │
│  [0] ///                  → parse error, not added to $matches      │
│  [1] POST /wp/v2/posts    → $matches[0] (posts handler)             │
│  [2] POST /batch/v1       → $matches[1] (batch handler)             │
│                                                                     │
│  Desync: request[1] dispatched via $matches[1]                      │
│          POST /wp/v2/posts body interpreted as batch → inner fires  │
│                                                                     │
└──────────────────────────────────────┬──────────────────────────────┘
                                       │
    ┌──────────────────────────────────┘
    ▼
┌─ Inner Batch ───────────────────────────────────────────────────────┐
│                                                                     │
│  [0] ///                            → parse error (desync)          │
│  [1] GET  /wp/v2/widgets?UNION...   → dispatched by posts handler   │
│          ▲ WP_Query fires UNION, poisons object cache               │
│          ▲ the_content renders [embed] → oEmbed → hierarchy Loop 1  │
│              → changeset published → admin context set              │
│              → nav_menu_item UPDATE → hierarchy Loop 2              │
│                  → parse_request → REST re-entry ─────────────┐     │
│                                                               │     │
│  [2] GET  /wp/v2/posts              (categories handler)      │     │
│  [3] GET  /wp/v2/categories         (users handler)           │     │
│  [4] POST /wp/v2/users  {body}  ◄── re-entry with admin ──────┘     │
│          ▲ desync aligns this with users handler                    │
│          ▲ admin context → user created → die()                     │
│  [5] POST /wp/v2/users  {}          (desync spacer)                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Empoisonnement du cache (7 fausses publications via UNION) :

  • Une publication déclencheuse avec un shortcode [embed] dans son contenu
  • Une publication de changeset (customize_changeset, statut future, date dans le passé)
  • Un partenaire de boucle externe (parent=changeset, créant Loop 1)
  • Une cible oEmbed (ID anti-récursion dynamique, parent=changeset, contenu vide)
  • Une publication d'élément de menu de navigation (empoisonnée en post_type=nav_menu_item pour le contrôle is_nav_menu_item)
  • Une publication de ré-entrée (post_type=request, post_status=parse, parent=inner)
  • Un partenaire de boucle interne (parent=re-entry, créant Loop 2)

Flux d'exécution :

  1. UNION empoisonne le cache d'objets avec les 7 fausses publications
  2. Le handler Posts restitue le contenu de la publication déclencheuse → le shortcode [embed] se déclenche
  3. La recherche dans le cache oEmbed trouve une publication de support au contenu vide → retombe sur wp_update_post
  4. wp_update_post lit le changeset en cache (parent=outer) → le contrôle de hiérarchie détecte Loop 1
  5. Le correctif écrit le changeset en base avec le statut future → conversion automatique en publish
  6. _wp_customize_publish_changeset se déclenche → wp_set_current_user(admin_id) → contexte admin actif
  7. Le changeset traite nav_menu_item[real_id] — le cache indique type=nav_menu_item → chemin UPDATE
  8. object_id se résout en une publication en cache avec post_parent=re-entry → wp_update_post sur la publication réelle

Une variable de session MySQL anti-récursion (@_wp2s) garantit que la chaîne se déclenche exactement une fois et ne boucle pas.

Fonctionnalités

  • Trois modes d'extraction avec détection automatique : UNION (1 requête/valeur), basé sur les erreurs via EXTRACTVALUE (~30 caractères/requête), recherche binaire aveugle booléenne (~7 requêtes/caractère)
  • RCE complète sans authentification — aucun identifiant, aucun craquage, l'escalade se déclenche en un seul aller-retour
  • Découverte automatique — préfixe de table via INFORMATION_SCHEMA, ID utilisateur admin via les métadonnées de capacités
  • Post-exploitation — webshell en plugin avec authentification par jeton, shell interactif avec suivi du CWD, lecture/écriture de fichiers
  • Mode nettoyage — --cleanup supprime l'utilisateur créé et retire le webshell à la sortie
  • Zéro dépendance — stdlib uniquement, fichier unique, fonctionne avec Python 3.8+

Installation

root@kitploit:~
git clone https://github.com/Crypto-Cat/wp2shell.git
cd wp2shell
chmod +x wp2shell.py

Pas de pip install, pas de virtualenv. C'est un seul fichier.

Utilisation

Vérifier si une cible est vulnérable

root@kitploit:~
# Passive boolean oracle test
python3 wp2shell.py check http://target.com

# Also confirm with timing and UNION
python3 wp2shell.py check http://target.com --confirm-timing --confirm-union

Extraire des données

root@kitploit:~
# Auto-selects fastest technique (UNION > error > blind)
python3 wp2shell.py read http://target.com --preset users
python3 wp2shell.py read http://target.com --preset secrets
python3 wp2shell.py read http://target.com --query "SELECT @@version"

# Force a specific technique
python3 wp2shell.py read http://target.com --technique blind --preset users

# Auto-discover table prefix
python3 wp2shell.py read http://target.com --auto-prefix --preset users

Exploitation complète

root@kitploit:~
# Exploit and drop into interactive shell
python3 wp2shell.py exploit http://target.com -i

# Exploit, run one command, clean up
python3 wp2shell.py exploit http://target.com -c "cat /etc/passwd" --cleanup

# Skip auto-discovery if you know the prefix
python3 wp2shell.py exploit http://target.com --prefix wp_ --no-discover -i

# Through a proxy (Burp, mitmproxy, etc.)
python3 wp2shell.py exploit http://target.com --proxy http://127.0.0.1:8080 -i

Shell authentifié (avec des identifiants existants)

root@kitploit:~
python3 wp2shell.py shell http://target.com --user admin --password 'P@ssw0rd' -i

Conditions requises pour la RCE complète

Les commandes check et read fonctionnent sur toute cible affectée. La chaîne exploit a trois conditions supplémentaires :

Si la cible utilise Redis ou Memcached comme cache d'objets, split_the_query est forcé quel que soit per_page, et les lignes UNION sont rejetées lors de la récupération par ID uniquement. La commande read fonctionne toujours (l'extraction aveugle n'a pas besoin que UNION survive dans le cache), mais exploit échouera.

Versions affectées

BrancheVersions vulnérablesCorrigé
6.9.x6.9.0 – 6.9.46.9.5
7.0.x7.0.0 – 7.0.17.0.2

Le correctif ajoute $matches[] = $single_request; pour les cas d'erreur (corrigeant le off-by-one) et une garde de ré-entrée dans serve_request().

Architecture

root@kitploit:~
wp2shell.py (single file, ~1650 lines)
├── Client          HTTP transport with batch URL negotiation
├── Desync          Nested batch payload construction
├── BlindExtractor  Boolean binary search (universal)
├── UnionExtractor  In-band via forged post_title (fastest)
├── ErrorExtractor  EXTRACTVALUE-based (intermediate)
├── PoisonGraph     Hierarchy loop structure for cache poisoning
├── Exploiter       Chain orchestration (seed → extract → escalate)
└── AdminSession    Authenticated session, webshell, cleanup

Détails techniques

Pourquoi /wp/v2/widgets comme route source ?

Le contrôleur Widgets n'enregistre pas per_page, orderby ni author_exclude dans son schéma d'endpoint. Ces paramètres passent la validation sans être modifiés (les paramètres inconnus sont ignorés par le validateur de schéma). Lorsque la désynchronisation achemine cette requête via le contrôleur Posts, ces valeurs brutes circulent directement dans WP_Query.

Pourquoi per_page=500 ?

class-wp-query.php:3375 — split_the_query nécessite !empty($limits) && posts_per_page < 500. Avec per_page=500, la condition 500 < 500 est fausse, donc split_the_query est désactivé. La requête complète (y compris UNION) s'exécute comme une seule instruction, et toutes les lignes injectées survivent dans le jeu de résultats et le cache.

Pourquoi nav_menu_item[real_id] (ID positif) ?

L'utilisation d'un ID de publication positif conduit au chemin UPDATE à nav-menu.php:614, qui appelle wp_update_post avec un $post_id non nul. C'est critique car wp_check_post_hierarchy_for_loops à post.php:8070 retourne prématurément lorsque $post_id = 0 (nouvelles publications). Le cache est empoisonné avec post_type=nav_menu_item pour cet ID afin que is_nav_menu_item() passe le contrôle de type à nav-menu.php:426. Le chemin UPDATE déclenche ensuite le contrôle de hiérarchie qui détecte Loop 2.

Pourquoi deux boucles de hiérarchie ?

La boucle 1 (changeset ↔ outer) déclenche la publication du changeset et définit le contexte admin. La boucle 2 (re-entry ↔ inner) se déclenche pendant la fenêtre admin (à l'intérieur de l'appel save() du paramètre d'élément de menu de navigation dans la boucle de publication du changeset) et déclenche parse_request → ré-entrée REST. Les boucles sont indépendantes car le correctif de la boucle 2 doit écrire la publication de ré-entrée en base pendant la fenêtre admin à la ligne 3581 — avant la réinitialisation à la ligne 3589.

Avertissement

Cet outil est publié à des fins de tests de sécurité autorisés et de recherche. Utilisez-le uniquement contre des systèmes que vous possédez ou pour lesquels vous disposez d'une autorisation écrite explicite de test. L'accès non autorisé à des systèmes informatiques est illégal.

Crédits

Recherche et développement par CryptoCat.

Télécharger l’outil
  • Le contrôle de hiérarchie ($post_id non nul) détecte Loop 2 (re-entry ↔ inner)
  • Le correctif appelle wp_update_post(re-entry) → écrit type=request, status=parse en base
  • wp_transition_post_status déclenche do_action("parse_request") → rest_api_loaded() → serve_request()
  • L'API REST ré-entre et retraite l'intégralité du lot avec les privilèges admin
  • POST /wp/v2/users en fin de lot réussit → administrateur créé → die()
  • ExigencePourquoiWP par défaut ?
    Au moins une publication publiéeoEmbed a besoin d'une URL locale pour déclencher le traitement des embedsOui (Hello World)
    Aucun cache d'objets persistantSplit-the-query doit être désactivé pour que les lignes UNION surviventOui (cache fichier par défaut)
    API REST accessibleLa ré-entrée via parse_request nécessite le serveur RESTOui
    Écriture directe sur le système de fichiersLe téléversement de plugin nécessite FS_METHOD=direct ou que PHP possède wp-contentOui (la plupart des hébergeurs)