The code that installed WordPress 7.1.4 on your site was WordPress 7.1.3. CVE-2026-119977 lives in the upgrade routine, so the last thing every patched site did on the way to safety was run the vulnerable path one more time.
CISA added the flaw to the Known Exploited Vulnerabilities catalog on November 3 with a due date of November 6. That is tomorrow. The entry also carries the forensic-triage flag, so applying the patch does not close the item on its own. NVD scores it 8.1, classifies it CWE-367, and records CISA's decision values as exploitation "active" and technical impact "total". WordPress released the fix on October 28 and backported it across every supported branch: 25 releases in one day, from 7.1.4 down to 4.7.39.
Relevance verdict. If a managed host runs your site, ask for the date it patched the fleet and stop there. If you run WordPress yourself on anything from 4.7.0 through 7.1.3, two facts decide your workload: whether your site took a full core package rather than the small incremental one, and whether it did that while reachable from the internet. Sites that took the incremental package were never handed the staged installer the observed attacks go after. Sites that fell back to the complete package were, and that fallback happens more often than most operators expect. Anyone still on 4.6 or older has no patch at all, and the hardening section is the only part of this that helps them. If you run no PHP and no WordPress, this one is not yours.
Why 7.1.4 does not settle the question
The reasonable belief is that a patched version means a closed case. For most vulnerabilities that holds. Here the vulnerable code is the updater, and the updater that unpacked 7.1.4 came from the release you were replacing. Your site ran the old, predictable staging logic for the duration of that one upgrade. Version 7.1.4 protects upgrade number N plus one.
That ordering is why CISA set the forensic-triage flag rather than a plain patch instruction. The question the catalog asks you is not which version you run today. It is whether anyone was watching the directory your server created while it was patching. Reporting from The Hacker News puts the scale of that watching in useful terms: Wordfence logged more than 18,500 requests for staged setup-config.php paths in the first 21 hours after disclosure, and 12,632 blocked attempts in a single 24-hour period. Previdian saw three attempts against its honeypots from one UK-geolocated address on October 30. Blocked requests are attempts, not compromises, and nobody has published a count of sites that actually lost.
What the old upgrader put on the web root
The mechanism is three ordinary decisions that only become a vulnerability in sequence. WP_Upgrader::unpack_package() built its working directory from the package filename, which is published and therefore guessable. That directory sits under wp-content/upgrade/, which web servers serve. And update_core() then copied the staged tree over your live installation.
# 7.1.3, wp-admin/includes/class-wp-upgrader.php
$working_dir = $upgrade_folder . basename( basename( $package, '.tmp' ), '.zip' );
# -> wp-content/upgrade/wordpress-7.1.4/wordpress/
# a path an attacker can type before your site creates it
# 7.1.4 adds an unguessable suffix and an index file so the
# directory cannot be listed either
private function get_working_dir_name( $package ) {
$name = basename( basename( $package, '.tmp' ), '.zip' );
return $name . '-' . wp_generate_password( 16, false );
}
# 7.1.4, wp-admin/includes/update-core.php
# "Never copy a wp-config.php, which could have been written
# to the working directory."
$skip = array( 'wp-content', 'wp-includes/version.php', 'wp-config.php' );
The second change names the consequence out loud. A full package stages a complete WordPress tree, including wp-admin/setup-config.php, the installer that writes wp-config.php. That installer refuses to run when it finds a config file beside it. The staged copy has none, because a fresh unpacked tree never does, so the guard passes and the installer offers to write one from unauthenticated POST input. In 7.1.3 the copy step then promoted that file into your document root, where PHP loads it on every request. The attacker never needed an account, a plugin, or a theme.
Both ends are now closed: the staging directory name carries 16 random characters, the directory gets a silent index.php, and the copy skips wp-config.php even if one appears. Both changes are readable in the 7.1.4 upgrader and the 7.1.4 copy step, which NVD cites as the patch commits.
Which package your site actually downloads
Core picks between four packages, and the choice is where your exposure was decided. WP_Automatic_Updater and the manual button both run Core_Upgrader::upgrade(), which asks for the incremental package only when your current version matches the one the package was built against and every core file passes its checksum. One modified core file sets $partial = false and the site takes the no-content or full package instead. Reinstalls and rollbacks skip the incremental path by definition.
On Red Hound engagements, core-file modification is one of the findings that shows up over and over on small-business sites: a hotfix pasted into a core file years ago, a hosting panel that rewrote something, a half-removed malware cleanup. Every one of those sites quietly upgraded by full package every time, and that was invisible until this CVE made it matter.
# Which release am I on, and is any core file modified?
wp core version
wp core verify-checksums # lists every file that fails
# No WP-CLI? Read the version from the filesystem.
grep '^\$wp_version' wp-includes/version.php
# The release floor for your branch (NVD, 25 branches patched 2026-10-28):
# 7.1.4 7.0.8 6.9.11 6.8.12 6.7.11 6.6.11 6.5.14 6.4.14
# 6.3.14 6.2.15 6.1.16 6.0.18 5.9.20 ... 4.7.39
# Anything on 4.6 or older has no patched release.
The timing matters too. WordPress has applied minor core releases automatically since 3.7, and 7.1.3 to 7.1.4 is a minor release. For most self-hosted sites the upgrade therefore ran unattended, at a moment chosen by the update cron rather than by an administrator, and finished before anyone read the release notes. That is the opposite of a maintenance window someone was watching. It also means the exposure is already in the past for the majority of sites, which is why the work in front of you is a log review rather than a patch.
Fix the checksum failures as part of this work. A site that cannot take the incremental package will keep staging a full installer tree on every future upgrade, and the next bug in that path will find it again.
Read your access log for the staged installer
The hunt is specific because the attack has one unmistakable fingerprint: unauthenticated requests to a path under wp-content/upgrade/. No legitimate visitor, crawler, or plugin requests that directory. A single hit is a scan; a POST that returned 200 is an incident.
# Any request at all for the staging directory (Apache/nginx combined logs)
grep -hiE 'wp-content/upgrade/' /var/log/nginx/access.log* /var/log/apache2/access.log* \
| awk '{print $1, $4, $6, $7, $9}' | sort | uniq -c | sort -rn | head -40
# The two files that matter, and the status codes that matter more
grep -hiE 'wp-content/upgrade/.*(setup-config|install)\.php' /var/log/nginx/access.log* \
| grep -E '"POST|" (200|302) '
# Did a config file appear or change outside your own upgrade window?
ls -l --time-style=full-iso wp-config.php
stat -c '%n %y %s' wp-config.php wp-settings.php
grep -nvE '^\s*($|//|/\*|\*|\*/|<\?php|\?>)' wp-config.php | grep -iE \
'eval|base64_decode|gzinflate|system|passthru|shell_exec|assert|preg_replace.*\/e'
# Accounts and content added since your upgrade date
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
wp option get active_plugins --format=json
Reading the result
- No requests for the path. The most likely outcome, and the one to record with the date range your logs actually cover.
GETrequests returning 404. Scanning against a directory that did not exist at that second. Nothing to do beyond the hardening below.- Any 200 under
wp-content/upgrade/. The staged tree was reachable. Move to the config and account checks and widen the search to file writes in the same minute. - A
POSTto a stagedsetup-config.php. Treat as an incident. Preserve the logs, snapshot the filesystem, then rotate credentials.
Correlate the timestamps against the moment your site upgraded. WordPress holds a 15-minute lock on the core updater, and the real unpack-to-copy window is seconds to tens of seconds inside that. The requests Wordfence observed ran for hours against small groups of sites, because an attacker who cannot predict the minute simply polls until it arrives. If your logs have already rotated past your upgrade date, say so in the write-up and treat the result as unknown rather than clean.
Deny the staging directory before your next upgrade
A plugin-based firewall is weaker here than its dashboard suggests. The request reaches a staged PHP file directly, and WordPress never loads, so a firewall that runs as a WordPress plugin sees nothing unless it is installed as an auto_prepend_file at the PHP level. Put the control in the web server, where it runs regardless.
# nginx: nothing under wp-content/upgrade/ should ever be served
location ^~ /wp-content/upgrade/ { deny all; return 404; }
# Apache: drop the same path into wp-content/upgrade/.htaccess
# (and confirm AllowOverride is on, or move it to the vhost)
Require all denied
# Verify from outside, during and after an upgrade
curl -sS -o /dev/null -w '%{http_code}\n' \
https://example.com/wp-content/upgrade/wordpress-7.1.4/wordpress/wp-admin/setup-config.php
Two more controls cost nothing. Set define( 'DISALLOW_FILE_MODS', true ); on sites that should never self-update, and run their upgrades from WP-CLI on a host the public cannot reach. And keep wp-config.php out of the document root where your layout allows it, since WordPress reads it one directory up as well.
Compare wp-config.php against the hour your site upgraded
Three things before the deadline. Confirm every WordPress instance you own is on its branch floor, since a staging copy and a forgotten microsite count. Grep every access log you still hold for wp-content/upgrade/ and keep the result as evidence either way. Then open wp-config.php, check its modification time against your upgrade, and read it line by line: database credentials you do not recognise, a table prefix that changed, or any executable statement in a file that should only define constants. If any of that is off, rotate the database password and the salts, and treat the host as compromised rather than patched.
That last call is the one small teams get wrong, and it is the one we are asked about after the fact. If your logs cannot answer whether the window was abused, the honest answer is that you do not know, and the response is a compromise assessment rather than a reinstall over the top of whatever is already there.
Not sure whether that upgrade window was abused?
We run compromise assessments and build the incident response playbook that answers this question before the next deadline. Book a session and bring your access logs.
