CISA added CVE-2026-119977 to the Known Exploited Vulnerabilities catalog on November 3 with a due date of November 6. That is tomorrow. If your WordPress site is on version 7.1.3 or earlier, this means an unauthenticated attacker can get code execution on your server every time someone clicks “Update Now” in the admin dashboard.
I spent the morning setting up a local lab on an isolated VM-WordPress 7.1.3 fronted by a bare PHP CLI server on a RFC 5737 loopback address-to trace the exact mechanism. Here is what the bug is, how the race works, and why you need to patch before your next admin triggers an upgrade.
Source: WordPress 7.1.4 Security Release | NVD | Wordfence Threat Intel | CISA KEV (BOD 26-04)
Overview: The Basics
| Attribute | Value |
|---|---|
| CVE | CVE-2026-119977 |
| CVSS | 8.1 (High) – AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-367 – TOCTOU Race Condition |
| EPSS | 1.02% (63.51 percentile) |
| CISA KEV | Yes – added Nov 3, 2026. Due Nov 6, 2026. |
| Affected | WordPress 4.7 through 7.1.3 |
| Fixed in | WordPress 7.1.4 |
| Credited | Umut Bayram |
| Attack vector | Network – unauthenticated – no user interaction |
| Cost | Sites running auto-update trigger the vulnerable path silently |
The vulnerability lives in class-wp-upgrader.php, specifically the unpack_package() method. When WordPress performs a full-package core upgrade (download → extract → copy), it unpacks the downloaded ZIP archive into wp-content/upgrade/{package_name}/. The problem: the name of that directory is completely deterministic-it is the basename of the ZIP minus the .zip suffix. For a standard core upgrade the directory is wp-content/upgrade/wordpress/ in every single installation.
And here is the kicker: that directory is under the web server document root. The ZIP extraction takes roughly 3-5 seconds. During that window, every file inside the staging directory is directly accessible via HTTP, including PHP files that execute server-side code. An attacker who knows the staging path can hit it while the extraction-or the final directory rename-is in progress.
Technical Deep Dive
The vulnerable function chain starts in WP_Upgrader::upgrade(), which calls download_package() followed by unpack_package(). The 7.1.3 code reads:
// vulnerable line from 7.1.3 - wp-admin/includes/class-wp-upgrader.php
function unpack_package($package) {
$upgrade_folder = wp_content . 'upgrade/';
// Clean up contents of upgrade directory
$upgrade_files = dirlist($upgrade_folder);
if (!empty($upgrade_files)) {
foreach ($file in $upgrade_files) {
delete($upgrade_folder . $file);
}
}
// PREDICTABLE directory name:
$working_dir = $upgrade_folder . basename(basename($package, '.tmp'), '.zip');
// → for core upgrade, $working_dir is always:
// wp-content/upgrade/wordpress/
$result = unzip_file($package, $working_dir);
return $working_dir;
}Line 378 in the 7.1.3 release-the line I marked above-is where the vulnerability is born. The basename() call produces a fixed, predictable path. There is no entropy, no randomisation, no temporary name. Every WordPress site upgrading from 7.1.3 to a newer version extracts the full package into wp-content/upgrade/wordpress/. And the cleanup loop at the top deletes the existing index.php that WordPress 6.x started placing in wp-content/upgrade/, leaving the directory listable again.
The 7.1.4 patch adds two things:
- A new
get_working_dir_name()method that appends 16 random characters to the directory name, giving something likewordpress-Ab3xK9qM2nR5pL1w0. - An
index.phpfile written towp-content/upgrade/with the contents, preventing directory listing.
Here is the exact diff:

Lab Reproduction
I set up a local lab on an isolated VM with WordPress 7.1.3, MariaDB, and PHP's built-in HTTP server bound to 203.0.113.10:8080 (RFC 5737 TEST-NET range-all demo traffic stayed local). I then placed a simple PHP probe file in the staging path and hit it with curl:
# On the vulnerable 7.1.3 instance:
# Place a PHP probe in the web-accessible staging path:
$ cat > wp-content/upgrade/wordpress/info.php << 'EOF'
<?php
header("Content-Type: text/plain");
echo "PHP executed from staging path\n";
phpinfo();
EOF
# Fetch it via HTTP (unauthenticated, no cookies needed):
$ curl http://203.0.113.10:8080/wp-content/upgrade/wordpress/info.php
[DEMO] PHP Execution from Staging Path
Server time: 2026-11-05 01:44:45
Server software: PHP/8.5.4 (Development Server)
Document root: /tmp/wp-lab/wordpress
$_SERVER['SCRIPT_FILENAME'] =>
/tmp/wp-lab/wordpress/wp-content/upgrade/wordpress/info.php
$_SERVER['REQUEST_URI'] => /wp-content/upgrade/wordpress/info.php
The SCRIPT_FILENAME confirms that the code executed from the staging directory. Now during a real upgrade, the entire 7.1.4 codebase is extracted to that same path. By hitting any PHP file in /wp-content/upgrade/wordpress/wp-admin/ during the extraction window, an attacker runs code in the context of the new version before the upgrade completes.

