WordPress CVE-2026-119977 is on CISA's exploited list; here's how to hunt past updates on your clients' WordPress sites.
For a few seconds during a WordPress update, an attacker who wins a race could run code on the server, and that tiny gap carries a federal deadline. CISA added CVE-2026-119977 to its exploited-vulnerabilities catalog on 3 November 2026 and gave US federal agencies until 6 November 2026. Check your clients' WordPress sites today, because the question is now whether an old update was already hit.
wp-includes/version.php on your clients' servers to find out./wp-content/upgrade/ at the web server, update, then search retained access logs for PHP requests under that folder that succeeded.CVE-2026-119977 is a race condition (a timing flaw) in WordPress Core: during full-package updates, WordPress's GitHub advisory says an unauthenticated attacker who wins a race against extraction could reach remote code execution. CISA added it on 3 November 2026 and set a due date of 6 November 2026 for federal agencies, pointing them to forensic triage (checking for compromise) alongside mitigations. WordPress rates it Low; the US vulnerability database NVD lists 8.1 (CVSS), with no login needed but high attack complexity.
Wordfence counted more than 18,500 requests for staged installer paths in the first 21 hours; The Hacker News later cited 62,969 blocked attacks in 24 hours. Our 31 October article covers the mechanism; the CVE record is new since then.
WordPress 4.7 through 7.1.3 are affected, and the fix depends on the branch.
| Product | Branch | Fixed in |
|---|---|---|
| WordPress | 7.1 | 7.1.4 |
| WordPress | 7.0 | 7.0.8 |
| WordPress | 6.9 | 6.9.11 |
| WordPress | 6.8 | 6.8.12 |
| WordPress | 6.7 | 6.7.11 |
| WordPress | 6.6 | 6.6.11 |
| WordPress | 6.5 | 6.5.14 |
| WordPress | 6.4 | 6.4.14 |
| WordPress | 6.3 | 6.3.14 |
| WordPress | 6.2 | 6.2.15 |
| WordPress | 6.1 | 6.1.16 |
| WordPress | 6.0 | 6.0.18 |
| WordPress | 5.9 | 5.9.20 |
| WordPress | 5.8 | 5.8.19 |
| WordPress | 5.7 | 5.7.21 |
| WordPress | 5.6 | 5.6.23 |
| WordPress | 5.5 | 5.5.24 |
| WordPress | 5.4 | 5.4.25 |
| WordPress | 5.3 | 5.3.27 |
| WordPress | 5.2 | 5.2.30 |
| WordPress | 5.1 | 5.1.28 |
| WordPress | 5.0 | 5.0.31 |
| WordPress | 4.9 | 4.9.35 |
| WordPress | 4.8 | 4.8.34 |
| WordPress | 4.7 | 4.7.39 |
Fixed versions from WordPress's GitHub advisory. It lists no release older than 4.7, so move those sites to a listed branch.
Run these on Linux servers with sudo; on managed hosting, ask the host to run them and send the output. No source we read dates the first exploitation, and Wordfence's post gives no attacker IPs, so search from the oldest retained logs. Server logs usually cover less time than that: use the client's SIEM (central log store) where one exists. A clean result on short logs can't rule out an earlier hit.
Preserve evidence. Copy and hash logs and web roots before changing anything. Pass: the .sha256 file lists the copied files.
D=/root/wp-evidence-$(date +%F); sudo mkdir -p "$D"
# "No such file" errors for folders this server lacks are expected
sudo cp -a /var/log/apache2 /var/log/nginx /var/log/httpd /usr/local/apache/domlogs /usr/local/apache/logs /etc/apache2/logs/domlogs /var/www /home "$D"/
for c in $(sudo docker ps -q); do sudo docker logs "$c" 2>&1 | sudo tee "$D/docker-$c.log" >/dev/null; done
sudo find "$D" -type f -exec sha256sum {} + | sudo tee "$D.sha256"
List installs and versions. This reads the WordPress version on the host and in running containers. Pass: all lines show a release from the Fixed in column.
sudo find / \( -path /proc -o -path '/root/wp-evidence*' \) -prune -o -path '*/wp-includes/version.php' -exec grep -H '^\$wp_version' {} +
for c in $(sudo docker ps -q); do sudo docker exec "$c" find / -path /proc -prune -o -path '*/wp-includes/version.php' -exec grep -H '^\$wp_version' {} +; done
Search logs for answered requests. Log paths come from Liquid Web's cPanel list and Plesk's docs. A hit means the file answered: treat the site as likely compromised. Pass: no output.
P='wp-content/upgrade/[^ ]*\.php[^ ]* HTTP/[0-9.]+. 200 '
# A site that returns 200 for missing pages shows false hits; test one with a made-up path
sudo find /var/log/apache2 /var/log/nginx /var/log/httpd /usr/local/apache/domlogs /usr/local/apache/logs /etc/apache2/logs/domlogs /var/www/vhosts -type f \( -path '*/log*' -o -path '*/domlogs/*' \) -exec zgrep -HE "$P" {} + 2>/dev/null | grep -v 'upgrade/index\.php'
for c in $(sudo docker ps -q); do sudo docker logs "$c" 2>&1 | grep -E "$P" | grep -v 'upgrade/index\.php'; done
Find leftover PHP in upgrade folders while no update is running. Patchstack's analysis makes index.php the normal guard file, so it is excluded. Pass: no output.
sudo find / \( -path /proc -o -path '/root/wp-evidence*' \) -prune -o -path '*/wp-content/upgrade/*' -name '*.php' ! -name index.php -print
for c in $(sudo docker ps -q); do sudo docker exec "$c" find / -path /proc -prune -o -path '*/wp-content/upgrade/*' -name '*.php' ! -name index.php -print; done
Check database hosts. Patchstack says a reached installer could use an attacker's database. The ss line is a spot check of current connections only. Pass: only hosts the client recognises appear.
sudo find / \( -path /proc -o -path '/root/wp-evidence*' \) -prune -o -name wp-config.php -exec grep -H DB_HOST {} +
for c in $(sudo docker ps -q); do sudo docker exec "$c" find / -path /proc -prune -o -name wp-config.php -exec grep -H DB_HOST {} +; done
sudo ss -tnp state established '( dport = :3306 )'
Check web workers and cron. The process tree shows only what is still running. Pass: no sh, bash, curl, wget or python sits under a web worker, and no unrecognised cron entry appears.
sudo ps -eo pid,ppid,user,comm,args --forest | grep -E -A5 'apache2|httpd|nginx|php-fpm|lsphp'
sudo sh -c 'ls -la /etc/cron* /var/spool/cron/*'
Check admins, core files and planted PHP with WP-CLI, WordPress's command-line tool: user list and core verify-checksums. Pass: no PHP in uploads, only recognised admins and must-use plugins, and "Success: WordPress installation verifies against checksums." under all paths.
sudo find / \( -path /proc -o -path '/root/wp-evidence*' \) -prune -o \( -path '*/wp-content/uploads/*' -name '*.php' -o -path '*/wp-content/mu-plugins/*' \) -print
for c in $(sudo docker ps -q); do sudo docker exec "$c" find / -path /proc -prune -o \( -path '*/wp-content/uploads/*' -name '*.php' -o -path '*/wp-content/mu-plugins/*' \) -print; done
# Runs as the file owner, not root. mu-plugins and wp-config.php still load, so read the listing and check 5 first.
# If WP-CLI errors on a root-owned or container install, review Users > All Users > Administrator in the dashboard.
sudo find / \( -path /proc -o -path '/root/wp-evidence*' \) -prune -o -path '*/wp-includes/version.php' -print | while read f; do p=$(dirname "$(dirname "$f")"); u=$(stat -c %U "$f"); echo "== $p"; sudo -u "$u" wp --skip-plugins --skip-themes --path="$p" core verify-checksums; sudo -u "$u" wp --skip-plugins --skip-themes --path="$p" user list --role=administrator --fields=user_login,user_registered; done
Close the folder before updating: by Rescana's reading, the update to 7.1.4 is still unpacked by the old code.
Preserve evidence on any server where check 1 hasn't run. Pass: this lists a .sha256 file.
sudo sh -c 'ls -la /root/wp-evidence-*.sha256'
Block the folder at the web server. In nginx, ^~ stops regex locations matching first and deny works in a location. In Apache, RedirectMatch in .htaccess needs AllowOverride FileInfo. Prefix subfolder installs; other front ends need the equivalent rule. Pass: all domains print 403.
# nginx: inside the site's server block
location ^~ /wp-content/upgrade/ { deny all; }
# shell, after saving the nginx change
sudo nginx -t && sudo systemctl reload nginx
# Apache: in the vhost or .htaccess
RedirectMatch 403 "^/wp-content/upgrade/"
# shell, after a vhost change
sudo apachectl configtest && sudo apachectl graceful
# shell, from outside, with client domains in sites.txt
while read s; do printf '%s ' "$s"; curl -s -o /dev/null -w '%{http_code}\n' "https://$s/wp-content/upgrade/"; done < sites.txt
Update all installs with wp core update, which moves to the latest version; add --minor to stay on a branch. Pass: rerun check 2 and all lines show a Fixed in release.
# Containers: update the image and redeploy it instead
sudo find / \( -path /proc -o -path '/root/wp-evidence*' \) -prune -o -path '*/wp-includes/version.php' -print | while read f; do sudo -u "$(stat -c %U "$f")" wp --skip-plugins --skip-themes --path="$(dirname "$(dirname "$f")")" core update; done
Remove footholds if a check hit. Keep the evidence, take the site offline, and rebuild it from a backup you have verified with these checks. Pass: checks 4 to 7 on the rebuilt site match their Pass lines.
Rotate secrets on hit sites after the update. Change the database password at the host, put it in wp-config.php, then run shuffle-salts and reset-password. Pass: logging in with an old admin password fails.
sudo -u <owner> wp --path=<site> --skip-plugins --skip-themes config shuffle-salts
sudo -u <owner> wp --path=<site> --skip-plugins --skip-themes user reset-password $(sudo -u <owner> wp --path=<site> --skip-plugins --skip-themes user list --format=ids --role=administrator)
Rerun the checks. Pass: checks 2 and 4 to 7 match their Pass lines, and the sites.txt loop prints 403 for all domains.
KinoSec's autonomous web app pentest checks whether each client site's /wp-content/upgrade/ folder answers from the internet and which WordPress version it shows, then writes that up with remediation in the report you hand your client. It won't read server logs, so checks 3 to 7 stay with you. For a partner, that turns a CISA deadline into a list of client sites to fix instead of a guess at who runs what. It is built to test for this class of exposure across your clients' web apps and external networks, on demand.
No. wp2shell is a separate chain, CVE-2026-63030 and CVE-2026-60137, which GreyNoise says an attacker used from about 20 July 2026. CVE-2026-119977 is the upgrade-window race fixed in WordPress 7.1.4, released 28 October 2026. A site patched only for wp2shell can still be exposed.
First copy and hash the logs and web root. Then search retained access logs with zgrep -E 'wp-content/upgrade/[^ ]*\.php' access.log* and treat any 200 response, apart from index.php, as a likely compromise. Next, list stray files with find / -path '*/wp-content/upgrade/*' -name '*.php', and run wp core verify-checksums in the install.
Not on its own. Wordfence says the exploit requests a staged PHP file without first loading WordPress, so its firewall needs Extended Protection, and Wordfence Free users get the rule on 27 November 2026. Block the folder at the web server instead, with location ^~ /wp-content/upgrade/ { deny all; } on nginx.
CISA's 6 November 2026 due date is set for federal agencies. Whether a private client is bound by it depends on that client's own contracts. We would still use it as the working deadline for public client sites, and record the block, update and check results in the client's report.