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"
+ }
+ ]
+ }
+}
+