diff --git a/google/README.md b/google/README.md index f9493cf98..b4e4902a0 100644 --- a/google/README.md +++ b/google/README.md @@ -24,7 +24,7 @@ This directory contains all Tsunami plugins published by Google. * [Exposed Elasticsearch API Detector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/google/detectors/exposedui/elasticsearch) * [Exposed Hadoop Yarn ResourceManager API Detector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/google/detectors/exposedui/hadoop/yarn) * [Exposed Jenkins UI Detector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/google/detectors/exposedui/jenkins) -* [Exposed Jupyter Notebook Detector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/google/detectors/exposedui/jupyter) +* [Exposed Jupyter Notebook Detector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/templated/templateddetector/plugins/exposedui/JupyterNotebook_ExposedUI.textproto) * [Exposed Kubernetes APIDetector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/google/detectors/exposedui/kubernetes) * [Exposed PHPUnit Vulnerable eval-stdin.php Detector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/google/detectors/exposedui/phpunit) * [Exposed Spring Boot Actuator Endpoint Detector](https://github.com/google/tsunami-security-scanner-plugins/tree/master/google/detectors/exposedui/spring) diff --git a/google/detectors/exposedui/jupyter/README.md b/google/detectors/exposedui/jupyter/README.md deleted file mode 100644 index 7333f708c..000000000 --- a/google/detectors/exposedui/jupyter/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Exposed Jupyter Notebook Detector - -This detector checks whether an unauthenticated Jupyter Notebook is exposed. -Jupyter allows by design to run arbitrary code on the host machine. Having it -exposed puts the hosting VM at risk of RCE. - -## 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/exposedui/jupyter/build.gradle b/google/detectors/exposedui/jupyter/build.gradle deleted file mode 100644 index d2f85f427..000000000 --- a/google/detectors/exposedui/jupyter/build.gradle +++ /dev/null @@ -1,48 +0,0 @@ -plugins { - id 'java-library' -} - -description = 'Tsunami VulnDetector plugin for exposed Jupyter Notebook.' -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.flogger:flogger:0.9" - implementation "com.google.flogger:google-extensions:0.9" - implementation "com.google.flogger:flogger-system-backend:0.9" - implementation "com.google.guava:guava:33.0.0-jre" - implementation "com.google.protobuf:protobuf-java:3.25.5" - implementation "com.google.protobuf:protobuf-javalite:3.25.5" - implementation "com.google.protobuf:protobuf-java-util:3.25.5" - 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 "javax.inject:javax.inject:1" - implementation "org.jsoup:jsoup:1.9.2" - - testImplementation "com.google.truth:truth:1.4.4" - testImplementation "com.google.truth.extensions:truth-java8-extension:1.4.4" - testImplementation "com.google.truth.extensions:truth-proto-extension:1.4.4" - testImplementation "com.squareup.okhttp3:mockwebserver:3.12.0" - testImplementation "junit:junit:4.13.2" - testImplementation "org.mockito:mockito-core:5.18.0" -} diff --git a/google/detectors/exposedui/jupyter/settings.gradle b/google/detectors/exposedui/jupyter/settings.gradle deleted file mode 100644 index ad49ae696..000000000 --- a/google/detectors/exposedui/jupyter/settings.gradle +++ /dev/null @@ -1,12 +0,0 @@ -rootProject.name = 'exposed_jupyter_notebook' - -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/exposedui/jupyter/src/main/java/com/google/tsunami/plugins/detectors/exposedui/jupyter/JupyterExposedUiDetector.java b/google/detectors/exposedui/jupyter/src/main/java/com/google/tsunami/plugins/detectors/exposedui/jupyter/JupyterExposedUiDetector.java deleted file mode 100644 index 13e454212..000000000 --- a/google/detectors/exposedui/jupyter/src/main/java/com/google/tsunami/plugins/detectors/exposedui/jupyter/JupyterExposedUiDetector.java +++ /dev/null @@ -1,145 +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.detectors.exposedui.jupyter; - -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.annotations.VisibleForTesting; -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.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.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.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; -import java.io.IOException; -import java.time.Clock; -import java.time.Instant; -import javax.inject.Inject; - -/** A {@link VulnDetector} that detects unauthenticated Jupyter Notebook shell page. */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "JupyterExposedUiDetector", - version = "0.1", - description = - "This detector checks whether a unauthenticated Jupyter Notebook is exposed. Jupyter" - + " allows by design to run arbitrary code on the host machine. Having it exposed puts" - + " the hosting VM at risk of RCE.", - author = "Tsunami Team (tsunami-dev@google.com)", - bootstrapModule = JupyterExposedUiDetectorBootstrapModule.class) - -@ForWebService -public final class JupyterExposedUiDetector implements VulnDetector { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - - @VisibleForTesting - static final String FINDING_RECOMMENDATION_TEXT = "If it is necessary to keep running this " - + "instance of Jupyter, DO NOT expose it externally, in favor of using SSH tunnels to " - + "access it. In addition, the service should only listen on localhost (127.0.0.1), and " - + "consider restrict the access to the Jupyter Notebook using an authentication method. " - + "See https://jupyter-notebook.readthedocs.io/en/stable/security.html"; - - private final Clock utcClock; - private final HttpClient httpClient; - - @Inject - public JupyterExposedUiDetector(@UtcClock Clock utcClock, HttpClient httpClient) { - this.utcClock = checkNotNull(utcClock); - this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); - } - - @Override - public ImmutableList getAdvisories() { - return ImmutableList.of( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("JUPYTER_NOTEBOOK_EXPOSED_UI")) - .setSeverity(Severity.CRITICAL) - .setTitle("Jupyter Notebook Exposed Ui") - // TODO(b/147455413): determine CVSS score. - .setDescription("Jupyter Notebook is not password or token protected") - .setRecommendation(FINDING_RECOMMENDATION_TEXT) - .build()); - } - - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log("Starting exposed ui detection for Jupyter Notebook"); - return DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(NetworkServiceUtils::isWebService) - .filter(this::isServiceVulnerable) - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - } - - private boolean isServiceVulnerable(NetworkService networkService) { - String targetUri = - NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + "terminals/1"; - try { - // This is a blocking call. - HttpResponse response = - httpClient.send(get(targetUri).withEmptyHeaders().build(), networkService); - return response.status().isSuccess() - // TODO(b/147455413): checking Jupyter Notebook string is not needed once we have plugin - // matching logic. - // Newer version of Jupyter no longer has websocket in the body. - && response - .bodyString() - .map( - body -> - body.contains("Jupyter Notebook") - && (body.contains("terminals/websocket/1") - || body.contains("jupyter-config-data")) - && !body.contains("authentication is enabled")) - .orElse(false); - } catch (IOException e) { - logger.atWarning().withCause(e).log("Unable to query '%s'.", targetUri); - return false; - } - } - - private DetectionReport buildDetectionReport( - TargetInfo scannedTarget, NetworkService vulnerableNetworkService) { - return DetectionReport.newBuilder() - .setTargetInfo(scannedTarget) - .setNetworkService(vulnerableNetworkService) - .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(this.getAdvisories().get(0)) - .build(); - } -} diff --git a/google/detectors/exposedui/jupyter/src/main/java/com/google/tsunami/plugins/detectors/exposedui/jupyter/JupyterExposedUiDetectorBootstrapModule.java b/google/detectors/exposedui/jupyter/src/main/java/com/google/tsunami/plugins/detectors/exposedui/jupyter/JupyterExposedUiDetectorBootstrapModule.java deleted file mode 100644 index ab9301c59..000000000 --- a/google/detectors/exposedui/jupyter/src/main/java/com/google/tsunami/plugins/detectors/exposedui/jupyter/JupyterExposedUiDetectorBootstrapModule.java +++ /dev/null @@ -1,27 +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.detectors.exposedui.jupyter; - -import com.google.tsunami.plugin.PluginBootstrapModule; - -/** A {@link PluginBootstrapModule} for {@link JupyterExposedUiDetector}. */ -public final class JupyterExposedUiDetectorBootstrapModule extends PluginBootstrapModule { - - @Override - protected void configurePlugin() { - registerPlugin(JupyterExposedUiDetector.class); - } -} diff --git a/google/detectors/exposedui/jupyter/src/test/java/com/google/tsunami/plugins/detectors/exposedui/jupyter/JupyterExposedUiDetectorTest.java b/google/detectors/exposedui/jupyter/src/test/java/com/google/tsunami/plugins/detectors/exposedui/jupyter/JupyterExposedUiDetectorTest.java deleted file mode 100644 index 8b45023fa..000000000 --- a/google/detectors/exposedui/jupyter/src/test/java/com/google/tsunami/plugins/detectors/exposedui/jupyter/JupyterExposedUiDetectorTest.java +++ /dev/null @@ -1,195 +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.detectors.exposedui.jupyter; - -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.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.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; - -/** Tests for {@link JupyterExposedUiDetector}. */ -@RunWith(JUnit4.class) -public final class JupyterExposedUiDetectorTest { - - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); - - private MockWebServer mockWebServer; - - @Inject private JupyterExposedUiDetector detector; - - @Before - public void setUp() { - mockWebServer = new MockWebServer(); - - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - new JupyterExposedUiDetectorBootstrapModule()) - .injectMembers(this); - } - - @After - public void tearDown() throws IOException { - mockWebServer.shutdown(); - } - - @Test - public void detect_whenJupyterDoesNotRedirectToLogin_reportsVuln() throws IOException { - assetReportVuln( - "Fake Jupyter Notebook terminal page that connect WebSocket to /terminals/websocket/1"); - } - - @Test - public void detect_whenNewVersionOfJupyterWebApp_reportsVuln() throws IOException { - assetReportVuln( - ">