日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

php excel中解析显示html代码_骑士cms从任意文件包含到远程代码执行漏洞分析

發(fā)布時(shí)間:2024/9/19 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 php excel中解析显示html代码_骑士cms从任意文件包含到远程代码执行漏洞分析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

前些日子,騎士cms 官方公布了一個(gè)系統(tǒng)緊急風(fēng)險(xiǎn)漏洞升級(jí)通知:騎士cms 6.0.48存在一處任意文件包含漏洞,利用該漏洞對(duì)payload文件進(jìn)行包含,即可造成遠(yuǎn)程代碼執(zhí)行漏洞。這篇文章將從漏洞公告分析開始,敘述一下筆者分析漏洞與構(gòu)造payload時(shí)遇到的有趣的事情。

漏洞情報(bào)

官方發(fā)布的系統(tǒng)緊急風(fēng)險(xiǎn)漏洞升級(jí)通知如下:

http://www.74cms.com/news/show-2497.html

從官方公布的信息來看,官方修復(fù)了兩個(gè)地方:

1、/Application/Common/Controller/BaseController.class.php

2、/ThinkPHP/Library/Think/View.class.php

從BaseController.class.php這處補(bǔ)丁來看:

筆者猜測(cè)漏洞多半出在了渲染簡(jiǎn)歷模板的assign_resume_tpl方法中。從補(bǔ)丁修復(fù)上來看,增添了如下代碼

$tpl_file = $view->parseTemplate($tpl);
if(!is_file($tpl_file)){
return false;
}

可以發(fā)現(xiàn)程序通過$view->parseTemplate對(duì)$tpl參數(shù)進(jìn)行處理,并對(duì)處理結(jié)果$tpl_file進(jìn)行is_file判斷

我們先跟入$view->parseTemplate看看

從上圖143行的結(jié)果來看,parseTemplate中也是先通過is_file判斷,然后將符合的結(jié)果返回。

如果此處傳入的$tpl變量是文件,那么這個(gè)文件可以順利的通過parseTemplate與assign_resume_tpl方法中的is_file判斷。回想一下,這是一個(gè)文件包含漏洞,成功利用的先前條件是惡意的文件得存在,然后被包含。這個(gè)漏洞多半是通過assign_resume_tpl方法的$tpl參數(shù)傳入一個(gè)真實(shí)存在的待包含的惡意文件,而補(bǔ)丁先通過parseTemplate方法內(nèi)的is_file判斷了一次這個(gè)惡意文件是否存在,接著又在assign_resume_tpl方法通過is_file方法判斷一次,成功的利用一定會(huì)使is_file為true。那assign_resume_tpl方法中增加的代碼是否有作用?又有著什么作用?

這個(gè)問題筆者將在文章最后介紹。

接下來從第二處View.class.php這處補(bǔ)丁來看:

補(bǔ)丁將fetch 方法中

if(!is_file($templateFile)) E(L('_TEMPLATE_NOT_EXIST_').':'.$templateFile);

代碼注釋替換為

if(!is_file($templateFile)) E(L('_TEMPLATE_NOT_EXIST_'));

在thinkphp中,E()函數(shù)是用來拋出異常處理的。可見這處的修改應(yīng)該是不想讓$templateFile變量值寫到日志log文件中。

單從這點(diǎn)來看,命令執(zhí)行所需的payload百分百是可以通過$templateFile變量寫到log文件里的,然后配合任意文件包含漏洞將這個(gè)log文件包含并執(zhí)行。

漏洞分析

