CVE-2022-23544 (Java, published 2022-12-28T00:15Z)
Description: value: MeterSphere is a one-stop open source continuous testing platform, covering test management, interface testing, UI testing and performance testing. Versions prior to 2.5.0 are subject to a Server-Side Request Forgery that leads to Cross-Site Scripting. A Server-Side request forgery in `IssueProxyResourceService::getMdImageByUrl` allows an attacker to access internal resources, as well as executing JavaScript code in the context of Metersphere's origin by a victim of a reflected XSS. This vulnerability has been fixed in v2.5.0. There are no known workarounds.
Severity: nan (CVSS v3 base score: 6.1)
Weakness: CWE-918 \u2014 Server-Side Request Forgery (SSRF)
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
Repository: https://github.com/metersphere/metersphere
Files changed:
framework/gateway/src/main/java/io/metersphere/gateway/filter/SessionFilter.java
framework/sdk-parent/xpack-interface/src/main/java/io/metersphere/xpack/track/issue/IssuesPlatform.java
pom.xml
test-track/backend/src/main/java/io/metersphere/controller/IssueProxyResourceController.java
test-track/backend/src/main/java/io/metersphere/service/IssuesService.java
test-track/backend/src/main/java/io/metersphere/service/PlatformPluginService.java
test-track/backend/src/main/java/io/metersphere/service/issue/client/ZentaoClient.java
test-track/backend/src/main/java/io/metersphere/service/issue/platform/AbstractIssuePlatform.java
test-track/backend/src/main/java/io/metersphere/service/issue/platform/ZentaoPlatform.java
test-track/backend/src/main/java/io/metersphere/service/wapper/IssueProxyResourceService.java
Commit message: fix(测试跟踪): 缺陷平台请求转发添加白名单


