diff --git a/community/README.md b/community/README.md index e651c1cea..1e5a39fa5 100644 --- a/community/README.md +++ b/community/README.md @@ -51,7 +51,7 @@ This directory contains plugins contributed by community members. #### XML External Entity (XXE) Injection -* [CVE-2019-9670 Synacor Zimbra Collaboration Suite XXE](https://github.com/google/tsunami-security-scanner-plugins/tree/master/community/detectors/zimbra_cve_2019_9670) +* [CVE-2019-9670 Synacor Zimbra Collaboration Suite XXE, now updated to Templated format](https://github.com/google/tsunami-security-scanner-plugins/tree/master/templated/templateddetector/plugins/cve/2019/Zimbra_CVE_2019_9670.textproto) #### Arbitrary File Read diff --git a/community/detectors/zimbra_cve_2019_9670/README.md b/community/detectors/zimbra_cve_2019_9670/README.md deleted file mode 100644 index 4adc6abf2..000000000 --- a/community/detectors/zimbra_cve_2019_9670/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Synacor Zimbra Collaboration Suite XXE CVE-2019-9670 - -This plugin detects CVE-2019-9670, an XXE vulnerability affecting Synacor Zimbra -Collaboration Suite. - -## Details - -The XXE affects the mailboxd component of Synacor Zimbra Collaboration Suite and -it is found in the autodiscover feature, available to unauthenticated users -through the endpoint `/Autodiscover/Autodiscover.xml`. The vulnerability allows -malicious actors to extract sensitive files from the system and can be chained -with another vulnerability (CVE-2019-9621) in order to obtain an unauthenticated -RCE. - -Specifically, by using the XXE (CVE-2019-9670) it is possible to read a -configuration file that contains an LDAP password for the zimbra account. The -zimbra credentials are then used to get a user authentication cookie with an -AuthRequest message. Using the user cookie, a SSRF (CVE-2019-9621) in the Proxy -Servlet is used to proxy an AuthRequest with the zimbra credentials to the admin -port to retrieve an admin cookie. After gaining an admin cookie the Client -Upload servlet is used to upload a JSP webshell that can be triggered from the -web server to obtain RCE. - -**Affected Versions** from 8.5 to 8.7.11p10 - -## References - -* https://nvd.nist.gov/vuln/detail/cve-2019-9670 -* https://blog.tint0.com/2019/03/a-saga-of-code-executions-on-zimbra.html -* https://blog.zimbra.com/2019/03/new-zimbra-8-7-11-patch-10/ -* https://attackerkb.com/topics/7bMNsBStux/zimbra-collaboration-suite-autodiscover-xxe/vuln-details - -## 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/zimbra_cve_2019_9670/build.gradle b/community/detectors/zimbra_cve_2019_9670/build.gradle deleted file mode 100644 index 1f5848940..000000000 --- a/community/detectors/zimbra_cve_2019_9670/build.gradle +++ /dev/null @@ -1,42 +0,0 @@ -plugins { - id 'java-library' -} - -description = 'CVE-2019-9670 XXE vulnerability in Synacor Zimbra Collaboration Suite.' -group = 'com.google.tsunami' -version = '0.0.1-SNAPSHOT' - -repositories { - maven { // The google mirror is less flaky than mavenCentral() - url 'https://maven-central.storage-download.googleapis.com/repos/central/data/' - } - mavenCentral() - mavenLocal() -} - -def coreRepoBranch = System.getenv("GITBRANCH_TSUNAMI_CORE") ?: "stable" -def tcsRepoBranch = System.getenv("GITBRANCH_TSUNAMI_TCS") ?: "stable" - -dependencies { - implementation("com.google.tsunami:tsunami-common") { - version { branch = "${coreRepoBranch}" } - } - implementation("com.google.tsunami:tsunami-plugin") { - version { branch = "${coreRepoBranch}" } - } - implementation("com.google.tsunami:tsunami-proto") { - version { branch = "${coreRepoBranch}" } - } - testImplementation("com.google.tsunami:tcs-proto") { - version { branch = "${tcsRepoBranch}" } - } - - testImplementation "junit:junit:4.13.2" - testImplementation "com.google.inject:guice:6.0.0" - testImplementation "com.google.inject.extensions:guice-testlib:6.0.0" - testImplementation "org.mockito:mockito-core:5.18.0" - testImplementation "com.google.truth:truth:1.4.4" - testImplementation "com.google.truth.extensions:truth-java8-extension:1.4.4" - testImplementation "com.google.truth.extensions:truth-proto-extension:1.4.4" - testImplementation "com.squareup.okhttp3:mockwebserver:3.12.0" -} diff --git a/community/detectors/zimbra_cve_2019_9670/settings.gradle b/community/detectors/zimbra_cve_2019_9670/settings.gradle deleted file mode 100644 index c5d1c5f00..000000000 --- a/community/detectors/zimbra_cve_2019_9670/settings.gradle +++ /dev/null @@ -1,16 +0,0 @@ -rootProject.name = 'zimbra_cve_2019-9670' - -def coreRepository = System.getenv("GITREPO_TSUNAMI_CORE") ?: "https://github.com/google/tsunami-security-scanner.git" -def tcsRepository = System.getenv("GITREPO_TSUNAMI_TCS") ?: "https://github.com/google/tsunami-security-scanner-callback-server.git" - -sourceControl { - gitRepository("${coreRepository}") { - producesModule("com.google.tsunami:tsunami-common") - producesModule("com.google.tsunami:tsunami-plugin") - producesModule("com.google.tsunami:tsunami-proto") - } - - gitRepository("${tcsRepository}") { - producesModule("com.google.tsunami:tcs-proto") - } -} diff --git a/community/detectors/zimbra_cve_2019_9670/src/main/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Annotations.java b/community/detectors/zimbra_cve_2019_9670/src/main/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Annotations.java deleted file mode 100644 index b9a75aafb..000000000 --- a/community/detectors/zimbra_cve_2019_9670/src/main/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Annotations.java +++ /dev/null @@ -1,35 +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.zimbra.cve20199670; - -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 Cve20199670Detector}. */ -final class Annotations { - @Qualifier - @Retention(RetentionPolicy.RUNTIME) - @Target({PARAMETER, METHOD, FIELD}) - @interface OobSleepDuration {} - - private Annotations() {} -} diff --git a/community/detectors/zimbra_cve_2019_9670/src/main/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Cve20199670Detector.java b/community/detectors/zimbra_cve_2019_9670/src/main/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Cve20199670Detector.java deleted file mode 100644 index 891f086e5..000000000 --- a/community/detectors/zimbra_cve_2019_9670/src/main/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Cve20199670Detector.java +++ /dev/null @@ -1,271 +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.zimbra.cve20199670; - -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.common.util.concurrent.Uninterruptibles; -import com.google.protobuf.ByteString; -import com.google.protobuf.util.Timestamps; -import com.google.tsunami.common.data.NetworkServiceUtils; -import com.google.tsunami.common.net.http.HttpClient; -import com.google.tsunami.common.net.http.HttpHeaders; -import com.google.tsunami.common.net.http.HttpRequest; -import com.google.tsunami.common.net.http.HttpResponse; -import com.google.tsunami.common.time.UtcClock; -import com.google.tsunami.plugin.PluginType; -import com.google.tsunami.plugin.VulnDetector; -import com.google.tsunami.plugin.annotations.PluginInfo; -import com.google.tsunami.plugin.payload.Payload; -import com.google.tsunami.plugin.payload.PayloadGenerator; -import com.google.tsunami.plugins.detectors.zimbra.cve20199670.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.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.util.UUID; -import javax.inject.Inject; - -/** A {@link VulnDetector} that detects CVE-2019-9670. */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "CVE-2019-9670 Detector", - version = "0.1", - description = "Detects CVE-2019-9670 XXE vulnerability in Synacor Zimbra Collaboration Suite.", - author = "Leonardo Tamiano (leonardo.tamiano@mindedsecurity.com)", - bootstrapModule = Cve20199670DetectorBootstrapModule.class) -public final class Cve20199670Detector implements VulnDetector { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - - @VisibleForTesting static final String VULNERABILITY_REPORT_PUBLISHER = "TSUNAMI_COMMUNITY"; - @VisibleForTesting static final String VULNERABILITY_REPORT_ID = "CVE-2019-9670"; - - @VisibleForTesting - static final String VULNERABILITY_REPORT_TITLE = "Synacor Zimbra XXE CVE-2019-9670"; - - @VisibleForTesting - static final String VULN_DESCRIPTION = - "The mailboxd component of Synacor Zimbra Collaboration Suite, in versions from 8.5 to" - + " 8.7.11p10, is vulnerable to an XML External Entity injection (XXE). The vulnerability" - + " is found in the autodiscover feature, available to unauthenticated users through the" - + " endpoint /Autodiscover/Autodiscover.xml. The vulnerability allows malicious actors to" - + " extract sensitive files from the system and can be chained with another" - + " vulnerability (CVE-2019-9621) in order to obtain an unauthenticated RCE." - + " Specifically, by using the XXE (CVE-2019-9670) it is possible to read a configuration" - + " file that contains an LDAP password for the zimbra account. The zimbra credentials" - + " are then used to get a user authentication cookie with an AuthRequest message. Using" - + " the user cookie, a SSRF (CVE-2019-9621) in the Proxy Servlet is used to proxy an" - + " AuthRequest with the zimbra credentials to the admin port to retrieve an admin" - + " cookie. After gaining an admin cookie the Client Upload servlet is used to upload a" - + " JSP webshell that can be triggered from the web server to obtain RCE."; - - @VisibleForTesting - static final String RECOMMENDATION = - "Upgrade to non-vulnerable versions of Synacor Zimbra Collaboration Suite such as 8.7.11p14" - + " or later versions such as 8.8.x, 9.x and 10.x."; - - @VisibleForTesting static final String TEST_STRING = String.format("%s", UUID.randomUUID()); - - @VisibleForTesting - static final String ERROR_MSG = "Error 503 Requested response schema not available"; - - @VisibleForTesting - static final String ZIMBRA_FINGERPRING = "Zimbra Collaboration Suite Web Client"; - - // When a callback server is not available, the payload inserted in this template will be - // reflected by the vulnerable instance. - private static final String PAYLOAD_TEMPLATE_REFLECTED = - """ - - ]> - - email - &xxe; - \ - """; - - private static final String PAYLOAD_TEMPLATE_OOB = - """ - - ]> - - email - &oob; - \ - """; - - private static final String AUTODISCOVER_PATH = "Autodiscover/Autodiscover.xml"; - - private final Clock utcClock; - private final HttpClient httpClient; - private final PayloadGenerator payloadGenerator; - - private final int oobSleepDuration; - - @Inject - Cve20199670Detector( - @UtcClock Clock utcClock, - HttpClient httpClient, - PayloadGenerator payloadGenerator, - @OobSleepDuration int oobSleepDuration) { - this.utcClock = checkNotNull(utcClock); - this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); - this.payloadGenerator = checkNotNull(payloadGenerator); - this.oobSleepDuration = oobSleepDuration; - } - - @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(VULN_DESCRIPTION) - .setRecommendation(RECOMMENDATION) - .build()); - } - - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log("Starting CVE-2019-9670 RCE detection."); - - return DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(NetworkServiceUtils::isWebService) - .filter(this::isZimbra) - .filter(this::isServiceVulnerable) - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - } - - private boolean isZimbra(NetworkService networkService) { - boolean isZimbra = false; - - // Check presence of zimbra fingerpring in root - String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService); - HttpRequest request = HttpRequest.get(targetUri).withEmptyHeaders().build(); - - try { - HttpResponse response = this.httpClient.send(request, networkService); - isZimbra = - (response.status().code() == 200) - && response.bodyString().map(body -> body.contains(ZIMBRA_FINGERPRING)).orElse(false); - } catch (IOException e) { - logger.atWarning().withCause(e).log("Request to target '%s' failed", targetUri); - return false; - } - - // Check presence of autodiscover endpoint - targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + AUTODISCOVER_PATH; - request = HttpRequest.get(targetUri).withEmptyHeaders().build(); - - try { - HttpResponse response = this.httpClient.send(request, networkService); - isZimbra = isZimbra && (response.status().code() == 200); - } catch (IOException e) { - logger.atWarning().withCause(e).log("Request to target '%s' failed", targetUri); - return false; - } - - return isZimbra; - } - - private boolean isServiceVulnerable(NetworkService networkService) { - PayloadGeneratorConfig config = - PayloadGeneratorConfig.newBuilder() - .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.SSRF) - .setInterpretationEnvironment( - PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY) - .setExecutionEnvironment(PayloadGeneratorConfig.ExecutionEnvironment.EXEC_ANY) - .build(); - - Payload payload = payloadGenerator.generate(config); - String callbackUrl = payload.getPayload(); - - String xmlPayload; - if (payload.getPayloadAttributes().getUsesCallbackServer()) { - xmlPayload = String.format(PAYLOAD_TEMPLATE_OOB, callbackUrl); - } else { - xmlPayload = String.format(PAYLOAD_TEMPLATE_REFLECTED, TEST_STRING); - } - - String targetUri = - NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + AUTODISCOVER_PATH; - HttpRequest request = prepareRequest(targetUri, xmlPayload); - HttpResponse response = null; - - try { - response = httpClient.send(request, networkService); - } catch (IOException e) { - logger.atWarning().withCause(e).log("Request to target '%s' failed", targetUri); - return false; - } - - if (payload.getPayloadAttributes().getUsesCallbackServer()) { - logger.atInfo().log("Waiting for RCE callback."); - Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(oobSleepDuration)); - return payload.checkIfExecuted(); - } - - // To decrease false positive rate, when using reflective payload - // match also with specific error message - return (response.status().code() == 503) - && response - .bodyString() - .map(body -> (body.contains(TEST_STRING) && body.contains(ERROR_MSG))) - .orElse(false); - } - - private HttpRequest prepareRequest(String targetUri, String payload) { - return HttpRequest.post(targetUri) - .setHeaders(HttpHeaders.builder().addHeader("Content-Type", "application/xml").build()) - .setRequestBody(ByteString.copyFromUtf8(payload)) - .build(); - } - - 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(getAdvisories().get(0)) - .build(); - } -} diff --git a/community/detectors/zimbra_cve_2019_9670/src/main/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Cve20199670DetectorBootstrapModule.java b/community/detectors/zimbra_cve_2019_9670/src/main/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Cve20199670DetectorBootstrapModule.java deleted file mode 100644 index f53f00cd5..000000000 --- a/community/detectors/zimbra_cve_2019_9670/src/main/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Cve20199670DetectorBootstrapModule.java +++ /dev/null @@ -1,38 +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.zimbra.cve20199670; - -import com.google.inject.Provides; -import com.google.tsunami.plugin.PluginBootstrapModule; -import com.google.tsunami.plugins.detectors.zimbra.cve20199670.Annotations.OobSleepDuration; - -/** A {@link PluginBootstrapModule} for {@link Cve20199670Detector}. */ -public final class Cve20199670DetectorBootstrapModule extends PluginBootstrapModule { - - @Override - protected void configurePlugin() { - registerPlugin(Cve20199670Detector.class); - } - - @Provides - @OobSleepDuration - int provideOobSleepDuration(Cve20199670DetectorConfigs configs) { - if (configs.oobSleepDuration == 0) { - return 10; - } - return configs.oobSleepDuration; - } -} diff --git a/community/detectors/zimbra_cve_2019_9670/src/main/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Cve20199670DetectorConfigs.java b/community/detectors/zimbra_cve_2019_9670/src/main/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Cve20199670DetectorConfigs.java deleted file mode 100644 index 06bf8aff9..000000000 --- a/community/detectors/zimbra_cve_2019_9670/src/main/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Cve20199670DetectorConfigs.java +++ /dev/null @@ -1,23 +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.zimbra.cve20199670; - -import com.google.tsunami.common.config.annotations.ConfigProperties; - -@ConfigProperties("plugins.community.detectors.zimbra.cve20199670") -final class Cve20199670DetectorConfigs { - int oobSleepDuration; -} diff --git a/community/detectors/zimbra_cve_2019_9670/src/test/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Cve20199670DetectorTest.java b/community/detectors/zimbra_cve_2019_9670/src/test/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Cve20199670DetectorTest.java deleted file mode 100644 index baa53a4bc..000000000 --- a/community/detectors/zimbra_cve_2019_9670/src/test/java/com/google/tsunami/plugins/detectors/zimbra/cve20199670/Cve20199670DetectorTest.java +++ /dev/null @@ -1,281 +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.zimbra.cve20199670; - -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.tsunami.plugins.detectors.zimbra.cve20199670.Annotations.OobSleepDuration; -import static com.google.tsunami.plugins.detectors.zimbra.cve20199670.Cve20199670Detector.ERROR_MSG; -import static com.google.tsunami.plugins.detectors.zimbra.cve20199670.Cve20199670Detector.TEST_STRING; -import static com.google.tsunami.plugins.detectors.zimbra.cve20199670.Cve20199670Detector.ZIMBRA_FINGERPRING; - -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.JsonFormat; -import com.google.protobuf.util.Timestamps; -import com.google.tsunami.callbackserver.proto.PollingResult; -import com.google.tsunami.common.net.http.HttpClientModule; -import com.google.tsunami.common.net.http.HttpStatus; -import com.google.tsunami.common.time.testing.FakeUtcClock; -import com.google.tsunami.common.time.testing.FakeUtcClockModule; -import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule; -import com.google.tsunami.proto.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.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.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Unit tests for {@link Cve20199670DetectorTest}. */ -@RunWith(JUnit4.class) -public final class Cve20199670DetectorTest { - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z")); - - @Bind(lazy = true) - @OobSleepDuration - private int sleepDuration = 1; - - @Inject private Cve20199670Detector detector; - - private final SecureRandom testSecureRandom = - new SecureRandom() { - @Override - public void nextBytes(byte[] bytes) { - Arrays.fill(bytes, (byte) 0xFF); - } - }; - - private MockWebServer mockZimbraService; - private MockWebServer mockCallbackServer; - - @Before - public void setUp() throws IOException { - mockZimbraService = new MockWebServer(); - mockZimbraService.start(); - - mockCallbackServer = new MockWebServer(); - mockCallbackServer.start(); - } - - @After - public void tearDown() throws Exception { - mockZimbraService.shutdown(); - mockCallbackServer.shutdown(); - } - - private void createInjector(boolean tcsAvailable) { - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - FakePayloadGeneratorModule.builder() - .setCallbackServer(tcsAvailable ? mockCallbackServer : null) - .setSecureRng(testSecureRandom) - .build(), - Modules.override(new Cve20199670DetectorBootstrapModule()) - .with(BoundFieldModule.of(this))) - .injectMembers(this); - } - - @Test - public void detect_whenVulnerableAndTcsNotAvailable_reportsVulnerability() throws IOException { - createInjector(false); - - // Zimbra is detected - mockZimbraService.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(ZIMBRA_FINGERPRING)); - mockZimbraService.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.code())); - - // Detects XXE with reflected payload, as no TCS is available - mockZimbraService.enqueue( - new MockResponse() - .setResponseCode(HttpStatus.SERVICE_UNAVAILABLE.code()) - .setBody(ERROR_MSG + TEST_STRING)); - - NetworkService service = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockZimbraService.getHostName(), mockZimbraService.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build(); - - TargetInfo target = - TargetInfo.newBuilder() - .addNetworkEndpoints(forHostname(mockZimbraService.getHostName())) - .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_whenVulnerableAndTcsAvailable_reportsVulnerability() throws IOException { - createInjector(true); - - // Zimbra is detected - mockZimbraService.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(ZIMBRA_FINGERPRING)); - mockZimbraService.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.code())); - - // Detect XXE with TCS interaction - mockZimbraService.enqueue(new MockResponse().setResponseCode(HttpStatus.BAD_REQUEST.code())); - - PollingResult log = PollingResult.newBuilder().setHasHttpInteraction(true).build(); - String body = JsonFormat.printer().preservingProtoFieldNames().print(log); - mockCallbackServer.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(body)); - - NetworkService service = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockZimbraService.getHostName(), mockZimbraService.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build(); - - TargetInfo target = - TargetInfo.newBuilder() - .addNetworkEndpoints(forHostname(mockZimbraService.getHostName())) - .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_whenNotVulnerableAndTcsNotAvailable_doesNotReportVulnerability() - throws IOException { - createInjector(false); - - // Zimbra is detected - mockZimbraService.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(ZIMBRA_FINGERPRING)); - mockZimbraService.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.code())); - - // Does not detect XXE - mockZimbraService.enqueue(new MockResponse().setResponseCode(HttpStatus.BAD_REQUEST.code())); - - NetworkService service = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockZimbraService.getHostName(), mockZimbraService.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build(); - - TargetInfo target = - TargetInfo.newBuilder() - .addNetworkEndpoints(forHostname(mockZimbraService.getHostName())) - .build(); - - DetectionReportList detectionReports = detector.detect(target, ImmutableList.of(service)); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - @Test - public void detect_whenNotVulnerableAndTcsAvailable_doesNotReportVulnerability() - throws IOException { - createInjector(true); - - // Zimbra is detected - mockZimbraService.enqueue( - new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(ZIMBRA_FINGERPRING)); - mockZimbraService.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.code())); - - // Does not detect XXE - mockZimbraService.enqueue(new MockResponse().setResponseCode(HttpStatus.BAD_REQUEST.code())); - mockCallbackServer.enqueue(new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.code())); - - NetworkService service = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockZimbraService.getHostName(), mockZimbraService.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build(); - - TargetInfo target = - TargetInfo.newBuilder() - .addNetworkEndpoints(forHostname(mockZimbraService.getHostName())) - .build(); - - DetectionReportList detectionReports = detector.detect(target, ImmutableList.of(service)); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } - - @Test - public void detect_whenNonZimbraService_doesNotReportVulnerability() throws IOException { - createInjector(false); - // Zimbra is not detected - mockZimbraService.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.code())); - - NetworkService service = - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockZimbraService.getHostName(), mockZimbraService.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build(); - - TargetInfo target = - TargetInfo.newBuilder() - .addNetworkEndpoints(forHostname(mockZimbraService.getHostName())) - .build(); - - DetectionReportList detectionReports = detector.detect(target, ImmutableList.of(service)); - - assertThat(detectionReports.getDetectionReportsList()).isEmpty(); - } -} diff --git a/templated/templateddetector/plugins/cve/2019/Zimbra_CVE_2019_9670.textproto b/templated/templateddetector/plugins/cve/2019/Zimbra_CVE_2019_9670.textproto new file mode 100644 index 000000000..91cc5c35b --- /dev/null +++ b/templated/templateddetector/plugins/cve/2019/Zimbra_CVE_2019_9670.textproto @@ -0,0 +1,152 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "Zimbra_CVE_2019_9670" + author: + "Robert Dick (robert@doyensec.com) for templated version, " + "Leonardo Tamiano (leonardo.tamiano@mindedsecurity.com) for original Java version" + version: "2.0" +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "CVE_2019_9670" + } + severity: CRITICAL + title: "Synacor Zimbra XXE CVE-2019-9670" + description: + "The mailboxd component of Synacor Zimbra Collaboration Suite, in versions from 8.5 to" + " 8.7.11p10, is vulnerable to an XML External Entity injection (XXE). The vulnerability" + " is found in the autodiscover feature, available to unauthenticated users through the" + " endpoint /Autodiscover/Autodiscover.xml. The vulnerability allows malicious actors to" + " extract sensitive files from the system and can be chained with another" + " vulnerability (CVE-2019-9621) in order to obtain an unauthenticated RCE." + " Specifically, by using the XXE (CVE-2019-9670) it is possible to read a configuration" + " file that contains an LDAP password for the zimbra account. The zimbra credentials" + " are then used to get a user authentication cookie with an AuthRequest message. Using" + " the user cookie, a SSRF (CVE-2019-9621) in the Proxy Servlet is used to proxy an" + " AuthRequest with the zimbra credentials to the admin port to retrieve an admin" + " cookie. After gaining an admin cookie the Client Upload servlet is used to upload a" + " JSP webshell that can be triggered from the web server to obtain RCE." + recommendation: + "Upgrade to non-vulnerable versions of Synacor Zimbra Collaboration Suite such as 8.7.11p14" + " or later versions such as 8.8.x, 9.x and 10.x." + related_id: { + publisher: "CVE", + value: "CVE-2019-9670" + } +} + +########### +# ACTIONS # +########### + +actions: { + name: "fingerprint_zimbra" + http_request: { + method: GET + uri: "/" + response: { + http_status: 200 + expect_all: { + conditions: [ + { + body {} + contains: 'Zimbra Collaboration Suite Web Client' + } + ] + } + } + } +} + +actions: { + name: "trigger_callback" + http_request: { + method: POST + uri: "/Autodiscover/Autodiscover.xml" + headers: [ + { name: "Content-Type" value: "application/xml" } + ] + data: + "" + " ]>" + "" + "email" + "&oob;" + "\\" + } +} + +actions: { + name: "sleep" + utility: { sleep: { duration_ms: 5000 } } +} + +actions: { + name: "check_callback_server_logs" + callback_server: { action_type: CHECK } +} + +# backup action in case callback server is disabled or unreachable + +actions: { + name: "check_reflected_xxe" + http_request: { + method: POST + uri: "/Autodiscover/Autodiscover.xml" + headers: [ + { name: "Content-Type" value: "application/xml" } + ] + data: + "" + " ]>" + "" + "email" + "&xxe;" + "\\" + response: { + http_status: 503 + expect_all: { + conditions: [ + { + body {} + contains: 'Error 503 Requested response schema not available' + }, + { + body {} + contains: '{{ T_UTL_CURRENT_TIMESTAMP_MS }}' + } + ] + } + } + } +} + +############# +# WORKFLOWS # +############# + +workflows: { + condition: REQUIRES_CALLBACK_SERVER + actions: [ + "fingerprint_zimbra", + "trigger_callback", + "sleep", + "check_callback_server_logs" + ] +} + +workflows: { + actions: [ + "fingerprint_zimbra", + "check_reflected_xxe" + ] +} \ No newline at end of file diff --git a/templated/templateddetector/plugins/cve/2019/Zimbra_CVE_2019_9670_test.textproto b/templated/templateddetector/plugins/cve/2019/Zimbra_CVE_2019_9670_test.textproto new file mode 100644 index 000000000..ecb8cd117 --- /dev/null +++ b/templated/templateddetector/plugins/cve/2019/Zimbra_CVE_2019_9670_test.textproto @@ -0,0 +1,104 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "Zimbra_CVE_2019_9670" +} + +tests: { + name: "whenOobVulnerable_returnsVuln" + expect_vulnerability: true + + mock_callback_server: { + enabled: true + has_interaction: true + } + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 200 + body_content: + '... Zimbra Collaboration Suite Web Client ...' + }, + { + uri: "/Autodiscover/Autodiscover.xml" + status: 503 + body_content: + "... error ..." + } + ] + } +} + +tests: { + name: "whenReflectedVulnerable_returnsVuln" + expect_vulnerability: true + + mock_callback_server: { + enabled: false + has_interaction: false + } + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 200 + body_content: + '... Zimbra Collaboration Suite Web Client ...' + }, + { + uri: "/Autodiscover/Autodiscover.xml" + status: 503 + body_content: + "... Error 503 Requested response schema not available ... {{ T_UTL_CURRENT_TIMESTAMP_MS }} ..." + } + ] + } +} + +tests: { + name: "whenOobNotVulnerable_returnsNotVuln" + expect_vulnerability: false + + mock_callback_server: { + enabled: true + has_interaction: false + } + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 200 + body_content: + '... Zimbra Collaboration Suite Web Client ...' + }, + { + uri: "/Autodiscover/Autodiscover.xml" + status: 503 + body_content: + "... error ..." + } + ] + } +} + +tests: { + name: "whenRandomServer_returnsFalse" + expect_vulnerability: false + + + mock_http_server: { + mock_responses: [ + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: "Hello world" + } + ] + } +} +