WordPress REST API batch route confusion with SQL injection payload - CVE-2026-63030 - STRICT
SIGMA detection rule by Adama ASSIONGBON (SOC & CTI Consultant), https://linkedin.com/in/adama-assiongbon-9029893a · Unknown
Detects exploitation of the WordPress REST API batch route confusion (CVE-2026-63030) chained with the WP_Query author__not_in SQL injection (CVE-2026-60137). Matches a batch request body that nests an item-route sub-request (numeric post or page ID) carrying collection-only query parameters, combined with a SQL injection keyword. STRICT. SOC L1 should verify the source, affected host, user, process or request fields, timing, and related activity.
Détecte l'exploitation de la confusion de route du batch REST API WordPress (CVE-2026-63030) chaînée avec l'injection SQL author__not_in de WP_Query (CVE-2026-60137). Cible un corps de requête batch qui imbrique une sous-requête item-route (ID de post ou page numérique) portant des paramètres de requête réservés à la collection, combiné à un mot-clé d'injection SQL. Aucune requête REST légitime ne construit ce pattern. Règle STRICT : isoler l'IP source et bloquer immédiatement, le taux de FP attendu est quasi nul. Le SOC L1 doit vérifier la source, l’hôte affecté, l’utilisateur, les champs de processus ou de requête, la chronologie et les activités associées.
title: WordPress REST API batch route confusion with SQL injection payload - CVE-2026-63030 - STRICT
id: 49c3984c-d57e-4d57-9fe0-676a949c8d47
status: experimental
description: |
Detects exploitation of the WordPress REST API batch route confusion (CVE-2026-63030) chained with the WP_Query author__not_in SQL injection (CVE-2026-60137). Matches a batch request body that nests an item-route sub-request (numeric post or page ID) carrying collection-only query parameters, combined with a SQL injection keyword. STRICT. SOC L1 should verify the source, affected host, user, process or request fields, timing, and related activity.
Détecte l'exploitation de la confusion de route du batch REST API WordPress (CVE-2026-63030) chaînée avec l'injection SQL author__not_in de WP_Query (CVE-2026-60137). Cible un corps de requête batch qui imbrique une sous-requête item-route (ID de post ou page numérique) portant des paramètres de requête réservés à la collection, combiné à un mot-clé d'injection SQL. Aucune requête REST légitime ne construit ce pattern. Règle STRICT : isoler l'IP source et bloquer immédiatement, le taux de FP attendu est quasi nul. Le SOC L1 doit vérifier la source, l’hôte affecté, l’utilisateur, les champs de processus ou de requête, la chronologie et les activités associées.
related:
- id: 1142bb3f-7113-439d-a08f-435bddad23f6
type: derived
- id: fd7e84d0-7add-4438-8677-0ce7ed575def
type: derived
references:
- https://www.cve.org/CVERecord?id=CVE-2026-63030
- https://www.cve.org/CVERecord?id=CVE-2026-60137
- https://github.com/advisories/GHSA-ff9f-jf42-662q
- https://github.com/Icex0/wp2shell-poc
- https://www.rapid7.com/blog/post/etr-cve-2026-63030-wp2shell-a-critical-remote-code-execution-vulnerability-in-wordpress-core/
- https://thehackernews.com/2026/07/new-wp2shell-wordpress-core-flaw-lets.html
author: Adama ASSIONGBON (SOC & CTI Consultant), https://linkedin.com/in/adama-assiongbon-9029893a
date: 2026-07-19
tags:
- attack.initial-access
- attack.t1190
- attack.credential-access
- attack.t1552
- cve.2026-63030
- cve.2026-60137
logsource:
category: webserver
definition: >
Web server or WAF access logs with full POST request body inspection. Fields expected:
UriPath, RequestBody, HttpMethod, StatusCode, SrcIp and UserAgent. The exploit sends its
payload inside a JSON body, not the outer query string, so RequestBody logging must
capture the full POST payload (not truncated) for this rule to fire. Compatible with
nginx, Apache, WAF or reverse proxy access/request logging with body capture enabled.
detection:
selection_post:
HttpMethod: POST
# The route confusion bug lets an attacker send a batch-shaped body either to the
# documented batch endpoint or directly to a collection endpoint such as /wp/v2/posts,
# which is the actual entry point used by the public wp2shell PoC.
selection_batch_capable_endpoint:
UriPath|contains:
- '/wp-json/batch/v1'
- 'rest_route=/batch/v1'
- 'rest_route=%2Fbatch%2Fv1'
- '/wp-json/wp/v2/posts'
- '/wp-json/wp/v2/pages'
- 'rest_route=/wp/v2/posts'
- 'rest_route=/wp/v2/pages'
# Structural marker of a nested batch payload as produced by serve_batch_request_v1().
selection_nested_batch_marker:
RequestBody|contains:
- '"requests"'
# Core route-confusion fingerprint: an item-route sub-request path (numeric post/page ID)
# carrying collection-only query parameters (author_exclude, author__not_in, orderby,
# per_page). No legitimate REST client builds this combination; it only exists to trigger
# the $matches/$validation array desync in serve_batch_request_v1().
# AM-FIX: PHP's json_encode() and Python's default json.dumps() both insert a space after
# the key separator ("path": "..." not "path":"..."), the \s* tolerates that space and its
# absence in compact encoders. Second variant covers JSON encoders that escape the forward
# slash as \/ (still valid JSON, used by some HTTP client libraries).
# AM-FIX (2026-08-07 red team pass): added the case-insensitive (?i) flag. The original
# regex was case-sensitive by default under the |re modifier (unlike |contains, which is
# case-insensitive in Sigma by default); a client or proxy that normalizes the JSON key to
# "Path" or reorders casing anywhere in the matched span would have silently missed.
# Pure upside, no legitimate traffic relies on a specific case here.
selection_item_route_confusion:
RequestBody|re:
- '(?i)"path":\s*"/wp/v2/(posts|pages)/[0-9]+\?[^"]*(author_exclude|author__not_in|orderby|per_page)'
- '(?i)"path":\s*"\\/wp\\/v2\\/(posts|pages)\\/[0-9]+\?[^"]*(author_exclude|author__not_in|orderby|per_page)'
# SQL injection keyword indicators covering UNION-based, error-based, time-based and plain
# boolean-based blind techniques against the author__not_in column (CVE-2026-60137). Relies
# on single-token function/keyword names (SLEEP(, EXTRACTVALUE, information_schema, SELECT)
# that MySQL cannot split with inline-comment whitespace obfuscation, and on the standalone
# UNION keyword rather than the exact phrase "UNION SELECT" so that comment-separated
# variants (UNION/**/SELECT) still match. SELECT/select is included because a subquery-based
# boolean-blind condition, e.g. "(SELECT SUBSTRING(user_pass,1,1) FROM wp_users LIMIT 1)=0x61",
# needs no UNION, SLEEP(, EXTRACTVALUE or trailing "-- -" comment to work and would otherwise
# evade every other keyword in this list (confirmed during red-team testing, see
# labs/wp2shell_batch_route_confusion/README_ANGLES_MORTS.md).
selection_sqli_keyword:
RequestBody|contains:
- 'UNION'
- 'union'
- 'SELECT'
- 'select'
- 'SLEEP('
- 'sleep('
- 'EXTRACTVALUE'
- 'extractvalue'
- 'information_schema'
- 'INFORMATION_SCHEMA'
- 'BENCHMARK('
- 'benchmark('
- '-- -'
# AM-FIX (2026-08-07 red team pass, AM5): a raw boolean tautology against the unsanitized
# author_exclude value, e.g. "author_exclude=0) OR (1=1", needs no SELECT, UNION, SLEEP(,
# EXTRACTVALUE, information_schema, BENCHMARK( or trailing "-- -" comment if the injected
# value is concatenated directly into a NOT IN(...) clause without a subquery (consistent
# with the CVE-2026-60137 advisory wording "not sanitized as a string"). Every keyword
# above requires a function call or the SELECT keyword; this is the one variant that needs
# neither, so it was a complete miss before this fix. A regex is used instead of a literal
# keyword list because the operator can be immediately followed by an optional opening
# parenthesis and inconsistent spacing around "=" (e.g. "OR(1=1", "OR ( 1 = 1", "AND(1 = 0"),
# which a fixed literal string cannot generalize over. See
# labs/wp2shell_batch_route_confusion/README_ANGLES_MORTS.md, AM5.
selection_sqli_tautology:
RequestBody|re: '(?i)(or|and)\s*\(?\s*1\s*=\s*[01]'
condition: >
selection_post and selection_batch_capable_endpoint and selection_nested_batch_marker and
selection_item_route_confusion and (selection_sqli_keyword or selection_sqli_tautology)
fields:
- SrcIp
- HttpMethod
- UriPath
- RequestBody
- StatusCode
- UserAgent
falsepositives:
- Authorized penetration testing or vulnerability scanning against a staging instance, confirm by correlating SrcIp with the scanner inventory
- None expected in production traffic, no legitimate REST client nests collection-only parameters inside an item-route batch sub-request
level: high