Why this matters now
WordPress released version 7.1.4 on October 28, 2026, fixing 22 security vulnerabilities, including a core remote code execution flaw. Within days, mass exploitation tooling had moved: Wordfence blocked 60,471 attack attempts against the flaw in a single 24-hour window, as reported on October 31. That number is the hunt trigger, and it needs to be read precisely. It counts blocked attempts, not confirmed compromises, and the number of individual sites targeted cannot be derived from it. But the direction is unambiguous: the patch is public, the probing is automated, and any site still on a pre-7.1.4 release is now in the window where a successful attempt is a matter of exposure time, not attacker attention.
The flaw itself targets a race condition in which WordPress briefly creates temporary installation files during an upgrade. That detail changes where you hunt. The vulnerable moment is tied to update activity, so the highest-value window to review is not steady-state traffic — it is the minutes around each upgrade, and the days immediately after a site was patched late or not at all.
In MITRE ATT&CK terms, the chain maps to T1190 (Exploit Public-Facing Application) for initial access, T1505.003 (Server Software Component: Web Shell) for persistence on the server, T1078 (Valid Accounts) if the attacker creates or elevates an administrator, and T1053 (Scheduled Task/Job) for a scheduled persistence mechanism. This article hunts the footprints of that chain, not the blocked probes.
The technique, in hunt terms
Think of a successful exploitation as leaving four kinds of evidence, in roughly this order. First, a burst of requests against the site clustered around an upgrade window. Second, the web server process doing something it normally never does: spawning a shell or a scripting interpreter. Third, a new executable file appearing somewhere writable, most often under wp-content/uploads or a plugin directory, where PHP should never execute. Fourth, a persistence change: an administrator account nobody on the team recognises, a role change on an existing account, or a scheduled task created around the same window.
None of these four, on its own, proves the WordPress core flaw was the entry point — other web flaws produce the same footprints, and a blocked probe produces none of them. That is the point of the hunt: you are not looking for the attack that your firewall already stopped. You are looking for the one that got through and then went quiet.
Hunt 1: The web server spawning a shell
A web server process should serve pages, not run shells. When httpd, apache2, nginx, or php-fpm becomes the parent of sh, bash, cmd.exe, or PowerShell, treat it as a compromise indicator until proven otherwise. This query runs in Microsoft Defender XDR Advanced Hunting over the last 14 days; narrow the window to your upgrade dates first if the result set is large.
DeviceProcessEvents
| where Timestamp > ago(14d)
| where InitiatingProcessFileName in~ ("httpd", "apache2", "nginx", "php-fpm", "php-cgi", "w3wp.exe")
| where FileName in~ ("sh", "bash", "cmd.exe", "powershell.exe", "pwsh.exe", "python3", "perl")
| project Timestamp, DeviceName, FileName, ProcessCommandLine,
InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName
| order by Timestamp desc
Triage the command line before anything else. A shell spawned by a legitimate deployment script or a plugin's scheduled job will usually show a known path and a known operator account. A shell spawned with encoded arguments, a reverse-connection address, or a download cradle is the incident. Correlate the timestamp with your upgrade log: a shell minutes after an upgrade attempt is a very different finding from one at 3 a.m. on an untouched site, and both deserve a ticket.
Hunt 2: New executable files under wp-content
WordPress uploads are meant to hold media, not code. A newly created .php or .phtml file under wp-content — especially under uploads — is the classic web-shell footprint, and it is exactly the kind of file the post-upgrade check in the November 1 brief tells operators to look for. This query surfaces those creations on Defender-enrolled servers:
DeviceFileEvents
| where Timestamp > ago(14d)
| where ActionType == "FileCreated"
| where FolderPath contains "wp-content"
| where FileName contains ".php" or FileName contains ".phtml"
| project Timestamp, DeviceName, FolderPath, FileName,
InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName
| order by Timestamp desc
For every hit, answer three questions: who created it (the initiating process should be the web server or PHP, not an admin's file manager), when (inside or near an upgrade window), and whether it has ever been requested (join the filename to your web logs — a shell nobody has called yet is still a shell, but one with inbound hits is an active foothold). If your servers are not Defender-enrolled, the same logic works as a file-integrity check: diff the current wp-content tree against your last known-good snapshot and investigate every new executable, not just the ones with suspicious names. Attackers name shells image.php and style.php precisely because those names look boring.
Hunt 3: First-seen PHP hits and rogue admins in web logs
Where Defender telemetry is unavailable, web access logs carry the same signal. PHP files under uploads should never return a 200 response, because PHP should never execute from that directory. The Splunk search below finds first-seen executable hits there; adapt the sourcetype to your web server's log format.
index=web_logs (sourcetype=apache_access OR sourcetype=nginx_access)
| eval php_in_uploads = if(match(uri_path, "(?i)wp-content/uploads/.*\.(php|phtml)"), 1, 0)
| where php_in_uploads = 1 AND status = 200
| stats earliest(_time) as first_seen, latest(_time) as last_seen,
count as hits, values(status) as statuses by clientip, uri_path
| sort first_seen
Pair that with your WordPress audit trail. If you ship activity-log data to Splunk, hunt for administrator accounts created or elevated around the same window, and for logins from unfamiliar addresses to those accounts:
index=wordpress_audit (action="user_created" OR action="user_role_changed" OR action="user_login")
| stats earliest(_time) as first_seen, values(action) as actions,
values(user_role) as roles, values(src_ip) as src_ips by user_login
| sort first_seen
Adapt the field names to your audit plugin — the hunt is the pattern (new or elevated admin, close in time to an upgrade window or a shell creation), not the exact field spelling. A rogue admin created days after the upgrade is still in scope: attackers often plant the shell first and return for the account later. The same post-upgrade discipline applies to scheduled tasks: the November 1 brief explicitly flags tasks created around the upgrade window, and the playbook for chasing them is covered in Hunting Scheduled Task Persistence.
Triage: sorting real compromise from noise
Work each hit through this order. First, confirm the patch state: if the site is still pre-7.1.4, patching is urgent but it does not remove a shell that is already there — patch and hunt in parallel, never patch instead of hunting. Second, check the file itself: read the new PHP file, hash it, and compare it against your plugin and theme inventory; a legitimate plugin file will match a known release, a shell will not. Third, check the account: ask the site owner whether the new administrator is theirs, and check whether that account has posted, installed, or changed anything since creation. Fourth, check the network: look for outbound connections from the web server process to unfamiliar addresses in the hours after the first shell hit. A shell that never phones home is still a foothold, but one with an active channel is an incident in progress.
Close a hit as benign only with evidence, not with familiarity. "That plugin always does that" is not evidence; a matching hash from the plugin vendor's release, or a change record from your own deployment pipeline, is. Anything you cannot explain goes into containment: take the file offline, disable the account, and preserve logs before they rotate.
Remediation and hardening
If a hunt hit confirms compromise, the sequence matters. Remove the shell files and any rogue administrator accounts, then rotate every credential that lived on that server — administrator passwords, database credentials, API keys, and salts in wp-config.php — because a web-shell operator typically reads the configuration before anything else. Force-logout all sessions so a stolen session cookie dies with the account. Only then consider the site clean, and re-scan it from a known-good baseline rather than trusting the cleaned tree.
For sites that hunt clean, the hardening that prevents the next round is straightforward: update to WordPress 7.1.4 or later and keep automatic updates on for minor releases; disable PHP execution in wp-content/uploads at the web server level so a planted file cannot run even if it lands; restrict wp-admin to known operator addresses where feasible; and keep a file-integrity baseline so the next Hunt 2 is a diff, not a search. The same server-process logic generalises: when a web worker starts spawning shells, the application behind it has already lost control, whether the flaw is WordPress core or something else — see Hunting Server-Side Template Injection and Web Shells on IIS for the same pattern on other stacks.
The 60,471 blocked attempts are a warning about volume. The hunt above is about the one attempt that was not blocked. Run it over every WordPress estate you operate, starting with the sites whose upgrades lagged the October 28 patch — those are the sites where the race-condition window stayed open longest.
