WordPress Stored-XSS Admin Takeover: Fake "WP Smart Thumbnails" Plugin, Hidden Administrator and Backdoor Login

Two unrelated plugin flaws (Ninja Forms CVE-2026-94504, WPC Product Bundles CVE-2026-93836) deliver one JavaScript implant that rides a logged-in administrator's session. Exploited since 2026-10-04. Attribution: not stated by sources.
Threat
CVE-2026-94504, CVE-2026-93836
Severity
EXPLOITED ITW
Type
Stored XSS · WordPress post-exploitation implant
Access
Unauthenticated injection, fires in admin session
CVSS
7.1 (Patchstack) to 7.2 (SecurityOnline)
Version
v0.1 · 2026-10-07
Author
HuntPack
Confidence
High (vulnerabilities, behavior) · Medium (atomic IOCs, single primary source)
01

Executive Summary

Two unrelated WordPress plugins, WPC Product Bundles for WooCommerce (CVE-2026-93836, versions up to 8.6.6) and Ninja Forms (CVE-2026-94504, versions up to 3.15.3), carry stored cross-site scripting flaws that are being exploited to deliver one shared JavaScript implant from imgcdn1[.]com. Patchstack first saw it on 2026-10-04 against WPC Product Bundles and on 2026-10-05 through Ninja Forms, and reported it on 2026-10-06. The XSS is only the front door: the payload runs inside a logged-in administrator's browser, rides that authenticated session, scrapes the nonces WordPress requires, and then uses WordPress's own plugin installer to drop a fake plugin.

One successful execution leaves the attacker with four independent ways back in: a visible administrator account, an administrator hidden from the Users screen by a must-use plugin, a magic login URL that authenticates as the site's oldest administrator, and an unauthenticated file manager inside the fake plugin. Removing the vulnerable plugin, or even the fake one, closes none of the last three. Patchstack also notes that the installer backdates the files it writes, so a search for recently changed files misses them.

Honest endpoint scope. The exploit, nonce theft, plugin upload, administrator creation and database option writes all happen inside the browser, WordPress and PHP. Falcon endpoint telemetry cannot see them. What Falcon can see is narrower: the administrator workstation looking up the C2 domain, file-integrity events for the dropped PHP files if a FIM policy covers wp-content, shells spawned by the web or PHP worker, and command lines that name the campaign's artifacts. Everything else needs WordPress audit logs, database queries, web server logs, a WAF or a file-integrity tool, and Section 7 and Section 15 say so plainly.

Defender priority: update both plugins, then treat any site where an administrator opened a WooCommerce order or Ninja Forms submission since 2026-10-04 as potentially compromised. Updating stops new injections but does not clean an existing infection. Enumerate administrators directly in the database, inspect the mu-plugins directory by content rather than date, and rotate credentials and salts.

02

Source Review & Web Hunter Notes

TierSourceKey FindingCarry Forward
1 · Primary researchPatchstack, "Four ways back in", 2026-10-06 (snapshot 01)Full campaign analysis: both CVEs, x.js behavior, plugin and installer analysis, four persistence routes, infrastructure, IOC table, detection and mitigation stepsYes: primary source for every indicator
1 · Vendor changelogNinja Forms readme changelog (snapshot 04)Version 3.15.4 (21 September 2026) lists strengthened output escaping in the admin submission edit screen; confirms 3.15.3 as the preceding releaseYes: fix version
1 · Vendor changelogWPC Product Bundles readme changelog (snapshot 05)Version 8.6.7 lists a fixed vulnerability; version 8.7.3 separately lists sanitizing bundle ids from request, cart session and order item meta to prevent stored XSS; current listed release is 8.7.4Yes: fix version, with the 8.7.3 caveat
2 · Security journalismBleepingComputer, 2026-10-06 (snapshot 02)Independent write-up of Patchstack findings: same two CVEs, 500,000 and 30,000 install counts, four access mechanisms, backdated auxiliary pluginsYes: corroborates CVEs and fix versions (derivative of Patchstack, not independent research)
2 · Security journalismReconBee, 2026-10-07 (snapshot 06)Repeats the CVE pairing and the imgcdn1[.]com domainPartial: aggregator
3 · CVE listingSecurityOnline.info homepage "Active Exploits in the Wild" panel (snapshot 03)Lists CVE-2026-94504 and CVE-2026-93836 as active exploits with CVSS 7.2 and short descriptions matching the two pluginsPartial: confirms CVE numbers and flags a CVSS discrepancy
2 · Hardening authorityWordPress.org developer documentation: Hardening WordPress and wp-config (snapshots 07, 08)DISALLOW_FILE_EDIT, DISALLOW_FILE_MODS, file permission guidance, Must-Use Plugins referenceYes: hardening controls
2 · ATT&CKMITRE ATT&CK T1505.003 (snapshot 09)Web Shell technique, mitigations M1042 and M1018, detection strategy for file creation in web directories followed by web server child processesYes: mapping and detection logic

Decision log

  • CVE numbers. CVE-2026-93836 and CVE-2026-94504 are the only CVE identifiers shipped. Both appear in the Patchstack article, BleepingComputer, ReconBee and the SecurityOnline panel. Other Ninja Forms CVE identifiers circulate in low-quality aggregator posts and search snippets; none of them appears in any saved snapshot, and one that surfaced in a search snippet described a different, earlier Ninja Forms version range, so none is shipped.
  • CVSS discrepancy. Patchstack lists CVSS 7.1 for both flaws; SecurityOnline lists 7.2. The pack reports "7.1 to 7.2, high" rather than choosing one.
  • "Authenticated" wording. BleepingComputer and ReconBee say the flaws "require an authenticated session to exploit". Patchstack calls both unauthenticated stored XSS. These reconcile: the attacker injects the payload without logging in, but the script only does damage when an authenticated administrator views the stored content. The pack follows Patchstack.
  • WPC Product Bundles fix version. Patchstack and BleepingComputer name 8.6.7. The vendor changelog lists a vulnerability fix in 8.6.7 and a separate stored-XSS sanitization fix in 8.7.3. The pack recommends moving to the current release, not stopping at 8.6.7.
  • Single-source indicators ship as detect. Every hash, the domain and the plugin artifacts originate in the Patchstack analysis. BleepingComputer and ReconBee repeat the domain but do not independently verify it. Under the two-source rule nothing ships at prevent, and no row in the Falcon import CSV is prevent.
  • Hashes that cannot exist. Patchstack states the two must-use plugins are obfuscated with a fresh key per site, so no fixed hash is published and none is invented here. They are listed by path and detected by behavior.
  • No coined name. The sources give the campaign no label. The pack uses descriptive wording and the fake plugin identity "WP Smart Thumbnails" exactly as the sources name it.
  • Fetched-content check. No fetched page contained instructions directed at an automated reader. Pages carried only ordinary site navigation, ads and unrelated headlines, which were ignored.
03

Hunt Brief & Attack Chain

Hypotheses

