
CVE-2026-87902: WordPress 7.1.2 Fixes a Critical Unauthenticated Path Traversal
CVE-2026-87902 lets anyone load local PHP files via WordPress page templates in 4.7 to 7.1.1. Which version to update to, and how to check your exposure.

WordPress 7.1.2 shipped on September 22, 2026 with a single fix, and it is a serious one. CVE-2026-87902 is a path traversal flaw in how WordPress picks the template file for a page. An attacker with no account can make WordPress load a PHP file from outside the active theme. On servers where a couple of other conditions line up, that turns into remote code execution.
Every release from 4.7.0 through 7.1.1 is affected, which covers close to ten years of WordPress. That includes 7.1.1, the security release from September 17, so a site you patched last week still needs this update. This guide covers which version to move to on each branch, how the flaw works without handing anyone an exploit, how to check how exposed a specific site is, and what to do if you can't update right away.
TL;DR
- CVE-2026-87902 (GHSA-7hp8-65ch-5whp) is an unauthenticated path traversal in page template resolution, rated critical with a CVSS 4.0 score of 9.2.
- Affected: WordPress 4.7.0 through 7.1.1. Fixed: 7.1.2, with fixed releases for every branch back to 4.7 (4.7.37). WordPress 4.6 and older get no fix.
- Update to the latest release on your branch today. There is no official workaround.
- Code execution needs two extra conditions: the active theme has a top-level folder whose name starts with
page-, and PHP hasregister_argc_argvturned on for web requests.- Check
register_argc_argvthrough a web request, not withphp -ior WP-CLI. The command line always reports it as on.- As of September 22, no attacks, no public exploit, and no CISA KEV listing had been reported.
What is CVE-2026-87902?
CVE-2026-87902 is a flaw in WordPress core's get_page_template() function, in wp-includes/template.php. When WordPress renders a page, it builds a list of candidate template file names and loads the first one it finds in the theme. One of those names comes from the page slug in the request, and on affected versions WordPress did not check that value for directory traversal before using it.
The result, in the words of the official advisory, is that an unauthenticated attacker can make page template resolution include a chosen readable local .php file outside the active theme directories. Whether that goes further depends on the server and the theme, which is why WordPress describes the outcome as conditional remote code execution.
| Detail | Value |
|---|---|
| CVE | CVE-2026-87902 |
| Advisory | GHSA-7hp8-65ch-5whp |
| Severity | Critical, CVSS 4.0 score 9.2 |
| Weakness | CWE-98, improper control of filename for include |
| Authentication needed | None |
| User interaction needed | None |
| Affected | 4.7.0 through 7.1.1 |
| Fixed | 7.1.2 and backports down to 4.7.37 |
| Reported by | Robert Ressl |
| Disclosed | September 22, 2026 |
The CVSS vector marks "Attack Requirements: Present." That is the scoring system's way of saying the attack only fully works when certain conditions exist in the deployment. It is the reason this is a 9.2 and not a 10, and it is also the reason you can measure your own exposure, which the section on checking your site covers.
Which WordPress versions are affected, and what should you update to?
Every branch from 4.7 onward is affected. WordPress released a fixed version for each of them on the same day, so you do not need to jump major versions to get the fix. Update to the latest release on the branch you run.
| Branch | Vulnerable versions | Update to |
|---|---|---|
| 7.1 | 7.1.0 to 7.1.1 | 7.1.2 |
| 7.0 | 7.0.0 to 7.0.5 | 7.0.6 |
| 6.9 | 6.9.0 to 6.9.8 | 6.9.9 |
| 6.8 | 6.8.0 to 6.8.9 | 6.8.10 |
| 6.7 | 6.7.0 to 6.7.8 | 6.7.9 |
| 6.6 | 6.6.0 to 6.6.8 | 6.6.9 |
| 6.5 | 6.5.0 to 6.5.11 | 6.5.12 |
| 6.4 | 6.4.0 to 6.4.11 | 6.4.12 |
| 6.3 | 6.3.0 to 6.3.11 | 6.3.12 |
| 6.2 | 6.2.0 to 6.2.12 | 6.2.13 |
| 6.1 | 6.1.0 to 6.1.13 | 6.1.14 |
| 6.0 | 6.0.0 to 6.0.15 | 6.0.16 |
| 5.9 | 5.9.0 to 5.9.17 | 5.9.18 |
| 5.8 | 5.8.0 to 5.8.16 | 5.8.17 |
| 5.7 | 5.7.0 to 5.7.18 | 5.7.19 |
| 5.6 | 5.6.0 to 5.6.20 | 5.6.21 |
| 5.5 | 5.5.0 to 5.5.21 | 5.5.22 |
| 5.4 | 5.4.0 to 5.4.22 | 5.4.23 |
| 5.3 | 5.3.0 to 5.3.24 | 5.3.25 |
| 5.2 | 5.2.0 to 5.2.27 | 5.2.28 |
| 5.1 | 5.1.0 to 5.1.25 | 5.1.26 |
| 5.0 | 5.0.0 to 5.0.28 | 5.0.29 |
| 4.9 | 4.9.0 to 4.9.32 | 4.9.33 |
| 4.8 | 4.8.0 to 4.8.31 | 4.8.32 |
| 4.7 | 4.7.0 to 4.7.36 | 4.7.37 |
WordPress 4.6 and older no longer receive security updates and have no fix for this flaw. The older-branch releases are a courtesy. WordPress's own release post repeats that only the most recent version is actively supported, so treat a backport as a way to close this hole today, not as a reason to stay on an old branch.
The change in every fixed release is limited to one file, wp-includes/template.php. That matters for planning: this is about as low-risk as a core update gets, and there is no good reason to hold it for a maintenance window.
How does the page template flaw work?
This section explains the mechanism at the level a site owner or developer needs to judge risk. It does not include a working request, and neither do the WordPress advisory or the Patchstack write-up it draws on.
The check that was missing
When someone requests a page, WordPress works out which template file to use. For pages, get_page_template() builds a short list of candidates: a custom template assigned to the page, then page-{slug}.php, then page-{id}.php, then page.php. The slug candidate is built from the pagename query variable, which comes from the request.
The custom-template candidate right above it was already passed through validate_file(), the WordPress helper that rejects ../ traversal. The slug-based candidate was not. On top of that, the code decodes the slug once more before building a second candidate name, so a traversal sequence that was encoded to survive the first round of decoding comes out the other side as a real ../.
The file name is built as page-{value}.php. That shape limits what an attacker can reach in two ways. The path has to start from something that really exists and begins with page-, and whatever file it lands on has to end in .php, because WordPress adds the extension.
Why WordPress says "on some servers"
Including a local PHP file runs whatever that file already does. By itself, that is not the same as running code the attacker wrote. To get from file inclusion to arbitrary code, the server needs a readable PHP file that does something useful to an attacker when it is loaded this way.
Patchstack's analysis names the best-known candidate: PEAR's pearcmd.php, which only becomes dangerous when PHP runs with register_argc_argv enabled for web requests. With that setting on, PHP fills $argv from the query string, and a command-line tool loaded over HTTP starts reading attacker-controlled "arguments."
So the practical chain looks like this:
- Always possible on affected versions: an unauthenticated request makes WordPress include a local
.phpfile it should not. - Needs the theme to cooperate: the active theme (or its parent) has a top-level folder whose name starts with
page-. - Needs the server to cooperate: a useful PHP file exists on disk and PHP settings, especially
register_argc_argv, let it be abused.
If all three line up, the attacker runs code as the web server user. That typically means full control of the site's files and database.
What 7.1.2 changed beyond the one-line fix
WordPress 7.1.2 applies the same validate_file() check to the slug-based candidate that the neighboring code already used. According to Patchstack, 7.1.2 also adds a second, broader check: every resolved template path must now sit inside the active theme's stylesheet directory, template directory, or the theme-compat folder, regardless of which code path produced it.
That second change is the interesting one. It treats template resolution as a class of problem rather than a single bug, which suggests the security team wanted a backstop in case the reported path was not the only one. Patchstack describes this for 7.1.2; whether each older-branch backport carries the same broader check is not stated in the sources reviewed for this article.
How exposed is your site? The two conditions that matter
Patching is the fix. These checks are still worth running, for two reasons. They tell you how urgently to treat a site you cannot update in the next hour, and they tell you which sites deserve a closer look for signs of earlier compromise.
Check 1: Does the active theme have a folder starting with page-?
The path the attacker builds has to begin inside a real directory whose name starts with page-. The most common one is page-templates, and it is common for a reason: the WordPress Theme Handbook uses a page-templates folder as its example of where to put custom page templates. Twenty Fourteen, a former default theme, ships one. Many commercial themes and a lot of agency-built child themes follow the same convention.
WordPress looks for templates in both the child theme and its parent, so check both. On a single site with WP-CLI:
wp eval 'foreach ( array_unique( [ get_stylesheet_directory(), get_template_directory() ] ) as $dir ) { foreach ( glob( $dir . "/page-*", GLOB_ONLYDIR ) ?: [] as $match ) { echo $match, PHP_EOL; } }'The command prints the full path of any matching folder in the active child and parent theme. No output means neither has a top-level folder starting with page-.
On a multisite network, each site can run a different theme, so loop over them:
for url in $(wp site list --field=url); do
echo "== $url"
wp --url="$url" eval 'foreach ( array_unique( [ get_stylesheet_directory(), get_template_directory() ] ) as $dir ) { foreach ( glob( $dir . "/page-*", GLOB_ONLYDIR ) ?: [] as $match ) { echo $match, PHP_EOL; } }'
doneTwo things to keep in mind. Inactive themes do not matter for this path, since template resolution only searches the active theme and its parent. And this check tells you about today's theme; if someone switches themes before you patch, the answer can change.
Warning: Do not rename a
page-templatesfolder to get around this. Pages that use a custom template store that template's path, including the folder name, in the database. Renaming the folder silently drops those pages back to the default template. Update WordPress instead.
Check 2: Is register_argc_argv on for web requests?
This is the check most people will get wrong. On the command line, PHP forces register_argc_argv on no matter what php.ini says. So php -i, php -r 'var_dump(ini_get("register_argc_argv"));', and wp eval all report it as enabled, even when your web requests have it off. The only reliable answer comes from PHP running under the web server.
To check it, create a small file with a random, hard-to-guess name in the site's web root:
<?php
var_dump( ini_get( 'register_argc_argv' ) );Load it once in a browser, note the result, and delete the file straight away. An empty string or "0" means off. "1" means on.
Why the result varies so much between hosts:
- Before PHP 8.5, the setting was on by default in PHP's compiled defaults, but both php.ini files that ship with PHP (
php.ini-productionandphp.ini-development) turn it off. - That means any PHP running without a php.ini file gets it on. Patchstack notes this is the case in the official PHP Docker images, and in cPanel environments on PHP versions below 8.5.
- PHP 8.5 changed the compiled default to off and deprecated the setting for web use.
- The setting can be changed per directory, so a
.user.inifile or an Apachephp_flagline can switch it back on for one site. That is one more reason to test through a real web request rather than reading php.ini.
If you manage the server, it is also worth knowing whether pearcmd.php exists anywhere PHP can read it:
find / -name pearcmd.php -not -path '*/proc/*' 2>/dev/nullRemoving PEAR removes the best-known way to turn this inclusion into code execution. It is not a fix, though. Other files on a given server could serve the same purpose, and nobody can list them all for you.
Reading your results
Theme has a page- folder |
register_argc_argv on (web) |
What it means |
|---|---|---|
| No | Off | Lowest exposure. Still update; the inclusion bug itself is present. |
| Yes | Off | The inclusion path is reachable. Code execution needs some other useful PHP file. Update today. |
| No | On | The known code execution route is blocked by the theme, but the server is set up for it. Update, and turn the setting off. |
| Yes | On | Closest to the worst case. Update immediately and review the site for signs of compromise. |
None of these rows means "safe to skip the update." The inclusion bug exists on every affected version, and these checks only measure how far an attacker could take it through the route that has been publicly described.
How to update WordPress and confirm the fix
For most sites, updating is quick and uneventful. The steps below go from simplest to most hands-on.
Let automatic background updates do it
WordPress installs minor and security releases automatically by default, and it does so within your current branch. A site on 6.5.x moves to 6.5.12, not to 7.1.2. WordPress's release post says sites that support automatic background updates will start the update on their own.
Automatic updates can be switched off, though, and on long-lived sites they often are. Look in wp-config.php for either of these:
define( 'AUTOMATIC_UPDATER_DISABLED', true );
define( 'WP_AUTO_UPDATE_CORE', false );Either line stops core from updating itself. If you find one, update manually now and decide separately whether that setting still makes sense.
Update from the dashboard
Go to Dashboard > Updates and click Update Now. If your site is on an older branch, WordPress may offer both the latest major version and the latest release in your branch. Taking the branch release is the smaller change if you need to move carefully.
Update with WP-CLI
Check the current version and what is available:
wp core version
wp core check-updateTo take only the security release for your current branch, without jumping to a new major version:
wp core update --minorTo move to the newest WordPress:
wp core updateThen confirm that core files match the official release. This also flags any core file that has been changed, which is useful if you are worried the site was touched before you patched:
wp core verify-checksumsOn multisite, core is shared across the network, so one update covers every site. You may still be prompted to run the database update step afterward:
wp core update-db --networkImportant: Take a backup before any core update on a production site, even a small one like this. If you manage many sites, update one or two first, check that the front end and wp-admin load, then roll out the rest.
What if you can't update right away?
WordPress has not published a workaround. The honest answer is that there is no substitute for the patch, and the gap between a public fix and people studying it is usually short. The change here is small and sits in one file, which makes it easy to compare against the vulnerable version.
If a site truly cannot be updated today, because of a code freeze, a heavily modified core, or an approval process you cannot skip, these steps reduce exposure while you arrange the update:
- Turn off
register_argc_argvfor web requests. Setregister_argc_argv = Offin the PHP configuration for the site, or in its.user.iniwhere your setup reads those files. Then re-run the browser check above to confirm it took effect. Very little web code relies on this setting; if something breaks, that code was reading$argvfrom the query string, which is worth knowing about anyway. - Put a web application firewall in front of the site and confirm with your provider whether it has a rule for this specific CVE. Some security vendors, Patchstack among them, have announced rules. A firewall rule is a stopgap and can be bypassed with encoding tricks nobody has thought of yet.
- Watch the site more closely until it is patched. The next section covers what to look for.
None of these stop the file inclusion bug itself. They only narrow the known route from inclusion to code execution.
How do you check whether a site was already targeted?
As of September 22, 2026, there were no public reports of this flaw being used in attacks, no public proof-of-concept exploit, and no entry in CISA's Known Exploited Vulnerabilities catalog. That can change quickly, so check the current status before you decide how deep to dig.
No indicators of compromise have been published for this CVE. What follows is general guidance for traversal-style attacks and for code execution after the fact, not a signature for this specific exploit.
Start with the web server access logs. Traversal attempts usually show up as encoded dot sequences in the request, and because this bug decodes the slug an extra time, attempts are likely to be double-encoded:
grep -Ei '(%252e|%2e%2e|\.\./)' /path/to/access.log | lessExpect some noise. Scanners probe every site on the internet for traversal all day, so a match is not proof of anything. What matters is a match that returned a 200 status on a request that should have been a missing page, especially if it came shortly before other unusual activity.
Then look for what an attacker leaves behind after code execution:
# Core files that do not match the official release
wp core verify-checksums
# Plugins from WordPress.org whose files have been changed
wp plugin verify-checksums --all
# PHP files in the uploads folder, where they almost never belong
find wp-content/uploads -type f -name '*.php'
# PHP files changed in the last 14 days, outside expected update activity
find . -type f -name '*.php' -mtime -14 -not -path './wp-content/cache/*'
# Administrator accounts, to spot any you did not create
wp user list --role=administrator --fields=ID,user_login,user_email,user_registeredIf any of these turn up something you cannot explain, treat the site as compromised. Restore from a backup taken before the suspicious activity, patch, rotate passwords and secret keys, and only then put it back online. Cleaning a live compromised site in place tends to miss things.
What this means for old and abandoned WordPress sites
This CVE reaches back to WordPress 4.7, released in December 2016. That puts a lot of forgotten sites in scope: the campaign microsite from 2018, the old staging copy someone left public, the client site whose maintenance contract ended years ago. Sites like these often have automatic updates disabled or broken, and nobody is watching them.
If you run an agency or manage many installs, today is a good day to pull a list of every WordPress site you are responsible for and check the version on each one. Anything on 4.6 or older has no fix at all for this flaw, and moving it to a supported version is the only real option. Anything that should not exist anymore is better taken offline than patched.
This is also the second core security release in less than a week. The September 17 release, 7.1.1, fixed a different set of issues, including the flaw known as Click2Shell. Two emergency updates this close together is a good argument for making sure automatic minor updates actually work on every site you run, and for testing that they do, rather than assuming.
Frequently Asked Questions
Is my WordPress site vulnerable to CVE-2026-87902?
Your site is vulnerable if it runs any WordPress version from 4.7.0 through 7.1.1 and has not yet received the fixed release for its branch. Check your version under Dashboard > Updates or with wp core version, then compare it with the table above. Whether an attacker could go as far as running code depends on your active theme and PHP settings, but the file inclusion bug itself exists on every affected version.
What version of WordPress fixes CVE-2026-87902?
WordPress 7.1.2 fixes it for the current branch. Every older branch back to 4.7 also received a fixed release on September 22, 2026, such as 7.0.6, 6.9.9, 6.8.10, and 4.7.37. Update to the latest release on your branch, or move to 7.1.2. WordPress 4.6 and earlier have no fix.
Does CVE-2026-87902 require the attacker to log in?
No. The attacker does not need an account, and no logged-in user has to click anything. That is a large part of why it is rated critical. The only requirements are on the site's side: an affected WordPress version, and for the most serious outcome, a theme with a page- folder and a server configuration that allows the inclusion to become code execution.
Will WordPress update itself automatically?
Usually, yes. By default WordPress installs security releases automatically within your current branch, so a site on 6.8.x should move to 6.8.10 on its own. This does not happen if automatic updates were disabled in wp-config.php, if file permissions block the update, or if a plugin turned updates off. Confirm the version on each site rather than assuming the update ran.
Why does php -i say register_argc_argv is on when my php.ini says off?
The PHP command line forces register_argc_argv on regardless of php.ini, so php -i and WP-CLI will always report it as enabled. What matters for this flaw is the value PHP uses for web requests. Check it with a short, temporary PHP file loaded through the browser, then delete the file. That shows the real setting, including any per-directory overrides.
Is it safe to delete the page-templates folder from my theme?
No, and it would not fix the problem. Pages assigned a custom template store the template's path in the database, so removing or renaming the folder quietly switches those pages back to the default layout. The flaw is in WordPress core, not in your theme. The folder only affects how far an attacker could take it. Update WordPress instead.
Is CVE-2026-87902 being exploited in the wild?
As of September 22, 2026, there were no public reports of attacks, no public proof-of-concept, and no listing in CISA's Known Exploited Vulnerabilities catalog. That status can change within days of a fix being published, since the patch itself shows where the bug is. Check a current source before deciding the risk is low, and do not wait for exploitation reports to update.
Is this the same vulnerability that WordPress 7.1.1 fixed?
No. WordPress 7.1.1, released September 17, 2026, fixed separate issues, including one known as Click2Shell. CVE-2026-87902 was fixed in 7.1.2 on September 22, and 7.1.1 is on the affected list. A site that was updated to 7.1.1 last week still needs 7.1.2 or the matching release for its branch.
Conclusion
CVE-2026-87902 is the kind of WordPress core flaw that justifies dropping other work: no login needed, nearly a decade of versions affected, and a clear path to code execution on servers where the theme and PHP settings line up. The fix is a one-file change with little risk of side effects, and a fixed release exists for every branch back to 4.7. Update to 7.1.2, or the latest release on your branch, today.
After that, use the two exposure checks to decide which sites need a closer look. Pay the most attention to any site where the theme has a page- folder and PHP has register_argc_argv on for web requests, and remember to test that setting through the browser, not the command line.
On MagicWP, core security updates are applied automatically with rollback, and each site runs in its own isolated container behind a web application firewall. It is still worth confirming the WordPress version on each of your sites in the dashboard or with WP-CLI over SSH. You can read more about how the platform handles this on the MagicWP security features page, and about restoring a site from a clean point in the backups documentation.
Get the best of MagicWP in your inbox.
Monthly engineering notes, product updates, and WordPress performance tips. No spam, unsubscribe anytime.

