Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-87902 — WordPress Core <= 7.1.1 unauthenticated LFI to RCE - validation lab, PoC, nuclei template (GHSA-7hp8-65ch-5whp) | Kitploit
Tools/GitHubGitHub/hassham1/cve-2026-87902
Vulnerability ScannersVulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingPapers & ResearchLearning & EducationPayload DevelopmentLabs & Practice
GitHub
1416h 21m agoNot yet reviewed
hassham1/cve-2026-87902

CVE-2026-87902

WordPress Core <= 7.1.1 unauthenticated LFI to RCE - validation lab, PoC, nuclei template (GHSA-7hp8-65ch-5whp)

View Repository

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-87902 - WordPress Core Unauthenticated LFI → RCE Validation Lab

Security-research material for reproducing and validating CVE-2026-87902 (GHSA-7hp8-65ch-5whp) in an isolated, loopback-only Docker lab: an unauthenticated local file inclusion in WordPress core page-template resolution, escalable to remote code execution.

  • Affected: WordPress 4.7.0 through 7.1.1 (every maintained branch)
  • Fixed: 7.1.2 and per-branch backports (4.7.37, 5.9.18, 6.9.9, 7.0.6, …) - 22 Sep 2026
  • CVSS 4.0: 9.2 (AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N)
  • CWE-98 - Improper Control of Filename for Include/Require Statement

Verdict

Status: proven in the supplied lab.

WordPress 7.1.1 (Twenty Twelve theme) permits an unauthenticated attacker to include PHP files from outside the theme directory via the pagename query variable, and - with the stock register_argc_argv = On of the official PHP image - to write and execute a marker file via PEAR pearcmd.php:

root@kitploit:~
WordPress: 7.1.1 Theme: twentytwelve (page-templates/ present) PHP: 8.2 (official image, register_argc_argv=On, PEAR present) Result: LFI oracle fired (<opml version="1.0">); marker probe executed as uid=33(www-data) and self-deleted

WordPress 7.1.2 with the identical setup blocks the oracle entirely (negative control). This demonstrates code execution as the WordPress web-server user inside the container. It does not demonstrate root access, container escape, lateral movement, or host compromise.

Quick start

Requirements: Docker with Compose v2, Python 3.10+, network access to Docker Hub and wordpress.org (theme install).

root@kitploit:~
./lab verify

This builds the vulnerable lab, requires the LFI oracle and the RCE marker to fire, then builds the patched lab and requires the oracle to stay silent.

To use another loopback port: LAB_PORT=9499 ./lab verify.

Remove everything afterwards with ./lab reset.

Expected evidence

Vulnerable control:

root@kitploit:~
[+] page-* folder(s) reachable: page-templates (traversal can resolve)
[!!!] LFI CONFIRMED (safe oracle: wp-links-opml.php)
...
[RCE] pearcmd.php reachable and taking argv: /usr/local/lib/php/pearcmd.php
[!!!] RCE CONFIRMED (pearcmd.php config-create file write):
  evidence: uid=33(www-data) (command `id` ran as the web user)
  cleanup : probe self-deleted on execution: confirmed
RESULT: VULNERABLE CONTROL CONFIRMED

Patched control:

root@kitploit:~
[=] oracles did not fire: target patched (>= 7.1.2), ...
RESULT: PATCHED CONTROL CONFIRMED

An HTTP 200, a WAF block, or a partial traversal without the oracle marker is not accepted as proof.

Manual lab lifecycle

root@kitploit:~
./lab start vulnerable   # fresh 7.1.1 lab on 127.0.0.1:9471
./lab test               # assert LFI + RCE
./lab start patched      # fresh 7.1.2 lab
./lab test               # assert oracle absence
./lab status
./lab reset              # containers, volumes, state

Run the tooling by hand against the running lab:

root@kitploit:~
python3 poc/triage_cve_2026_87902.py --url http://127.0.0.1:9471
python3 poc/poc_cve_2026_87902.py --url http://127.0.0.1:9471 --rce
nuclei -t nuclei/cve-2026-87902.yaml -u http://127.0.0.1:9471 -silent

For systems you own or are explicitly authorized to test (bug-bounty scope, written engagement), the drivers require the explicit override:

root@kitploit:~
python3 poc/poc_cve_2026_87902.py --url https://target.example \
  --allow-authorized-non-loopback [--insecure] [--rce]

Root cause

get_page_template() in wp-includes/template.php builds a candidate template filename from the pagename query variable. The sibling branch three lines above passes the page-template slug through validate_file(); the pagename branch never did, and additionally pushes an extra urldecode()-decoded candidate:

root@kitploit:~
// wp-includes/template.php, get_page_template(), WordPress <= 7.1.1
if ( $pagename ) {
    $pagename_decoded = urldecode( $pagename );
    if ( $pagename_decoded !== $pagename ) {          // 7.1.2 adds validate_file()
        $templates[] = "page-{$pagename_decoded}.php";
    }
    $templates[] = "page-{$pagename}.php";
}

7.1.2 applies validate_file() to the decoded candidate and adds a containment gate (_wp_is_template_path_allowed()) that every resolved template path must pass.

Preconditions

  1. The active theme (child or parent - locate_template() checks both) ships a top-level directory starting with page-. Legacy default themes (Twenty Twelve, Twenty Fourteen) and several wp.org themes (Neve, Hestia, Sydney) qualify. Researched commercial themes (Divi, Avada, Flatsome, Woodmart, Betheme, The7, Enfold, Salient, Jupiter/X, Porto, Houzez, XStore, Kalium, Uncode) ship none - the traversal cannot resolve; see PRO_THEME_PAGE_DIRS in the PoC for the mirror-sourced list.
  2. The included file must end in .php (appended by the code).
  3. RCE additionally needs register_argc_argv = On and PEAR's pearcmd.php.

