Article

CVE-2026-87902: unauthenticated file inclusion in WordPress core

Mohammad Jorjandi, Security ResearcherSep 23, 202615 min read

WordPress 7.1.2 closes a critical unauthenticated file inclusion in core, now exploited in the wild. How it works, what makes it code execution, what to check.

CVE-2026-87902, an unauthenticated path traversal in WordPress page-template resolution. Rated critical at CVSS 9.2, requires no login, affects WordPress 4.7 through 7.1.1 and is fixed in 7.1.2.

Update, 23 September 2026: Active exploitation attempts targeting CVE-2026-87902 have now been observed in the wild. Reported activity includes attempts to abuse PEAR's pearcmd.php and write PHP files to affected servers. WordPress administrators should update immediately and review logs for signs of exploitation.

On 22 September 2026 the WordPress security team shipped 7.1.2. It is a security release addressing a single publicly disclosed vulnerability, CVE-2026-87902, and the flaw it closes has been reachable in core since 4.7, close to a decade of releases. The patch itself includes more than one defensive change: direct validation of the affected template-selection path, and broader containment checks around template loading. The flaw needs no account, no cookie and no token.

The short version

Identifier

CVE-2026-87902

Severity

Critical, CVSS v4.0 9.2

Affected

WordPress 4.7.0 through 7.1.1

Fixed in

7.1.2, backported to every branch still receiving security updates

Authentication

None required

Class

CWE-98, improper control of a filename in an include

Reported by

Robert Ressl

Exploitation

Attempts observed in the wild from 22 September 2026

If you run WordPress and you are not on a patched version, the rest of this article is context. Update first.

How it works

WordPress decides what to render by building a list of candidate template filenames and handing them to the template loader. For a page request it constructs one of those candidates from the pagename query variable, in the shape page-{pagename}.php.

A neighbouring branch of the very same resolver passes its candidate through validate_file() before using it. That function exists precisely to reject directory traversal. The pagename branch never called it. The value is URL-decoded along the way, which is what turns a traversal-shaped slug into a real filesystem path.

Diagram of the two branches in WordPress page-template resolution. The sibling branch passes its candidate filename through validate_file and rejects traversal. The pagename branch skips that check, so a PHP file outside the theme directory is included.

Two things are worth sitting with. The first is that this is not an exotic bug class, it is the same file-inclusion shape the web has been patching for twenty years. The second is that the defence already existed a few lines away. Nobody had to invent validate_file() for this fix. It simply was never called on that path.

Why "conditional" is carrying a lot of weight

A 9.2 score can make this sound as though every affected WordPress site is one request away from arbitrary code execution. That is not the case.

The underlying vulnerability is an unauthenticated local PHP file-inclusion primitive. Reaching that primitive, and then turning it into attacker-controlled code execution, depends on additional conditions.

For the file inclusion itself, the important conditions include:

  1. A real, published Page must be reachable. The demonstrated attack uses a valid numeric page_id so WordPress continues far enough into page-template resolution for the vulnerable code to run.

  2. The active parent or child theme needs a top-level directory whose name begins with `page-`. page-templates is the obvious and commonly encountered example. The directory only needs to exist and be traversable; it does not need to be writable.

  3. An earlier custom page template must not win template resolution first. If the selected Page has a valid assigned custom template that resolves before the malicious candidate, that template can prevent the vulnerable candidate from being reached.

  4. The target must be a readable local PHP file. WordPress ultimately attempts to load a .php target, and filesystem controls such as open_basedir, container isolation, or mandatory access-control policies can prevent access to files outside permitted locations.

Turning that inclusion into arbitrary code execution requires still more.

The publicly demonstrated chain uses PEAR's pearcmd.php. For that specific route, PEAR must be present and readable, PHP's register_argc_argv setting must be enabled for the web SAPI, and the PHP process needs a writable location if the attacker intends to create a new PHP payload on disk. The official WordPress advisory notes that this technique is viable where register_argc_argv is enabled, including the official PHP Docker image and default cPanel configurations on PHP versions before 8.5.

