JavaSec系列 - 5. SpEL注入
本章源码: https://github.com/hey3e/JavaSec-Code/tree/main/javasec5
先前,我们在系列2中jdk>8u191的环境下实现JNDI注入时,使用到了EL表达式。SpEL,是EL的一种,”Sp”代表”Spring”,即用于Spring的表达式。而最近,Spring Cloud爆出两个RCE,其原理均为SpEL注入,因此我们下面通过接触这两个实例来了解SpEL注入的实现。
(1) SpEL基础
下面是SpEL表达式解析的过程:
1 | void testSpEL() throws Exception { |
输出为Hello World!
。其中:
- a:通过
SpelExpressionParser
类构造解析器。 - b:传入SpEL表达式。
- c:通过
StandardEvaluationContext
构造上下文。 - d:在上下文中,我们可以自定义变量、函数等。
- e:调用
getValue
方法,根据上下文解析SpEL表达式。
我们重点来关注一下SpEL如何来实现类:
1 | public void testClass() { |
输出如下:
可知,通过SpEL表达式T(type)
,可以实现类型为type
的类。非常像反射。意味着如果攻击者可以控制传入的SpEL表达式,就可能实现RCE。
(2) Spring Cloud Gateway RCE (CVE-2022-22947)
Spring Cloud Gateway (以下简写为SCG),提供了Spring上的路由功能。
默认情况下,SCG的属性management.endpoint.gateway.enabled
为true
,即允许远程地调用/actuator/gateway
对路由进行配置。如POST到/gateway/routes/{id_route_to_create}
添加路由,并再次POST到/actuator/gateway/refresh
以使配置生效。
下面开始分析其是否存在SpEL注入的可能。我们先来看SCG源码,根据SpEL基础,我们全局搜索SpelExpressionParser,在ShortcutConfigurable
接口中,我们找到了getValue
方法:
可见,若entryValue
以”#{“开头、以”}”结尾,则将其看作SpEL表达式并进行解析。因此,若entryValue
可控,则可能实现SpEL注入。那接下来的任务便是寻找entryValue
的来源。
向上回溯,ShortcutConfigurable
接口下的三个ShortcutType
枚举均调用了getValue
方法:
注意entryValue
的传入,即entry.getValue()
,可知来自于键值对形式的args
变量的值。
继续向上,normalize()
方法调用自ConfigurationService
类的normalizeProperties()
方法,而args
即该类的properties
属性:
normalizeProperties()
调用自同类下的bind()
:
其中强调了name
和properties
属性不能为空。
而bind()
在RouteDefinitionRouteLocator
类的loadGatewayFilters()
中被调用:
着重分析下该方法。可见,参数filterDefinitions
包含多个filter的定义,在127行中,将filter实例化为GatewayFilterFactory
对象,并自138行开始configure:name
属性即filter的name,properties
属性即filter的args。
到这里,payload的构造已经清晰:我们要创建一个路由,其filter是实现了GatewayFilterFactory
接口的对象,args包含恶意的SpEL表达式。
随意选择一个实现GatewayFilterFactory
的类,如Retry
:
结合官方提供的路由创建样例,构造payload:
1 | { |
此处args中SpEL表达式的key
,经测试可随意更改。
路由创建成功后,返回201 Created:
刷新路由,弹出计算器,实现SpEL注入:
查看调用栈,与前文分析一致:
由于是本地测试,我们可以选择任意一个实现了GatewayFilterFactory
的类作为filter。但若是远程测试需要回显,则需要选择AddResponseHeaderGatewayFilterFactory
类。
(3) Spring Cloud Function RCE
老样子,在Spring Cloud Function (以下简写为SCF) 框架中,全局搜索SpelExpressionParser,定位到RoutingFunction
类的functionFromExpression
的方法:
很明显,要利用的目标是routingExpression
。向上跟:
可见,routingExpression
来自于请求头中的spring.cloud.function.routing-expression
参数。而想要触发SCF的function routing功能,根据官方文档,要访问的接口应为functionRouter
,其在RoutingFunction
类中也有定义:
构造POST请求:
发现未能弹出计算器。调试发现,在route()
方法中,传入的input
为FluxEmpty
类,因此未能进入functionFromExpression
方法:
向上追溯到FunctionController
类的post()
方法中,会发现input
来自于请求的body:
于是加入body,再次请求,实现SpEL注入:
调用栈如下:
另外,官方文档提供了访问任意路径触发function routing的方法,即在application.properties中添加如下属性:
测试:
(3) Countermeasures
SCG的漏洞修复,可以设置management.endpoint.gateway.enabled
属性为false
以拒绝远程调用。
更为通用的修复方法,我们可以看到两个框架中对SpEL的处理均使用的StandardEvaluationContext
:
StandardEvaluationContext
提供了全部SpEL语法,因此,将其替换为不能实例化类的SimpleEvaluationContext
会更加安全。
JavaSec系列 - 5. SpEL注入