[备份]CVE-2017-8046分析

简介

Spring Data REST的目的是消除CURD的模板代码,减少程序员的刻板的重复劳动,但实际上并没有很多人使用。很少有请求直接操作数据库的场景,至少也要做权限校验等操作。而Spring Data REST允许请求直接操作数据库,中间没有任何的业务逻辑

漏洞的原因是对PATCH方法处理不当,导致攻击者能够利用JSON数据造成RCE。本质还是因为Spring的SPEL解析导致的RCE,大部分Spring框架的RCE都是源于此

环境搭建

使用Spring官方教程:https://github.com/spring-guides/gs-accessing-data-rest.git 下载后包含多个模块,使用其中的complete项目,导入IDEA后发现是SpringBoot项目

官方不可能在教程中采用存在漏洞的代码,所以我们需要手动将pom依赖文件中SpringBoot的版本修改为存在漏洞的版本。SpringBoot是一个父依赖,其中包含spring-data-rest-webmvc这个核心组件

启动项目比较简单,直接运行AccessingDataRestApplication类。如果有报错,应该是junit的问题,删除src/test/java下的文件即可解决。访问localhost:8080返回如下

漏洞复现

PATCH

首先应该讲一下什么是PATCH,准确一点来说是JSON-PATCH。字面意思是补丁,实际意义也是补丁。主要功能是做修补,按照JSON-PATCH官方的定义:

发送这样的PATCH请求:

一开始的数据就会变成:

可以这样简单理解:op是一种操作标识,比如增删改查;path是修改的key,value是修改的value

复现

使用POST的方式为系统新增一个用户:

返回如下:

返回说明创建这个人成功,接下来我们需要使用PATCH请求对这个人的信息做更改

成功修改了名字:

漏洞存在于这个PATCH请求的path参数,我们将它修改为恶意代码,造成RCE:

漏洞分析

从处理JSON的地方开始分析:org.springframework.data.rest.webmvc.config.JsonPatchHandler:apply()

判断是否是JSON-PATCH请求,如果是那么调用applyPatch方法,并传入请求的body。关于isJsonPatchRequest的内容,判断了请求方法和请求头:

关于applyPatch,看命名猜测是获得其中所有的op操作

重点关注其中的convert方法,因为传入了请求body的流,也就是包含payload的部分。代码稍复杂,不过可以看出没有对path做多余的判断,直接读取后封装到Patch中返回出去。这里可以下断点具体观察,读入了path中的payload

这里初始化Patch的方法传入了一个ops,找到Patch的构造方法,发现ops是PatchOperation的List,找到PatchOperation的构造

发现一处有趣的地方:spel。Spring表达式,也是大部分SpringRCE的本质原因

发现这里对path分割后,pathNodesToSpEL方法做了简单的字符串重组,组成spel表达式字符串,没有做任何的校验。这里一层一层地传上去,到了convert方法的ops.add方法。PatchOperation类是一个抽象类,需要有具体的类继承,由于我们传入的op是replace,所以继承的类是ReplaceOperation

回到最上面apply方法

这里传入的PatchOperation其实是子类ReplaceOperation,看下它的perform方法

到这里就可以结束了,payload成功传入spel的setValue方法,造成RCE

修复

官方修复方案:https://github.com/spring-projects/spring-data-rest/commit/8f269e28fe8038a6c60f31a1c36cfda04795ab45

解决代码如上,比如it.matches("\d")这一步,不允许存在数字,导致上面的payload失效