Selected prerequisites of the demonstrated PEAR exploitation chain, shown side by side: a theme folder beginning with page-, the PHP setting register_argc_argv enabled in the web-facing runtime, and a reachable PHP file on the server. The complete chain also depends on a readable PEAR pearcmd.php, suitable request handling and filesystem permissions, and a writable destination when creating attacker-controlled PHP content.

Breaking one of those PEAR-specific conditions can stop the demonstrated PEAR-to-RCE chain. It does not remove the underlying WordPress file-inclusion vulnerability, and it does not prove that another useful local PHP target could not be abused. What remains on any unpatched site is an unauthenticated stranger deciding which PHP file your site includes, which is not a footnote either.

That distinction matters. A vulnerability scanner can tell you that the WordPress core version is affected. It cannot, from the version alone, tell you whether your particular server satisfies every condition needed for the known RCE chain. Both facts matter. Only one of them is visible from outside.

How WordPress fixed it

The vulnerable code path handled a decoded pagename value while selecting page templates, but it did not apply the filename validation used elsewhere in WordPress. The patch adds that validation, rejecting unsafe template paths before they can be used.

WordPress did not stop at one narrowly targeted check. The update also adds broader template-path containment through a new private function, _wp_is_template_path_allowed(), which the template loader consults before including a file.

For an existing template path that does not contain a parent-directory traversal component, the function allows the normal lookup to continue. When a candidate contains traversal such as .., WordPress resolves the path with realpath() and permits it only if the final destination remains inside one of the directories WordPress considers valid for template loading, the active stylesheet or template directory, core's theme-compat directory, and the direct parent directory of a theme that is itself installed in a subdirectory.

That matters because the fix is not a blocklist for one exploit string. WordPress now combines direct validation of the vulnerable pagename path with containment checking at the template loader. Custom code that deliberately traverses outside the directories WordPress now considers valid may therefore stop working after the security update.

A note about register_argc_argv

register_argc_argv is an important part of the publicly demonstrated PEAR chain, but disabling it is not a substitute for patching WordPress.

When it is off, the known pearcmd.php technique becomes significantly harder or unavailable, because the attacker can no longer build the expected argument array through the web request in the demonstrated manner. However, the file-inclusion bug still exists on an unpatched installation; other readable PHP files may exist on the system; a different local gadget could produce a different impact; and server configuration varies a great deal between hosts.

PHP has also been moving away from enabling this behaviour for HTTP workloads, and newer versions have tightened it, which reduces exposure on some modern installations. The safe response is the same either way: update WordPress, and treat PHP configuration as a compensating control rather than the primary mitigation.

Threat status, updated 23 September 2026

This vulnerability is no longer only a proof-of-concept or reconnaissance concern.

Initial exploitation activity was observed on 22 September, within hours of the WordPress 7.1.2 release. (Patchstack's own report and BleepingComputer's account of the same telemetry give different first-observation times, so this article does not quote a minute; both place it on release day.) Those early requests primarily attempted to include ordinary WordPress core files and appeared to be probing Internet-facing sites to determine which hosts were vulnerable.

The situation has since escalated.

On 23 September, researchers reported roughly a tenfold increase in CVE-2026-87902-related traffic and observed attackers moving from vulnerability probing to the PEAR-based file-write stage. Observed requests have attempted to include pearcmd.php and use PEAR's config-create command to write attacker-controlled PHP files to disk. Some payloads merely create a marker proving that the server is exploitable, but malicious samples have also been observed writing PHP that executes a shell command when the resulting file is requested. Both GET and POST requests are in use, and public scanning tooling for the CVE is circulating, which substantially lowers the barrier to Internet-scale scanning.

Reported output locations:

/tmp
/var/tmp

Observed filenames have included patterns such as:

wp-pear-rce-flag.php
poc87902.php
luci_<random>.php
zeta_<random>.php

CISA added CVE-2026-87902 to its Known Exploited Vulnerabilities catalogue on 25 September 2026, with a due date of 28 September. It was not yet listed when this post was first published on 23 September, a reminder that KEV inclusion follows exploitation rather than preceding it.

