A door that only opens during a 10-second maintenance swap, but there are millions of doors and attackers are already yanking handles
CVE-2026-119977 is a time-of-check/time-of-use race in WordPress Core's full-package upgrade process. Between WordPress 4.7 and 7.1.3, when a manual or automatic full core upgrade runs, WordPress extracts package files into a predictable path under wp-content/upgrade/. If the web server executes PHP from that staging directory, an unauthenticated attacker can request the staging copy of install.php or setup-config.php during the seconds-to-minutes cleanup window and achieve arbitrary code execution on the host.
The vendor HIGH (8.1) severity is broadly fair, but the AC:H complexity is doing serious work. The attacker cannot trigger the upgrade window themselves without an admin action or a known auto-update event; the exploit is a race, not an always-on remote shell. That said, WordPress's auto-update behavior and huge installed base create a periodic, predictable window that scanners can monitor, and CIRT.GY reports nearly 63,000 attack attempts within days of disclosure. No patch appears to exist yet, which raises practical urgency but not the underlying CVSS.
5 steps from start to impact.
Fingerprint WordPress version and auto-update posture
readme.html, or known asset hashes. They then infer whether automatic core updates are enabled, which will dictate whether the upgrade window is predictable or requires an administrator action.- Network access to the target
- Version fingerprinting can be blocked by security plugins
Wait for or provoke a full-package core upgrade
wp-content/upgrade/ only during a full-package core upgrade. Automatic security updates for WordPress core happen on many sites shortly after a new release, providing a predictable window. Manual upgrades occur only when an admin clicks update.- Target is running an affected WordPress version
- A full core upgrade is triggered
- Upgrade window lasts seconds to a few minutes
- Manual updates are infrequent and hard to predict
Access the predictable staging path
wp-content/upgrade/wordpress-<version>/. Because the path is predictable, scanners can automate requests for common version names as soon as a new WordPress release appears.- Web server serves PHP files from the staging directory
- Some managed WordPress hosts already deny PHP execution in
wp-content/upgrade/
/wp-content/upgrade/ would stop this, but generic rules rarely cover itExecute code via the staging installer or config race
install.php or setup-config.php can run PHP in an unconfigured state, allowing the attacker to write a wp-config.php or drop a PHP payload before WordPress cleans up the staging directory.- File write permissions in the staging directory
- Hardened file permissions can prevent writes
- WordPress may clean up the staging directory quickly
Turn code execution into site takeover and persistence
wp-config.php for database credentials, and persists through plugin or theme file modifications. From there they can deface the site, exfiltrate customer or membership data, or use the web server as a pivot point.- Web server user has write access to web root
- Least-privilege PHP users may limit host-level pivot
- EDR or host-based intrusion detection may catch a web shell
wp-content/upgrade/ — Add a web-server rule to block PHP execution from the staging directory (Apache php_admin_flag engine off or <FilesMatch> deny; Nginx location ^~ /wp-content/upgrade/ with deny all). This directly breaks the exploit at step 4. Deploy within hours under the noisgate mitigation SLA override for active exploitation, not the 30-day HIGH window./wp-content/upgrade/ entirely — Block all external requests to that path at the CDN/WAF layer; internal WordPress processes don't need web access to staging. Deploy immediately and monitor logs for old attempts. This suppresses the predictable-path requirement while you plan patching.wp-content/upgrade/ anomalies — Alert on any external request to staging paths or new PHP files outside normal themes/plugins. This doesn't stop the attack but compresses detection time and is essential until patch. Deploy within 24 hours.- MFA or strong admin passwords — the vulnerability is unauthenticated and does not touch the login flow.
- Disabling XML-RPC or hiding
wp-login.php— irrelevant to the staging path. - Network ACL/IP allowlists — distributed scanners rotate IPs and the path is on the normal web port.
- Applying a regular WordPress update is currently impossible because no patch has been released; do not confuse status-quo with patching.
The supporting signals.
| In-the-wild status | Active exploitation attempts; CIRT.GY reports ~63,000 attack attempts between Oct 31 and Nov 3, 2026. |
|---|---|
| Proof-of-concept availability | No public PoC or ready-made Nuclei/OpenVAS template at time of analysis per cve.tools; exploitation appears opportunistic rather than PoC-driven. |
| EPSS score | 0.006 (0.6%) — 49th percentile |
| KEV status | Added to CISA KEV on 2026-11-03; required action due 2026-11-06. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H — network, no privileges, no UI, but high complexity |
| Affected versions | WordPress Core 4.7 through 7.1.3; all maintenance branches below those ceilings |
| Fixed versions | None published as of 2026-11-03; cve.tools lists upgrade targets only as 'after' each affected branch maintenance release |
| Exposure / scanning | No GreyNoise/Shodan/Censys breakdown provided; WordPress powers a huge fraction of the web, and auto-update expands the window population |
| Disclosure date | 2026-10-31 |
| Reporting source | Wordfence ([email protected]) per Intruder; MITRE/NVD published Oct 31, 2026 |
Sources.
Why this verdict
- Vendor HIGH is fair, not inflated: unauthenticated RCE on a ubiquitous CMS; however the
AC:Hrace condition and upgrade-window requirement keep this below CRITICAL. - Friction audit: the chain requires a live upgrade window; manual upgrades are rare and auto-updates are seconds long. Attackers must continuously probe predictable version paths to land.
- Role multiplier: WordPress is an application-tier member host across low-value blogs and line-of-business sites, but it is not a canonical identity/CA/hypervisor/backup/edge component; a successful compromise yields site/host takeover and data theft, not domain/fleet/supply-chain impacts at ≥1% of installs.
- Active exploitation overrides patch cadence: 63k attempts and KEV listing force immediate mitigation even though the base score is HIGH.
Why not higher?
Not CRITICAL because the exploit requires the narrow upgrade window and a predictable staging path that some hardened WordPress hosts already block. The attack is not always-on; a site that is not mid-upgrade is not exploitable, and upgraded sites with the eventual patch are removed entirely. If the race were trivial or the path always existed, this would be a 9.8.
Why not lower?
Not MEDIUM or lower because WordPress is extremely widely deployed, the attacker needs no credentials or user interaction, and CIRT.GY confirms thousands of active exploitation attempts within days. A successful hit gives full PHP code execution and likely database credential theft; ignoring this until the next patch cycle would be reckless.
Crowdsourced verification payload.
Run from an auditor workstation with network access to the target WordPress host. Example: ./check_cve_2026_119977.sh https://example.com (no privileges needed). It detects WordPress version via generator meta and checks whether /wp-content/upgrade/ is reachable.
#!/usr/bin/env bash
# CVE-2026-119977 quick check
set -u
TARGET="${1:-http://localhost}"
TARGET="${TARGET%/}"
UA='Mozilla/5.0'
html=$(curl -skL --max-time 15 -A "$UA" "$TARGET/" 2>/dev/null)
if [ -z "$html" ]; then
echo "UNKNOWN: could not fetch homepage"
exit 2
fi
VERSION=$(printf '%s' "$html" | grep -oE '<meta name="generator" content="WordPress [0-9.]+"' | grep -oE '[0-9]+(\.[0-9]+)+' | head -1)
if [ -z "$VERSION" ]; then
VERSION=$(curl -skL --max-time 10 -A "$UA" "$TARGET/readme.html" 2>/dev/null | grep -oE 'Version [0-9.]+' | grep -oE '[0-9]+(\.[0-9]+)+' | head -1)
fi
if [ -z "$VERSION" ]; then
echo "UNKNOWN: could not determine WordPress version"
exit 2
fi
IFS='.' read -ra SV <<< "$VERSION"
major=${SV[0]}
minor=${SV[1]:-0}
patch=${SV[2]:-0}
affected=1
if [ "$major" -ge 8 ] || { [ "$major" -eq 7 ] && [ "$minor" -gt 1 ]; } || { [ "$major" -eq 7 ] && [ "$minor" -eq 1 ] && [ "$patch" -gt 3 ]; } || [ "$major" -lt 4 ] || { [ "$major" -eq 4 ] && [ "$minor" -lt 7 ]; }; then
affected=0
fi
if [ "$affected" -eq 0 ]; then
echo "PATCHED: WordPress $VERSION is outside affected range"
exit 0
fi
status=$(curl -skL -o /dev/null -w '%{http_code}' --max-time 10 -A "$UA" "$TARGET/wp-content/upgrade/" 2>/dev/null)
if [ "$status" = "200" ] || [ "$status" = "403" ]; then
echo "VULNERABLE: WordPress $VERSION affected; upgrade staging path returned HTTP $status"
exit 1
else
echo "UNKNOWN: WordPress $VERSION affected but staging path not reachable now (HTTP $status); race-window dependent"
exit 2
fi