How To

Patch WordPress Against CVE-2026-87902 File Inclusion

On 22 September 2026, WordPress shipped 7.1.2 to close CVE-2026-87902, an unauthenticated path traversal in how the core resolves page templates. It rates critical, CVSS 9.2, because a request nobody has to log in to send can make WordPress load a PHP file from outside your theme, and on the wrong server that turns into code execution. CISA added it to the Known Exploited Vulnerabilities catalog three days later, and scanning started within hours of the fix going public.

Original content from computingforgeeks.com - post 176798

The fix is a core update, so the work is small and the window is not. This guide shows you how to confirm the version you run and apply the patch on a tarball install, a Docker image or a managed host. It then helps you judge whether your server meets the conditions for code execution, and scan your logs for the attack pattern. Every step was run in October 2026 on an Ubuntu 26.04 server, with WordPress upgraded from 7.1.1 to the current release, so the output below comes from that box.

The version that tells you if you are exposed

Every WordPress from 4.7.0 through 7.1.1 is vulnerable. The flaw was present since 4.7 shipped at the end of 2016, so a site that hasn’t been touched in years is as exposed as one updated last month. The patched builds are 7.1.2 and, because the fix was backported to every branch still getting security updates, 7.0.6, 6.9.9, 6.8.10 and so on back to 4.7.37. The current public release is 7.1.3, which carries this fix plus seven more, so that is the version to land on.

If you manage the server over SSH, WP-CLI gives the fastest answer:

sudo -u www-data wp core version --path=/var/www/wordpress

Anything below 7.1.2 on the 7.1 branch, or below the patched build for your branch, is unpatched. Without WP-CLI, read the version straight from core:

grep "wp_version =" /var/www/wordpress/wp-includes/version.php

That prints the exact string the install reports:

$wp_version = '7.1.1';

From outside the box, an unhardened WordPress still prints its version in the home page source, which is also how a scanner picks targets. Check your own site the same way they do:

curl -s https://your-site.example.com/ | grep -i generator

A line reading <meta name="generator" content="WordPress 7.1.1" /> hands the version to anyone who asks. Patch first, then consider removing that tag.

Update core to the patched release

The only real fix is the core update. There’s no setting to toggle and no plugin that stands in for it, because the bug is in wp-includes/template.php, which is core. How you apply it depends on how the site is installed, so use the path that matches your setup.

From the dashboard

For a single site you click through, this is the quickest route. Open Dashboard, then Updates, and run the WordPress core update. Most installs on the 4.7 and later branches already pulled the patch through automatic background updates, which core ships for minor and security releases. Confirm it landed rather than assuming it did: the Updates screen names the running version, and it should read 7.1.2 or higher on the 7.1 branch.

With WP-CLI

On a server you run yourself, WP-CLI patches the site in one command and is scriptable across many installs. Run it as the web-server user so file ownership stays correct:

sudo -u www-data wp core update --path=/var/www/wordpress

It downloads the current release, unpacks it over core, and reports the result:

Updating to version 7.1.3 (en_US)...
Downloading WordPress 7.1.3 (en_US)...
Unpacking the update...
Success: WordPress updated successfully.

Running the version check again should now print the patched build. The screenshot below shows the full run, from the vulnerable 7.1.1 to the patched release, on the Ubuntu 26.04 lab server. If you run WordPress behind nginx on this OS, the base setup is covered in our WordPress on Ubuntu 26.04 install guide.

WP-CLI updating WordPress from 7.1.1 to the patched release for CVE-2026-87902

That last command proves the patch is in the code, not just the version string. The patched template.php adds a function named _wp_is_template_path_allowed, and grepping for it is a reliable check that the right file replaced the old one.

On a tarball or Git install

If the site was unpacked by hand and WP-CLI isn’t present, replace core from the official archive. Back up the install first, then swap wp-admin and wp-includes wholesale, which is where the patched files live:

