On Friday 25 September CISA put CVE-2026-87902 into its Known Exploited Vulnerabilities catalogue. It is a 9.2, it is WordPress core, and a proof of concept has been public since the 22nd. All of that is true, and none of it answers the only question that matters to you this morning: whether your site is exposed. That answer is not in WordPress. It is in a PHP configuration file nobody has probably looked at since the server was built.
We have been running other people's infrastructure for a good many years and we have seen the pattern often enough to spot it from a distance: somebody chose the application, somebody else built the platform, and when an advisory like this lands, nobody owns the answer. So let us separate the two things, because this time the distance between "the flaw exists" and "it affects you" is wider than usual.
What the flaw does, in one sentence
CISA's entry puts it like this: WordPress contains a remote file inclusion flaw that lets an unauthenticated attacker make page-template resolution include a readable local .php file of their choosing, outside the active theme's directories, and have that end in remote code execution. The WordPress advisory names the function: get_page_template(). The classification is CWE-98: improper control of the filename in a PHP include or require.
The patch shipped on 22 September. The thing to look at is not the new version but the list: WordPress published a fix for twenty-five branches, from 7.1.2 all the way down to 4.7.37. WordPress 4.7 shipped on 6 December 2016. The advisory frames the gesture as a courtesy, "as a courtesy to users on older branches"; we also read it as an acknowledgement of what is still switched on out there, because nobody backports to a branch almost ten years old for sport.
9.2 measures the damage, not your exposure
The published vector is CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N. The right-hand half explains the 9.2: confidentiality, integrity and availability all high. No credentials, no interaction, over the network. But there are three characters in the middle almost nobody reads, and they are the ones that talk about you: AT:P. In CVSS 4.0, Attack Requirements is N when nothing special is needed and P when conditions are required on the target. Here it is P, and that is a warning printed inside the score itself.
The researcher who reported it, Robert Ressl, published the preconditions one by one. There are five and they all have to line up at once:
- 1A published page, anonymously reachable, selectable by
page_id. - 2That the page does not already have a valid custom template assigned.
- 3A top-level directory whose name starts with
page-inside a theme root WordPress searches. Mind the wording, because this is where everyone trips: it is a directory, not thepage-whatever.phpfiles any theme ships and which are entirely normal. It is the WordPress advisory that names the themes: "This affects the legacy Twenty Twelve and Twenty Fourteen themes, as well as some popular third party themes such as Neve, Hestia, and Sydney." Ressl, for his part, says the versions of Twenty Twenty-Three, Twenty Twenty-Four and Twenty Twenty-Five he inspected did not have one. - 4An existing readable local
.phpfile to aim at. - 5That filesystem policy permits the include.
And now the line from the researcher himself that we like best in the whole advisory, because it appears in no headline: "I have not measured their prevalence across live sites." He has not measured how many sites meet those conditions. Neither have we, so we are not going to tell you half the internet is owned. We are going to tell you how to find out whether you are.
The deciding condition does not belong to WordPress
Look at the fourth condition. For a local include to end in code execution you need a .php file the attacker can influence. The public demonstration used the classic one: pearcmd.php, PEAR's command-line front end, with the register_argc_argv directive enabled in PHP. WordPress does not install that file. WordPress does not set that directive. Both of them come from how somebody built the platform.
register_argc_argv was born for the command line: it fills $argv and $argc with the script's arguments. What almost nobody keeps in mind is what it does outside the command line, and the PHP manual says it plainly while deprecating it: on non-CLI SAPIs, $_SERVER['argv'] is derived from the request's query string. Which means: with that directive on, whatever a stranger types after the question mark in a URL lands inside a script that was expecting console arguments. That is the channel.
And here comes the awkward part. The default value of register_argc_argv in PHP is 1 — on. The php.ini-production file PHP itself distributes sets it to Off and carries the explicit comment "Production Value: Off". But that file does not apply itself: somebody has to copy it, and the documentation for the official PHP Docker images says the image "ships with the default php.ini-development and php.ini-production configuration files" and strongly recommends using the production one. If nobody copies it, PHP starts with its compiled-in values. With the directive on. The WordPress advisory leaves none of this to interpretation and names two names: "The official php image for Docker is affected, and the default cPanel configuration is affected when PHP prior to 8.5 is in use." The official image, and the default configuration of the panel a great deal of shared hosting runs on.
Two identical WordPress installs — same version, same theme, same published page: one ends in code execution and the other in an error. The difference is not in the application. It is in whether somebody copied a configuration file the day the image was built. That is, literally, the shape of the sentence we wrote about the PNG that was PostScript inside: the opinion that counts is the last program in the chain, and that program is almost never the one you think you are auditing.
Ressl is honest about the scope of this and it is worth repeating verbatim, because it is the part that evaporates the moment anyone summarises: disabling the setting or removing PEAR "breaks this demonstrated route. It does not repair WordPress's underlying file-inclusion flaw." It breaks that route. The hole stays where it was.
The check that will lie to you
If you have got this far, the next thing you are going to do is SSH into the server and type php -i | grep register_argc_argv. It is what any of us would do. And it will give you a false answer.
We measured it on a box running PHP 8.3.6 while writing this, and it comes out this cleanly:
$ grep -n '^register_argc_argv' /etc/php/8.3/cli/php.ini 690:register_argc_argv = Off $ php -i | grep register_argc_argv register_argc_argv => On => On
The configuration file says Off, on line 690, and PHP answers On. Nobody made a mistake: php.ini-production itself carries the explanation five lines above the directive — "Note: This directive is hardcoded to On for the CLI SAPI." The PHP documentation confirms it from the other side: the list of directives the console SAPI forces cannot be changed from php.ini, and register_argc_argv is on it.
The practical consequence is bigger than this CVE, which is why we are writing it separately: a configuration audit run from the shell does not describe what your website does. The console SAPI and the FPM SAPI load different files — in Debian and Ubuntu packaging each SAPI gets its own directory: /etc/php/8.3/cli/, /etc/php/8.3/fpm/ — and on top of that some directives are forced by the console regardless of the file. The only valid check is one that goes through the web server: a one-line file served by the same interpreter that serves your WordPress.
The four checks that actually answer
They are not ordered by severity but by the order in which you can actually run them. None of them needs anything of ours and all four fit in a morning:
- 1The version, and whether it updates itself. WordPress ships with automatic updates for minor releases enabled by default — and since 5.6, on new installs, for major ones too — so plenty of sites are already patched without anyone lifting a finger. The problem is the ones with that feature switched off "because something broke once". If that is you, the list of fixed branches reaches 4.7.37: there is a patch for your version, whatever it is.
- 2The active theme, looking for directories rather than files. An
ls -d wp-content/themes/*/page-*/answers the third condition. If it returns nothing, that condition is not met today, with the theme you have installed today. Write it down with both italics. - 3The directive, as read by the web server. A temporary file containing
<?php var_dump(ini_get('register_argc_argv'));, served over the web and deleted straight afterwards. That is your real configuration. The output ofphp -ifrom a console is not. - 4What else lives on that filesystem. The fourth condition wants a readable
.phpoutside WordPress. Look for PEAR, yes, but the better question is wider and nofindanswers it: what else lives on the same server as your site? The 2019 copy of the shop, the panel installed for a migration, the second "test" WordPress. Every one of them sits in somebody else's inventory, not yours.
There is a fifth question that is not a check, and it is the one that really decides how bad the bad day gets: what does that host reach? A file inclusion gives you execution as the web server user, and that user can already read wp-config.php, where the database password lives. And with the database come whatever credentials the plugins store, SMTP among them. The question is not whether the attacker gets into the website. It is what they reach from the website. If the answer includes the office network, today's problem is not WordPress.
Why you patch anyway, even if you fail the conditions
We could have skipped this section and looked just as good, but it would be dishonest. Everything above tells you how fast, not whether. The reason is that none of the five conditions is stable. The third depends on the theme, and a theme gets swapped on a Thursday afternoon without telling operations. The fourth depends on which files are on disk, and that changes every time somebody installs a package. You can meet zero conditions today and three next month without touching a line of WordPress. The flaw, meanwhile, is still sitting in get_page_template().
That said, urgency is a data point too, and this case brings one: Ressl publishes the full timeline. He reported the flaw through HackerOne on 20 July, receipt was acknowledged the next day, he was told of the fix on 15 September, and the advisory and proof of concept went out on the 22nd. Sixty-four days of orderly silence. And then three: from 22 to 25 September, from "there is a proof of concept" to "CISA adds it to the catalogue for active exploitation". That is the real clock. You do not set it.
And if you suspect it already happened: do not reinstall first
The catalogue entry carries a field almost nobody looks at, and on this one it reads Yes: forensicTriage. CISA asks you to collect evidence before remediating. And the industry reflex when a site gets touched is exactly the opposite: delete the directory, restore last night's backup and breathe out. That does two bad things at once. It destroys the only thing that answered "what did they take and since when?", and it restores a WordPress with the same hole and — if the backup predates the moment you noticed — possibly with the same intruder inside.
It is the same trap we wrote up in the kernel patch that is a reboot, only here it is even easier to fall into, because restoring a website feels free. The order we argue for is boring and it works: a cold copy of the filesystem and the database before touching anything, the web server access logs pushed somewhere nobody rotates them, and only then patch. Whatever you were not already shipping off the box, you will not get to look at afterwards.
The company website is a production server
What makes this vulnerability interesting is not the 9.2. It is that it puts in writing something we say in every meeting and that sounds like a sermon until a case shows up: the corporate website is a production server, not a marketing asset. It has a version, it has dependencies, it has a configuration somebody chose years ago and it has a path inwards. The difference with the ERP is that the ERP has an owner.
If you had to answer in ten minutes today how many WordPress installs your company has published, on which version, with which theme and on top of which PHP, and the honest answer is "we would have to ask the agency", then CVE-2026-87902 did not create the problem. It exposed it. The failure was inevitable; whether it becomes an outage is a design decision, and that decision gets taken long before the day of the advisory.
Sources (verified on 2026-09-26): the CVE-2026-87902 entry — description, dateAdded 2026-09-25, dueDate 2026-09-28, forensicTriage "Yes", knownRansomwareCampaignUse "Unknown" and CWE-98 — read from the primary JSON file of CISA's KEV catalogue, version 2026.09.25, 1,726 entries. Affected versions (4.7.0–7.1.1), the 25 patched branches, the CVSS 4.0 score of 9.2, the full vector, the get_page_template() description, the publication date (2026-09-22), the list of affected themes, the statement that the official Docker image and the default cPanel configuration are affected on PHP prior to 8.5, and the backport to older branches "as a courtesy to users on older branches" — GHSA-7hp8-65ch-5whp. The five preconditions, the observation that the Twenty Twenty-Three, Twenty Twenty-Four and Twenty Twenty-Five versions inspected carried no top-level page- directory, the role of pearcmd.php, the disclosure timeline and both quoted sentences from the researcher — Robert Ressl's analysis. The default value ("1"), the INI_PERDIR changeability, deprecation in PHP 8.5 and the derivation of $_SERVER['argv'] from the query string on non-CLI SAPIs — the PHP manual; the console SAPI forcing the directive beyond what the file says — CLI differences; "Production Value: Off" and the hardcoded to On for the CLI SAPI note, read in php.ini-production on the PHP-8.3 branch of the PHP repository and in the file shipped with Ubuntu's PHP 8.3 package. Official PHP images: Docker Hub documentation. Automatic updates on by default — WordPress documentation. The console output we reproduce comes from a machine of ours running PHP 8.3.6 with Ubuntu packages; on another distribution or package, line 690 need not be the same line, but the console SAPI behaviour is. We have not measured how many sites meet all five conditions and we claim nothing about it: the researcher states explicitly that he has not either. Cover photo: WordPress Photo Directory (CC0).
How many WordPress installs has your company published? We will count them
At everyWAN we inventory what you have published to the internet and the platform underneath it: version, theme, PHP, what else lives on that disk and — the important question — what that host reaches from where it sits. It is part of how we understand cybersecurity and how we run other people's data and applications. If the answer is that you are fine, we will tell you that too, and sell you nothing.
Talk to everyWAN