Spring Cloud Gateway CVE-2022-22947 漏洞分析
作者:cdhe@白帽汇安全研究院
1、漏洞信息
当Gateway Actuator端点是启用状态并且是允许访问的以及相关配置不安全时,使用Spring Cloud Gateway的应用程序容易受到代码注入攻击。远程攻击者可以发出恶意制作的请求,允许在远程主机上进行任意远程执行。
https://tanzu.vmware.com/security/cve-2022-22947
1.1 、影响版本
Spring Cloud Gateway 3.1.0
Spring Cloud Gateway 3.0.0 - 3.0.6
不受支持的版本也会受到影响
1.2、漏洞补丁
https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e
通过查看修复补丁以及对比原文件,此修复的地方是ShortcutConfigurable接口的getValue方法中的StandardEvaluationContext类,将此类换成了GatewayEvaluationContext类,而GatewayEvaluationContext类是自定义的且继承EvaluationContext接口GatewayEvaluationContext
类内部是对SimpleEvaluationContext类的一些调用封装等。
StandardEvaluationContext类:支持更全面的SpEL功能
SimpleEvaluationContext类:基本的SpEL功能,仅支持 SpEL 语言语法的一个子集,例如排除对 Java 类型、构造函数和 bean 引用。
2、漏洞分析
2.1、调用链
环境可以直接下载对应版本然后载入ide运行即可
https://github.com/spring-cloud/spring-cloud-gateway/tags
直接开始从漏洞修复的地方看,也就是ShortcutConfigurable接口的getValue方法
既然此处会被执行SpEL表达式,那就可以查看都有哪些地方对此进行调用,可以选中getValue
方法并且ctrl+alt+h即可查看被调用情况,可以看到有三个枚举常数对getValue方法进行调用并且都在ShortcutConfigurable接口中,而且三处都对normalize方法进行重写
继续向上追踪,查看哪些地方对normalize方法进行了调用,这次进入到了ConfigurationService类的内部类ConfigurableBuilder中的normalizeProperties方法,而normalizeProperties方法调用了normalize方法
ConfigurationService类的内部类AbstractBuilder中的bind方法调用了normalizeProperties方法
而bind方法被五处地方所引用:
AbstractRateLimiter类中的onApplicationEvent方法
RouteDefinitionRouteLocator类中的loadGatewayFilters方法与lookup方法
WeightCalculatorWebFilter类中的handle方法
BetweenRoutePredicateFactoryTests类中的bindConfig方法
本次主要关注loadGatewayFilters方法,loadGatewayFilters方法继续向前路径为:loadGatewayFilters() -> getFilters() -> convertToRoute() -> getRoutes()
整个的调用链为
getValue:273, Spelexpression (org.springframework.expression.spel.standard)
getValue:60, ShortcutConfigurable (org.springframework.cloud.gateway.support)
normalize:94, ShortcutConfigurable$ShortcutType$1 (org.springframework.cloud.gateway.support)
normalizeProperties:140, ConfigurationService$ConfigurableBuilder (org.springframework.cloud.gateway.support)
bind:241, ConfigurationService$AbstractBuilder (org.springframework.cloud.gateway.support)
loadGatewayFilters:144, RouteDefinitionRouteLocator (org.springframework.cloud.gateway.route)
getFilters:176, RouteDefinitionRouteLocator (org.springframework.cloud.gateway.route)
convertToRoute:117, RouteDefinitionRouteLocator (org.springframework.cloud.gateway.route)
...
2.2、分析
首先getRoutes()调用convertToRoute方法并把参数routeDefinition传入,routeDefinition参数就是自定义的路由,而RouteDefinition的内容就是路由的语法格式
可以从toString方法看到数据字段的情况,而且filters字段是要求是ArrayList类型
之后定义了一个List<GatewayFilter>类型的gatewayFilters变量,其中GatewayFilter继承ShortcutConfigurable接口
之后调用getFilters方法并且传入自定义路由
首先判断是否有默认的filters,系统默认会有,所以(!this.gatewayProperties.getDefaultFilters().isEmpty())返回true,调用loadGatewayFilters设置默认的filters,(!routeDefinition.getFilters().isEmpty())结果也是true,调用loadGatewayFilters方法传入id以及filters数组
传入到loadGatewayFilters方法后filters就成为了filterDefinitions,首先获取filterDefinitions的大小然后开启循环,将filterDefinitions中的数据放入definition,获取definition.getName放入factory,然后判断factory是否是null,如果是就抛出异常,提示"Unable to find GatewayFilterFactory with name {filter}",不是的话判断logger是否已开始debug模式,然后打印日志"RouteDefinition {id} applying filter {args} to {filter}"
定义变量configuration,通过加载definition参数,也就是传入的自定义路由,创建配置服务然后调用bind方法
bind方法处主要是一些判断以及提示,判断configurationService的一些内容字段,比如configurable
是否为null,name等是否为null以及是否是空白等,(this.normalizedProperties == null) 为true,调用normalizeProperties方法
(this.service.beanFactory != null) 为true,调用normalize方法传入若干参数
以args作为参数开启循环,args是Map<String, String>类型,然后取出key与value,其中key要求不能是"_genkey_"开头,否则就报错"Must not instantiate utility class."以及其他几个并列要求,然后调用getValue方法
将entryValue赋值给rawValue,entryValue中的数据就是要执行的恶意代码,调用trim去除前后空格,判断rawValue是否为null以及是否"#{"开头和"}"结尾,设置context变量,将beanFactory
放入context.BeanResolver
调用parseexpression方法,对字符串做处理,最终把恶意字符串开头的"#{"和结尾的"}"去掉
expression调用getValue方法,进入Spelexpression.class,判断context是否为空,获取compiledAst赋值给compiledAst其值为null,if语句为假没进入逻辑,调用expressionState方法,设置rootobject
再次调用getValue方法,得到想要的结果
互联网上的利用代码大致相同,主要区别在于filters的name字段,根据上面的分析可以看出是对参数有各种判定以及要求的,那么在创建路由时也可能会进行判断或者说在那里能看到对name字段的对比判断
这里可以利用ide下方的Actoator中的映射功能查看创建路由对应的方法,这里就是AbstractGatewayControllerEndpoint.java中的save方法,可以直接在这里下断点
首先对传入的路由做格式化检查,然后调用validateRouteDefinition方法做内容检查
validateRouteDefinition方法又调用isAvailable方法,其中对比恶意路由中自定义的filters.name是否在GatewayFilters.name中
GatewayFilters.name一共29个
AddRequestHeader
MapRequestHeader
AddRequestParameter
AddResponseHeader
ModifyRequestBody
DedupeResponseHeader
ModifyResponseBody
CacheRequestBody
PrefixPath
PreserveHostHeader
RedirectTo
RemoveRequestHeader
RemoveRequestParameter
RemoveResponseHeader
RewritePath
Retry
SetPath
SecureHeaders
SetRequestHeader
SetRequestHostHeader
SetResponseHeader
RewriteResponseHeader
RewriteLocationResponseHeader
SetStatus
SaveSession
StripPrefix
RequestHeaderToRequestUri
RequestSize
RequestHeaderSize
之后两行语句还是进行对比,对比Predicates.name,漏洞原作者的自定义路由里对其进行了定义,没有这个也可以利用,调用isAvailable方法,对比Predicates.name是否在routePredicates.name中
routePredicates.name一共13个
After
Before
Between
Cookie
Header
Host
Method
Path
Query
ReadBody
RemoteAddr
Weight
CloudFoundryRouteService
之后的if语句将判断返回值,如果自定义的name与系统要求的name不一致那么就返回报错信息
3、修复
3.1.x升级到3.1.1+
3.0.x升级到3.0.7+
不需要Gateway actuator endpoint的话可以management.endpoint.gateway.enabled: false 禁用它
参考:
https://wya.pl/2022/02/26/cve-2022-22947-spel-casting-and-evil-beans/
https://tanzu.vmware.com/security/cve-2022-22947
本文为白帽汇原创文章,如需转载请注明来源:https://nosec.org/home/detail/5008.html
最新评论