cd /tmp
curl -LO https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz
sudo rsync -a --delete wordpress/wp-admin/ /var/www/wordpress/wp-admin/
sudo rsync -a --delete wordpress/wp-includes/ /var/www/wordpress/wp-includes/
sudo cp wordpress/*.php /var/www/wordpress/
sudo chown -R www-data:www-data /var/www/wordpress

Leave wp-content alone, since that holds your themes, plugins and uploads. After the copy, load /wp-admin/ in a browser once so WordPress runs any database upgrade the new build expects. Note that latest.tar.gz is a jump to the current major for anyone on an older branch, which can disturb themes or plugins, so test it on staging first. To stay on your branch instead, fetch that branch’s patched tarball rather than latest.

In a container image

A containerised site is patched by pulling a rebuilt image, not by editing files inside a running container, because the next deploy would wipe the edit. Pin a patched tag and recreate the service:

docker pull wordpress:7.1.3-php8.3-fpm  # a current patched tag, https://hub.docker.com/_/wordpress/tags
docker compose up -d --force-recreate wordpress

The official image does not publish patched tags for the older branches, so a container on 6.9 or 6.8 moves to a current patched tag like the one above rather than pinning a backport build that was never pushed. Match the PHP version to what your themes and plugins support. The container angle matters beyond the WordPress version, because the base PHP image decides whether the flaw reaches code execution, which the next section explains.

On managed or control-panel hosting

On cPanel, Plesk or a managed WordPress host, the control panel owns updates. Use its WordPress manager or the one-click updater rather than editing core by hand, so the host’s own file integrity and staging stay consistent. If the panel offers WordPress auto-updates, confirm they are enabled and that the reported version is 7.1.2 or higher. Hosts that run a platform-level virtual patch still expect you to update, so treat that as a safety net, not the fix. If you are choosing a host partly on how fast it ships these patches, our roundup of secure WordPress hosting providers compares how they handle it.

Whether your server can be pushed to code execution

Patch regardless of what follows. Still, the difference between file disclosure and remote code execution on this flaw comes down to the server, and knowing where you stand tells you how hard your logs were probed and how urgent a compromise check is. The classic Twenty Twelve and Twenty Fourteen themes have a qualifying page- directory, and so do popular third-party themes including Neve, Hestia and Sydney, while PEAR’s pearcmd.php is the readable target that usually completes the chain. Code execution needs all three of these true at once:

  • The active theme has a top-level directory whose name starts with page-.
  • A readable local PHP file exists that turns inclusion into execution, such as pearcmd.php.
  • PHP runs with register_argc_argv enabled, which hands the query string to that script as $argv.

That last setting is the one most within your control, and it is where the common stacks diverge. The official php:8.4-fpm Docker image ships register_argc_argv on and carries pearcmd.php, so a WordPress container built on it meets two of the three conditions by default. Default cPanel configurations are affected on PHP before 8.5 for the same reason. On the Ubuntu 26.04 server I tested, the PHP 8.5 FPM pool ships the setting off, and the 8.5 base image does too, which closes the pearcmd route even before the patch. Check your own value from the pool PHP sees:

php-fpm8.5 -i | grep register_argc_argv

A value of Off breaks the pearcmd chain, but it doesn’t fix the inclusion itself, and a different readable target could still be reached. Turning it off is worth doing as defence in depth, never as a substitute for the update. On RHEL-family hosts, a correct SELinux policy for WordPress adds another layer that limits what an included file can touch.

A stopgap when you cannot patch this minute

If a change-control window keeps you from updating right now, block the request shape at the edge. The exploit smuggles the traversal through the pagename parameter, encoded so it survives WordPress’s slug cleaning, and a real page slug never contains a dot sequence or an encoded slash. An nginx rule that rejects those requests buys time without touching core:

if ($args ~* "pagename=.*(\.\.|%2e%2e|%252e)") { return 403; }

Put that inside the server block, reload with sudo nginx -t && sudo systemctl reload nginx, and the encoded traversal gets a 403 before it reaches PHP. A WAF rule that matches the same pattern in the query string and POST body does the same job. This is a filter, not a fix. It narrows one known path while you schedule the update, and it comes off once core is patched. Blocklisting attacker IPs is weaker still, because the scanning spread across hundreds of addresses within a day, so pair any edge rule with Fail2ban on repeat offenders rather than a static list.

Signs the flaw was already used against you

Because exploitation began within hours of disclosure, a site that sat unpatched for any stretch after 22 September should be checked, not assumed clean. The traffic leaves a recognisable shape in your access logs. The pagename value carries an encoded dot sequence, usually the double-encoded %252e%252e that survives the slug cleaning, and it often opens with templates%252f to climb out of a real page- directory:

sudo grep -aiE 'pagename=.*(\.\.|%2e|%252e|%2f|%252f)' /var/log/nginx/*access.log

Matches are requests worth reading closely. The capture below shows the pattern in a log, along with the pearcmd stage attackers reach for next:

Scanning nginx access logs and temp directories for CVE-2026-87902 attack indicators

The second stage of the attack writes PHP into a world-writable directory through the pearcmd chain, so a clean temp directory is a good sign and a stray PHP file in it isn’t. Scan the usual drop locations:

sudo find /tmp /var/tmp -maxdepth 1 -name '*.php' -printf '%Tc  %p\n'

Names vary, so the file itself is the signal rather than any single name. Any unexpected PHP file in /tmp or /var/tmp means you should treat the host as compromised: rotate secrets, review the whole document root for newly written PHP, and restore from a known-good backup rather than deleting the one file you found. To go further on finding planted or vulnerable components, WPScan enumerates the install from the outside the way an attacker does.

Post-patch audit checklist

Run through these once the update is in. Each line is a condition that should hold on a patched, uncompromised host:

  1. Core reports 7.1.2 or higher on the 7.1 branch, or the patched build for your branch, and grep _wp_is_template_path_allowed wp-includes/template.php returns a match.
  2. Automatic background updates are enabled, so the next minor security release lands without you.
  3. The access-log scan for the encoded pagename traversal returns nothing after the patch time, or every hit predates it and was blocked.
  4. /tmp and /var/tmp hold no unexpected PHP files, and the document root has no PHP written since your last deploy.
  5. If the stack runs PHP with register_argc_argv on, it is turned off or the host no longer ships pearcmd.php in the web account’s reach.
  6. Any emergency nginx or WAF rule you added is documented, and scheduled for removal now that core is patched.

The patch is the whole fix, and it takes a minute. The rest of this list is about proving the minute you were exposed did not cost you anything, which on a flaw that CISA flagged as actively exploited is worth the extra ten.

Keep reading

UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Security UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP Php PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Php PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Best GPUs for Hashcat: Password-Cracking Benchmarks by Card Kali Linux Best GPUs for Hashcat: Password-Cracking Benchmarks by Card Best Phone for GrapheneOS in 2026 (and Why to Skip the Pixel 11) Security Best Phone for GrapheneOS in 2026 (and Why to Skip the Pixel 11) Install Pritunl VPN Server on Ubuntu 24.04 / 22.04 Networking Install Pritunl VPN Server on Ubuntu 24.04 / 22.04

Leave a Comment

Press ESC to close