2. SSTI(模板注入)漏洞(cms實例篇)


上篇《SSTI(模板注入)漏洞(入門篇)》主要介紹了PHP/Python/Java常見的幾種模板注入,本篇主要通過cms實例來更好的理解並且挖掘SSTI。

以下cms的源碼地址:https://github.com/bmjoker/Code-audit/

蘋果CMS模板注入導致代碼執行

先給出漏洞payload:

http://127.0.0.1/maccms_php_v8.x/index.php?m=vod-search&wd={if-x:phpinfo()}{endif-x}

通過搜索代碼執行常用的字段(eval,assert...),定位到此處

查看 maccms_php_v8.x\inc\common\template.php 文件,判斷變量是否可控:

可以看到如果想構造代碼執行,需要控制變量 $strif ,通讀上下文發現一條數據鏈:

最原本的傳進來的數據是 $this->H ,經過 preg_match_all() 函數進行正則匹配,把匹配到的結果賦值給 $iar 這個二維數組,進入for循環遍歷數組,將 \$iar[2][$m] 所指的元素傳入 asp2phpif() 方法進行安全過濾,最后的返回值就是 $strif 。 

這里看到 asp2phpif() 方法僅是對一些字符的替換。

先來追蹤一下 ifex() 函數在哪里被調用:

最后定位到了 maccms_php_v8.x\index.php 文件,發現了調用的地方 $tpl->ifex(),那么 $this->H 參數從哪里傳進來呢?

最上面看到了這樣一段代碼:$m = be('get','m'),跟進 be() 這個方法:

再結合網站的請求:

大概了解了網站接收參數的方法。 $m = be('get','m'):就是通過get請求獲取m的參數。然后獲取到的參數被 explode() 方法以 - 分割成數組傳遞給 $par,取數組的第一個元素賦值給$ac,判斷 $ac 所指的元素是否在 $acs 的數組中,如果存在的話就使用 include 包含 /inc/module/ 目錄下以 $ac 所指元素命名的php文件。

根據payload,進入vod.php文件,這里給出關鍵代碼:

當我們調用 search 方法時,就會進入此分支,通過 be("all","wd") 獲取用戶傳進來的wd的參數,傳入 chkSql 方法,然后賦值給 $tpl->P["wd"]

僅是使用 htmlEncode() 方法對一些字符判空,轉義。

$tpl->H 就是傳入ifex() 方法中的 $this->H 參數,因為 $tpl = new AppTpl()。上圖代碼中 $tpl->H 加載文件 vod_search.html 然后展示給前端。

$colarr$valarr兩個參數數組,經過 str_replace() 方法,將 vod_seach.html 中的類似 {page:des} 的字段替換成 $tpl 所指向的字段,漏洞導致的關鍵是這個 $tpl->P["wd"] 是我們前端可控的參數。

執行完上面的賦值,回到index.php中下一步就調用 ifex() 方法

$tpl->H 就是替換過后的 vod_search.html 文件

這樣的話再倒過來看最初的 template.php 文件,是不是就清楚多了

通過for循環一次一次的匹配到類似  {if-A:"{page:typepid}"="0"}  的字段,賦值給變量 $strif,傳入eval方法導致代碼執行漏洞 

下面就是如何構造合適的payload繞過正則表達式:{if-([\s\S]*?):([\s\S]+?)}([\s\S]*?){endif-\1}

 類似這樣即可:

{if-dddd:phpinfo()}{endif-dddd}

真正代碼調試過的人,可能有的人會有疑問,因為使用上面payload的話  if (strpos(",".\$strThen,\$labelRule2)>0),if (strpos(",".\$strThen,\$labelRule3)>0) 兩個循環都無法進入,所以真正的漏洞出發點在 else:

漏洞演示效果:

靜態插樁打印 $iar 的值:

打印 $strif 的值:

參照上面打印 $iar 的值,第三個 phpinfo() 進入else分支的eval函數中,導致代碼執行。

OFCMS模板注入導致任意命令執行

ofcms是由JFinal開發的內容管理系統。

從pom.xml可以看到引入freemarker-2.3.21依賴

JFinal允許多模板共存,如果想要使用freemarker模板,需要在configConstant配置

me.setViewType(ViewType.FREE_MARKER);

然后再使用 JFinal.me() 調用模板,使用 put 用來替換原來輸出response html代碼輸出到瀏覽器

到這里我們是不是可以理解為網站的html文件由FreeMarker模板進行渲染。

ofcms后台模板文件

任意選擇一個html文件,再文件中插入我們的payload:

<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}

保存,在前台訪問404.html界面

 

FreeMarker解析了404.html文件中我們插入的payload,導致命令執行

74CMS模板注入導致Getshell

由於74CMS是基於Thinkphp3的語法魔改而成。建議先去大概看一下Thinkphp3的開發手冊:http://document.thinkphp.cn/manual_3_2.html#

先來看一下漏洞代碼:ThinkPHP\Library\Think\View.class.php

在第122行,include $templateFile 典型的文件包含,如果 $templateFile 可控就可以getshell。