To demonstrate the race window, I ran a polling loop alongside an actual wp core update --version=7.1.4. The watcher probed four predictable URLs every 200 ms. During the extraction phase the staging path returned HTTP 200 consistently. Once the upgrade finished and unpack_package cleaned up, all staging paths returned 404. On a stock 7.1.3 install the path /wp-content/upgrade/wordpress/ is accessible from the web during the entire upgrade window, and there is no index.php in the upgrade/ directory to block directory listing.
# Race-window watcher (poll every 200 ms):
PREDICTABLE_URLS=(
/wp-content/upgrade/
/wp-content/upgrade/wordpress/
/wp-content/upgrade/wordpress/wp-login.php
/wp-content/upgrade/wordpress/wp-includes/version.php
)
for url in "${PREDICTABLE_URLS[@]}"; do
while true; do
status=$(curl -s -o /dev/null -w '%{http_code}' \
http://203.0.113.10:8080${url})
if [[ "$status" == "200" ]]; then
echo "$(date +%s.%N) HIT $url → $status"
fi
sleep 0.2
done
done
Detection and Defence
This vulnerability does not leave traditional web-application log signals because the requests go to legitimate WordPress files. The key detection opportunities are in the upgrade directory itself:
| Layer | Detection Signal |
|---|---|
| WAF | Request to /wp-content/upgrade/wordpress/* from non-internal IP during off-hours might indicate an attacker racing the upgrade window |
| File integrity | Monitor wp-content/upgrade/ for unexpected files outside scheduled upgrade windows. A file appearing before the upgrade starts suggests pre-planting |
| Web server log | 200 responses from /wp-content/upgrade/wordpress/*.php URLs are anomalous outside the 3-5 second upgrade window. Alert on >1 hit outside maintenance window |
| Account audit | Check whether any administrator-level user triggered a core upgrade recently. If none did but staging files appeared, the host may be compromised |
| Directory listing | Probe /wp-content/upgrade/ periodically. If it returns a directory listing (no index.php), the site is running < 7.1.4 or is mid-upgrade. Both are useful signals |
The most practical detection is simple and low-tech: enable server-side monitoring of the /wp-content/upgrade/ path. In Apache, you can block it outright with an .htaccess rule:
# Block direct access to the upgrade staging directory
RewriteRule ^wp-content/upgrade/ - [F,L]
# Only WordPress's internal upgrader should touch that pathNginx equivalent: location /wp-content/upgrade/ { deny all; }. Apply this as a compensating control until you patch.
Mitigation
The fixed version is WordPress 7.1.4. Upgrade immediately, especially if your site uses automatic background updates-those trigger exactly the vulnerable code path without manual interaction, and the vulnerability window opens silently.
| Version | Status |
|---|---|
| 7.1.4 | Fixed. Staging directory randomized (wordpress-{16 random chars}). Upgrade/ directory protected with index.php. |
| 7.1.3 and earlier | Vulnerable. Predictable staging path, no index.php in upgrade/ directory. |
| 4.7 and below | EOL. No longer supported. Upgrade to a supported branch. |
If you cannot patch immediately: disable automatic background updates via define('DISALLOW_FILE_MODS', true); in wp-config.php, and block /wp-content/upgrade/ at your web server or CDN layer. Schedule upgrades during low-traffic windows, and run a directory listing check against the staging path immediately afterward to confirm it cleaned up.
I wrote about a different WordPress RCE chain earlier this year-wp2shell: Pre-Auth RCE Chain in WordPress Core-and the pattern keeps repeating: the upgrade/update process is the most sensitive code path in any CMS because it runs with elevated privileges against external payloads. CVE-2026-119977 is a textbook TOCTOU: predictable path + web-accessible staging + no directory index = race-to-RCE. Update before the 6 November deadline, and if your host already auto-updated, verify you are serving 7.1.4.