← Back to Feed CACHED · 2026-11-04 04:30:24 · CACHE_KEY CVE-2026-119977
CVE-2026-119977 · CWE-367 · Disclosed 2026-10-31

WordPress Core predictable staging path RCE in full-package upgrades

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

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.

"Race-condition RCE? Yes. No patch, KEV-listed, 63k attack attempts, and WordPress is everywhere — treat as HIGH, mitigate in hours."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Fingerprint WordPress version and auto-update posture

The attacker identifies a target WordPress site and determines the running version through generator meta tags, 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.
Conditions required:
  • Network access to the target
Where this breaks in practice:
  • Version fingerprinting can be blocked by security plugins
Detection/coverage: Basic WAF and bot protection may flag repeated version discovery requests, but most WordPress sites leak version data
STEP 02

Wait for or provoke a full-package core upgrade

WordPress creates a staging directory under 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.
Conditions required:
  • Target is running an affected WordPress version
  • A full core upgrade is triggered
Where this breaks in practice:
  • Upgrade window lasts seconds to a few minutes
  • Manual updates are infrequent and hard to predict
Detection/coverage: Web server logs may show the admin-triggered update request, but automated upgrades often happen silently
STEP 03

Access the predictable staging path

During the upgrade, the attacker requests the extracted full-package files under 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.
Conditions required:
  • Web server serves PHP files from the staging directory
Where this breaks in practice:
  • Some managed WordPress hosts already deny PHP execution in wp-content/upgrade/
Detection/coverage: A WAF rule specifically blocking /wp-content/upgrade/ would stop this, but generic rules rarely cover it
STEP 04

Execute code via the staging installer or config race

The staging copy contains a full WordPress installer. Because the staging directory is web-accessible, requesting 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.
Conditions required:
  • File write permissions in the staging directory
Where this breaks in practice:
  • Hardened file permissions can prevent writes
  • WordPress may clean up the staging directory quickly
Detection/coverage: File integrity monitoring may catch unexpected PHP files in the upgrade directory
STEP 05

Turn code execution into site takeover and persistence

With arbitrary PHP execution, the attacker drops a web shell, reads 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.
Conditions required:
  • Web server user has write access to web root
Where this breaks in practice:
  • Least-privilege PHP users may limit host-level pivot
  • EDR or host-based intrusion detection may catch a web shell
Detection/coverage: Endpoint detection and web shell scanners can detect the dropped payload after the fact
03 · Compensating Control

1
HIGH 7.8→LOW 2.0
SEVERITY REDUCED
Disable PHP execution in 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.
2
HIGH 7.8→LOW 3.0
SEVERITY REDUCED
Add WAF or reverse-proxy rule to deny /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.
3
HIGH 7.8→HIGH 7.5
Monitor access logs and file integrity for 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.
What doesn't work
  • 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.
04 · Intelligence Metadata

The supporting signals.

In-the-wild statusActive exploitation attempts; CIRT.GY reports ~63,000 attack attempts between Oct 31 and Nov 3, 2026.
Proof-of-concept availabilityNo public PoC or ready-made Nuclei/OpenVAS template at time of analysis per cve.tools; exploitation appears opportunistic rather than PoC-driven.
EPSS score0.006 (0.6%) — 49th percentile
KEV statusAdded to CISA KEV on 2026-11-03; required action due 2026-11-06.
CVSS vectorCVSS: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 versionsWordPress Core 4.7 through 7.1.3; all maintenance branches below those ceilings
Fixed versionsNone published as of 2026-11-03; cve.tools lists upgrade targets only as 'after' each affected branch maintenance release
Exposure / scanningNo GreyNoise/Shodan/Censys breakdown provided; WordPress powers a huge fraction of the web, and auto-update expands the window population
Disclosure date2026-10-31
Reporting sourceWordfence ([email protected]) per Intruder; MITRE/NVD published Oct 31, 2026

Sources.

  1. NVD
  2. VulDB
  3. Intruder CVE Monitor
  4. Red Hat Bugzilla
  5. CVE Tools
  6. CIRT.GY
  7. CVE.org
05 · The Call

Final Verdict
= UNCHANGED to HIGH (7.8/10)

Why this verdict

  • Vendor HIGH is fair, not inflated: unauthenticated RCE on a ubiquitous CMS; however the AC:H race 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.

06 · Verification

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.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/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