通過對(duì)漏洞情報(bào)的分析,我們差不多知道了這個(gè)漏洞的來龍去脈:

  • 通過控制fetch 方法中$templateFile變量,將payload寫入log文件

  • 通過assign_resume_tpl方法包含這個(gè)存在payload的log文件

  • 首先我們拋開怎么把payload寫入log文件,先來看看文件包含漏洞怎么回事。

    經(jīng)過上文的猜測(cè),我們可以通過assign_resume_tpl方法包含任意文件。首先我們要看看怎么通過請(qǐng)求調(diào)用assign_resume_tpl方法

    如何訪問assign_resume_tpl方法

    assign_resume_tpl方法位于common模塊base控制器下。通過對(duì)Thinkphp路由的了解,assign_resume_tpl方法多半是用如下url進(jìn)行調(diào)用

    http://127.0.0.1//74cms/index.php?m=common&c=base&a=assign_resume_tpl

    但是實(shí)際上,程序拋出了個(gè)錯(cuò)誤

    這是為什么呢?經(jīng)過動(dòng)態(tài)調(diào)試發(fā)現(xiàn)一個(gè)有意思的事情:common模塊是并不能被直接調(diào)用的。原因如下:

    \ThinkPHP\Library\Think\Dispatcher.class.php中存在如下代碼

    從上圖代碼可見,因?yàn)槲覀僣ommon模塊位于MODULE_DENY_LIST中,因此不能直接通過m=common來調(diào)用common模塊。

    既然不能直接調(diào)用,看看有沒有其他的辦法調(diào)用common模塊base控制器下的assign_resume_tpl方法

    經(jīng)過研究發(fā)現(xiàn),幾乎所有其他的控制器,最終都繼承自common模塊的BaseController控制器

    我們拿Home模塊的AbcController控制器舉例,見下圖:

    AbcController 繼承FrontendController

    而FrontendController由繼承了BaseController

    因此可以通過get請(qǐng)求

    http://127.0.0.1/74cms/index.php?m=home&c=abc&a=assign_resume_tpl&variable=1&tpl=2

    來調(diào)用BaseController下的assign_resume_tpl,并將$variable=1、$tpl=2參數(shù)傳遞進(jìn)去

    同理,Home模塊下的IndexController控制器也是可以的,見下圖

    IndexController繼承FrontendController,從上文可知,FrontendController繼承BaseController。因此也可以通過get請(qǐng)求

    http://127.0.0.1/74cms/index.php?m=home&c=index&a=assign_resume_tpl&variable=1&tpl=2

    來訪問BaseController下的assign_resume_tpl并向該方法傳參

    我們后續(xù)分析就用

    http://127.0.0.1/74cms/index.php?m=home&c=index&a=assign_resume_tpl&variable=xxx&tpl=xxx

    這樣的形式調(diào)用assign_resume_tpl方法

    既然我們可以通過請(qǐng)求向存在漏洞的assign_resume_tpl方法傳參了,距離漏洞利用成功已經(jīng)不遠(yuǎn)了

    用測(cè)試文件觸發(fā)文件包含

    我們接下來”假裝”在后臺(tái)上傳一個(gè)payload,用assign_resume_tpl這個(gè)接口包含下試試

    筆者手動(dòng)在如下目錄里放了個(gè)test.html

    為什么這么放呢?因?yàn)楣P者在源代碼里看到如下代碼

    這里是74cms使用assign_resume_tpl調(diào)用word_resume.html的形式。因此筆者在測(cè)試時(shí)也在word_resume.html通目錄下放置了一個(gè)test.html,其內(nèi)容如下:

    構(gòu)造如下請(qǐng)求

    http://127.0.0.1/74cms/index.php?m=home&c=index&a=assign_resume_tpl&variable=1&tpl=Emailtpl/test

    請(qǐng)求將調(diào)用assign_resume_tpl方法。動(dòng)態(tài)調(diào)試過程如下:

    可見此時(shí)$tpl為Emailtpl/test,get請(qǐng)求中參數(shù)成功傳入了。

    我們來看一下fetch里怎么實(shí)現(xiàn)的

    程序會(huì)執(zhí)行到fetch方法中的Hook::listen('view_parse',$params);代碼處

    此處代碼很關(guān)鍵,需要詳細(xì)說明下。Hook::listen('view_parse',$params);這處代碼的作用大體上有兩個(gè):

  • Compiler:將模板文件經(jīng)過一定解析與編譯,生成緩存文件xxx.php

  • Load:通過include方法加載上一步生成的xxx.php緩存文件

  • 簡(jiǎn)而言之,Hook::listen('view_parse',$params);先通過Compiler將攻擊者傳入的模板文件編譯為一個(gè)緩存文件,隨后調(diào)用Load加載這個(gè)編譯好的緩存文件。

    首先我們來看下生產(chǎn)緩存文件過程

    Compiler

    從Hook::listen('view_parse',$params);到compiler方法的調(diào)用鏈如下:

    該方法會(huì)將thinkphp的html模板中定義的標(biāo)簽,解析成php代碼。例如模板中的”qscms:company_show/”

    就會(huì)被解析成

    除此之外,compiler方法還會(huì)將生成的xxx.php文件頭部加上一個(gè)如下代碼以防止該文件被直接執(zhí)行

    <?php if (!defined('THINK_PATH')) exit();

    說完compiler方法的功能后,我們來看下compiler方法是如何處理我們的test.html。

    test.html中的代碼為<?php phpinfo(); ?>,經(jīng)過解析之后,返回值見下圖

    上圖compiler方法最終返回的是strip_whitespace($tmplContent);

    但strip_whitespace方法的作用是去除代碼中的空白和注釋,對(duì)我們的payload沒什么實(shí)際意義。

    最終compiler方法返回值為

    <?php if (!defined('THINK_PATH')) exit(); phpinfo();?>

    這個(gè)值被寫入一個(gè)緩存文件,見下圖

    緩存文件位于data/Runtime/Cache/Home/8a848d32ad6f6040d5461bb8b5f65eb0.php

    到此為止,compiler流程已經(jīng)結(jié)束,我們接下來看看加載過程

    Load

    Load代碼如下圖所示

    從Hook::listen('view_parse',$params);到load方法的調(diào)用鏈如下:

    從第一張圖可見,load代碼最終會(huì)include 我們compiler流程中生產(chǎn)的那個(gè)data/Runtime/Cache/Home/8a848d32ad6f6040d5461bb8b5f65eb0.php緩存文件

    當(dāng)8a848d32ad6f6040d5461bb8b5f65eb0.php被include之后,其中的惡意代碼執(zhí)行,見下圖

    執(zhí)行成功后,瀏覽器如下

    等等,為什么沒有phpinfo的回顯呢?是不是我們phpinfo執(zhí)行失敗了?我們換一個(gè)payload試試,見下圖

    這次我們執(zhí)行一個(gè)生產(chǎn)目錄的命令

    可見命令執(zhí)行成功了。但是為什么phpinfo沒有回顯呢?

    phpinfo回顯哪去了

    從上文看,我們使用測(cè)試文件進(jìn)行包含利用成功了,但是phpinfo的回顯卻不見了。進(jìn)過研究發(fā)現(xiàn),原因還是在fetch方法里。在fetch中,注意看下圖紅框處代碼:

    Fetch中的load流程,即加載payload執(zhí)行phpinfo的過程在上圖126行處Hook::listen('view_parse',$params);代碼中完成的。

    而在此之前,程序通過ob_start打開緩沖區(qū),因此phpinfo輸出的信息被存儲(chǔ)于緩沖區(qū)內(nèi),而在Hook::listen代碼執(zhí)行之后,又通過ob_get_clean將緩沖區(qū)里的內(nèi)容取出賦值給$content并刪除當(dāng)前輸出緩沖區(qū)。因此phpinfo雖然執(zhí)行成功,但回顯并不會(huì)顯示在瀏覽器頁面上。

    如果想要獲取回顯,我們?cè)撛趺崔k呢?這其實(shí)很簡(jiǎn)單,見下圖

    此時(shí)生成的緩存文件如下:

    雖然在include這個(gè)緩存文件之前,程序通過ob_start打開緩沖區(qū)將phpinfo的輸出存到緩沖區(qū)里,但我們可以通過執(zhí)行ob_flush沖刷出(送出)輸出緩沖區(qū)中的內(nèi)容,打印到瀏覽器頁面上

    怎么將payload寫入文件

    上文我們一直在用一個(gè)手動(dòng)上傳的test.html,很顯然這在實(shí)際漏洞利用過程中是不行的。我們需要想辦法在目標(biāo)服務(wù)器里寫入一個(gè)payload。

    在這里筆者繞了很多彎路,嘗試著在圖片上傳處做文章,但最后失敗了。后來筆者突然想起來官方的補(bǔ)丁,還記得上文我們從官方補(bǔ)丁中得到的漏洞情報(bào)?

    補(bǔ)丁將fetch 方法中

    if(!is_file($templateFile)) E(L('_TEMPLATE_NOT_EXIST_').':'.$templateFile);

    代碼注釋替換為

    if(!is_file($templateFile)) E(L('_TEMPLATE_NOT_EXIST_'));

    修改之處的E()函數(shù)是用來拋出異常處理的,而補(bǔ)丁將$templateFile刪除,正是不想讓$templateFile變量值寫到日志log文件中。看來payload是可以寫到日志文件里的。

    我們回過頭來,看看fetch 方法中$templateFile變量怎么控制

    還記得上文的分析嗎?$templateFile變量其實(shí)就是請(qǐng)求中傳入的tpl變量可以被攻擊者控制。從上圖來看,只要請(qǐng)求中傳入的tpl變量不是文件,就可以將tpl變量值寫入log文件。

    那么我們就讓請(qǐng)求中傳入的tpl變量為payload字符串,滿足不是文件判斷,讓這個(gè)payload寫到日志中

    實(shí)際發(fā)送如下請(qǐng)求控制$templateFile變量寫入日志文件

    動(dòng)態(tài)調(diào)試如下:

    日志被寫到data/Runtime/Logs/Home/20_12_02.log,見下圖

    但有個(gè)問題:我們?yōu)槭裁床幌裆衔囊回炞黠L(fēng),使用get請(qǐng)求傳遞tpl變量值呢?因?yàn)閺膅et請(qǐng)求中url會(huì)在日志文件中被url編碼,而post請(qǐng)求則不然。因此只能發(fā)送post請(qǐng)求。

    到此,完整的利用鏈構(gòu)造出來了,發(fā)送如下請(qǐng)求即可包含日志文件并執(zhí)行payload

    寫在最后

    總得來說這個(gè)漏洞并不復(fù)雜,但是卻很巧妙。在此過程中遇到很多有趣是問題。

    構(gòu)造圖片payload問題

    在從官方補(bǔ)丁中發(fā)現(xiàn)利用log文件寫入payload思路之前,筆者花費(fèi)大量時(shí)間嘗試?yán)脠D片上傳寫入payload。因?yàn)?4cms中利用了ThinkImage(也就是php-GD)對(duì)圖片的渲染和處理導(dǎo)致webshell代碼錯(cuò)位失效,筆者嘗試了這篇文章里的思路

    https://paper.seebug.org/387/#2-bypass-php-gdwebshell

    這下倒是成功了一半:ThinkImage出現(xiàn)異常拋出錯(cuò)誤了,并沒有對(duì)筆者webshell圖片進(jìn)行渲染和處理,這看起來太棒了。但壞消息是,因?yàn)門hinkImage拋出異常,程序并沒有把筆者上傳成功后存儲(chǔ)于服務(wù)器上的圖片名稱拋出來,而圖片名稱是通過uniqid()函數(shù)生成的隨機(jī)數(shù)。uniqid() 函數(shù)基于以微秒計(jì)的當(dāng)前時(shí)間,生成一個(gè)唯一的ID。筆者也沒有辦法猜測(cè)出上傳后的圖片名是什么,因此作罷。

    這個(gè)問題與接下來的問題相關(guān),也就是官方的補(bǔ)丁到底有沒有效

    官方第一處補(bǔ)丁到底有沒有用

    還記得上文漏洞情報(bào)分析那里,關(guān)于第一處補(bǔ)丁筆者的分析嗎?

    補(bǔ)丁在assign_resume_tpl方法中增添了如下代碼

    $tpl_file = $view->parseTemplate($tpl);
    if(!is_file($tpl_file)){
    return false;
    }

    筆者在分析漏洞之前的想法是:因?yàn)檫@是一個(gè)文件包含漏洞,而assign_resume_tpl方法正是這個(gè)漏洞的入口,因此如果我們傳入的$tpl必定是一個(gè)文件,這樣可以輕松的繞過$view->parseTemplate($tpl);(parseTemplate中進(jìn)行判斷,如果傳入的tpl是文件則直接return)與if(!is_file($tpl_file))判斷。

    但經(jīng)過深入的漏洞分析發(fā)現(xiàn),assign_resume_tpl方法不僅是文件包含漏洞的入口,也是后續(xù)將payload寫入log文件的接口,通過控制assign_resume_tpl方法的tpl參數(shù)為字符串形式的payload,則這個(gè)payload將會(huì)在fetch中被寫入日志文件。

    但在assign_resume_tpl方法中增加了判斷

    $tpl_file會(huì)是payload字符串拼接.html這樣的形式,接下來的if(!is_file($tpl_file))會(huì)return false,而保護(hù)程序不進(jìn)入fetch。

    但這樣真有必要嗎?因?yàn)閒etch中也打了補(bǔ)丁,經(jīng)過上文對(duì)補(bǔ)丁的分析,就算是assign_resume_tpl方法中沒有修改使得payload進(jìn)入了fetch,由于補(bǔ)丁的原因fetch中也不會(huì)把payload寫入日志了,因此這里的補(bǔ)丁顯的沒有太大必要。

    官方補(bǔ)丁可以繞過嗎

    經(jīng)過從上面兩個(gè)問題的思考,可以發(fā)現(xiàn)一個(gè)新的問題,那就是官方補(bǔ)丁是否可以繞過。通過對(duì)漏洞的了解,官方補(bǔ)丁實(shí)際起作用的是不讓payload寫入日志文件。如果真的有人有辦法在圖片中寫入payload并上傳成功,在assign_resume_tpl方法中直接包含這個(gè)文件即可利用成功。assign_resume_tpl方法中的補(bǔ)丁并沒有限制tpl參數(shù)為文件。

    也就是說:要么官方補(bǔ)丁是可以輕松繞過的、要么通過構(gòu)造圖片webshell這條路走不通。具體哪個(gè)是對(duì)的,就要看看官方后續(xù)是否又出補(bǔ)丁繞過公告與一個(gè)新的補(bǔ)丁了。

    轉(zhuǎn)載于https://xz.aliyun.com/t/8596

    更多技術(shù)文章請(qǐng)關(guān)注公眾號(hào):豬豬談安全

    師傅們點(diǎn)贊、轉(zhuǎn)發(fā)、在看就是最大的支持

    總結(jié)

    以上是生活随笔為你收集整理的php excel中解析显示html代码_骑士cms从任意文件包含到远程代码执行漏洞分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。