Vulnerable code:
    private static final String TRACK_IMAGE_PREFIX = "/resource/md/get/url";
     * @param url
    ResponseEntity proxyForGet(String url, Class responseEntityClazz);
        <platform-plugin-sdk.version>1.0.0</platform-plugin-sdk.version>
    @GetMapping(value = "/md/get/url")
    public ResponseEntity<byte[]> getFileByUrl(@RequestParam ("url") String url, @RequestParam (value = "platform", required = false) String platform,
                                               @RequestParam (value = "workspace_id", required = false) String workspaceId) {
        return issueProxyResourceService.getMdImageByUrl(url, platform, workspaceId);
import jodd.util.CollectionUtil;
        //处理MD图片链接内容
        handleJiraIssueMdUrl(request.getWorkspaceId(), request.getProjectId(), issues);
    private void handleJiraIssueMdUrl(String workPlaceId, String projectId, List<IssuesDao> issues) {
        issues.forEach(issue -> {
            if (StringUtils.isNotEmpty(issue.getDescription()) && issue.getDescription().contains("platform=Jira&")) {
                issue.setDescription(replaceJiraMdUrlParam(issue.getDescription(), workPlaceId, projectId));
            }
            if (StringUtils.isNotEmpty(issue.getCustomFields()) && issue.getCustomFields().contains("platform=Jira&")) {
                issue.setCustomFields(replaceJiraMdUrlParam(issue.getCustomFields(), workPlaceId, projectId));
            }
            if (CollectionUtils.isNotEmpty(issue.getFields())) {
                issue.getFields().forEach(field -> {
                    if (StringUtils.isNotEmpty(field.getTextValue()) && field.getTextValue().contains("platform=Jira&")) {
                        field.setTextValue(replaceJiraMdUrlParam(field.getTextValue(), workPlaceId, projectId));
                    }
                    if (StringUtils.isNotEmpty(field.getValue()) && field.getValue().contains("platform=Jira&")) {
                        field.setValue(replaceJiraMdUrlParam(field.getValue(), workPlaceId, projectId));
                    }
                });
            }
        });
    }

    private String replaceJiraMdUrlParam(String url, String workspaceId, String projectId) {
        if (url.contains("&workspace_id=")) {
            return url;
        }
        return url.replaceAll("platform=Jira&",
                "platform=Jira&workspace_id=" + workspaceId + "&");
    }

import io.metersphere.i18n.Translator;
import io.metersphere.commons.constants.IssueRefType;
                if (!path.contains("/resource/md/get/url")) {
            if (originSubUrl.contains("/url?url=")) {
                    try {
                        URI uri = new URI(zentaoClient.getBaseUrl());
                        path = uri.getScheme() + "://" + uri.getHost() + path.replaceAll("&amp;", "&");
                    } catch (URISyntaxException e) {
                        path = zentaoClient.getBaseUrl() + path.replaceAll("&amp;", "&");
                        LogUtil.error(e);
                path = "/resource/md/get/url?url=" + URLEncoder.encode(path, StandardCharsets.UTF_8);
import io.metersphere.commons.exception.MSException;
import io.metersphere.i18n.Translator;
import org.springframework.http.HttpMethod;
     * @param url
    public ResponseEntity<byte[]> getMdImageByUrl(String url, String platform, String workspaceId) {
        if (url.contains("md/get/url")) {
            MSException.throwException(Translator.get("invalid_parameter"));
        }
        if (StringUtils.isNotBlank(platform)) {
            return platformPluginService.getPlatform(platform, workspaceId)
                    .proxyForGet(url, byte[].class);
        return restTemplate.exchange(url, HttpMethod.GET, null, byte[].class);

Fixed code:
    private static final String TRACK_IMAGE_PREFIX = "/resource/md/get/path";
     * @param path
    ResponseEntity proxyForGet(String path, Class responseEntityClazz);
        <platform-plugin-sdk.version>1.1.0</platform-plugin-sdk.version>
    @GetMapping(value = "/md/get/path")
    public ResponseEntity<byte[]> getFileByPath(@RequestParam ("path") String path,
                                                @RequestParam (value = "platform") String platform,
                                                @RequestParam (value = "workspaceId") String workspaceId) {
        return issueProxyResourceService.getMdImageByPath(path, platform, workspaceId);
        pluginRequest.setWorkspaceId(workspaceId);
import java.net.URI;
import java.net.URISyntaxException;

    public ResponseEntity proxyForGet(String path, Class responseEntityClazz) {
        im.metersphere.plugin.utils.LogUtil.info("zentao proxyForGet: " + path);
        String url = this.ENDPOINT + path;
        try {
            if (!StringUtils.containsAny(new URI(url).getPath(), "/index.php", "/file-read-")) {
                // 只允许访问图片
                MSException.throwException("illegal path");
            }
        } catch (URISyntaxException e) {
            LogUtil.error(e);
            MSException.throwException("illegal path");
        }
        return restTemplate.exchange(url, HttpMethod.GET, null, responseEntityClazz);
    }
import java.net.URLEncoder;
    public static final String PROXY_PATH = "/resource/md/get/path?platform=%s&workspaceId=%s&path=%s";

    protected String getProxyPath(String path) {
        return String.format(PROXY_PATH, this.key, this.workspaceId, URLEncoder.encode(path, StandardCharsets.UTF_8));
    }

                if (!path.contains("/resource/md/get/url") && !path.contains("/resource/md/get/path")) {
            if (originSubUrl.contains("/url?url=") || originSubUrl.contains("/path?")) {
                    path = path.replaceAll("&amp;", "&");
                }
                StringBuilder stringBuilder = new StringBuilder();
                for (String item : path.split("&")) {
                    // 去掉多余的参数
                    if (!StringUtils.containsAny(item, "platform", "workspaceId")) {
                        stringBuilder.append(item);
                        stringBuilder.append("&");
                path = getProxyPath(stringBuilder.toString());

    @Override
    public ResponseEntity proxyForGet(String path, Class responseEntityClazz) {
        return zentaoClient.proxyForGet(path, responseEntityClazz);
    }
import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.service.issue.platform.IssueFactory;
import io.metersphere.xpack.track.dto.request.IssuesRequest;
     * @param path
    public ResponseEntity<byte[]> getMdImageByPath(String path, String platform, String workspaceId) {
        if (StringUtils.equals(IssuesManagePlatform.Zentao.name(), platform)) {
            IssuesRequest issuesRequest = new IssuesRequest();
            issuesRequest.setWorkspaceId(workspaceId);
            return IssueFactory.createPlatform(platform, issuesRequest)
                    .proxyForGet(path, byte[].class);
        } else {
            return platformPluginService.getPlatform(platform, workspaceId)
                    .proxyForGet(path, byte[].class);