- 类别:bytecode 别名:— 优先级:10
- 作者:Pen4uin
- 实现:
Gadget(直接实现,无基类) - 依赖:内置 java-echo-generator(Jeg v1.0.0,2024-12-06)、
slf4j。@GadgetAnnotation.dependencies为空——Jeg SDK 已随平台打包,运行期由本类以反射方式驱动。
调用内置的 Jeg(java-echo-generator)SDK,按用户选择的「目标中间件(Tomcat/SpringMVC/Jetty/…)× 模式(命令执行 / 字节码加载)」现场生成一段回显马字节码(byte[]),作为链尾字节码 sink(END)。目标 JVM 加载该类后,会从当前线程正在处理的 HTTP 请求中读取指定请求头,执行命令(或加载字节码),并把结果写回指定响应头/响应体,实现无需外连的同步回显命令执行。它与 JettyEcho、OneForAllEcho、ResinEcho 等回显 sink 同族,但依托 Jeg 框架支持多中间件、支持加密传参。
- 入口
tags:Bytecode、Echo、ENDBytecode:产物是字节码byte[],需由上游「字节码装载器」加载执行。Echo:标识其为「回显」类载荷(结果直接写回响应,区别于外连型DNSLog/ReverseShell)。END:链终点,其后不再接 gadget(nextTags为空)。
- 衔接
nextTags:空。 excludes:无。
由上游「字节码装载器」承接:回显类 byte[] 需被目标加载并执行,通常经 BytecodeConvert 装入 TemplatesImpl(_bytecodes → defineClass → newTransformer 触发实例化)等载体,再由某条反序列化链把该 TemplatesImpl 送入 sink getter。也可将生成结果手工导出后经 BytecodeFromBase64 复用。
本类不含反序列化触发逻辑,职责单一:把 5 个 @Param 灌进 Jeg 的 jEGConfig,驱动 jEGenerator 产出回显字节码。
public byte[] getObject() throws Exception {
jEGConfig config = new jEGConfig(){
{
this.setServerType(JegGadget.this.serverType); // 目标中间件
this.setReqParamName(JegGadget.this.param); // 代码加载模式的参数名
this.setModelType(JegGadget.this.model); // Command / Code
this.setReqHeaderName(JegGadget.this.header); // 命令/代码从哪个请求头读取
this.setRespHeaderName(JegGadget.this.respHeader); // 结果写回哪个响应头
this.setFormatType("1234"); // 固定:回显编码/包装格式
this.build(); // 组装模板
}
};
jEGenerator generator = null;
Class<JegGadget> clazz = JegGadget.class;
synchronized (JegGadget.class) {
try {
generator = new jEGenerator(config); // 现场生成回显类
}
catch (Throwable e) {
log.error(e.getMessage());
ThrowsUtil.throwGadgetException(e.getMessage()); // 生成失败 → 抛 GadgetException
}
log.info("Jeg request header name: " + config.getReqHeaderName());
byte[] bytes = (byte[])Reflections.getFieldValue(generator, "clazzBytes"); // 反射取字节码
return bytes;
}
}
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
chain.doCreate(context); // 触发内层链构建,但丢弃返回值
return this.getObject();
}逐段解释:
- 匿名
jEGConfig+ 实例初始化块:源码用new jEGConfig(){ { ... } }的双花括号写法,在实例初始化块里连续调用 setter,把 5 个字段和固定的formatType="1234"写入配置后立刻build()。formatType="1234"是写死的——对应 description 中解密辅助里那组 JPEG 幻数包装标记(/9j/4A开头、/9k==结尾),即回显结果会被伪装成 JPEG 字节流的 Base64 塞进响应,绕过部分内容检测。 model(modelType)两种模式:Command直接执行请求头里的命令并回显;Code则把请求头当作字节码/代码加载点,此时param(reqParamName)指定承载代码的请求参数名——即 description 所述「适用于代码加载的场景」。synchronized (JegGadget.class):Jeg SDK 通过generator内部状态产出,整段生成包在类锁里以串行化,避免并发生成互相污染(与同族 JmgGadget 用synchronized (JmgGadget.class)同理)。注意反编译产物里Class<JegGadget> clazz = JegGadget.class;是无用局部变量、// ** MonitorExit是 CFR 对 monitorexit 字节码的残留标注,均不影响逻辑。- 反射取产物:
Reflections.getFieldValue(generator, "clazzBytes")直接抠出jEGenerator的私有字段clazzBytes作为最终byte[]——这是本类唯一的「反射点」,也是它不依赖 Jeg 公开 API、只依赖其内部字段名的原因(Jeg 版本升级若改名clazzBytes会失效,故平台锁定内置 v1.0.0)。 - 失败处理:
new jEGenerator(config)抛任何Throwable(如不支持的serverType/model组合)都会被捕获并转成ThrowsUtil.throwGadgetException,向上以统一的 GadgetException 冒泡。 invoke丢弃内层结果:作为END叶子 sink,invoke仍调用chain.doCreate(context)走完内层链构建流程,但丢弃其返回值,随后独立返回getObject()的字节码。也就是说本 gadget 不「包裹」内层对象,产物完全自成一体——这是回显/字节码型 sink 的典型形态。
| 字段 | 名称 | 说明 | 默认 | 类型 | 可选值 |
|---|---|---|---|---|---|
serverType |
中间件类型 | 目标 Web 容器,决定回显模板取用哪套 Request/Response 反射路径 | Tomcat |
ParamType.Choice | Tomcat / SpringMVC / Jetty / Resin / WebLogic / Struts2 / Undertow / WebSphere / Unknown |
header |
请求header | 命令执行(或代码加载)的请求头名,攻击者在此头携带载荷 | X-Authorization |
— | 任意头名 |
param |
请求参数 | 仅 Code 模式生效:承载字节码/代码的请求参数名 |
data |
— | 任意参数名 |
respHeader |
响应header | 回显结果写回的响应头名 | Via |
— | 任意头名 |
model |
模式 | Command 命令执行 / Code 字节码加载 |
Command |
ParamType.Choice | Command / Code |
(默认值取自源码字段初始化,非 gadgets.json 中的 null——null 仅表示注解未显式声明 defaultValue。)
- 内置 Jeg v1.0.0(2024-12-06)。产物是回显类字节码,运行不依赖第三方库,但生成端锁定内置 SDK 版本(依赖私有字段名
clazzBytes)。 - 传参与回显两种用法(据 description):
- 明文:
X-Authorization: ls直接执行; - 加密:
X-Authorization: <前缀>.<xor+base64 命令>.<后缀>,配合 CyberChef 的XOR → To_Base64配方加密请求、From_Base64 → XOR配方解密响应,响应体外套 JPEG 幻数(/9j/4A … /9k==)伪装。
- 明文:
- 必须搭配上游字节码装载器:本类只产出
byte[],需 BytecodeConvert →TemplatesImpl之类载体在目标 JVM 内defineClass并触发。 - 依赖「当前线程能拿到 HTTP 请求/响应对象」:回显靠反射抓取中间件线程局部的 Request/Response,故仅在 Web 中间件的请求处理线程上下文中触发才有回显;非 Web 场景(纯 CLI 反序列化)无回显对象可写。
- 参数组合约束:
serverType与目标真实中间件不符会导致回显类反射失败(拿不到 Request/Response)而静默无回显;生成阶段的不支持组合则直接抛 GadgetException。 - 手工替代路径:description 明确可在原版 java-echo-generator 生成 Base64 字节码后,改用 BytecodeFromBase64 装载,效果等价。
usedInChains 为空——自由构件,未直接出现在 51 条预设链中。作为回显型命令执行 sink,可替换任意链尾的 Exec / ReverseShell 等 sink,把「盲打命令执行」升级为「同步回显命令执行」。
- 上游(吃
Bytecode的装载器):BytecodeConvert →TemplatesImpl等;或手工经 BytecodeFromBase64 复用。 - 同族回显 sink(
Echo标签):JettyEcho、OneForAllEcho、ResinEcho、GlassFishEcho、ApusicEcho。 - 同作者(Pen4uin)SDK 型 sink:JmgGadget(内存马注入器)、JmgCustomShellGadget、MemShellPartyGadget。
- 流量特征:
- 请求侧出现异常请求头承载命令,默认
X-Authorization(可改),值形如明文命令或<前缀>.<Base64>.<后缀>三段式;Code模式还会带自定义参数(默认data)。 - 响应侧出现非常规响应头回带数据,默认
Via(可改);响应体可能以 JPEG 幻数 Base64(/9j/4A…/9k==)包装的密文形式夹带回显。 - 上述头名均可自定义,检测应基于「反序列化点后新增可疑请求/响应头 + Base64/XOR 密文」的组合行为,而非固定头名。
- 请求侧出现异常请求头承载命令,默认
- 主机侧特征:目标进程在反序列化后动态
defineClass一个陌生类并从请求线程反射读取 Request/Response 对象;类字节码指纹可与 Jeg v1.0.0 模板比对。 - 加固建议:
- 反序列化入口做类白名单,切断
TemplatesImpl/BytecodeConvert这类字节码装载载体(参见 BytecodeConvert 条目的加固)。 - 目标 JVM 升级到不支持
TemplatesImpl.newTransformer触发链的版本(JDK 17+ 模块化限制),或以 SecurityManager/Agent 拦截运行期defineClass与对 Request/Response 的反射访问。 - WAF 侧对「反序列化响应中出现新响应头 + 高熵 Base64/图片幻数正文」建立关联告警,覆盖头名被改的情形。
- 反序列化入口做类白名单,切断
- 反编译源码:
- 上游项目:https://github.com/pen4uin/java-echo-generator
- 机器可读元数据:
index/gadgets.json