feat(waf): block anonymous WP REST batch endpoint (wp2shell CVE-2026-63030)
HAProxy Manager Build and Push / Build-and-Push (push) Successful in 2m56s
HAProxy Manager Build and Push / Build-and-Push (push) Successful in 2m56s
Adds a frontend virtual patch denying /wp-json/batch/v1 and the ?rest_route=/batch/v1 fallback (including the %2F-encoded spelling) for requests without a wordpress_logged_in_* cookie. wp2shell chains CVE-2026-60137 (core SQL injection) with CVE-2026-63030 (REST batch-route confusion) into unauthenticated RCE on WP 6.9.0-6.9.4 and 7.0.0-7.0.1. Exploits are public and were used against this fleet on 2026-07-19/20; one site was compromised through this path, including a re-injection of wp-includes/plugin.php nine minutes after it was patched. Anonymous-only by design: batch/v1 is used legitimately by the block editor for multi-entity saves, so a blanket deny would break wp-admin. Placed ahead of the SPOE handoff so blocked requests never cost a WAF round-trip, and it works regardless of Coraza mode (whp02 and sdbees run detect_only, where a Coraza rule would log but not block). This is a virtual patch, not a fix. It removes reachability only, and stays until every site is confirmed on a fixed release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -96,6 +96,35 @@ frontend web
|
|||||||
acl has_login_cookie req.cook(whplc) -m found
|
acl has_login_cookie req.cook(whplc) -m found
|
||||||
http-request deny deny_status 403 if METH_POST wp_login_path !has_login_cookie !is_local !is_trusted_ip !is_whitelisted
|
http-request deny deny_status 403 if METH_POST wp_login_path !has_login_cookie !is_local !is_trusted_ip !is_whitelisted
|
||||||
|
|
||||||
|
# WordPress REST batch endpoint lockdown ("wp2shell": CVE-2026-63030 +
|
||||||
|
# CVE-2026-60137). Chaining a core SQL injection with REST batch-route
|
||||||
|
# confusion gives unauthenticated RCE on WP 6.9.0-6.9.4 and 7.0.0-7.0.1
|
||||||
|
# (fixed in 6.9.5 / 7.0.2). Exploits are public and were used against this
|
||||||
|
# fleet on 2026-07-19/20; one site was compromised via this path before
|
||||||
|
# patching. This is a virtual patch: it does not repair the vulnerable
|
||||||
|
# application logic, it only removes reachability, so it stays until every
|
||||||
|
# site is confirmed on a fixed release.
|
||||||
|
#
|
||||||
|
# Both routing forms must be covered -- a rule matching only the pretty
|
||||||
|
# permalink path leaves the ?rest_route= fallback wide open, and urlp()
|
||||||
|
# does not URL-decode, hence the third ACL for the %2F spelling.
|
||||||
|
#
|
||||||
|
# Anonymous-only. batch/v1 is used legitimately by the block editor for
|
||||||
|
# multi-entity saves, so a blanket deny would break wp-admin for real
|
||||||
|
# users; requiring a wordpress_logged_in_* cookie costs them nothing.
|
||||||
|
# req.cook() needs an exact name and WordPress suffixes a per-site hash,
|
||||||
|
# so this substring-matches the raw Cookie header instead.
|
||||||
|
#
|
||||||
|
# Immediate deny, not tarpit -- holding connections open helps an attacker
|
||||||
|
# who is already scripting this. Honors the same whitelist as above.
|
||||||
|
acl wp_batch_path path_beg /wp-json/batch/v1
|
||||||
|
acl wp_batch_route urlp(rest_route) -i -m beg /batch/v1
|
||||||
|
acl wp_batch_route_enc query -i -m sub rest_route=%2Fbatch%2Fv1
|
||||||
|
acl has_wp_logged_in req.hdr(Cookie) -i -m sub wordpress_logged_in_
|
||||||
|
http-request deny deny_status 403 if wp_batch_path !has_wp_logged_in !is_local !is_trusted_ip !is_whitelisted
|
||||||
|
http-request deny deny_status 403 if wp_batch_route !has_wp_logged_in !is_local !is_trusted_ip !is_whitelisted
|
||||||
|
http-request deny deny_status 403 if wp_batch_route_enc !has_wp_logged_in !is_local !is_trusted_ip !is_whitelisted
|
||||||
|
|
||||||
# IP blocking using map file (manual blocks only)
|
# IP blocking using map file (manual blocks only)
|
||||||
# Map file format: /etc/haproxy/blocked_ips.map contains "<ip_or_cidr> 1" per line
|
# Map file format: /etc/haproxy/blocked_ips.map contains "<ip_or_cidr> 1" per line
|
||||||
# Runtime updates: echo "add map #0 IP_ADDRESS 1" | socat stdio /var/run/haproxy.sock
|
# Runtime updates: echo "add map #0 IP_ADDRESS 1" | socat stdio /var/run/haproxy.sock
|
||||||
|
|||||||
Reference in New Issue
Block a user