At this point the appropriate assumption is: CVE-2026-87902 is publicly reproducible, being scanned at Internet scale, and active exploitation attempts capable of writing PHP payloads to affected servers have been observed in the wild. Patch immediately and investigate sites that were exposed while running a vulnerable release.

Is my site affected?

Three checks, in the order worth doing them.

Your core version. Dashboard, then Updates. Anything below your branch's patched release is affected. The patched versions are 7.1.2, 7.0.6, 6.9.9, 6.8.10, 6.7.9, 6.6.9, 6.5.12, 6.4.12, 6.3.12, 6.2.13, 6.1.14, 6.0.16, 5.9.18 and downward through 4.7.37.

Your theme. Look in the active theme's folder, and in its parent if it is a child theme, for any top-level directory whose name starts with page-. One ls answers it.

Your PHP. Check the value of register_argc_argv in the PHP runtime that actually serves the website. Your hosting control panel or a temporary phpinfo() page is usually the clearest way to verify it; delete the phpinfo() file immediately after checking. php -i | grep register_argc_argv can help too, but only if the command-line PHP uses the same configuration as the web-facing runtime, PHP CLI and PHP-FPM or Apache can load different configuration files, so a CLI result alone does not establish whether the website is exposed to the demonstrated PEAR chain. PHP 8.5 deprecated deriving argv from the query string for web requests and changed the default to Off, which is why newer hosts are less often exposed.

What you should do

The primary remediation is straightforward: update WordPress immediately to a patched release. Do not rely only on WAF rules, on removing PEAR, or on disabling register_argc_argv. Those measures reduce exposure to specific techniques; they do not repair the vulnerable code.

Dashboard, then Updates, then Update Now. On the command line, wp core update does it. Your branch's patched release is enough, you do not have to jump to 7.1.2 from 5.9 to be safe, because the fix was backported. Jumping is still a good idea for other reasons. On multisite, update from the network admin; individual sites do not update core.

Turn on automatic core updates if they are off. This release is the argument for them. A security point release is exactly the kind of update that should not wait for someone to notice it.

The full response, in order:

  1. Update WordPress to the latest patched release for your branch.

  2. Verify the update completed, the version on the Updates screen is the one that counts.

  3. Purge application and opcode caches if your environment needs it.

  4. Review HTTP and security logs for suspicious pagename activity (below).

  5. Inspect writable directories for unexpected PHP files (below).

  6. Review recently changed WordPress files.

  7. Check administrative users and persistence mechanisms if exploitation is suspected.

  8. Keep WAF and server monitoring on after patching.

If you genuinely cannot update immediately, reduce exposure with temporary compensating controls while you prepare the update:

  • block traversal attempts in pagename, including a WAF rule for double-encoded sequences such as %252e%252e and %252f;

  • set register_argc_argv=Off for HTTP workloads, if your applications permit it;

  • remove or deny web access to unused PEAR command-line entry points;

  • apply filesystem restrictions such as an appropriate open_basedir or container confinement;

  • monitor writable directories for new PHP files.

Renaming the theme's page- directory would also break the demonstrated chain, but altering a production theme's structure is not a clean mitigation and can break template resolution; with a patch available for every branch since 4.7, updating or a WAF rule is the better move. These are compensating controls. They buy time; they are not the fix.

Were you targeted?

Check both network evidence and the filesystem. Neither one is sufficient on its own.

HTTP and security logs

Look for requests containing both page_id= and pagename=. Pay particular attention to pagename values carrying encoded or double-encoded traversal sequences:

%2e%2e
%252e%252e
%252f

Current attack traffic has used values beginning with forms such as templates%2f (or its double-encoded equivalent), because the traversal needs to continue through an existing top-level theme directory whose name begins with page-. Uppercase and lowercase hex have both been seen. Also search for references to:

pearcmd
pearcmd.php
config-show
config-create