IDHypothesisMITREFalcon eventQueryFidelity
H1An administrator workstation resolved imgcdn1[.]com after opening an order or submissionT1071.001, T1059.007DnsRequestQ1High
H2Campaign PHP files were written into the plugins or mu-plugins directoryT1505.003, T1564CriticalFileModified (FIM)Q5, Q6High (needs FIM)
H3A shell or downloader ran under the web or PHP worker after the file manager was reachedT1505.003, T1059ProcessRollup2Q3Medium
H4Someone ran commands naming the implant files, options or login tokenT1505.003, T1136.001, T1078ProcessRollup2Q4High
H5The web server fetched a second-stage payload from a rare addressT1105NetworkConnectIP4Q2Low
H6The delivery IPs reached the web portsT1190NetworkReceiveAcceptIP4Q7Low
H7Stored payload, hidden administrator, backdoor login and file manager use (WordPress, DB, web log layer)T1190, T1136.001, T1078, T1505.003None (non-Falcon)Section 7 native huntsHigh (off-endpoint)

Attack chain

StepBehaviorTelemetryHunt angle
1Attacker submits a payload through woosb_ids[...][qty] (WPC, from 2026-10-04) or admin-ajax.php with action=nf_ajax_submit (Ninja Forms, from 2026-10-05)Web server and WAF logs, WordPress databaseGAP on endpoint. Web logs, WAF, database
2Payload sits in WooCommerce order metadata or a Ninja Forms submission until an administrator views itWordPress databaseGAP on endpoint. DB search for the domain string
3Administrator opens the content; browser loads /fz/x.js from imgcdn1[.]comAdmin workstation DNS and proxyQ1
4x.js checks in at /fz/c.php?xa=check&site=, tracks stages in browser Local Storage key prefix __xp_v9_Admin workstation DNS and proxy; browser storageQ1; browser forensics
5Using the admin session, x.js scrapes the plugin-upload nonce, pulls a ZIP from c.php?xa=pkg, installs wp-smart-thumbnailsWeb server file writes, WordPress audit logQ5 (FIM); WordPress audit log
6Visible administrator created via user-new.php with credentials from xa=admin_credsWordPress audit log, databaseGAP on endpoint. WordPress audit log, DB
7emer-run.php fetched and run in the admin session: hidden administrator, two must-use plugins, magic login tokenFile writes in mu-plugins, database optionsQ5, Q6 (FIM); DB options check
8Installer backdates every file it writes to the oldest timestamp in the WordPress rootFile timestampsQ5 and Q6 use event time, not mtime
9Result reported to the C2 via fetch, sendBeacon and an image-request fallbackAdmin workstation DNS and proxyQ1
10Later: attacker uses /wp-login.php?_wplogin=<token> or hits the file manager directly, uploads a second shellWeb access logs; web-worker child processesQ3, Q4; web logs for the direct requests
04

Consolidated IOC Table

TypeValueConfidenceActionContextExpiry
domainimgcdn1[.]comHighdetectServes x.js, c.php, plugin ZIP. Registered 2026-10-01 (Patchstack). Single original source+6 months
urlhttps://imgcdn1[.]com/fz/x.jsHighdetectBrowser implant, 19,378 bytes (Patchstack)+6 months
urlhttps://imgcdn1[.]com/fz/c.phpHighdetectC2 and payload server; ?xa=check, ?xa=pkg, ?xa=admin_creds endpoints+6 months
sha256af803bc822cf9596b1f1785e1d59642f91f6fbc21f8e9943feefa19a6df8c2a4Mediumdetectx.js (single source)+6 months
md58a4413c1aa058179ff54b95571bcf2a3Mediumdetectx.js (single source)+6 months
sha256ccc95113334357c2a3671aae12043bff00020858cec115f7e724fe43e9f1fc24Mediumdetectwp-smart-thumbnails.php, packed dropper and file manager+6 months
md5da651d4f191a7c363806fe5357752015Mediumdetectwp-smart-thumbnails.php (main plugin file)+6 months
sha256ed00234ad2b67dbbc9073e8c711b18b7d39e0a5255b6159189f75cf2ed99cfb9Mediumdetectemer-run.php, persistence installer+6 months
md51024732009983dd5e54b4cf5593f04d4Mediumdetectemer-run.php+6 months
path/wp-content/plugins/wp-smart-thumbnails/ (wp-smart-thumbnails.php, emer-run.php, readme.txt, includes/class-thumb-cache.php)HighhuntFake plugin "WP Smart Thumbnails" 1.2.4 by "MediaPress Labs". Slug is campaign-specificn/a
path/wp-content/mu-plugins/class-wp-query-<8 hex>.phpHighhuntHides the hidden administrator (hooks pre_user_query, users_list_table_query_args, views_users). Obfuscated, per-site key, no fixed hashn/a
path/wp-content/mu-plugins/class-wp-token-validate.phpHighhuntTrades the login token for a session cookie (hooks init at priority 1)n/a
wp optionfz_emer_done_v1, fz_emer_login_tokensHighhuntCampaign state. Presence of fz_emer_login_tokens means a backdoor login token is liven/a
url pattern/wp-login.php?_wplogin=<token>HighhuntBackdoor login, authenticates as the oldest administratorn/a
stringdyiv:// stream wrapper; title string "Moon, tell me if I could"; Local Storage prefix __xp_v9_MediumhuntDropper and file manager internals; browser-side state (Patchstack)n/a
account patternusernames drawn from words such as support, updater, maintenance, backup; @wordpress.org email; 16-character random passwordMediumenrichHidden administrator profile. Dictionary of about fifty words, so names vary+1 month
ipv4204.8.96.109, 147.90.235.22, 90.184.10.74, 94.16.115.121 (WPC, 2026-10-04); 43.250.53.42 (Ninja Forms, 2026-10-05)LowhuntObserved delivery sources. Four are Tor exit nodes; the fifth is flagged for spam and brute force (Patchstack). Not durable, not exhaustive+3 months

Patchstack states these indicators describe the campaign observed in its telemetry and should not be read as exhaustive indicators for exploitation of either CVE.

05

Affected Surface & Telemetry Matrix

SurfaceRequired telemetryPriorityGap risk
WordPress administrator browser (Windows, macOS, Linux workstations)DnsRequest, proxy and DNS logsHighPartial DNS visible; script execution, nonce theft and session riding are NOT visible to Falcon
Linux web server (Apache, NGINX, PHP-FPM)ProcessRollup2, CriticalFileModified (FIM), NetworkConnectIP4HighPartial file events need a FIM policy covering wp-content; PHP file writes are not a native Falcon file-write type
Windows web server (IIS with w3wp.exe, or Apache/PHP on Windows)ProcessRollup2, CriticalFileModified (FIM), NetworkConnectIP4MediumPartial same FIM dependency
WordPress application layer (users, options, plugin install)WordPress audit log plugin, database queriesHighGAP not endpoint-visible. Hidden administrator and fz_emer_* option rows exist only in the database
Web access and error logsApache or NGINX logs, WAF, CDN logsHighGAP injection requests, _wplogin logins and direct file-manager hits live here
Databasewp_users, wp_usermeta, wp_options, order and submission tablesHighGAP only direct SQL reveals the hidden administrator, because the mu-plugin filters WordPress's own user queries
06

ATT&CK Mapping

