日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > php >内容正文

php

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

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

前言

前些日子,騎士cms 官方公布了一個系統緊急風險漏洞升級通知:騎士cms 6.0.48存在一處任意文件包含漏洞,利用該漏洞對payload文件進行包含,即可造成遠程代碼執行漏洞。這篇文章將從漏洞公告分析開始,敘述一下筆者分析漏洞與構造payload時遇到的有趣的事情。

漏洞情報

官方發布的系統緊急風險漏洞升級通知如下:

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

從官方公布的信息來看,官方修復了兩個地方:

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

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

從BaseController.class.php這處補丁來看:

筆者猜測漏洞多半出在了渲染簡歷模板的assign_resume_tpl方法中。從補丁修復上來看,增添了如下代碼

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

可以發現程序通過$view->parseTemplate對$tpl參數進行處理,并對處理結果$tpl_file進行is_file判斷

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

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

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

這個問題筆者將在文章最后介紹。

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

補丁將fetch 方法中

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

代碼注釋替換為

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

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

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

漏洞分析

通過對漏洞情報的分析,我們差不多知道了這個漏洞的來龍去脈:

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

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

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

    經過上文的猜測,我們可以通過assign_resume_tpl方法包含任意文件。首先我們要看看怎么通過請求調用assign_resume_tpl方法

    如何訪問assign_resume_tpl方法

    assign_resume_tpl方法位于common模塊base控制器下。通過對Thinkphp路由的了解,assign_resume_tpl方法多半是用如下url進行調用

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

    但是實際上,程序拋出了個錯誤

    這是為什么呢?經過動態調試發現一個有意思的事情:common模塊是并不能被直接調用的。原因如下:

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

    從上圖代碼可見,因為我們common模塊位于MODULE_DENY_LIST中,因此不能直接通過m=common來調用common模塊。

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

    經過研究發現,幾乎所有其他的控制器,最終都繼承自common模塊的BaseController控制器

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

    AbcController 繼承FrontendController

    而FrontendController由繼承了BaseController

    因此可以通過get請求

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

    來調用BaseController下的assign_resume_tpl,并將$variable=1、$tpl=2參數傳遞進去

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

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

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

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

    我們后續分析就用

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

    這樣的形式調用assign_resume_tpl方法

    既然我們可以通過請求向存在漏洞的assign_resume_tpl方法傳參了,距離漏洞利用成功已經不遠了

    用測試文件觸發文件包含

    我們接下來”假裝”在后臺上傳一個payload,用assign_resume_tpl這個接口包含下試試

    筆者手動在如下目錄里放了個test.html

    為什么這么放呢?因為筆者在源代碼里看到如下代碼

    這里是74cms使用assign_resume_tpl調用word_resume.html的形式。因此筆者在測試時也在word_resume.html通目錄下放置了一個test.html,其內容如下:

    構造如下請求

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

    請求將調用assign_resume_tpl方法。動態調試過程如下:

    可見此時$tpl為Emailtpl/test,get請求中參數成功傳入了。

    我們來看一下fetch里怎么實現的

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

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

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

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

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

    首先我們來看下生產緩存文件過程

    Compiler

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

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

    就會被解析成

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

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

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

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

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

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

    最終compiler方法返回值為

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

    這個值被寫入一個緩存文件,見下圖

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

    到此為止,compiler流程已經結束,我們接下來看看加載過程

    Load

    Load代碼如下圖所示

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

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

    當8a848d32ad6f6040d5461bb8b5f65eb0.php被include之后,其中的惡意代碼執行,見下圖

    執行成功后,瀏覽器如下

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

    這次我們執行一個生產目錄的命令

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

    phpinfo回顯哪去了

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

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

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

    如果想要獲取回顯,我們該怎么辦呢?這其實很簡單,見下圖

    此時生成的緩存文件如下:

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

    怎么將payload寫入文件

    上文我們一直在用一個手動上傳的test.html,很顯然這在實際漏洞利用過程中是不行的。我們需要想辦法在目標服務器里寫入一個payload。

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

    補丁將fetch 方法中

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

    代碼注釋替換為

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

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

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

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

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

    實際發送如下請求控制$templateFile變量寫入日志文件

    動態調試如下:

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

    但有個問題:我們為什么不像上文一貫作風,使用get請求傳遞tpl變量值呢?因為從get請求中url會在日志文件中被url編碼,而post請求則不然。因此只能發送post請求。

    到此,完整的利用鏈構造出來了,發送如下請求即可包含日志文件并執行payload

    寫在最后

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

    構造圖片payload問題

    在從官方補丁中發現利用log文件寫入payload思路之前,筆者花費大量時間嘗試利用圖片上傳寫入payload。因為74cms中利用了ThinkImage(也就是php-GD)對圖片的渲染和處理導致webshell代碼錯位失效,筆者嘗試了這篇文章里的思路

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

    這下倒是成功了一半:ThinkImage出現異常拋出錯誤了,并沒有對筆者webshell圖片進行渲染和處理,這看起來太棒了。但壞消息是,因為ThinkImage拋出異常,程序并沒有把筆者上傳成功后存儲于服務器上的圖片名稱拋出來,而圖片名稱是通過uniqid()函數生成的隨機數。uniqid() 函數基于以微秒計的當前時間,生成一個唯一的ID。筆者也沒有辦法猜測出上傳后的圖片名是什么,因此作罷。

    這個問題與接下來的問題相關,也就是官方的補丁到底有沒有效

    官方第一處補丁到底有沒有用

    還記得上文漏洞情報分析那里,關于第一處補丁筆者的分析嗎?

    補丁在assign_resume_tpl方法中增添了如下代碼

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

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

    但經過深入的漏洞分析發現,assign_resume_tpl方法不僅是文件包含漏洞的入口,也是后續將payload寫入log文件的接口,通過控制assign_resume_tpl方法的tpl參數為字符串形式的payload,則這個payload將會在fetch中被寫入日志文件。

    但在assign_resume_tpl方法中增加了判斷

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

    但這樣真有必要嗎?因為fetch中也打了補丁,經過上文對補丁的分析,就算是assign_resume_tpl方法中沒有修改使得payload進入了fetch,由于補丁的原因fetch中也不會把payload寫入日志了,因此這里的補丁顯的沒有太大必要。

    官方補丁可以繞過嗎

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

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

    轉載于https://xz.aliyun.com/t/8596

    更多技術文章請關注公眾號:豬豬談安全

    師傅們點贊、轉發、在看就是最大的支持

    總結

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

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。