Early probing has also been aimed at harmless core files such as wp-links-opml.php to confirm the inclusion works before anything dangerous is attempted, so a traversal pointing at an innocuous file is still a signal.

Do not restrict the search to GET requests. Both GET and POST variants have been observed. WordPress can obtain pagename from POST data, and standard Apache or Nginx access logs normally do not record request bodies, so an access log entry such as POST / HTTP/1.1 may not contain the malicious value at all. Where available, also review WAF logs, reverse-proxy or CDN security logs, ModSecurity logs, application request logs, IDS/IPS telemetry and your hosting provider's security logs. Absence of a malicious pagename value in a standard access log is not proof that the request was never received, and many hosts trim access logs to a few days.

Check the filesystem

This has become particularly important now that active exploitation attempts involving file creation have been observed. The demonstrated PEAR chain writes PHP content outside the WordPress directory.

Inspect writable locations used by the web-server account, especially /tmp and /var/tmp, for recently created PHP files and for files whose names, ownership, contents or timestamps cannot be explained. Reported names associated with current activity include wp-pear-rce-flag.php, poc87902.php, luci_<random>.php and zeta_<random>.php, but do not rely on those names; an attacker can choose another.

Also inspect wp-content/uploads/, cache directories, temporary application directories, writable plugin or theme directories, must-use plugins, and recently modified PHP files anywhere under the WordPress root:

find /tmp /var/tmp -type f -name "*.php" -mtime -7 -ls 2>/dev/null
find /path/to/wordpress -type f -name "*.php" -mtime -7 -ls

Adjust the time window to match the period during which the server was running an affected WordPress version.

If you find evidence that a malicious PHP file was successfully written or executed, treat the server as potentially compromised rather than merely vulnerable. Review WordPress administrator accounts, scheduled tasks, cron jobs, must-use plugins, SSH keys, modified application files, and outbound activity from the web-server account. A suspicious request does not by itself mean exploitation succeeded; a successful file write does.

Where PowerSEC fits

PowerSEC users should still update WordPress. Vulnerability detection and attack monitoring are additional layers, not replacements for the upstream security patch.

What PowerSEC does here is name the problem: it watches the version WordPress reports and checks it against a curated vulnerability feed, so a site running an affected build is listed rather than inferred. Across a fleet that turns a morning of opening dashboards into one list. If suspicious activity is detected, investigate both the WordPress installation and the underlying server for file writes and persistence, the file-integrity monitor and the audit log speak to that in evidence, not reassurance.

Two things are worth stating plainly, because the industry is not always careful here.

Vulnerability scanning is on every plan, including the free one. Knowing that your site is vulnerable is not a premium feature. It runs on your own server and reports what it finds. What the paid tiers add is the fleet view, off-site backups, scheduled rescans from our side, and incident tooling.

A scan tells you what is out of date. It does not tell you that you were not attacked. Those are different questions, and anyone selling you the second one on the strength of the first is overselling.

If you manage more than a handful of WordPress sites, connect them and let the version check run. For this particular flaw, the answer you want is boring: every site on a patched build.

Bottom line

CVE-2026-87902 is not simply a theoretical path-traversal issue. The underlying flaw lets an unauthenticated attacker steer WordPress template resolution into including a local PHP file, and under additional server-side conditions that becomes remote code execution.

The demonstrated PEAR technique is environment-dependent, but that distinction must not be used to minimise the vulnerability. Breaking the PEAR chain closes one known route to code execution; it does not repair the file-inclusion flaw. And exploitation attempts have now been observed in the wild.

If your installation is running an affected version, update it now. After patching, review logs and writable server locations, particularly if the site was reachable from the Internet before the update. The safe assumption is no longer that this vulnerability is being studied only by researchers. It is part of the active WordPress threat landscape.

Sources

Update, 25 September 2026: CISA added CVE-2026-87902 to its Known Exploited Vulnerabilities catalogue, with a due date of 28 September. Exploitation attempts have been observed in the wild; treat the window as closed, not short.

WordPressVulnerabilitiesWordPress Core

Keep reading

Hacked? Talk to us