TacticTechniqueObserved behaviorQuery / control
Initial AccessT1190 Exploit Public-Facing ApplicationStored XSS injected through WooCommerce order data or Ninja Forms submissionsGAP (web/WAF/DB); Q7 context
ExecutionT1059.007 JavaScriptx.js runs in the administrator's authenticated browser originGAP on endpoint; Q1 sees the fetch
Command and ControlT1071.001 Web ProtocolsHTTPS check-in and reporting to imgcdn1[.]com/fz/c.phpQ1
Command and ControlT1105 Ingress Tool TransferPlugin ZIP pulled from c.php?xa=pkg; emer-run.php fetchedQ1, Q2
PersistenceT1505.003 Web ShellUnauthenticated file manager in wp-smart-thumbnails.php; second shells via its upload and saveQ3, Q4, Q5
PersistenceT1136.001 Create Account: Local AccountVisible administrator via user-new.php and hidden administrator via emer-run.phpGAP (WP audit log, DB); Q4 for CLI use
Persistence / Defense EvasionT1564 Hide ArtifactsMust-use plugin filters the account out of Users screen, counts and queriesQ5, Q6; DB cross-check
Persistence / Initial AccessT1078 Valid AccountsMagic login URL authenticates as the oldest administratorGAP (web logs); Q4
Defense EvasionT1070.006 TimestompInstalled files stamped with the oldest timestamp in the WordPress rootQ5, Q6 (event time)
Defense EvasionT1027 Obfuscated Files or InformationSeven-layer packed dropper, XOR plus gzdeflate must-use plugins, stream wrapper instead of eval()Partial: name and path based only
Defense EvasionT1036 MasqueradingFake plugin identity "WP Smart Thumbnails" 1.2.4 by "MediaPress Labs"; decoy readme and class filesQ5
07

Native Audit-Log Hunts

Most of this campaign is invisible to the Falcon sensor. These checks run on the WordPress host, the database and the web server, and are the primary hunt for the application-layer steps. Commands come from the Patchstack detection section unless marked as derived guidance.

1. Enumerate administrators in the database, not in wp-admin

SELECT u.user_login, u.user_email, u.user_registered
FROM wp_users u
JOIN wp_usermeta m ON m.user_id = u.ID
WHERE m.meta_key = 'wp_capabilities'
  AND m.meta_value LIKE '%administrator%';

Compare to Users, All Users. Any account present in SQL but absent in the interface is hidden by a dropped must-use plugin. An @wordpress.org address on a local account, or a routine word such as support, updater, maintenance or backup, is a further signal. Adjust the wp_ prefix to your table prefix. Use raw SQL: WP-CLI loads must-use plugins, so wp user list can be filtered by the same hooks.

2. Check the campaign's own options

SELECT option_name FROM wp_options
WHERE option_name IN ('fz_emer_done_v1', 'fz_emer_login_tokens');

fz_emer_login_tokens present means at least one backdoor login token is live. Deleting the options alone is not enough while the must-use plugins remain.

3. Inspect the mu-plugins directory and the fake plugin

ls -la wp-content/mu-plugins/
ls -la wp-content/plugins/wp-smart-thumbnails/ 2>/dev/null
find wp-content/mu-plugins -type f -name 'class-wp-query-*.php' -o -name 'class-wp-token-validate.php'
grep -rl "dyiv://" wp-content/plugins 2>/dev/null

File modification times are not reliable here, because the installer backdates what it writes. Sort by content and by whether the file belongs to your known-good baseline. The mu-plugins directory should normally be empty or hold only files your host or team placed there. Both campaign must-use plugins are obfuscated, so a plain-text grep for hook names will not find them.

4. Search web server logs