通讀 fetch() 函數代碼,發現如果想要文件包含,傳進來的參數 $content 必須為空,才能進入if循環與下面的三元表達式。把前端獲取的 $templateFile 傳進 parseTemplate() 方法:

啊...這?傳進來的參數 $template 經過 is_file() 僅僅是做了文件是否存在以及是否為正常的文件,就直接把 $template return ...

當使用PHP原生模板時會進入下面的if循環,緊接着就 include $templateFile 。

什么是PHP原生模板?

由開發者手冊可知,如果要使用PHP代碼時盡量采用php標簽,也就是 <php>...</php> 這種形式

大概知道這些,我們就可以通過上傳內容為PHP原生模板的文件,再使用 include 包含。

繼續回溯代碼,尋找哪里調用 fetch() 這個函數,包括參數從哪里傳過來

同文件下的 display() 方法里調用了fetch() 方法,看過模板手冊的都知道,渲染模板輸出最常用的是使用display方法,繼續查找 display() 在哪里被調用

可以看到在 Controller.class.php 中有調用

直接在 MController.class.php 文件中就可以看到 display() 函數的調用

熟悉Tp框架的應該知道 I 方法時用來接收參數,而第20行 I('get.type'...) 說明 $type 可以通過get方式從前端獲取

可見,這里將 $type 參數傳入 display() 函數,display() 函數是 ThinkPHP 中展示模板的函數。然后又將參數傳入 View 類的 display() 函數,最后調用 fetch() 函數,導致文件包含漏洞

使用自帶的 favicon.ico 做下試驗,看是否能成功包含:

http://127.0.0.1/74cms_v4.1.5/upload/index.php?m=&c=M&a=index&page_seo=1&type=../favicon.ico

成功包含。

如果想要包含我們構造的惡意文件,需要滿足兩個條件:

  1. 可以將惡意文件上傳到服務器

  2. 有文件的絕對路徑

注冊一個賬戶,登錄后台尋找文件上傳的地方:

個人用戶創建簡歷后支持上傳 docx 格式的簡歷。上傳一個內容為PHP原生模板的 docx 文件,將其作為模板。數據包如下:

上傳文件的絕對路徑為:/74cms_v4.1.5/upload/data/upload/word_resume/2009/14/5f5f8bdb56593.docx

再將這個文件名作為type的值,成功執行代碼:

http://127.0.0.1/74cms_v4.1.5/upload/index.php?m=&c=M&a=index&type=../data/upload/word_resume/2009/14/5f5f8bdb56593.docx

PbootCms-2.0.7模板注入導致Getshell

首先通過搜索關鍵字定位到導致漏洞的代碼:core\view\View.php  ——>  parser()

根據payload,參數大概調用過程如上圖

漏洞的關鍵點是 include $tpl_c_file,文件包含模板文件導致getshell。

parser() 方法接受傳過來的模板文件 $file,經過 preg_replace() 方法來過濾掉相對路徑(例如:../,..\),這里使用了不安全的替換,因為 preg_replace() 匹配到不安全的字符不是直接exit,而是選擇替換成空,利用這個可以嘗試構造繞過,像下圖這樣:

然后將替換過后的 $file 傳入if 循環,第一個if判斷 $file 是否是以 / 開頭,第二個elseif判斷 $file 是否包含 @ ,如果都不滿足進入else拼接,\$tpl_file = 模板路徑 + / + $file

$tpl_file 是模板文件,\$tpl_c_file 是要編譯的文件。

繼續看代碼發現在121行又做了一次拼接,\$tpl_c_file = 模板路徑 + / + md5($tpl_file) + .php。緊接這是一個if判斷,判斷文件是否存在,判斷 $tpl_c_file 文件的修改時間是否小於 $tpl_file,判斷讀取配置文件是否成功,很尷尬,全都是false,直接跳過if循環,到達關鍵地方,直接 include 包含我們構造的文件,導致漏洞產生。

這里可能有的人會有疑問,因為在到達 include 的時候,$tpl_c_file是 模板路徑 + / + md5($tpl_file) + .php 這種形式,我們構造的文件路徑早已面目全非。這個地方需要注意這個漏洞的根本原因是PbootCMS2.07內核處理缺陷導致的一個前台任意文件包含漏洞,他的內核函數在生成編譯文件的時候造成任意文件讀取。

parser() 方法已經分析完畢,現在需要尋找調用了parser函數的地方,且參數可控

進入 Controller.php 文件

這里看到顯示模板,解析模板都有調用到 parser 方法,繼續跟蹤判斷哪里調用

顯示模板 display() 方法發現沒有參數可控,但是解析模板 parser() 方法,發現有變量傳入

先來看 SearchController.php 文件

index() 方法中,接收前端傳遞過來的參數,進入正則匹配,這正則匹配任意的字符,還包括-,.,/,最后直接傳入 parser() 方法中,直接構造利用讀取robots.txt:

同樣構造利用的還有 TagController.php 文件

包含寫入shell的txt文件:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM