PHPCMS 2008遠端程式碼執行
這個漏洞的起因是阿里雲抓到的黑產樣本,前臺RCE的漏洞,但是PHPCMS 2008這個版本有點老,所以這個漏洞可以用來學習。
阿里雲安全於11月5日捕獲到該漏洞的多個利用樣本,分析後因未聯絡上PHPCMS官方,已報告給國家資訊安全漏洞共享平臺,且在cve公共漏洞庫中編號為CVE-2018-19127。
0x02 分析
首先先看看捕獲到的payload
/type.php?template=tag_(){};@unlink(_FILE_);assert($_POST[1]);{//../rss
看到這個payload,那麼漏洞觸發點應該在 type.php 檔案中,話不多說,先看看程式碼吧。
這裡的漏洞觸發點實際是 第20行 的程式碼。
include template('phpcms', $template);
根據 payload 也就是說需要這裡的 $template 變數可控。但是實際上程式碼 第6行 已經將 $template 變數賦值了一個初始值 type ,也就是說這個漏洞實際上是繞過了這個地方的變數賦值。
我們先看為啥會繞過變數賦值,看到開頭包含了一個 /include/common.inc.php ,我尋思應該繞過部分的問題出在了這個檔案裡,跟進一下。在 /include/common.inc.php:58 找到了問題所在,我們來看一下
if($_REQUEST) { if(MAGIC_QUOTES_GPC) { $_REQUEST = new_stripslashes($_REQUEST); if($_COOKIE) $_COOKIE = new_stripslashes($_COOKIE); extract($db->escape($_REQUEST), EXTR_SKIP); } else { $_POST = $db->escape($_POST); $_GET = $db->escape($_GET); $_COOKIE = $db->escape($_COOKIE); @extract($_POST,EXTR_SKIP); @extract($_GET,EXTR_SKIP); @extract($_COOKIE,EXTR_SKIP); }
這部分程式碼的主要作用是接收到請求的時候判斷是否開啟了 MAGIC_QUOTES_GPC 方法,沒有的話,針對 $_GET 、 $_POST 、 $_COOKIE 等陣列進行處理,然後使用 extract 函式,這個函式的作用就是用來註冊變數,但是 EXTR_SKIP 作用是如果有衝突,不覆蓋已有的變數。而我們知道針對 $template 實現判斷它是否不為空,如果為空才賦值給它一個初始值 type ,也就是說我們可以註冊 $template 變數的值,來讓它不為空,從而繞過
if(empty($template)) $template = 'type';
好了 $template 變數可控的原理找到了之後,我們繼續看看漏洞觸發原因。
這裡我們跟進一下 template 函式,相關函數出現在 include/global.func.php:772 ,我們看一下實際程式碼。
我們看到 第二行 存在一個常量 TPL_CACHEPATH ,跟進一下這個常量的定義,相關位置在 include/config.inc.php:56 。也就是說這個常量的實際作用是定義了模版的快取物理路徑。
然後我們繼續往下看, 第4行 if判斷中也有一個常量 TPL_REFRESH ,跟進一下這個常量定義,相關位置在 include/config.inc.php:57 。這個常量用來判斷模版快取是否自動更新,預設值是1。
然後實際上這個 if判斷 裡剩下的內容就是判斷檔案是否存在,以及一些修改時間的判斷。這裡如果這個if語句做的是邏輯的與運算,換句話來說if判斷中的 兩個條件必須都成立 的情況下才能進入到這個迴圈體中進行相關操作。換句話來說,也就是該系統不能將模版快取自動更新功能關閉,恰巧該功能是預設值為1。
那我們繼續往下看,看看這個if條件達成之後,會繼續做什麼操作,相關程式碼如下:
require_once PHPCMS_ROOT.'include/template.func.php'; template_compile($module, $template, $istag);
這裡如果進入if語句之後,會先包含 include/template.func.php 這個檔案,在呼叫 template_compile 函式。繼續跟進 template_compile 函式,相關函式位置在 include/template.func.php:2
這裡我們看到了一個很直觀的函式 file_put_contents ,這個函式經常會導致任意檔案寫入的漏洞。也就是說如果 第13行 程式碼中的 $content 可控的情況下就會導致任意檔案寫入的問題。
$content = ($istag || substr($template, 0, 4) == 'tag_') ? '<?php function _tag_'.$module.'_'.$template.'($data, $number, $rows, $count, $page, $pages, $setting){ global $PHPCMS,$MODULE,$M,$CATEGORY,$TYPE,$AREA,$GROUP,$MODEL,$templateid,$_userid,$_username;@extract($setting);?>'.template_parse($content, 1).'<?php } ?>' : template_parse($content);
這裡進行了邏輯與運算,原來 $istag 初始化了為0,也就是 substr($template, 0, 4) == ‘tag_’ 的結果必須為true。
$content = ($istag || substr($template, 0, 4) == 'tag_')
要使得 substr($template, 0, 4) == ‘tag_’ 的結果必須為true,很簡單,只需要 $template 變數中以 tag_ 開頭。
我們回過頭梳理一下過程。
0x03 動態分析
這裡可以看到傳入 template=tag_(){};@unlink(FILE);assert($_POST[1]);{//../rss ,這裡的 $template 通過變數註冊已經不會再賦值初始值 type 了。
我們看到 $template 已經傳入到漏洞觸發點了。
然後我們可以看到這裡的 $compiledtplfile 變數通過賦值 payload 實際上是判斷了 data/cache_template/rss.tpl.php 檔案是否存在。而實際上確實該檔案不存在,所以這裡的if判斷過去了,並且成功將 $template 帶入到 template_compile 函式中了。
這裡我們可以看到 template_compile 函式中利用payload成功經過了 $content 變數的判斷,並且將 $template 的內容寫到了 data/cache_template/rss.tpl.php 檔案中。
0x04 修復建議
臨時最簡單直接的修復建議就是把 TPL_REFRESH 的定義改成 0 。當然這樣修改之後會導致功能無法使用。
當然也可以更新到最新。
0x05 後記
由於這個漏洞環境因為mysql版本的問題,他預設資料檔案是的type=,需要替換成ENGINE=,這樣才能用。
我已經修改好了一稿,但是還是有點問題,有需要的時候安裝的時候可以不要勾選下圖這個廣告模組資料,一樣可以用來複現。不得不說,務實派的黑產感覺永遠走在技術應用的最前端。