漏洞情况
当Spring Cloud Function
启用动态路由functionRouter
时,由于Spring Cloud Function
中RoutingFunction
类的apply方法将请求头中的spring.cloud.function.routing-expression
参数作为Spel表达式进行处理,造成了Spel表达式注入漏洞,攻击者可利用该漏洞远程执行任意代码。
影响范围
3.0.0.RELEASE <= Spring Cloud Function <= 3.2.2
环境搭建
官方的环境(需要修改pom.xml):
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 | <?xml version="1.0" encoding="UTF-8"?> |
拉取仓库后用IDEA maven进行打包调试即可。
漏洞复现
可以看到官方给的测试用例如下:
有个核心的地方就是设置一个请求头
1 | setHeader(FunctionProperties.PREFIX + ".routing-expression","T(java.lang.Runtime).getRuntime().exec(\"open -a calculator.app\")") |
请求头中的FunctionProperties.PREFIX
为spring.cloud.function
所以拼接起来,漏洞的利用是在请求的headers头上添加一个spring.cloud.function.routing-expression
参数,然后会将其参数内容直接带入到SPEL中查询,造成SpEL漏洞注入。
POC1:
1 | POST / HTTP/1.1 |
application.properties
POC2:
1 | POST /functionRouter HTTP/1.1 |
application.properties
漏洞分析
漏洞是出在SpringCloud Function
的RoutingFunction
功能上,其功能的目的本身就是为了微服务应运而生的,可以直接通过HTTP请求与单个的函数进行交互,同时为spring.cloud.function.definition
参数提供您要调用的函数的名称。
比如我们可以自定义一个函数,这里随意编写一个反转函数用来测试
1 | @Bean //第一个String代表输入类型,第二个String代表输出类型 |
通过在请求头中添加spring.cloud.function.definition:
函数名即可调用我们的函数
下面分析漏洞利用的过程
第1种利用:需要修改配置+任意路由
首先我们在漏洞利用的时候是先发起一个post请求的,因此,我们可以先查看后端的Controller层对提交的请求做了哪些操作
定位到控制层代码org\springframework\cloud\function\web\mvc\FunctionController.java#post
方法,在此处打上断点
点击调试按钮,burp发送poc
此时停在了断点处,可以在Variables视图看到spring.cloud.function.routing-expression
头的赋值情况
1 | ServletWebRequest->HttpServletRequest->Request->MimeHeaders->headers->spring.cloud.function.routing-expression |
往下,程序会获取body中的参数,并传入processRequest方法中,在processRequest方法中会获取通过wrapper对象获取到请求头,并将请求体和请求头封装成inputMessage,接着调用FunctionInvocationWrapper#isRoutingFunction
方法判断当前请求是否为RoutingFunction。
接着往下,通过FunctionInvocationWrapper#apply
方法将请求的内容和Header头封装成的input带入到FunctionInvocationWrapper.apply
方法中,随后又进入其中的doApply方法
在doApply方法中,会判断当前的RoutingFunction和Composed是否为true,如果是,则调用RoutingFunction
的apply方法
跟进apply方法,发现其调用了RoutingFunction#route
方法
跟进route方法,if判断中,获取header请求头中的字段,判断是否存在spring.cloud.function.definition
,若不存在则判断是否存在spring.cloud.function.routing-expression
,如存在则调用functionFromExpression
方法解析对应的值。
进入functionFromExpression
方法中,可以看到通过SpelExpressionParser
来解析内容,导致Spel表达式注入。
这里可以看到设置EvaluationContext
的值是StandardEvaluationContext
,而它包含了SpEL的所有功能,在允许用户控制输入的情况下可以成功造成任意命令执行。
如下,evalContext对象创建采用的是StandardEvaluationContext
第2种利用:默认配置+特定路由
定位到FunctionHandlerMapping.java#HandlerMethod
方法,方法中会获取请求的路径,然后调用findFunction
方法
接着跟进doFindFunction
方法
后调用FunctionCatalog#lookup
方法
继续跟进lookup方法
又调用dolookup方法
dolookup方法大概是在为function进行赋值
返回function之后,往下走,在discoverFunctionInBeanFactory
方法处,做了一个类似于查询的操作,查找bean工厂中是否有functionRouter,如果有就返回,这里可以看到返回了RoutingFunction对象(经过调试,如果这里没有查询到,后面就返回null,就不会结果post方法进行后续的调用,也就是不会触发SpEL表达式注入)
最后调用父类的doLookup方法,返回function对象
返回之后,回到FunctionWebRequestProcessingHelper.java
类调用doFindFunction
方法
继续往下就回到了FunctionHandlerMapping.java#getHandlerInternal
方法,将function保存到request的作用域中
最后经过一连串的F8,就到了熟悉的post方法,后续的步骤就是一样的了
补丁分析
在补丁中,通过SimpleEvaluationContext
来构建EvalContext
对象
在functionFromExpression
函数中添加了一个boolean型的参数
根据传入的值为true,即调用getValue(this.headerEvalContext, input, String.class)
,也就防止SpEL注入的发生。
参考
https://www.cnblogs.com/wh4am1/p/16062306.html
https://mp.weixin.qq.com/s/O2vJmMgqrQp5RTLwvZ5X_w