- 类别:
jndi别名:(无) 优先级:(未设定) - 作者:(未署名)
- 依赖:
tomcat(提供org.apache.naming.factory.BeanFactory与org.apache.naming.ResourceRef)、org.codehaus.groovy:groovy(提供org.apache.groovy.util.SystemUtil) - 实现:
Gadget(非抽象,直接可用)
构造一个指向 Tomcat BeanFactory 的 JNDI ResourceRef:当受害端对该 Reference 发起 JNDI lookup 时,BeanFactory 会实例化 bean 类 org.apache.groovy.util.SystemUtil,并借助 forceString 机制以单 String 入参反射调用其 setSystemPropertyFrom(String) 方法,从而在目标 JVM 内任意修改一条系统属性(key=value)。典型用途是把 com.sun.jndi.ldap.object.trustURLCodebase=true 写入系统属性,为后续 JNDI 远程加载类重新打开缺口——是一个纯本地 classpath(无需拉取远程文件)的 JNDI 防护绕过 / 环境预置构件,位于链尾(END)。
- 入口
tags:ResourceRef、TomcatBeanFactory、ENDResourceRef/TomcatBeanFactory:表明本 gadget 的产物是一个org.apache.naming.ResourceRef、且 factory 为 Tomcat 的BeanFactory,需由「能投递 JNDI Reference / ResourceRef」的上游承接(即上游 gadget 的nextTags含ResourceRef)。END:本 gadget 自身产出最终 payload 对象(Reference),是链尾 sink——invoke未调用chain.doCreate(context),其内不再有更内层对象。
- 衔接
nextTags:(空)——链尾,不再向下衔接。 excludes:(空)。
说明:
tags在本知识库语义中是「入口标签」,表示本环能被哪类上游承接。能产出该入口(nextTags含ResourceRef)的上游投递器包括RefWrap、DirContextJndi、SwingLazyValueJarWrite、XBean/XBeanToString、C3p0_C3p0Reference/MchangeC3p0Reference等 Reference/ResourceRef 载体;本 gadget 通常作为它们背后的最终 sink。
全类仅 getObject 一个核心方法,invoke 直接转发(无内层对象)。以下为 中 CFR 反编译原文的核心片段:
@GadgetTags(tags={"ResourceRef", "TomcatBeanFactory", "END"})
@GadgetAnnotation(name="groovy 修改系统属性",
description="factory: org.apache.naming.factory.BeanFactory\n通过BeanFactory调用单String方法:org.apache.groovy.util.SystemUtil#setSystemProperties(),实现修改系统属性",
dependencies={"tomcat", "org.codehaus.groovy:groovy"})
public class SystemConfigurationRef3 implements Gadget {
@Param(name="键值对", description="eg: com.sun.jndi.ldap.object.trustURLCodebase=true")
public String kv = "com.sun.jndi.ldap.object.trustURLCodebase=true";
public Reference getObject() throws Exception {
ResourceRef ref = new ResourceRef(
"org.apache.groovy.util.SystemUtil", // bean 类名 = 要实例化的目标类
null, "", "", true,
"org.apache.naming.factory.BeanFactory", null); // objectFactory = Tomcat BeanFactory
ref.add((RefAddr)new StringRefAddr("forceString", "x=setSystemPropertyFrom"));
ref.add((RefAddr)new StringRefAddr("x", this.kv));
return ref;
}
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
return this.getObject();
}
}逐段解释:
-
new ResourceRef("org.apache.groovy.util.SystemUtil", ..., "org.apache.naming.factory.BeanFactory", null)构造一个Reference:className = org.apache.groovy.util.SystemUtil(BeanFactory 将要加载并实例化的 bean),factoryClassName = org.apache.naming.factory.BeanFactory(Tomcat 内置的对象工厂)。JNDI 解析该 Reference 时会先加载并调用BeanFactory.getObjectInstance()。注意 bean 类来自目标本地 classpath(groovy),并非远程下载,因此不受trustURLCodebase限制——这正是绕过点。 -
StringRefAddr("forceString", "x=setSystemPropertyFrom")这是本 gadget 的关键。TomcatBeanFactory.getObjectInstance()会读取名为forceString的 RefAddr,将其解析为属性名=方法名的映射(逗号分隔)。默认情况下 BeanFactory 对属性x会调用 JavaBean 规范的 settersetX(String);一旦在forceString中声明x=setSystemPropertyFrom,BeanFactory 便改为反射查找签名为单个String参数的方法setSystemPropertyFrom(String),并以属性x的字符串值作为实参调用。这就把「设置一个 bean 属性」偷换成「调用目标类上任意void method(String)」。 -
StringRefAddr("x", this.kv)为属性x提供实参值kv(默认com.sun.jndi.ldap.object.trustURLCodebase=true)。最终等价于在受害 JVM 内执行:org.apache.groovy.util.SystemUtil.setSystemPropertyFrom("com.sun.jndi.ldap.object.trustURLCodebase=true")。 Groovy 的SystemUtil.setSystemPropertyFrom(String nameValue)会以=拆分字符串并调用System.setProperty(name, value),于是目标系统属性被改写。 -
invoke未调用chain.doCreate(context),而是直接return this.getObject();,印证END语义:本 gadget 自产终态对象、无内层,参数完全由自身@Param kv决定。
与 @GadgetAnnotation.description 的差异(如实标注):description 写的是「调用 SystemUtil#setSystemProperties()」,但源码里 forceString 实际映射到的方法名是 setSystemPropertyFrom(org.apache.groovy.util.SystemUtil 并无 setSystemProperties)。以源码为准:真正被反射调用的是 setSystemPropertyFrom(String),且入参是内联的 key=value 而非远程文件 URL。
三者结构一致(同一 ResourceRef + BeanFactory + forceString 骨架),差异仅在 bean 类、被调方法与入参语义:
| 构件 | bean 类 | forceString 映射方法 | 入参 x 语义 |
额外依赖 |
|---|---|---|---|---|
| SystemConfigurationRef | org.apache.commons.configuration.SystemConfiguration |
setSystemProperties |
远程 .properties 文件 URL(需 HTTP 拉取) |
commons-configuration |
| SystemConfigurationRef2 | org.apache.commons.configuration2.SystemConfiguration |
setSystemProperties |
远程 .properties 文件 URL(需 HTTP 拉取) |
commons-configuration2 |
| SystemConfigurationRef3 | org.apache.groovy.util.SystemUtil |
setSystemPropertyFrom |
直接的 key=value 字符串(无需远程文件) |
groovy |
Ref/Ref2 需目标先向攻击者 HTTP 拉取 properties 文件才能落地属性;Ref3 把键值对直接内联在 kv 里,无外连、更隐蔽、更可靠,代价是要求目标 classpath 上存在 groovy。
| 字段 | 名称 | 说明 | 类型 | 默认值 | 可选值 |
|---|---|---|---|---|---|
kv |
键值对 | 要写入的系统属性,格式 key=value;eg: com.sun.jndi.ldap.object.trustURLCodebase=true |
String |
com.sun.jndi.ldap.object.trustURLCodebase=true |
(无枚举,自由填写) |
- 运行前提(缺一不可):
- 目标 classpath 具备 Tomcat 的
org.apache.naming.factory.BeanFactory与org.apache.naming.ResourceRef(即使用 Tomcat/嵌入式 Tomcat,或单独引入tomcat-catalina)。 - 目标 classpath 具备 groovy,暴露
org.apache.groovy.util.SystemUtil.setSystemPropertyFrom(String)(该工具类存在于较新的 Groovy 版本中)。 - 存在可控的 JNDI 注入点(
InitialContext.lookup(attacker)或等价),使ResourceRef被解析。
- 目标 classpath 具备 Tomcat 的
- 原理要点:利用 Tomcat
BeanFactory的forceString特性把「本地已存在的类 + 单 String 方法」变成任意void m(String)调用;由于 bean 来自本地 classpath,整个过程不触发远程类加载,因此不受 JDK 8u191+ / 11+ 默认com.sun.jndi.ldap.object.trustURLCodebase=false、ldap/rmi远程 codebase 关闭等防护限制。 - 典型用法(两段式绕过):第一次 JNDI lookup 命中本 gadget,把
trustURLCodebase改回true;随后攻击者引导目标发起第二次 lookup,即可回到经典 LDAP/RMI 远程 factory 类加载路径完成 RCE。本 gadget 本身只做「环境预置」,不直接 RCE。 - 局限:属性修改只在当前 JVM 生命周期内生效;若目标在启动早期已缓存该属性(部分 JDK 会缓存
trustURLCodebase判定),改写可能失效——是否成功取决于目标 JDK/版本对该属性的读取时机。
usedInChains:(空)。自由构件,未直接出现在 51 条预设链中;作为链尾 sink(END),可由任意nextTags含ResourceRef的 JNDI Reference 投递器组合使用。
- 同族兄弟:SystemConfigurationRef、SystemConfigurationRef2(commons-configuration 版,走远程 properties 文件)。
- 同为 Tomcat BeanFactory +
forceString家族(ResourceRef/TomcatBeanFactory入口):H2CreateDirRef、VelocityCreateDirRef、LoadNativeLibRef、PropertiesConfigurationRef、INIConfigurationReadFileRef、MemoryUserDatabaseFactoryWriteFile 等。 - 相关 JNDI 绕过构件:BypassRef、ResourceFactoryBypass。
- 上游投递器(
nextTags=ResourceRef):RefWrap、DirContextJndi、XBean/XBeanToString、C3p0_C3p0Reference/MchangeC3p0Reference、SwingLazyValueJarWrite。
- 检测特征:
- JNDI/LDAP 响应或反序列化数据中出现
org.apache.naming.factory.BeanFactory作为 factory、且 RefAddr 含forceString键——这是 Tomcat BeanFactory 任意方法调用的强指纹。 - bean className 为
org.apache.groovy.util.SystemUtil,或 RefAddr 值中出现setSystemPropertyFrom、com.sun.jndi.ldap.object.trustURLCodebase=true等敏感串。 - 运行期监控
System.setProperty对安全相关键(com.sun.jndi.*.object.trustURLCodebase、com.sun.jndi.rmi.object.trustURLCodebase)的写入。
- JNDI/LDAP 响应或反序列化数据中出现
- 加固建议:
- 从源头禁止不可信 JNDI lookup(升级到 JDK 8u191+/11+ 并保持
trustURLCodebase=false;对 lookup 名称做白名单)。 - 避免应用与 Tomcat
naming组件、groovy 同处一个可被 JNDI 触及的 classpath;如非必要移除 groovy 或收敛其可达性。 - 在 WAF/IDS 层面对 LDAP/RMI 响应中的
BeanFactory+forceString组合告警拦截。 - 对
com.sun.jndi.*.object.trustURLCodebase等属性做启动期固定并禁止运行期改写(如通过 SecurityManager/自定义策略或早读缓存)。
- 从源头禁止不可信 JNDI lookup(升级到 JDK 8u191+/11+ 并保持