Skip to content

Latest commit

 

History

History
111 lines (96 loc) · 10.4 KB

File metadata and controls

111 lines (96 loc) · 10.4 KB

调用 Jeg 生成回显字节码(JegGadget)

  • 类别: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 请求中读取指定请求头,执行命令(或加载字节码),并把结果写回指定响应头/响应体,实现无需外连的同步回显命令执行。它与 JettyEchoOneForAllEchoResinEcho 等回显 sink 同族,但依托 Jeg 框架支持多中间件、支持加密传参。

链接标签

  • 入口 tagsBytecodeEchoEND
    • Bytecode:产物是字节码 byte[],需由上游「字节码装载器」加载执行。
    • Echo:标识其为「回显」类载荷(结果直接写回响应,区别于外连型 DNSLog/ReverseShell)。
    • END:链终点,其后不再接 gadget(nextTags 为空)。
  • 衔接 nextTags:空。
  • excludes:无。

由上游「字节码装载器」承接:回显类 byte[] 需被目标加载并执行,通常经 BytecodeConvert 装入 TemplatesImpl_bytecodesdefineClassnewTransformer 触发实例化)等载体,再由某条反序列化链把该 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 内部状态产出,整段生成包在类锁里以串行化,避免并发生成互相污染(与同族 JmgGadgetsynchronized (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 的典型形态。

参数(@Param)

字段 名称 说明 默认 类型 可选值
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[],需 BytecodeConvertTemplatesImpl 之类载体在目标 JVM 内 defineClass 并触发。
  • 依赖「当前线程能拿到 HTTP 请求/响应对象」:回显靠反射抓取中间件线程局部的 Request/Response,故仅在 Web 中间件的请求处理线程上下文中触发才有回显;非 Web 场景(纯 CLI 反序列化)无回显对象可写。
  • 参数组合约束serverType 与目标真实中间件不符会导致回显类反射失败(拿不到 Request/Response)而静默无回显;生成阶段的不支持组合则直接抛 GadgetException。
  • 手工替代路径:description 明确可在原版 java-echo-generator 生成 Base64 字节码后,改用 BytecodeFromBase64 装载,效果等价。

所属预设链

usedInChains 为空——自由构件,未直接出现在 51 条预设链中。作为回显型命令执行 sink,可替换任意链尾的 Exec / ReverseShell 等 sink,把「盲打命令执行」升级为「同步回显命令执行」。

关联

防御与检测

  • 流量特征
    • 请求侧出现异常请求头承载命令,默认 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/图片幻数正文」建立关联告警,覆盖头名被改的情形。

参考