一:漏洞简介
Spring Data REST是Spring Data项目的一部分,可以在Spring Data存储库之上构建超媒体驱动的REST Web服务。Spring Data REST存在远程代码执行漏洞,攻击者通过构造恶意的PATCH请求提交给spring-data-rest服务器,使用特制的JSON数据来运行任意的Java代码,从而实现远程代码执行攻击。
二:影响版本
- Spring Data REST versions 2.5.12, 2.6.7, 3.0 RC3之前的版本
- Spring Boot versions 2.0.0M4 之前的版本
- Spring Data release trains Kay-RC3 之前的版本
三:漏洞复现
使用官方demo:https://github.com/spring-guides/gs-accessing-data-rest.git
下载后如图所示:
直接idea导入complete文件夹让它自动下载依赖。
下载完成后如图所示:
接下来我们需要将pom.xml文件中的springboot的版本修改为含有漏洞的版本,springboot是一个父依赖,其中含有spring-data-rest-webmvc这个核心组件。
然后直接运行AccessingDataRestApplication类就可以启动,启动成功后访问127.0.0.1:8080返回如下代表启动成功:
接下来先简单说一下Patch方法(已有的上传数据的方法有POST和PUT,但是功能上有些不足):
Patch方法是新引入的对PUT方法的补充,用来对已知资源进行局部更新。Patch请求方法有一个标准,必须包含一个path和op字段, op表示具体操作, path用于定位准确数据字段。
Patch方法的content-type类型为: application/json-patch+json
上边说到Patch方法的主要作用是用来对已知的资源进行更新,我们来举个例子:
已知json数据:
{
“baz”: “qux”,
“foo”: “bar”
}
发送下边的请求:
[
{ “op”: “replace”, “path”: “/baz”, “value”: “boo” },
{ “op”: “add”, “path”: “/hello”, “value”: [“world”] },
{ “op”: “remove”, “path”: “/foo” }
]
最初的json数据会变成(修改baz的值,新增hello值为world,删除foo):
{
“baz”: “boo”,
“hello”: [“world”]
}
接下来我们开始复现:
使用POST方式为系统新增一个用户:
可以看到用户新增成功,然后我们需要使用PATCH方式对该用户的信息进行修改:
可以看到用户信息修改成功。
漏洞点就在PATCH请求的path参数里,我们把path参数的值修改为恶意代码,如下:
注:
- 必须将Content-Type指定为application/json-patch+json。
- 请求数据必须是json数组。
弹计算器操作:
这里需要改变一下编码,可以使用下边这种方式:
[{ “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提交的是json格式,所以先从处理json的地方开始入手:
org.springframework.data.rest.webmvc.config.JsonPatchHandler:apply()
首先,我们提交json数据后程序会先判断请求头中的content-type是否为application/json-patch+json,请求方式是否为PATCH。如果符合的话调用applyPatch方法并传入请求体,否则调用applyMergePatch方法。
其中isJsonPatchRequest方法中是这样判断请求头和请求方法:
然后判断完请求方法和请求头后进入下一步的applyPatch方法。
继续往下,会进入getPatchOperations方法,该方法首先会利用mapper初始化JsonPatchPatchConverter对象之后调用convert方法:
继续跟进convert方法,可以看到convert方法会返回Patch对象,其中ops包含了我们发送的payload:
继续往下,进入Patch
通过上一步的返回结果我们可以看到ops是一个List
Path传到了pathToExpression,直接进去可以看到这是对spel表达式的解析操作,但是解析前调用了pathToSpEL,进去看下:
可以看到pathToSpEL方法中先对path进行分割操作(通过/进行分割)
然后pathNodesToSpEL方法做了简单的字符串重组
并没有做任何的检验,
然后继续执行,回到applyPatch方法中,然后继续执行发现PatchOperation是一个抽象类,实际上应该调用其实现类的perform()方法。通过动态调试分析,此时的operation实际是ReplaceOperation类的实例:
然后进入perform方法,继续执行,在setValueOnTarget()中会调用spelExpression对spel表示式进行解析从而触发该漏洞: