Skip to content
Exploit for CVE-2026

Exploit for CVE-2026

Sploitus • September 23, 2026

- **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

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`:

Theme: twentytwelve (page-templates/ present)

PHP: 8.2 (official image, register_argc_argv=On, PEAR present)

Result: LFI oracle fired (); marker probe executed as

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.

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

See [SECURITY.md](SECURITY.md) for disclosure handling. This repository does

not include production targets, customer data, access tokens, or evidence

- 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

(``) 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

- Requests are single-host, sequential, and rate-limited. No mass scanning.

Requirements: Docker with Compose v2, Python 3.10+, network access to

Docker Hub and wordpress.org (theme install).

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`.

[+] 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

[=] oracles did not fire: target patched (>= 7.1.2), ...

An HTTP 200, a WAF block, or a partial traversal without the oracle marker is

./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 reset # containers, volumes, state

Run the tooling by hand against the running lab:

python3 poc/triage_cve_2026_87902.py --url

python3 poc/poc_cve_2026_87902.py --url --rce

nuclei -t nuclei/cve-2026-87902.yaml -u -silent

For systems you own or are explicitly authorized to test (bug-bounty scope,

written engagement), the drivers require the explicit override:

python3 poc/poc_cve_2026_87902.py --url \

--allow-authorized-non-loopback [--insecure] [--rce]

`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

// wp-includes/template.php, get_page_template(), WordPress <= 7.1.1

$pagename_decoded = urldecode( $pagename );

if ( $pagename_decoded !== $pagename ) { // 7.1.2 adds validate_file()

$templates[] = "page-{$pagename_decoded}.php";

7.1.2 applies `validate_file()` to the decoded candidate **and** adds a

containment gate (`_wp_is_template_path_allowed()`) that every resolved

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,

| `poc/poc_cve_2026_87902.py` | Full 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.py` | Per-target or batch verdict with the named failing gate (EXPLOITABLE / NOT EXPLOITABLE / WAF SUSPECTED / INCONCLUSIVE) |

| `poc/bounty_screen.py` | Works an **in-scope** bug-bounty asset list: crt.sh wildcard expansion → WP filter → precondition screen → candidate handoff |

| `nuclei/cve-2026-87902.yaml` | Nuclei 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).

| WordPress 7.1.1 | LFI + pearcmd RCE reproduced end to end |

| WordPress 6.5.3 / 5.9.17 | LFI oracle reproduced |

| WordPress 4.7.0–7.1.1 (other branches) | Reported affected; branch table per GHSA |

| WordPress 7.1.2 | Patched negative control reproduced |

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

| Unauthenticated LFI outside the theme directory | Proven (7.1.1, 6.5.3, 5.9.17) |

| RCE via pearcmd.php `config-create` as the web user | Proven (7.1.1 lab) |

| Probe self-deletion / no persistent artifacts | Proven |

| Patched negative control (7.1.2) | Proven |

| Every intermediate affected release tested | Not tested |

| WAF bypass variants against live commercial WAFs | Lab-derived, not WAF-tested |

| Root privilege / container escape / host compromise | Not claimed |

- [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

- [WordPress 7.1.2 release announcement](

- [NVD entry](

Vulnerability reported by Robert Ressl. This repository is independent

Extracted Entities

Attack Types (1)

Platforms (2)

Ransomware Groups (1)