
Lab vulnérable (Docker) + PoC Python pour la CVE-2026-87902 — path traversal non authentifié dans WordPress Core (page-template -> LFI -> RCE conditionnel). Usage éducatif/autorisé.
Lab délibérément vulnérable + PoC en Python pour la CVE-2026-87902 : path traversal non authentifié dans la résolution de page template du WordPress Core, menant à une Local File Inclusion (LFI) et à une RCE conditionnelle.
| Champ | Valeur |
|---|---|
| Produit | WordPress Core |
| Versions affectées | 4.7.0 → 7.1.1 |
| Correctif | 7.1.2 (backport jusqu'à 4.7.37) |
| Type | Path Traversal (CWE-22) → LFI → RCE conditionnelle |
| Authentification | Aucune (non authentifié) |
| CVSS 4.0 | 9.2 (Critique) |
| Publication | 2026-09-23 |
⚠️ Avertissement légal / éthique. Tout le matériel ici est destiné à l'étude en environnement local et autorisé. Le lab est une image Docker volontairement non sécurisée — ne l'exposez pas sur Internet. L'
exploit.pycible par défauthttp://localhost:8091à dessein. Exécuter le PoC contre n'importe quel système sans autorisation écrite explicite est un crime. Vous êtes seul responsable de l'usage.
EXPLOIT-CVE-2026-87902/
├── README.md # este arquivo
├── docker-compose.yml # sobe o lab em localhost:8091
├── lab/
│ ├── Dockerfile # php:8.2-apache + PEAR + register_argc_argv=On
│ ├── config/zz-lab.ini # pré-condições ambientais de RCE
│ └── src/ # "WordPress mini" que reproduz o trecho vulnerável
│ ├── index.php # front controller (≈ template-loader.php)
│ ├── wp-mini.php # get_page_template()/locate_template()/... vulneráveis
│ ├── private/
│ │ └── secret-config.php # alvo .php fora do tema (prova de LFI)
│ └── wp-content/themes/twentytwelve-mini/
│ ├── style.css
│ ├── index.php
│ └── page-templates/ # diretório "page-*" exigido p/ a travessia
│ └── full-width.php
└── exploit/
├── exploit.py # PoC (pt-BR): LFI + cadeia RCE via pearcmd
└── requirements.txt
Prérequis : Docker + Docker Compose et Python 3 avec requests.
# 1) Suba o lab (fica em http://localhost:8091)
docker compose up --build
# 2) Em outro terminal, instale a dependência do PoC e rode
cd exploit
pip install -r requirements.txt
python3 exploit.py # roda LFI + RCE contra o lab local
# Opções úteis
python3 exploit.py --mode lfi # só a prova de LFI
python3 exploit.py --mode rce --cmd 'uname -a'
python3 exploit.py --target http://localhost:8091
# 3) Derrube o lab
docker compose down
Sortie attendue (résumé) :
[LFI] Sucesso! Arquivo externo incluído -> SEGREDO_DO_LAB{lfi_via_page_template_traversal}
[RCE] Comando executado no servidor:
------------------------------------------------------------
uid=33(www-data) gid=33(www-data) groups=33(www-data)
FLAG{cve_2026_87902_rce_no_wordpress_mini}
------------------------------------------------------------
Dans get_page_template(), la valeur de la query var publique pagename
est utilisée pour construire le nom du template. Le core valide la valeur
brute avec validate_file() (qui bloque .. littéral), mais ensuite
décode la valeur une fois et utilise le résultat comme candidat au template
sans revalider (reproduction fidèle dans wp-mini.php) :
if ($pagename !== '' && 0 === validate_file($pagename)) {
$templates[] = "page-{$pagename}.php"; // valor cru (ainda encodado)
$pagename_decoded = urldecode($pagename); // <-- 2ª decodificação
if ($pagename_decoded !== $pagename) {
$templates[] = "page-{$pagename_decoded}.php"; // travessia reintroduzida
}
}
Le locator concatène ensuite thème + "/" + template et inclut le premier
fichier qui existe, sans realpath() ni vérification que le résultat reste
à l'intérieur du répertoire du thème.
L'attaquant envoie pagename doublement encodé :
Valor no fio (POST body): templates%252f%252e%252e%252f...%252fpearcmd
1ª decodificação (HTTP): templates%2f%2e%2e%2f...%2fpearcmd <- sem '..' literal => passa por validate_file()
2ª decodificação (core): templates/../../.../pearcmd <- travessia real
Comme le locator préfixe page-, le chemin devient
page-templates/../../.../pearcmd.php. Cela résout en dehors du thème.
page- (ici,
page-templates/). C'est le véritable point de départ du path traversal :
sous Linux, chaque composant du chemin doit exister pour que les ../ se
résolvent. Les thèmes hérités (Twenty Twelve/Fourteen) et populaires (Neve,
Hestia, Sydney) remplissent cette condition.register_argc_argv = On dans PHP → $_SERVER['argv'] est peuplé à
partir de la query string.pearcmd.php lisible sur le serveur (l'image officielle php:8.2-apache
le fournit déjà dans /usr/local/lib/php/pearcmd.php, dans l'include_path
par défaut).Comme le locator ajoute .php, la LFI n'atteint que des fichiers .php. C'est
pourquoi la route pratique vers la RCE s'enchaîne avec pearcmd.php :
pearcmd.php en passant, via la query string
(=argv), config-create pour écrire un webshell .php dans /tmp./tmp/<webshell>.php via un nouveau path traversal
→ le PHP de l'attaquant s'exécute.Le patch traite la valeur de pagename de manière cohérente : il valide
après tout décodage (ou rejette les entrées contenant des séquences de
traversée encodées) et impose désormais la frontière du répertoire lors de
la résolution du template (le chemin final doit rester à l'intérieur du thème).
Résultat : la valeur doublement encodée ne survit plus jusqu'à l'include.
Mitigations : mettre à jour vers ≥ 7.1.2 ; et, en défense en
profondeur, register_argc_argv = Off, supprimer/restreindre pearcmd.php, et
un WAF qui détecte ../ encodé (%2e%2e%2f, %252e...) dans les requêtes.
../ encodé/doublement encodé
(%2e%2e%2f, %252e%252e%252f) dans les paramètres.pagename), surtout combinées à des query strings contenant
config-create / des références à pearcmd..php dans /tmp suivie d'une inclusion.