Skip to content

Latest commit

 

History

History
102 lines (89 loc) · 8.8 KB

File metadata and controls

102 lines (89 loc) · 8.8 KB

PostgreSqlDataSource PostgreSQL JDBC URL Attack(PostgreSqlDataSource)

  • 类别javanative 别名:— 优先级:—
  • 作者:—
  • 依赖org.postgresql:postgresql
  • 实现Gadget(直接实现接口,无基类)

作用

把一个恶意 PostgreSQL JDBC URL 装进 org.postgresql.ds.PGConnectionPoolDataSource。当上游 gadget 反序列化时触发本对象的 getConnection()(一个 getter)时,PostgreSQL 驱动会携该 URL 发起「数据库连接」,从而进入 PostgreSQL JDBC 利用链(socketFactory 参数触发 ClassPathXmlApplicationContext 加载远程 XML,即 CVE-2022-21724)。本类是「getter 触发 → PostgreSQL JDBC URL sink」之间的中间跳板,本身不产出 URL、也不直接执行代码。

链接标签

  • 入口 tagsDataSourceChainsGetter
    • Getter —— 承接上一环「能反射调用任意 getter」的 gadget(如 CommonsBeanutils1BeanComparator,或各类 EqualsBean/ToStringBean),由上游负责调用本对象的 getConnection
    • DataSourceChains —— 标识其属于「JDBC DataSource 跳板」这一族,供 UI/组合器归类。
  • 衔接 nextTagsPostgreSQLJdbcUrl —— 之后需接一个产出「PostgreSQL 恶意 JDBC URL 字符串」的 gadget(如 PostgreSqlJdbc,其 tags 正是 PostgreSQLJdbcUrl)。
  • excludes:无。

源码剖析

本类直接 implements Gadget,无基类,逻辑全部内联。两个私有常量记录了「触发方式」,供人阅读,运行时以 invoke 注入的 context 为准:

private final String getterMethodName = "getConnection";
private final String getterPropertyName = "connection";

getObject —— 构造恶意数据源对象

public Object getObject(String jdbcUrl) throws Exception {
    try {
        CtClass ctClass1 = ClassPool.getDefault().get("org.postgresql.ds.common.BaseDataSource");
        CtMethod writeReplace1 = ctClass1.getDeclaredMethod("getURL");
        ctClass1.removeMethod(writeReplace1);
        ctClass1.toClass();
    } catch (Exception ctClass1) {
        // empty catch block
    }
    PGConnectionPoolDataSource dataSource = new PGConnectionPoolDataSource();
    dataSource.setURL(jdbcUrl);
    return dataSource;
}
  • 先用 Javassist(javassist.ClassPool)从 PGConnectionPoolDataSource 的父类 org.postgresql.ds.common.BaseDataSource删除 getURL() 方法,再 toClass() 把改写后的类重新定义/载入。局部变量名 writeReplace1 是 CFR 反编译器的机械命名,与 writeReplace 序列化钩子无关——实际被取出并删除的方法就是 getURL
  • 删除 getURL 的原因:新版 BaseDataSource.getURL() 会依据 serverName/portNumber/databaseName 等已解析属性重新拼装 URL,从而丢弃 setURL 传入的原始查询串(尤其是 socketFactory / socketFactoryArg 这类攻击关键参数)。删掉 getURL 后,连接阶段读取 URL 时回落到 setURL 存下的原始字段,保证攻击者构造的完整恶意 URL 原样生效。
  • catch 为空块:若目标环境不存在该方法、已被删除或 Javassist 缺失,则静默跳过,不影响后续流程(低版本驱动无此拼装行为时本就不需要删除)。
  • 最后 new PGConnectionPoolDataSource()setURL(jdbcUrl),返回这个可序列化的数据源对象作为本环产物。

invoke —— 内向外包装并注入 context

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    String jdbcUrl = (String)chain.doCreate(context);
    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(jdbcUrl);
}
  • chain.doCreate(context) 先构造内层对象——即下游 PostgreSQLJdbcUrl gadget(如 PostgreSqlJdbc)产出的恶意 JDBC URL 字符串,强转为 String
  • 随后向 GadgetContext 注入三条元信息,供上游 getter 触发器读取,实现「跨环解耦」:
    • SUPER_CLASS_NAME_KEY = DataSource.class:告诉上游本包装对象应以 javax.sql.DataSource 类型看待(用于类型校验/强转)。
    • SPECIAL_METHOD_NAME_KEY = "getConnection":上游需要触发的方法名。
    • GETTER_PARAM_NAME_KEY = "connection":对应的属性名,供 CommonsBeanutils1 这类以 property 驱动的比较器读取——PropertyUtils.getProperty(obj, "connection") 会解析为 getConnection()(对照 CommonsBeanutils1property 由本 KEY 注入的机制)。
  • return this.getObject(jdbcUrl):把 URL 包进数据源对象返回给上游继续包装。