grep -E "imgcdn1|/fz/x\.js|/fz/c\.php" /var/log/apache2/*access* /var/log/nginx/*access*
grep -E "_wplogin=" /var/log/apache2/*access* /var/log/nginx/*access*
grep -E "wp-smart-thumbnails" /var/log/apache2/*access* /var/log/nginx/*access*
grep -E "woosb_ids|nf_ajax_submit" /var/log/apache2/*access* /var/log/nginx/*access*

Requests carrying _wplogin to wp-login.php and direct requests to wp-smart-thumbnails.php outside the plugin screens are usable signals (Patchstack). The last pattern is broad: nf_ajax_submit is a normal Ninja Forms action, so triage hits by looking for markup in the submitted values. POST bodies are usually not in access logs, so a WAF or audit log is needed to see the payload itself.

5. Find stored payloads before an administrator opens them (derived guidance)

wp db query "SELECT meta_id, post_id FROM wp_postmeta WHERE meta_value LIKE '%imgcdn1%'"
wp db query "SELECT meta_id, order_item_id FROM wp_woocommerce_order_itemmeta WHERE meta_value LIKE '%imgcdn1%'"

This searches the places the sources say the payload is stored (order metadata and form submissions). Table names vary with prefix and WooCommerce storage mode; treat it as a starting point. Remove matching rows without opening them in wp-admin.

6. Review administrator-created accounts and plugin installs around order or submission views

Patchstack advises investigating any administrator account created around the time an administrator viewed WooCommerce orders or Ninja Forms submissions. This needs a WordPress audit-log plugin or equivalent; the stock log does not record it.

7. Administrator workstation (browser)

Search proxy and DNS logs for imgcdn1[.]com. On a workstation known to have opened affected content, check browser Local Storage for keys beginning __xp_v9_ (Patchstack). Treat the administrator's WordPress session as compromised.

08

CrowdStrike LogScale CQL Hunt Queries

Pick your tenant's cloud first — every "Open in Falcon" button below uses this selection.

Seven endpoint queries cover only the endpoint-visible slice. Q5 and Q6 depend on a Falcon FIM or critical-file policy that includes wp-content, which is not on by default (marked "validate in tenant"). Q7 depends on inbound-accept telemetry. XSS execution, nonce theft, hidden-administrator creation and the database option rows have no Falcon query; see Section 7 and Section 15.

Q1 · imgcdn1[.]com DNS lookup (payload and C2 host)
CONF HIGHFP LOWCOST LOW

Looks for: any DNS lookup of the single domain that serves every stage: the browser implant, the C2 check-in and the plugin ZIP. The lookup is made by the administrator's browser, so expect hits on admin workstations first. Works on Windows, macOS and Linux sensors.

// HUNT: DNS lookup of imgcdn1[.]com, the host serving the stored-XSS implant and its C2
// MITRE: T1071.001, T1105, T1059.007
// CONF: high
// FP: low
// COST: low
// REQUIRES: DnsRequest on admin workstations (Windows, macOS) and on web servers (Windows, Linux)
// FALSE POSITIVES: threat-intel sandboxes or DNS-sinkhole testing that deliberately resolves the domain
// NOTE: the implant runs in the ADMINISTRATOR'S BROWSER, so the lookup normally comes from an admin workstation, not the web server
#event_simpleName=DnsRequest
| DomainName=/(^|\.)imgcdn1\.com$/i
| groupBy([aid, ComputerName, ContextBaseFileName, DomainName], function=[count(as=Lookups), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)], limit=20000)
| sort(FirstSeen, order=asc)
| table([aid, ComputerName, ContextBaseFileName, DomainName, Lookups, FirstSeen, LastSeen])
Q2 · Web-server egress to rare external addresses
CONF LOWFP HIGHCOST MEDIUM

Looks for: low-count outbound web connections from the web or PHP worker, a follow-on indicator if a second-stage fetch happens server-side. FP: plugin and core update checks, payment and shipping APIs and CDNs are normal; baseline before alerting (see the TUNING line in the card).

// HUNT: Web-server or PHP worker making outbound web connections to rare external addresses
// MITRE: T1105, T1071.001
// CONF: low
// FP: high
// COST: medium
// REQUIRES: NetworkConnectIP4 from Linux (httpd, apache2, nginx, php-fpm) and Windows (w3wp.exe) web servers
// FALSE POSITIVES: WordPress core and plugin update checks, payment gateway and shipping APIs, license servers, CDN and font fetches, outbound mail APIs
// TUNING: baseline for 14 days in investigate mode, then exclude every destination the site legitimately calls, for example by adding: | !in(RemoteAddressIP4, values=["REPLACE_WITH_BASELINED_API_IP"]) and keep only the rare tail (Conns <= 5)
// NOTE: Patchstack describes the fetches as browser-side, so this is a second-stage and follow-on hunt, not a primary detection
#event_simpleName=NetworkConnectIP4
| ContextBaseFileName=/^(httpd|apache2|nginx|php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|w3wp\.exe)$/i
| in(RemotePort, values=[80, 443])
| !cidr(RemoteAddressIP4, subnet="10.0.0.0/8")
| !cidr(RemoteAddressIP4, subnet="172.16.0.0/12")
| !cidr(RemoteAddressIP4, subnet="192.168.0.0/16")
| !cidr(RemoteAddressIP4, subnet="127.0.0.0/8")
| groupBy([aid, ComputerName, ContextBaseFileName, RemoteAddressIP4], function=[count(as=Conns), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)], limit=20000)
| Conns <= 5
| sort(Conns, order=asc)
| table([aid, ComputerName, ContextBaseFileName, RemoteAddressIP4, Conns, FirstSeen, LastSeen])
Q3 · Shell or downloader spawned by a web or PHP worker
CONF MEDIUMFP MEDIUMCOST LOW

Looks for: the classic web-shell follow-on. The fake plugin's file manager cannot execute commands itself but can write a second shell. FP: image and PDF plugins that shell out; exclude their helper commands (see TUNING).

// HUNT: Shell, downloader or recon utility spawned by a web-server or PHP worker process
// MITRE: T1505.003, T1059
// CONF: medium
// FP: medium
// COST: low
// REQUIRES: ProcessRollup2 on Linux (apache2, httpd, nginx, php-fpm) and Windows (w3wp.exe) web servers
// FALSE POSITIVES: image and PDF plugins that shell out to convert, gs or wkhtmltopdf; backup and cache plugins; hosting-panel helpers
// TUNING: after a baseline, exclude known plugin helper commands, for example: | !(FileName=/^(sh|bash)$/i CommandLine=/convert|gs |wkhtmltopdf|ffmpeg/i) ; escalate when the parent is a PHP worker and the child is a downloader (curl, wget) or an interactive shell with no helper pattern
// NOTE: the fake plugin's file manager has no command-execution primitive (Patchstack), so this catches the SECOND shell an attacker drops through it, not the file manager itself
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| ParentBaseFileName=/^(httpd|apache2|nginx|php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|w3wp\.exe)$/i
| FileName=/^(sh|bash|dash|zsh|ash|nc|ncat|netcat|socat|curl|wget|python[0-9.]*|perl|ruby|whoami|id|uname|cmd\.exe|powershell\.exe|pwsh\.exe|certutil\.exe|bitsadmin\.exe|whoami\.exe|curl\.exe|mshta\.exe)$/i
| table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine])
Q4 · Command lines naming the campaign's files, options or login token
CONF HIGHFP MEDIUMCOST LOW

Looks for: shell or WP-CLI activity touching the plugin slug, the installer, the two must-use plugin names, the two option names or the _wplogin token. Also fires on your own responders, so tune by parent process. FP: IR triage and cleanup scripts (see TUNING).

// HUNT: Process command line referencing the campaign's plugin, installer, must-use plugin, option or login-token names
// MITRE: T1505.003, T1136.001, T1078
// CONF: high
// FP: medium
// COST: low
// REQUIRES: ProcessRollup2 on Linux and Windows web servers
// FALSE POSITIVES: your own incident-response triage (grep, find, ls, rm, wp db query) and file-integrity or backup tooling that names these paths while cleaning up
// TUNING: exclude your IR toolkit and approved admin sessions, for example: | !(ParentBaseFileName=/^(REPLACE_WITH_IR_WRAPPER)$/i) ; treat any hit whose parent is a web or PHP worker, or whose command line does not come from a known responder, as hostile
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| CommandLine=/(wp-smart-thumbnails|emer-run\.php|class-wp-token-validate|class-wp-query-[0-9a-f]{8}\.php|fz_emer_(done_v1|login_tokens)|_wplogin=)/i
| table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine])
Q5 · Campaign-named PHP files written (FIM)
CONF HIGHFP LOWCOST LOW

Looks for: the four filenames Patchstack lists. Depends on a Falcon FIM or critical-file policy that covers wp-content. This is the best endpoint-side proof the implant landed, and it is not on by default.

// HUNT: Campaign-named PHP files written to a WordPress host (dropper, installer, token-validate and query must-use plugins)
// MITRE: T1505.003, T1564, T1070.006
// CONF: high
// FP: low
// COST: low
// REQUIRES: CriticalFileModified (Falcon File Integrity Monitoring / critical-file policy covering wp-content) -- ⚠ validate in tenant: wp-content is NOT monitored by default and the policy must be scoped to plugins and mu-plugins
// FALSE POSITIVES: none expected; these filenames are not part of WordPress core or any legitimate plugin Patchstack describes
// NOTE: the event time is the write time as the sensor saw it, so the installer's file-timestamp backdating does not hide it here
#event_simpleName=CriticalFileModified
| FileName=/^(class-wp-token-validate\.php|class-wp-query-[0-9a-f]{8}\.php|emer-run\.php|wp-smart-thumbnails\.php)$/i
| table([@timestamp, aid, ComputerName, FileName, FilePath, ContextBaseFileName, ContextProcessId, UserName])
Q6 · Any PHP written into mu-plugins (FIM)
CONF MEDIUMFP MEDIUMCOST LOW

Looks for: new files in the must-use directory, which the Plugins screen never lists and which survive removal of the fake plugin. FP: managed hosts and security suites that ship their own mu-plugins (see TUNING).

// HUNT: Any PHP file created or changed in a WordPress mu-plugins directory
// MITRE: T1505.003, T1564
// CONF: medium
// FP: medium
// COST: low
// REQUIRES: CriticalFileModified scoped to wp-content/mu-plugins -- ⚠ validate in tenant: add the mu-plugins path to your FIM policy first
// FALSE POSITIVES: managed WordPress hosts and caching or security suites that ship their own mu-plugins, and deliberate deploys from CI
// TUNING: exclude the filenames your host or deploy pipeline legitimately writes, for example: | !FileName=/^(REPLACE_WITH_HOST_MU_PLUGIN)\.php$/i ; keep every other write, especially one made by a web or PHP worker process (ContextBaseFileName) with no matching change ticket
// NOTE: both campaign must-use plugins are obfuscated per site with a fresh key, so no fixed hash exists; the directory itself is the signal
#event_simpleName=CriticalFileModified
| FilePath=/mu-plugins/i
| FileName=/\.php$/i
| table([@timestamp, aid, ComputerName, FileName, FilePath, ContextBaseFileName, ContextProcessId, UserName])
Q7 · Inbound connections from the observed delivery IPs
CONF LOWFP LOWCOST LOW

Looks for: contact from the five addresses in the source table. Mostly Tor exits, so this is context for scoping, never a verdict.

// HUNT: Inbound web connections from the five source IPs Patchstack observed delivering the payload
// MITRE: T1190
// CONF: low
// FP: low
// COST: low
// REQUIRES: NetworkReceiveAcceptIP4 on web servers -- ⚠ validate in tenant: inbound-accept telemetry coverage differs by sensor version and OS
// FALSE POSITIVES: four of the five addresses are Tor exit nodes (Patchstack) shared with unrelated traffic, so a hit shows contact, not compromise
// NOTE: hunt and enrich only, the addresses are not durable and the list is not exhaustive (Patchstack)
#event_simpleName=NetworkReceiveAcceptIP4
| in(RemoteAddressIP4, values=["204.8.96.109", "147.90.235.22", "90.184.10.74", "94.16.115.121", "43.250.53.42"])
| in(LocalPort, values=[80, 443, 8080, 8443])
| table([@timestamp, aid, ComputerName, RemoteAddressIP4, LocalPort, ContextBaseFileName])
09

CrowdStrike Custom IOA Recommendations

IOA namePatternExclusionsDeployment path
Web worker spawns shell or downloader (from Q3)Process creation. Parent image matches httpd, apache2, nginx, php-fpm, php-cgi or w3wp.exe; child matches sh, bash, curl, wget, cmd.exe, powershell.exeExclude known image and PDF helper commands after baselineDetect-only first; promote to Prevent only after a clean baseline window. Confirm Linux rule-type availability in your tenant
imgcdn1[.]com domain lookup (from Q1)Domain name rule, imgcdn1.com and subdomainsNone expectedDetect (single-source indicator, so not prevent). Prefer the IOC Management entry in Section 10 for the same domain
Campaign command-line artifacts (from Q4)Process creation. Command line contains wp-smart-thumbnails, emer-run.php or class-wp-token-validateExclude your IR tooling by parent processDetect. FP medium, so no automated action

File-based Q5 and Q6 are FIM-dependent and not IOA candidates. IOA rule-type support differs between Windows and Linux sensors, so confirm each rule type exists for your sensor version before building.

10

Machine-Readable IOC Appendix

Falcon IOC Management CSVbulk import, detect only
type,value,action,severity,expiration,description,tags
domain,imgcdn1.com,detect,high,2027-04-07,Payload and C2 host for WordPress stored-XSS implant (Patchstack),wordpress;stored-xss;imgcdn1
sha256,af803bc822cf9596b1f1785e1d59642f91f6fbc21f8e9943feefa19a6df8c2a4,detect,high,2027-04-07,x.js browser implant 19378 bytes,wordpress;stored-xss;imgcdn1
md5,8a4413c1aa058179ff54b95571bcf2a3,detect,high,2027-04-07,x.js browser implant,wordpress;stored-xss;imgcdn1
sha256,ccc95113334357c2a3671aae12043bff00020858cec115f7e724fe43e9f1fc24,detect,high,2027-04-07,wp-smart-thumbnails.php packed dropper and file manager,wordpress;stored-xss;imgcdn1
md5,da651d4f191a7c363806fe5357752015,detect,high,2027-04-07,wp-smart-thumbnails.php main plugin file,wordpress;stored-xss;imgcdn1
sha256,ed00234ad2b67dbbc9073e8c711b18b7d39e0a5255b6159189f75cf2ed99cfb9,detect,high,2027-04-07,emer-run.php persistence installer,wordpress;stored-xss;imgcdn1
md5,1024732009983dd5e54b4cf5593f04d4,detect,high,2027-04-07,emer-run.php persistence installer,wordpress;stored-xss;imgcdn1
# No row is action=prevent: every indicator is single-sourced to the Patchstack analysis (two-source rule).
# The two mu-plugins are obfuscated per site with a fresh key, so no hash exists. Detect them by path (Q5, Q6).
Behavioral Signaturespaths, options, request patterns
# Filesystem (hunt by path and content, not by mtime)
wp-content/plugins/wp-smart-thumbnails/wp-smart-thumbnails.php
wp-content/plugins/wp-smart-thumbnails/emer-run.php
wp-content/plugins/wp-smart-thumbnails/readme.txt              (46 bytes, decoy)
wp-content/plugins/wp-smart-thumbnails/includes/class-thumb-cache.php   (41 bytes, decoy)
wp-content/mu-plugins/class-wp-query-<8 hex>.php
wp-content/mu-plugins/class-wp-token-validate.php

# Plugin identity (fake)
"WP Smart Thumbnails" 1.2.4 by "MediaPress Labs"

# WordPress options rows
fz_emer_done_v1
fz_emer_login_tokens

# Web request patterns
GET /fz/x.js, /fz/c.php?xa=check|pkg|admin_creds  (to imgcdn1[.]com, from admin browsers)
GET /wp-login.php?_wplogin=<token>
Direct request to wp-content/plugins/wp-smart-thumbnails/wp-smart-thumbnails.php
POST /wp-admin/admin-ajax.php action=nf_ajax_submit (markup in submitted fields)
Request parameter woosb_ids[...][qty] with a numeric prefix followed by markup

# Code and browser artifacts
stream wrapper scheme dyiv://
webshell title "Moon, tell me if I could"
Local Storage key prefix __xp_v9_
mu-plugin hooks: pre_user_query, users_list_table_query_args, views_users, init (priority 1)

# Hidden administrator profile
username from: support, updater, maintenance, backup (and about fifty similar words)
email domain: @wordpress.org      password: 16 random characters
Observed Delivery IPshunt / enrich only
# Observed delivery sources (Patchstack). HUNT / ENRICH ONLY, not for blocking.
# Four are Tor exit nodes; blocking addresses is not a durable mitigation (Patchstack).
2026-10-04  CVE-2026-93836 (WPC Product Bundles)
204.8.96.109
147.90.235.22
90.184.10.74
94.16.115.121
2026-10-05  CVE-2026-94504 (Ninja Forms)
43.250.53.42
Patch / Compromise AuditWP-CLI and SQL checks
# Version audit (WP-CLI). Fixed: Ninja Forms 3.15.4+, WPC Product Bundles 8.6.7+ (move to the current release)
wp plugin get ninja-forms --fields=name,version,status
wp plugin get woo-product-bundle --fields=name,version,status
wp plugin list --fields=name,version,status,update

# Fake plugin and must-use plugin presence
wp plugin is-installed wp-smart-thumbnails && echo "FAKE PLUGIN PRESENT"
ls -la wp-content/mu-plugins/

# Raw SQL checks (do not use wp user list, mu-plugins can filter it)
wp db query "SELECT u.user_login,u.user_email,u.user_registered FROM wp_users u JOIN wp_usermeta m ON m.user_id=u.ID WHERE m.meta_key='wp_capabilities' AND m.meta_value LIKE '%administrator%'"
wp db query "SELECT option_name FROM wp_options WHERE option_name IN ('fz_emer_done_v1','fz_emer_login_tokens')"
11

Detection Validation Gates

GateRequirementStatus in this pack
Telemetry readyFalcon sensor on every web server host and every WordPress administrator workstation; a FIM or critical-file policy covering wp-content/plugins and wp-content/mu-plugins before Q5 and Q6 are relied onVerify per-tenant FIM coverage is the usual gap
Benign baselineRun Q2, Q3, Q4 and Q6 in investigate mode for at least 14 days before any automated action. Record legitimate outbound APIs, plugin helper commands and host-managed mu-pluginsRequired before go-live
Positive testsQ1: resolve a harmless test hostname in a controlled lab and confirm the query shape; do not resolve the live domain from a production host. Q5: create a file named class-wp-token-validate.php under a monitored lab path and confirm the FIM eventPending tenant validation
Promotion criteriaZero unexplained hits over the baseline window before any Q3, Q4 or IOA is moved from audit to enforce. Q1 and Q5 may be promoted to alert earlier because their false-positive surface is smallPending
12

Hardening: Tiered & Deployable

Authorities: MITRE ATT&CK mitigations (the why), the WordPress.org Hardening WordPress and wp-config documentation (snapshots 07, 08), and the CIS Apache HTTP Server and NGINX Benchmarks for the web tier. No formal CIS WordPress benchmark was consulted for this pack, so WordPress controls cite the WordPress.org documentation and are marked where they are best practice only.

Immediate (this week)
  • Update Ninja Forms to 3.15.4 or later and WPC Product Bundles to the current release. Patchstack names 8.6.7; the vendor changelog lists a further stored-XSS sanitization fix in 8.7.3. MITRE M1051 (Update Software), T1190. Verify: wp plugin list --fields=name,version,update.
  • Clean stored payloads and assume prior compromise. Search order and submission data for the domain string (Section 7, check 5), remove matches without opening them in wp-admin, then run the administrator, option and mu-plugins checks (checks 1 to 3). Updating does not clean an existing infection (BleepingComputer, Patchstack). MITRE M1047 (Audit).
  • Sinkhole imgcdn1[.]com for administrator workstations and web servers at DNS or the web proxy. MITRE M1037 (Filter Network Traffic), M1031 (Network Intrusion Prevention). Verify: Q1 returns only the sinkhole lookups and a resolver query returns the sinkhole address.
  • Block the backdoor login and the fake plugin at the web server. Reject any request whose query string contains _wplogin= and deny the wp-smart-thumbnails plugin path (playbook step 3). MITRE M1050 (Exploit Protection), T1078. Verify: curl -I "https://site.example.com/wp-login.php?_wplogin=x" returns 403.
  • Stop reviewing orders and form submissions as an administrator. Give order and submission reviewers a role without install_plugins (for example Shop Manager). The implant needs administrator rights to install the plugin. MITRE M1026 (Privileged Account Management), M1018. Source: WordPress.org hardening guidance on least privilege (snapshot 07). Verify: wp user list --role=administrator --fields=user_login contains only people who must administer.
Near term (1 to 4 weeks)
  • Set DISALLOW_FILE_MODS in wp-config.php. It blocks plugin and theme installation and update from the admin area, which removes the step the implant uses to land the fake plugin. Trade-off: updates must then come through WP-CLI, Composer or CI. MITRE M1054 (Software Configuration), M1042. Source: WordPress.org wp-config documentation (snapshot 08). Verify: wp config get DISALLOW_FILE_MODS returns 1. Pilot on one site first.
  • Make plugin and mu-plugins directories non-writable by the web user. Files owned by a deploy account, directories 755 and files 644, web user read-only (WordPress.org File Permissions guidance, snapshot 07). The installer cannot write its must-use plugins if PHP cannot write the directory. MITRE M1022 (Restrict File and Directory Permissions). Verify: sudo -u www-data touch wp-content/mu-plugins/t fails. Pilot first, because dashboard auto-updates stop working.
  • Restrict wp-login.php and wp-admin to a VPN or office range. This does not stop the XSS, which fires from an allowed administrator's browser, but it stops attackers from using the magic login URL and a created account from their own network. MITRE M1030 (Network Segmentation). CIS Apache HTTP Server and NGINX Benchmarks cover access restriction. Verify: external request to wp-login.php returns 403.
  • File-integrity monitoring on wp-content/plugins and wp-content/mu-plugins (Falcon FIM policy, AIDE or auditd) so Q5 and Q6 have data. MITRE M1047 (Audit). Verify: create a test file in a lab copy and confirm an event.
  • Deploy a WAF rule or virtual patch for markup in woosb_ids quantity values and in Ninja Forms AJAX submissions, in detection-only mode first. MITRE M1050. ⚠ best-practice: OWASP Core Rule Set covers generic XSS, tune for these fields.
  • Add a WordPress audit-log plugin that records user creation, role changes and plugin installs, and ship it to your SIEM. MITRE M1047. ⚠ best-practice, no formal benchmark.
  • Be clear what MFA does not fix. The implant never reads the session cookie; it rides the live session, so MFA and HttpOnly cookies do not stop it (Patchstack). Keep MFA for the credential-theft cases it does address (M1032) but do not count it as a control for this attack.
Strategic (1 to 3 months)
  • Immutable, CI-driven WordPress deployment. Read-only application filesystem, plugins and mu-plugins deployed from a reviewed repository, so an unreviewed PHP file cannot persist. MITRE M1022, M1054. ⚠ best-practice, no formal benchmark.
  • Content-Security-Policy for wp-admin, Report-Only first. A policy that blocks third-party script origins would stop a script tag pointing at an attacker domain. WordPress admin uses inline scripts, so expect a long tuning phase. MITRE M1021 (Restrict Web-Based Content). ⚠ best-practice, no formal benchmark.
  • Plugin vulnerability feed with a patch SLA. Subscribe to a WordPress vulnerability intelligence source and hold an SLA for exploited flaws measured in days. Patchstack notes the domain appeared roughly ten days after public disclosure of both flaws. MITRE M1051, M1016 (Vulnerability Scanning).
  • Reduce the plugin estate and the number of administrators. Remove unused plugins (WordPress.org guidance) and keep administrator accounts to the minimum, reviewed quarterly against a database query, not the Users screen. MITRE M1018, M1026.
  • Harden the web tier to CIS. Apply the CIS Apache HTTP Server Benchmark or CIS NGINX Benchmark and a CIS Linux benchmark for the host OS. Verify with the benchmark's own assessment tooling.
13

Deployable Playbooks

Linux examples use Apache or NGINX with a Debian-style layout. Replace /var/www/html, www-data, deploy and 192.0.2.0/24 with your own values. Test each change on a staging copy first.

Step 1: Compromise triage (read-only)

Prerequisites: Shell access and WP-CLI on the WordPress host; database credentials from wp-config.php
Reboot required: No

cd /var/www/html
# 1. administrators straight from the database (not filtered by mu-plugins)
wp db query "SELECT u.user_login,u.user_email,u.user_registered FROM wp_users u JOIN wp_usermeta m ON m.user_id=u.ID WHERE m.meta_key='wp_capabilities' AND m.meta_value LIKE '%administrator%'"
# 2. campaign options
wp db query "SELECT option_name FROM wp_options WHERE option_name IN ('fz_emer_done_v1','fz_emer_login_tokens')"
# 3. must-use and fake plugin
ls -la wp-content/mu-plugins/ wp-content/plugins/wp-smart-thumbnails/ 2>/dev/null
# 4. take evidence before changing anything
tar -czf /root/wp-evidence-$(date +%F).tgz wp-content/mu-plugins wp-content/plugins/wp-smart-thumbnails 2>/dev/null

Rollback:

none required: every command in this step only reads state, except the evidence archive, which can be deleted with rm /root/wp-evidence-*.tgz

Step 2: Lock down wp-config.php

Prerequisites: Staging validated; an alternative update path (WP-CLI, Composer or CI) exists because dashboard installs stop working
Reboot required: No (PHP-FPM reload not needed; the constant is read per request)

cd /var/www/html
cp wp-config.php wp-config.php.bak-$(date +%F)
# add above the "That's all, stop editing" line
sed -i "/stop editing/i define( 'DISALLOW_FILE_MODS', true );\ndefine( 'DISALLOW_FILE_EDIT', true );\ndefine( 'FORCE_SSL_ADMIN', true );" wp-config.php
wp config get DISALLOW_FILE_MODS

Rollback:

# restore the previous file
cp wp-config.php.bak-YYYY-MM-DD wp-config.php

Step 3: Block the backdoor login, the fake plugin and (optionally) restrict admin access at the web server

Prerequisites: Apache 2.4 with mod_rewrite, or NGINX; an admin network range to allow
Reboot required: No (graceful reload of the web server)

# --- Apache 2.4 (vhost or .htaccess, outside the WordPress BEGIN/END block) ---
RewriteEngine On
RewriteCond %{QUERY_STRING} (^|&)_wplogin= [NC]
RewriteRule ^ - [F,L]
RewriteRule ^wp-content/plugins/wp-smart-thumbnails/ - [F,L]
<Files "wp-login.php">
    Require ip 192.0.2.0/24
</Files>

# --- NGINX ---
location ~* ^/wp-content/plugins/wp-smart-thumbnails/ { return 403; }
location = /wp-login.php {
    if ($arg__wplogin) { return 403; }
    allow 192.0.2.0/24;
    deny all;
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php-fpm.sock;
}
# check config, then reload
# apachectl configtest && systemctl reload apache2
# nginx -t && systemctl reload nginx

Rollback:

# remove the added lines (or restore your backup of the vhost/.htaccess/server block), then:
# apachectl configtest && systemctl reload apache2
# nginx -t && systemctl reload nginx

Step 4: Make plugin and must-use directories read-only to PHP

Prerequisites: Root or deploy-account access; a deployment path that does not depend on PHP writing to these directories; staging pilot
Reboot required: No

cd /var/www/html
chown -R deploy:deploy wp-content/plugins wp-content/mu-plugins
find wp-content/plugins wp-content/mu-plugins -type d -exec chmod 755 {} \;
find wp-content/plugins wp-content/mu-plugins -type f -exec chmod 644 {} \;
# keep uploads writable, but never executable PHP
# verify: this must FAIL
sudo -u www-data touch wp-content/mu-plugins/.write-test

Rollback:

# restore the previous ownership recorded before the change (example)
chown -R www-data:www-data wp-content/plugins wp-content/mu-plugins

Step 5: File-integrity watches and DNS sinkhole

Prerequisites: auditd installed; dnsmasq or your resolver supports local overrides; Falcon FIM policy configured separately in the console for Q5 and Q6
Reboot required: No

# /etc/audit/rules.d/wordpress-persistence.rules
-w /var/www/html/wp-content/mu-plugins -p wa -k wp_mu_plugins
-w /var/www/html/wp-content/plugins -p wa -k wp_plugins
# load: augenrules --load   verify: auditctl -l | grep wp_

# /etc/dnsmasq.d/sinkhole-imgcdn1.conf   (the domain is the Patchstack-published payload and C2 host)
address=/imgcdn1.com/0.0.0.0
# reload: systemctl restart dnsmasq   verify: dig +short imgcdn1.com @127.0.0.1  returns 0.0.0.0

Rollback:

# remove the rule files, then reload:
# rm /etc/audit/rules.d/wordpress-persistence.rules && augenrules --load
# rm /etc/dnsmasq.d/sinkhole-imgcdn1.conf && systemctl restart dnsmasq

Step 6: ModSecurity detection-only rules for the two delivery fields

Prerequisites: ModSecurity v2 or v3 with the OWASP Core Rule Set already in DetectionOnly; unique rule ids from your local range
Reboot required: No (web server reload)

# DetectionOnly first. Promote to deny only after reviewing a week of audit logs.
SecRule ARGS_NAMES "@rx ^woosb_ids" \
  "id:9100101,phase:2,t:none,log,auditlog,pass,chain,msg:'Markup in WPC Product Bundles quantity'"
  SecRule ARGS "@rx (?i)<\s*(script|img|svg)|onerror\s*=" "t:none"

SecRule REQUEST_URI "@contains admin-ajax.php" \
  "id:9100102,phase:2,t:none,log,auditlog,pass,chain,msg:'Markup in Ninja Forms AJAX submission'"
  SecRule ARGS:action "@streq nf_ajax_submit" "chain"
  SecRule ARGS "@rx (?i)</textarea>|<\s*(script|img|svg)|onerror\s*=" "t:none"

Rollback:

# delete the two SecRule blocks above and reload the web server
14

Containment Runbook

PhaseActionsOwnerEvidence
0 · ScopeList every WordPress site with Ninja Forms or WPC Product Bundles. For each, note version and whether an administrator opened an order or form submission since 2026-10-04. Run Section 7 checks 1 to 4. Identify the administrator workstations involvedIR lead, WordPress ownerPlugin version inventory; admin list from SQL; access-log extracts
1 · IsolatePut the site in maintenance mode or restrict access to responders. Sinkhole imgcdn1[.]com. Block _wplogin requests and the fake plugin path. Do not delete implant files yet. Treat the administrator's browser session as compromised and sign administrators out. Capture a disk snapshot, the database dump and the tar archive from playbook step 1Platform engineer, IR leadSnapshot IDs; hash of archive; web logs for the exposure window
2 · EradicateDelete unauthorized administrator accounts found by SQL, including the hidden one. Remove wp-smart-thumbnails, every unknown file in mu-plugins (not just the two named ones, the file manager can write anywhere) and the fz_emer_done_v1 and fz_emer_login_tokens options. Search the whole web root for other recently added PHP, remembering timestamps are backdated, so compare against known-good code. Update both plugins. Reinstall WordPress core and all plugins from clean sourcesWordPress ownerBefore and after file listings; SQL output; list of removed accounts
2b · RotateReset passwords for every administrator, and treat the oldest administrator's password as compromised, since the magic URL authenticated as that account. Rotate WordPress authentication keys and salts (this logs everyone out), database credentials, SFTP or SSH keys and API keys stored in the site. Remove stored payload rows from order and submission dataWordPress owner, IAMRotation record; new wp-config salts hash (not the values)
3 · RecoverRe-enable access behind the Section 12 controls (DISALLOW_FILE_MODS, read-only plugin directories, login restriction). Restore from a backup taken before 2026-10-04 only if the clean rebuild is not possible, then re-run checks. Watch Q1, Q5 and Q6 and the audit log for re-infection for at least 30 daysPlatform engineerPost-clean SQL and file checks; monitoring dashboard
4 · ReviewRecord which administrator views triggered execution, whether any other data was touched through the file manager, and notify per your breach obligations if user data was reachable. Feed gaps back into Section 15IR lead, SecurityIncident report; closed gaps
15

Detection Coverage Map

This pack is intentionally candid: more of the attack chain is outside Falcon endpoint visibility than inside it. The endpoint adds value on the administrator workstation and, with FIM enabled, on the web server file system.

TechniqueBehaviorCQLIOACoverage
T1190Stored XSS injection via order data or form submission--GAP not endpoint-visible; web/WAF/DB only. Q7 gives IP context only
T1059.007x.js executes in the administrator's browser; nonce scraping and session ridingQ1 (DNS)-GAP execution and nonce theft invisible to Falcon; the lookup is the only trace
T1071.001 / T1105C2 check-in, plugin ZIP and installer fetched from imgcdn1[.]comQ1, Q2Q1 domain IOAPartial admin-workstation DNS is solid; server-side fetch is unconfirmed
T1505.003Fake plugin and file manager written to wp-content/plugins; second shellsQ3, Q4, Q5Q3 IOAPartial Q5 depends on a FIM policy; PHP file writes are not a native Falcon file-write type; direct hits on the file manager are web-log only
T1136.001Visible and hidden administrator accounts createdQ4 (CLI only)-GAP creation happens inside WordPress and the database; needs the WordPress audit log and SQL
T1564Hidden administrator via mu-plugins hooksQ5, Q6-Partial file side only; the hook behavior is not observable
T1078Magic login URL as the oldest administratorQ4-GAP web access logs and WAF; a login by this route is indistinguishable by user ID
Database statefz_emer_done_v1 and fz_emer_login_tokens options rows--GAP database only (Section 7, check 2)
T1070.006Timestamp backdating of dropped filesQ5, Q6-Partial FIM event time defeats it; mtime-based hunts do not
T1027 / T1036Packed dropper, obfuscated must-use plugins, fake plugin identityQ5-Partial name and path only; no fixed hashes exist for the mu-plugins

Validation gates (Section 11): sensor and FIM coverage must be confirmed before Q5 and Q6 are trusted; run Q2, Q3, Q4 and Q6 for at least 14 days in investigate mode before enforcement; promote only after a clean baseline.

16

Hunt Summary Ticket

TITLE: WordPress stored-XSS admin takeover: fake wp-smart-thumbnails plugin, hidden administrator, backdoor login (Ninja Forms CVE-2026-94504, WPC Product Bundles CVE-2026-93836)
SEVERITY: High (exploited in the wild since 2026-10-04; CVSS 7.1 to 7.2 per sources)
SCOPE: WordPress sites running Ninja Forms 3.15.3 or earlier or WPC Product Bundles 8.6.6 or earlier where an administrator viewed an order or form submission; administrator workstations; Linux and Windows web servers
HYPOTHESIS: Attacker-stored script ran in an administrator's session, installed the fake plugin, created visible and hidden administrators, dropped two must-use plugins and a magic login token, and left an unauthenticated file manager
QUERIES: Q1-Q7 (Section 8); IOA candidates Q1, Q3, Q4 (Section 9); native DB, filesystem and log hunts (Section 7)
DO FIRST:
  1. Update both plugins, then run the SQL administrator and option checks (Section 7, checks 1 and 2)
  2. Run Q1 across 30 days for administrator workstations resolving imgcdn1[.]com
  3. List mu-plugins and the fake plugin directory on every WordPress host (Section 7, check 3)
FINDINGS: [populate per engagement]
GAPS: XSS execution, nonce theft, hidden-administrator creation, option rows and magic-login use are not endpoint-visible; Q5 and Q6 need a FIM policy on wp-content; PHP file writes are not a native Falcon file-write type (Section 5, Section 15)
ACTIONS: patch; clean stored payloads; remove implants; rotate credentials and salts; DISALLOW_FILE_MODS; read-only plugin directories; sinkhole the domain
OWNER: [assign per engagement]
VERSION: v0.1, 2026-10-07
17

Changelog

v0.12026-10-07Initial release. Built from the Patchstack analysis of 2026-10-06 (primary source for all indicators), BleepingComputer and ReconBee coverage, the SecurityOnline CVE panel, the Ninja Forms and WPC Product Bundles vendor changelogs, WordPress.org hardening documentation and MITRE ATT&CK T1505.003. 7 CQL hunt queries (several FIM-dependent), 3 IOA candidates, 7 atomic IOCs shipped as detect only, native DB/filesystem/log hunts, tiered hardening, 6-step playbook and phased containment. Endpoint coverage gaps stated plainly in Section 15.
18

References

TierSourceUsed ForAccess Date
1Patchstack, "Four ways back in: the WordPress XSS campaign that hides its own admin account"Primary analysis, all indicators, detection steps2026-10-07
2BleepingComputer, "Ninja Forms plugin flaw exploited to hack WordPress sites"Corroboration of CVEs, fix versions, four access mechanisms2026-10-07
2ReconBee, "Ninja Forms plugin flaw exploited to hack WordPress sites"Aggregator corroboration of CVE pairing and domain2026-10-07
3SecurityOnline.info, Active Exploits in the Wild panelCVE numbers and CVSS 7.2 listing2026-10-07
1Ninja Forms readme changelog (wordpress.org plugin repository)Fix version 3.15.4, 21 September 20262026-10-07
1WPC Product Bundles for WooCommerce readme changelog (wordpress.org plugin repository)Fix versions 8.6.7 and 8.7.32026-10-07
1WordPress.org, Hardening WordPressFile permissions, DISALLOW_FILE_EDIT, least privilege2026-10-07
1WordPress.org, Editing wp-config.phpDISALLOW_FILE_MODS, FORCE_SSL_ADMIN2026-10-07
1MITRE ATT&CK T1505.003 Server Software Component: Web ShellMapping, mitigations M1042 and M1018, detection strategy2026-10-07
1MITRE ATT&CK T1190 Exploit Public-Facing ApplicationInitial access mapping2026-10-07
1MITRE ATT&CK M1051 Update SoftwareMitigation citation2026-10-07
1CIS Apache HTTP Server BenchmarkWeb-tier hardening authority2026-10-07
1CIS NGINX BenchmarkWeb-tier hardening authority2026-10-07