From 8034ea2ca723a3f0844a7320ebb79a5df0b35e9f Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Thu, 14 May 2026 05:28:15 -0400 Subject: [PATCH 1/6] converted nodered cve-2021-3223 to templated --- .../cve/2021/NodeRed_CVE_2021_3223.textproto | 62 ++++++++++++++++ .../2021/NodeRed_CVE_2021_3223_test.textproto | 72 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto create mode 100644 templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto diff --git a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto new file mode 100644 index 000000000..2d5bf3ce5 --- /dev/null +++ b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto @@ -0,0 +1,62 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "NodeRed_CVE_2021_3223" + author: + "Robert Dick (robert@doyensec.com) for the Templated version, " + "Tsunami Team (tsunami-dev@google.com) for the original Java version" + version: "2.0" +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "CVE_2021_3223" + } + title: "Node-RED-Dashboard directory traversal vulnerability (CVE-2021-3223)" + description: "Directory Traversal vulnerability in exposed Node-RED-Dashboard" + recommendation: "Upgrade node-red-dashboard to version 2.26.2 or greater." + severity: CRITICAL + related_id: { + publisher: "CVE" + value: "CVE-2021-3223" + } +} + +########### +# ACTIONS # +########### + +# simple step, same as the original Java detector + +actions: { + name: "check_traversal" + http_request: { + method: GET + uri: "/ui_base/js/..%2f" + response: { + http_status: 200 + expect_all: { + conditions: [ + { body: {} contains: "Welcome to the Node-RED Dashboard" } + ] + } + } + } +} + +############# +# WORKFLOWS # +############# + +workflows: { + actions: [ + "check_traversal" + ] +} \ No newline at end of file diff --git a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto new file mode 100644 index 000000000..ec80b2536 --- /dev/null +++ b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto @@ -0,0 +1,72 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "NodeRed_CVE_2021_3223" +} + +tests: { + name: "whenVuln_returnsVuln" + expect_vulnerability: true + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 200 + body_content: '... Welcome to the Node-RED Dashboard ...' + }, + { + uri: "/ui_base/js/..%2f" + status: 200 + body_content: '... Welcome to the Node-RED Dashboard ...' + }, + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: '{"status":200}' + } + ] + } +} + +tests: { + name: "whenNotVuln_returnsNotVuln" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 200 + body_content: '... Welcome to the Node-RED Dashboard ...' + }, + { + uri: "/ui_base/js/..%2f" + status: 404 + body_content: '... File Not Found ...' + }, + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: '{"status":200}' + } + ] + } +} + + +tests: { + name: "whenRandomServer_returnsNotVuln" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: "... Hello world ..." + } + ] + } +} From 520a3447cf4fb4a5207ae4daed6522ee29be9892 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Thu, 14 May 2026 06:25:56 -0400 Subject: [PATCH 2/6] fixed to reduce false positives --- .../plugins/cve/2021/NodeRed_CVE_2021_3223.textproto | 10 ++++++---- .../cve/2021/NodeRed_CVE_2021_3223_test.textproto | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto index 2d5bf3ce5..3f0702259 100644 --- a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto +++ b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto @@ -33,18 +33,20 @@ finding: { # ACTIONS # ########### -# simple step, same as the original Java detector +# one step to check for /etc/passwd, changed from original detector +# because the original detector did not work correctly. actions: { name: "check_traversal" http_request: { method: GET - uri: "/ui_base/js/..%2f" + uri: "/ui_base/js/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc/passwd" response: { http_status: 200 - expect_all: { + expect_any: { conditions: [ - { body: {} contains: "Welcome to the Node-RED Dashboard" } + { body: {} contains: "root:x:0:0" }, + { body: {} contains: "root:*:0:0" } ] } } diff --git a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto index ec80b2536..1278da1f6 100644 --- a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto +++ b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto @@ -17,9 +17,11 @@ tests: { body_content: '... Welcome to the Node-RED Dashboard ...' }, { - uri: "/ui_base/js/..%2f" + uri: "/ui_base/js/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc/passwd" status: 200 - body_content: '... Welcome to the Node-RED Dashboard ...' + body_content: + "root:x:0:0:root:/root:/bin/ash\n" + "bin:x:1:1:bin:/bin:/sbin/nologin" }, { uri: "TSUNAMI_MAGIC_ANY_URI" @@ -42,9 +44,9 @@ tests: { body_content: '... Welcome to the Node-RED Dashboard ...' }, { - uri: "/ui_base/js/..%2f" + uri: "/ui_base/js/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc/passwd" status: 404 - body_content: '... File Not Found ...' + body_content: 'Not Found' }, { uri: "TSUNAMI_MAGIC_ANY_URI" From 5dabd5633ce853c0bacc34bf115a881ec99d4bf9 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Wed, 3 Jun 2026 10:14:03 -0400 Subject: [PATCH 3/6] Update templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto Co-authored-by: Giacomo Coluccelli --- .../plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto | 5 ----- 1 file changed, 5 deletions(-) diff --git a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto index 1278da1f6..d5c7797f0 100644 --- a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto +++ b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto @@ -11,11 +11,6 @@ tests: { mock_http_server: { mock_responses: [ - { - uri: "/" - status: 200 - body_content: '... Welcome to the Node-RED Dashboard ...' - }, { uri: "/ui_base/js/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc/passwd" status: 200 From 20301fd5170277a38e469fd86576096d74b430ce Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Wed, 3 Jun 2026 12:01:48 -0400 Subject: [PATCH 4/6] added fingerprint step --- .../cve/2021/NodeRed_CVE_2021_3223.textproto | 17 +++++++++++++++++ .../2021/NodeRed_CVE_2021_3223_test.textproto | 8 +++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto index 3f0702259..5766676fc 100644 --- a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto +++ b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto @@ -36,6 +36,23 @@ finding: { # one step to check for /etc/passwd, changed from original detector # because the original detector did not work correctly. +actions: { + name: "fingerprint_nodered" + http_request: { + method: GET + uri: "/" + response: { + http_status: 200 + expect_any: { + conditions: [ + { body: {} contains: "Node-RED" } + ] + } + } + } +} + + actions: { name: "check_traversal" http_request: { diff --git a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto index d5c7797f0..4def450fc 100644 --- a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto +++ b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223_test.textproto @@ -11,6 +11,12 @@ tests: { mock_http_server: { mock_responses: [ + { + uri: "/" + status: 200 + body_content: + "... Node-RED ..." + }, { uri: "/ui_base/js/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc/passwd" status: 200 @@ -36,7 +42,7 @@ tests: { { uri: "/" status: 200 - body_content: '... Welcome to the Node-RED Dashboard ...' + body_content: '... Node-RED ...' }, { uri: "/ui_base/js/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc/passwd" From 6aa9e455695a601584fc294fcd9184006d10ca41 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Wed, 3 Jun 2026 12:04:56 -0400 Subject: [PATCH 5/6] fixed mistake --- .../plugins/cve/2021/NodeRed_CVE_2021_3223.textproto | 1 + 1 file changed, 1 insertion(+) diff --git a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto index 5766676fc..95dd0d35a 100644 --- a/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto +++ b/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto @@ -76,6 +76,7 @@ actions: { workflows: { actions: [ + "fingerprint_nodered", "check_traversal" ] } \ No newline at end of file From fb3c5b1600447920bb657d32c6123ead180c4da2 Mon Sep 17 00:00:00 2001 From: Robert Dick Date: Thu, 11 Jun 2026 07:02:56 -0400 Subject: [PATCH 6/6] fixed link and removed old plugin --- google/README.md | 3 + .../directorytraversal/cve20213223/README.md | 13 -- .../cve20213223/build.gradle | 39 ---- .../cve20213223/settings.gradle | 12 -- ...edDashboardDirectoryTraversalDetector.java | 151 -------------- ...ctoryTraversalDetectorBootstrapModule.java | 31 --- ...shboardDirectoryTraversalDetectorTest.java | 197 ------------------ 7 files changed, 3 insertions(+), 443 deletions(-) delete mode 100644 google/detectors/directorytraversal/cve20213223/README.md delete mode 100644 google/detectors/directorytraversal/cve20213223/build.gradle delete mode 100644 google/detectors/directorytraversal/cve20213223/settings.gradle delete mode 100644 google/detectors/directorytraversal/cve20213223/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/cve20213223/NodeRedDashboardDirectoryTraversalDetector.java delete mode 100644 google/detectors/directorytraversal/cve20213223/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/cve20213223/NodeRedDashboardDirectoryTraversalDetectorBootstrapModule.java delete mode 100644 google/detectors/directorytraversal/cve20213223/src/test/java/com/google/tsunami/plugins/detectors/directorytraversal/cve20213223/NodeRedDashboardDirectoryTraversalDetectorTest.java diff --git a/google/README.md b/google/README.md index f9493cf98..a8b7d4bea 100644 --- a/google/README.md +++ b/google/README.md @@ -44,6 +44,9 @@ This directory contains all Tsunami plugins published by Google. * [Tomcat Ghostcat (CVE-2020-1938) Detector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/google/detectors/rce/tomcat/ghostcat) * [vBulletin Pre-Auth RCE (CVE-2019-16759) Detector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/google/detectors/rce/vbulletin/cve201916759) +#### Directory Traversal +* [Node-RED-Dashboard directory traversal vulnerability (CVE-2021-3223) Detector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/templated/templateddetector/plugins/cve/2021/NodeRed_CVE_2021_3223.textproto) + ## Planned Detectors * Exposed unauthenticated [Adminer](https://www.adminer.org/) server. diff --git a/google/detectors/directorytraversal/cve20213223/README.md b/google/detectors/directorytraversal/cve20213223/README.md deleted file mode 100644 index a11c46be9..000000000 --- a/google/detectors/directorytraversal/cve20213223/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Node-RED-Dashboard Directory Traversal Detector (CVE 2021-3223) - -This Tsunami plugin tests to see if the traversal of `ui_base/js/..%2f` directory is vulnerable to remote attackers, allowing them to read arbitrary files. - -## Build jar file for this plugin - -Using `gradlew`: - -```shell -./gradlew jar -``` - -Tsunami identifiable jar file is located at `build/libs` directory. diff --git a/google/detectors/directorytraversal/cve20213223/build.gradle b/google/detectors/directorytraversal/cve20213223/build.gradle deleted file mode 100644 index 8de3129b9..000000000 --- a/google/detectors/directorytraversal/cve20213223/build.gradle +++ /dev/null @@ -1,39 +0,0 @@ -plugins { - id 'java-library' -} - -description = 'Tsunami NodeRedDashboard DirectoryTraversal 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.squareup.okhttp3:mockwebserver:3.12.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/google/detectors/directorytraversal/cve20213223/settings.gradle b/google/detectors/directorytraversal/cve20213223/settings.gradle deleted file mode 100644 index d53002c2c..000000000 --- a/google/detectors/directorytraversal/cve20213223/settings.gradle +++ /dev/null @@ -1,12 +0,0 @@ -rootProject.name = 'nodereddashboard_directorytraversal' - -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/google/detectors/directorytraversal/cve20213223/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/cve20213223/NodeRedDashboardDirectoryTraversalDetector.java b/google/detectors/directorytraversal/cve20213223/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/cve20213223/NodeRedDashboardDirectoryTraversalDetector.java deleted file mode 100644 index 6f002fb2e..000000000 --- a/google/detectors/directorytraversal/cve20213223/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/cve20213223/NodeRedDashboardDirectoryTraversalDetector.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.tsunami.plugins.detectors.directorytraversal.cve20213223; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.tsunami.common.net.http.HttpRequest.get; - -import com.google.common.collect.ImmutableList; -import com.google.common.flogger.GoogleLogger; -import com.google.gson.JsonSyntaxException; -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.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.proto.AdditionalDetail; -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.Severity; -import com.google.tsunami.proto.TargetInfo; -import com.google.tsunami.proto.TextData; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; -import java.io.IOException; -import java.time.Clock; -import java.time.Instant; -import javax.inject.Inject; - -/** NodeRedDashboardDirectoryTraversalDetector plugin. */ -// PluginInfo tells Tsunami scanning engine basic information about the plugin. -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "NodeRedDashboardDirectoryTraversalDetector", - version = "0.1", - description = "This plugin detects directory traversal vulnerability in Node-RED-Dashboard.", - author = "Tsunami Team (tsunami-dev@google.com)", - bootstrapModule = NodeRedDashboardDirectoryTraversalDetectorBootstrapModule.class) -public final class NodeRedDashboardDirectoryTraversalDetector implements VulnDetector { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - - private final Clock utcClock; - private final HttpClient httpClient; - - // All the utility dependencies of the plugin must be injected through the constructor of the - // detector. Here the UtcClock is provided by the scanner. - @Inject - NodeRedDashboardDirectoryTraversalDetector(@UtcClock Clock utcClock, HttpClient httpClient) { - this.utcClock = checkNotNull(utcClock); - this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); - } - - @Override - public ImmutableList getAdvisories() { - return ImmutableList.of(getAdvisory(AdditionalDetail.getDefaultInstance())); - } - - // Main entry point of VulnDetector. Both parameters will be populated by the scanner. - // targetInfo contains the general information about the scan target. - // matchedServices parameter contains all exposed network services on the scan target. - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log("NodeRedDashboardDirectoryTraversalDetector starts detecting."); - - return DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(NetworkServiceUtils::isWebService) - // Check individual NetworkService whether it is vulnerable. - .filter(this::isServiceVulnerable) - // Build DetectionReport message for vulnerable services. - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - } - - Vulnerability getAdvisory(AdditionalDetail details) { - return Vulnerability.newBuilder() - .setMainId(VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2021_3223")) - .setSeverity(Severity.CRITICAL) - .setTitle("Node-RED-Dashboard directory traversal vulnerability") - .addRelatedId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-3223")) - .setDescription("Directory Traversal vulnerability in exposed Node-RED-Dashboard") - .setRecommendation("Upgrade node-red-dashboard to version 2.26.2 or greater.") - .addAdditionalDetails(details) - .build(); - } - - // Checks whether a given network service is vulnerable. - private boolean isServiceVulnerable(NetworkService networkService) { - String targetUri = - NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + "ui_base/js/..%2f"; - try { - logger.atInfo().log("Node-RED starts checking for target URI: '%s'.", targetUri); - - HttpResponse response = - httpClient.send(get(targetUri).withEmptyHeaders().build(), networkService); - - // If accessing targetUri does not give not found error, then returns true - return response.status().isSuccess() - && response.bodyString().get().contains("Welcome to the Node-RED Dashboard"); - } catch (IOException e) { - logger.atWarning().withCause(e).log("Unable to query '%s'.", targetUri); - return false; - } catch (JsonSyntaxException e) { - logger.atWarning().withCause(e).log( - "JSON syntax error occurred parsing response for target URI: '%s'.", targetUri); - return false; - } - } - - // This builds the DetectionReport message for a specific vulnerable network service. - private DetectionReport buildDetectionReport( - TargetInfo targetInfo, NetworkService vulnerableNetworkService) { - TextData details = - TextData.newBuilder() - .setText( - String.format( - "Node-RED-Dashboard before 2.26.2 allows %s directory traversal to read files.", - NetworkServiceUtils.buildWebApplicationRootUrl(vulnerableNetworkService) - + "ui_base/js/..%2f")) - .build(); - return DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(vulnerableNetworkService) - .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(getAdvisory(AdditionalDetail.newBuilder().setTextData(details).build())) - .build(); - } -} diff --git a/google/detectors/directorytraversal/cve20213223/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/cve20213223/NodeRedDashboardDirectoryTraversalDetectorBootstrapModule.java b/google/detectors/directorytraversal/cve20213223/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/cve20213223/NodeRedDashboardDirectoryTraversalDetectorBootstrapModule.java deleted file mode 100644 index 162826c3b..000000000 --- a/google/detectors/directorytraversal/cve20213223/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/cve20213223/NodeRedDashboardDirectoryTraversalDetectorBootstrapModule.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.tsunami.plugins.detectors.directorytraversal.cve20213223; - -import com.google.tsunami.plugin.PluginBootstrapModule; - -/** An example Guice module that bootstraps the {@link NodeRedDashboardExposedDirDetector}. */ -public final class NodeRedDashboardDirectoryTraversalDetectorBootstrapModule - extends PluginBootstrapModule { - - @Override - protected void configurePlugin() { - - // registerPlugin method is required in order for the Tsunami scanner to identify the new plugin - registerPlugin(NodeRedDashboardDirectoryTraversalDetector.class); - } -} diff --git a/google/detectors/directorytraversal/cve20213223/src/test/java/com/google/tsunami/plugins/detectors/directorytraversal/cve20213223/NodeRedDashboardDirectoryTraversalDetectorTest.java b/google/detectors/directorytraversal/cve20213223/src/test/java/com/google/tsunami/plugins/detectors/directorytraversal/cve20213223/NodeRedDashboardDirectoryTraversalDetectorTest.java deleted file mode 100644 index e710edfaa..000000000 --- a/google/detectors/directorytraversal/cve20213223/src/test/java/com/google/tsunami/plugins/detectors/directorytraversal/cve20213223/NodeRedDashboardDirectoryTraversalDetectorTest.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.tsunami.plugins.detectors.directorytraversal.cve20213223; - -import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; - -import com.google.common.collect.ImmutableList; -import com.google.inject.Guice; -import com.google.protobuf.util.Timestamps; -import com.google.tsunami.common.net.http.HttpClientModule; -import com.google.tsunami.common.net.http.HttpStatus; -import com.google.tsunami.common.time.testing.FakeUtcClock; -import com.google.tsunami.common.time.testing.FakeUtcClockModule; -import com.google.tsunami.proto.AdditionalDetail; -import com.google.tsunami.proto.DetectionReport; -import com.google.tsunami.proto.DetectionStatus; -import com.google.tsunami.proto.NetworkEndpoint; -import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Software; -import com.google.tsunami.proto.TargetInfo; -import com.google.tsunami.proto.TextData; -import com.google.tsunami.proto.TransportProtocol; -import java.io.IOException; -import java.time.Instant; -import javax.inject.Inject; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Unit tests for {@link NodeRedDashboardDirectoryTraversalDetector}. */ -@RunWith(JUnit4.class) -public final class NodeRedDashboardDirectoryTraversalDetectorTest { - - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2022-01-01T00:00:00.00Z")); - - private MockWebServer mockWebServer; - - @Inject private NodeRedDashboardDirectoryTraversalDetector detector; - - @Before - public void setUp() { - mockWebServer = new MockWebServer(); - - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - new NodeRedDashboardDirectoryTraversalDetectorBootstrapModule()) - .injectMembers(this); - } - - @After - public void tearDown() throws IOException { - mockWebServer.shutdown(); - } - - // In Tsunami, unit test names follow the following general convention: - // functionUnderTest_condition_outcome. - - // Test for the case when the node-red directory is exposed - @Test - public void detect_whenNodeRedDashboardDirExposed_reportsVuln() throws IOException { - startMockWebServer( - "/ui_base/js/..%2f", HttpStatus.OK.code(), "Welcome to the Node-RED Dashboard"); - ImmutableList httpServices = - ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setSoftware(Software.newBuilder().setName("Node-RED-Dashboard directory")) - .setServiceName("http") - .build()); - - assertThat( - detector - .detect(buildTargetInfo(forHostname(mockWebServer.getHostName())), httpServices) - .getDetectionReportsList()) - .containsExactly( - DetectionReport.newBuilder() - .setTargetInfo(buildTargetInfo(forHostname(mockWebServer.getHostName()))) - .setNetworkService(httpServices.get(0)) - .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - detector.getAdvisory( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText( - String.format( - "Node-RED-Dashboard before 2.26.2 allows" - + " http://%s:%d/ui_base/js/..%%2f directory" - + " traversal to read files.", - mockWebServer.getHostName(), mockWebServer.getPort()))) - .build())) - .build()); - } - - // Test for the case when the node-red directory is not exposed, hence safe - @Test - public void detect_whenNodeRedDashboardDirNotFound_doesNotReportVuln() throws IOException { - startMockWebServer("/ui_base/js/..%2f", HttpStatus.NOT_FOUND.code(), ""); - ImmutableList httpServices = - ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setSoftware(Software.newBuilder().setName("Node-RED-Dashboard directory")) - .setServiceName("http") - .build()); - - assertThat( - detector - .detect(buildTargetInfo(forHostname(mockWebServer.getHostName())), httpServices) - .getDetectionReportsList()) - .isEmpty(); - } - - // Test for the case when it is not a node-red directory - @Test - public void detect_whenNonNodeRedDashboardDir_ignoresServices() throws IOException { - startMockWebServer("/ui_base/js/..%2f", HttpStatus.OK.code(), "This is WordPress."); - ImmutableList httpServices = - ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setSoftware(Software.newBuilder().setName("WordPress")) - .setServiceName("http") - .build()); - - assertThat( - detector - .detect(buildTargetInfo(forHostname(mockWebServer.getHostName())), httpServices) - .getDetectionReportsList()) - .isEmpty(); - } - - // Test for the case when it is not an Http network service - @Test - public void detect_whenNonHttpNetworkService_ignoresServices() { - ImmutableList nonHttpServices = - ImmutableList.of( - NetworkService.newBuilder().setServiceName("ssh").build(), - NetworkService.newBuilder().setServiceName("rdp").build()); - assertThat( - detector - .detect(buildTargetInfo(forHostname(mockWebServer.getHostName())), nonHttpServices) - .getDetectionReportsList()) - .isEmpty(); - } - - // Test for the case when the network service is empty - @Test - public void detect_whenEmptyNetworkService_generatesEmptyDetectionReports() { - assertThat( - detector - .detect( - buildTargetInfo(forHostname(mockWebServer.getHostName())), ImmutableList.of()) - .getDetectionReportsList()) - .isEmpty(); - } - - private void startMockWebServer(String url, int responseCode, String response) - throws IOException { - mockWebServer.enqueue(new MockResponse().setResponseCode(responseCode).setBody(response)); - mockWebServer.start(); - mockWebServer.url(url); - } - - private static TargetInfo buildTargetInfo(NetworkEndpoint networkEndpoint) { - return TargetInfo.newBuilder().addNetworkEndpoints(networkEndpoint).build(); - } -}