Lab-verified operational notes (beyond the public advisories)

The advisory and the Patchstack writeup decline to publish a working request. These details were established empirically in this lab:

  1. A page_id anchor is mandatory. An unresolved pagename flips is_404, whose template shadows get_page_template() entirely. Anchor the query with any resolvable published page_id.
  2. Full double-encoding is required. get_posts() rewrites pagename through sanitize_title_for_query( wp_basename( … ) ): raw .. becomes -, but %hex sequences survive; the extra urldecode() in get_page_template() reveals the traversal. Only %252e/%252f works.
  3. Pretty permalinks force POST (redirect_canonical 301s GET and drops the payload; it returns immediately for non-GET/HEAD).
  4. Exact depth: four ../ from page-templates/ on a standard docroot.
  5. The payload fires from the POST body too (WP merges $_POST into query vars) - the basis of the WAF-evasion tiers.
  6. pearcmd transport quirks: PHP does not urldecode $argv tokens, and PEAR's config serializer escapes both quote styles - hence the raw, quoteless, +-separated probe.

Toolkit

PathPurpose
poc/poc_cve_2026_87902.pyFull PoC: version + WAF fingerprint, theme/page-* folder enumeration (wp.org SVN, asset-reference discovery, researched pro-theme map), anchor discovery, 8-variant LFI oracle, comparative empty-body fallback oracle, --rce pearcmd chain, --lab docker demo
poc/triage_cve_2026_87902.pyPer-target or batch verdict with the named failing gate (EXPLOITABLE / NOT EXPLOITABLE / WAF SUSPECTED / INCONCLUSIVE)
poc/bounty_screen.pyWorks an in-scope bug-bounty asset list: crt.sh wildcard expansion → WP filter → precondition screen → candidate handoff
nuclei/cve-2026-87902.yamlNuclei template: safe LFI oracle (174 payloads: 10 page-* remainders, double/triple-encoded tiers, ids 1–30)

WAF fingerprinting covers Imperva/Incapsula, Cloudflare, Akamai, AWS CloudFront/WAF, Sucuri, F5. Eight oracle variants: double-encoded, alternate %25%32%65 spelling, parameter pollution, triple-encoded - each in query string and POST body. Multipart bodies and raw-slash payloads were tested and rejected (WP does not process them for this sink).

Affected and tested versions

VersionAssessment
WordPress 7.1.1LFI + pearcmd RCE reproduced end to end
WordPress 6.5.3 / 5.9.17LFI oracle reproduced
WordPress 4.7.0–7.1.1 (other branches)Reported affected; branch table per GHSA
WordPress 7.1.2Patched negative control reproduced

Repository layout

root@kitploit:~
.
├── .github/workflows/validate.yml
├── .gitignore
├── LICENSE
├── README.md
├── SECURITY.md
├── docker-compose.yml
├── lab
├── nuclei/
│   └── cve-2026-87902.yaml
└── poc/
    ├── bounty_screen.py
    ├── poc_cve_2026_87902.py
    └── triage_cve_2026_87902.py

Not committed: .lab-state, __pycache__/, screener output files.

Validation boundaries

ClaimStatus
Unauthenticated LFI outside the theme directoryProven (7.1.1, 6.5.3, 5.9.17)
RCE via pearcmd.php config-create as the web userProven (7.1.1 lab)
Probe self-deletion / no persistent artifactsProven
Patched negative control (7.1.2)Proven
Every intermediate affected release testedNot tested
WAF bypass variants against live commercial WAFsLab-derived, not WAF-tested
Root privilege / container escape / host compromiseNot claimed

References

  • GHSA-7hp8-65ch-5whp
  • Patchstack: WordPress 7.1.2 security release - unauthenticated LFI to RCE
  • Hadrian: CVE-2026-87902 working PoC - independent reconstruction (confirms the page_id anchor and double-encoding requirements; source of the comparative wp-content/index.php empty-body oracle, included here as a fallback for hardened sites)
  • dinosn: cve-2026-87902-wordpress-lfi-lab (independent lab arriving at the same request shapes; source of the nonexistent-target causation control now applied to every oracle hit)
  • WordPress 7.1.2 release announcement
  • NVD entry

Vulnerability reported by Robert Ressl. This repository is independent third-party analysis and tooling.

License

MIT - see LICENSE.

Safety boundary

Run this repository only on systems you own or are explicitly authorized to test. The lab driver refuses variants other than vulnerable/patched, the Python drivers refuse non-loopback targets unless --allow-authorized-non-loopback is supplied, and the Compose port is bound to 127.0.0.1.

See SECURITY.md for disclosure handling. This repository does not include production targets, customer data, access tokens, or evidence from real systems.

Concretely:

  • Docker publishes WordPress on 127.0.0.1 only.
  • The default PoC mode is a harmless LFI oracle: it includes wp-links-opml.php, a stock core file that prints an OPML document.
  • --rce is opt-in and writes a quoteless, self-deleting marker probe (<?=id,unlink(__FILE__)?>) via the publicly documented pearcmd.php config-create technique; the probe removes itself on its single execution and the PoC verifies deletion. No shells, callbacks, or persistence.
  • The lab re-provisions from pristine official images on every start; no target instrumentation.
  • Requests are single-host, sequential, and rate-limited. No mass scanning.
Download Tool