触发路径

反序列化时的完整触发链(以 CB1 为上游为例):

PriorityQueue.readObject → BeanComparator.compare
  → PropertyUtils.getProperty(PGConnectionPoolDataSource, "connection")
  → PGConnectionPoolDataSource.getConnection()   (继承自 BaseDataSource)
  → PostgreSQL 驱动解析恶意 jdbcUrl(socketFactory=ClassPathXmlApplicationContext)
  → 加载远程 spring-bean.xml → RCE(CVE-2022-21724)

参数(@Param)

本类无 @Paramparams 为空)。恶意 URL 完全由下游 PostgreSQLJdbcUrl gadget(如 PostgreSqlJdbcurl 参数)决定;getterMethodName/getterPropertyName 为写死的私有常量,不可配置。

适用版本与原理要点

  • 依赖 org.postgresql:postgresqlPGConnectionPoolDataSourceBaseDataSource 均在该驱动内)。本跳板本身对版本不敏感,实际可利用范围由下游 JDBC gadget 决定PostgreSqlJdbc 声明命中 9.4.1208 <= org.postgresql:postgresql < 42.2.2542.3.0 <= org.postgresql:postgresql < 42.3.2(CVE-2022-21724,socketFactory 参数注入)。
  • 关键坑点:需删除 BaseDataSource.getURL() 才能让 setURL 的完整恶意 URL 不被属性拼装覆盖;该动作依赖 Javassist 在运行时改写并重载类,因此攻击环境需能加载 javassist.*(java-chains 生成器侧具备,目标侧无需)。
  • 触发依赖上游能反射调用任意 getter(getConnection),故常与 CB/Rome 等「Getter」触发器组合;对 JDK 无 TemplatesImpl 式的高版本限制(不走 defineClass,而走 JDBC socketFactory)。
  • 与同族 PostgreSqlDataSource2 的唯一差异:本类用 PGConnectionPoolDataSource(连接池数据源),后者用 PGSimpleDataSource;二者 getConnection 均继承自同一 BaseDataSource,getURL 处理与 context 注入完全一致。选用不同载体可用于绕过针对某一具体类名的黑名单。

所属预设链

自由构件,未直接出现在预设链中(usedInChains 为空)。可按标签自行组合,典型三环: [Getter 触发器, PostgreSqlDataSource, PostgreSqlJdbc],例如 [CommonsBeanutils1, PostgreSqlDataSource, PostgreSqlJdbc]

关联

  • 上游(承接 Getter):CommonsBeanutils1 及各类 EqualsBean/ToStringBean getter 触发器。
  • 下游(nextTags=PostgreSQLJdbcUrl):PostgreSqlJdbc(CVE-2022-21724,产出 socketFactory=ClassPathXmlApplicationContext 的加载 URL)、PostgreSqlJdbc4Jndi(JNDI 模块专用,自动缓存 spring-bean.xml)。
  • 同族:PostgreSqlDataSource2PGSimpleDataSource 版跳板)、MysqlDataSourceDataSourceChains 系列。

防御与检测

  • 检测特征
    • 反序列化流中出现 org.postgresql.ds.PGConnectionPoolDataSource / org.postgresql.ds.common.BaseDataSource 类名指纹(连同 serverName/url 字段)。
    • JDBC 连接 URL 中出现 socketFactory= 且值为 org.springframework.context.support.ClassPathXmlApplicationContext,或 socketFactoryArg 指向 http:///file:// 外部 XML——高危信号。
    • 运行期出现对 BaseDataSource 的 Javassist 重定义(toClass)、或应用无业务缘由地建立到 127.0.0.1:5432 之外可疑地址的 PostgreSQL 连接。
  • 加固建议
    • 升级 org.postgresql:postgresql42.2.25+ / 42.3.2+(修复 CVE-2022-21724,限制 socketFactory 等可实例化任意类的连接参数)。
    • 反序列化入口启用类白名单(JEP 290 ObjectInputFilter),拒绝 org.postgresql.ds.*javax.sql.DataSource 实现及常见 getter 触发器(org.apache.commons.beanutils.BeanComparatorjava.util.PriorityQueue 组合等)。
    • 出站网络管控:禁止应用服务器主动发起到非受信主机的数据库/HTTP 连接,切断 socketFactoryArg 拉取远程 XML 的路径。
    • 移除不必要的 javassistspring-context 于运行时 classpath,压缩可被复用的 sink 面。