FlawOpen

flawopen.com/Teardowns/cve-2026-87902-wordpress-page-template-path-traversal

● CVE-2026-87902 · CVSS 9.2[1] · Critical
FlawOpen Security Research

CVE-2026-87902: WordPress Page-Template Path Traversal

An unvalidated decoded page name entered WordPress template resolution, allowing unauthenticated local PHP file inclusion and conditional RCE when the theme and PHP environment met specific prerequisites.[1]

💡 Plain English Explainer (ELI5)

Think of a librarian who checks a book request against the shelves before fetching it. WordPress checked one route into its template list, but forgot to check the decoded page-name route; special path markers could make the librarian leave the theme shelves and fetch a PHP file elsewhere. The fix checks the decoded name and verifies the resolved template remains in an allowed theme directory.[1], [2], [3]

Core Concepts & Subsystem Terms

Page-template resolution
WordPress builds an ordered list of candidate PHP templates for a page and asks the active theme to provide the first matching file.[1], [2]
Path traversal
Path components such as .. can escape an intended directory when input is incorporated into a filename without validation.[1]
Local PHP file inclusion
The application loads a PHP file already present on the server. The advisory says this can become RCE only when additional theme and server conditions are met.[1]
`validate_file()`
A WordPress helper that rejects disallowed path components such as directory traversal before a candidate is used in a file path.[3]
CWE-98
CWE-98 — Improper Control of Filename for Include/Require: input is not adequately restricted before selecting a PHP file to include or require.[1], [7]

Framework mappings

Root Cause Analysis

In get_page_template() in wp-includes/template.php, WordPress URL-decodes the pagename query value and adds a decoded page-{name}.php candidate when it differs from the original. In vulnerable releases that decoded candidate was not passed to validate_file(), even though the adjacent custom-template branch was. The template loader could therefore resolve a candidate beyond the active theme directory. The official advisory limits the impact: an active parent or child theme needs a top-level directory beginning with page-, and a readable local PHP target must exist; RCE requires further PHP/server conditions. WordPress 7.1.2 validates the decoded candidate and adds a resolved-path containment check in locate_template(). The WordPress advisory classifies this weakness as CWE-98 (improper control of a filename used in a PHP include/require) and credits Robert Ressl with discovery and responsible disclosure.[1], [2], [3], [7]

CVE-2026-87902 reachability: exposure versus conditional RCE

Deployment conditionWhat the primary sources establishOperator action
Affected WordPress core branchThe GHSA lists affected versions by branch, from 4.7.0–4.7.36 through 7.1.0–7.1.1. The corresponding fixes range from 4.7.37 (oldest backport) to 7.1.2 (current branch), with a separate fixed release for each listed branch.Check the installed core version against the matching branch fix. Upgrade versions older than 4.7 to a supported release.
Active theme layoutThe active parent or child theme must contain a top-level directory whose name begins with page- (for example, page-templates).Inspect both active theme directories. Recheck this prerequisite when the active theme changes.
Readable local PHP targetA chosen .php file must exist outside the active theme directories and be readable by the web-server account.Review which local PHP files are readable by the web server; do not infer file inclusion from the CVE version range alone.
Conditional RCE environmentThe advisory describes RCE only with additional PHP/server conditions. Its examples include pearcmd.php when register_argc_argv=On, the official PHP Docker image, and default cPanel configurations using PHP before 8.5.Assess the actual PHP runtime and configuration. A vulnerable WordPress version alone does not establish RCE or compromise.

Step-by-Step Attack Flow

Step 1

Untrusted page name reaches template selection

An unauthenticated request supplies a page name that WordPress uses while building page-template candidates. No account or user click is required.[1]

Step 2

A second decode changes path meaning

get_page_template() URL-decodes the page name again. In affected versions, a decoded candidate could be added without the adjacent validate_file() check.[2], [3]

Step 3

Theme lookup can leave its intended root

If the active theme has the documented page-* directory and a readable local PHP target is present, template resolution may include a file outside the theme directory.[1]

Step 4

RCE depends on additional environment conditions

The WordPress advisory describes remote code execution as conditional on further PHP/server prerequisites. Do not treat every affected WordPress installation as remotely exploitable or infer compromise from version alone.[1]

Source Code: Flaw vs. Secure Implementation

This excerpt compares the official WordPress 7.1.1 and 7.1.2 source tags. The patch applies the existing WordPress path validator after decoding and before adding the candidate. The same security release also adds _wp_is_template_path_allowed() in locate_template() to constrain resolved templates to approved roots. This is the upstream source change, not a complete standalone patch or an exploit demonstration.[2], [3]

Source excerpt: wp-includes/template.php — get_page_template()

UNPATCHED FLAW
if ( $pagename ) {
    $pagename_decoded = urldecode( $pagename );

    if ( $pagename_decoded !== $pagename ) {
        $templates[] = "page-{$pagename_decoded}.php";
    }
}
HARDENED SECURE PATCH
if ( $pagename ) {
    $pagename_decoded = urldecode( $pagename );

    if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
        $templates[] = "page-{$pagename_decoded}.php";
    }
}

Engineering & System Hardening Checklist

Sources