Smarty <= 3.1.32 PHP代碼執行漏洞分析—【CVE-2017-1000480】

smarty簡介

smarty是一個php模板引擎,其項目地址:https://github.com/smarty-php/smarty。

smarty據有模板編譯功能。當訪問一個模板文件時,smarty會根據模板文件在設置的編譯目錄中生成對應的php腳本(即編譯文件),此后若再次訪問該模板文件時,倘若模板文件未更新,則smarty會直接讀取第一次生成的php腳本,而不是重新生成另一個。倘若訪問另一個模板,則會生成另一個新的php腳本(編譯文件)

環境搭建

測試環境:linux

根據commit信息,我們檢出 6768340,此時漏洞還未修復。

index.php:

漏洞分析

參數通過$smarty->display('test:'.$_GET['chybeta']);傳入,display定義在 smarty_internal_templatebase.php 中,它調用了 _execute

_execute定義在libs\sysplugins\smarty_internal_compile_assign.phpsmarty_internal_templatebase.php的 156 行左右,在該方法定義中,也即整個文件的174行左右:

會調用createTemplate方法,將我們的傳入的參數創建成一個模板,

接著會調用render方法,進行模板渲染。

render方法定義在libs\sysplugins\smarty_template_compiled.php中,第105行開始對前面生成的模板進行處理:

process方法定義在第131行。現在初次訪問,也即文件的第138行會對模板文件進行編譯,即如簡介中所言開始生成編譯文件:

compileTemplateSource方法定義在同文件的第169行,在第181行裝載完編譯器后(loadCompiler()),調用write方法進行寫操作:

跟入compileTemplate方法,定義libs\sysplugins\smarty_internal_templatecompilerbase.php第334行:

create是生成編譯文件代碼的方法,定義在libs\sysplugins\smarty_internal_runtime_codeframe.php第28行,為顯示變量情況,這里我加了一句var_dump

在第44行,在生成output內容時有如下代碼:

$_template->source->filepath的內容直接拼接到了$output里。這段代碼是為了生成編譯文件中的注釋,$output的頭尾有注釋符號/**/

現在考慮如何利用,我們需要閉合前面的注釋符號,即payload的最前面需要加上*/。同時還要把后面的*/給注釋掉,可以在payload最后加上//。中間填上php代碼即可。另外需要注意的是,在win平臺下,文件名中不允許有*,而smarty框架的生成的編譯文件的名字會含有我們的payload,所以在win下時會直接提示創建文件失敗。

在linux平臺下即可利用成功。

漏洞修補

查看commit記錄:https://github.com/smarty-php/smarty/commit/614ad1f8b9b00086efc123e49b7bb8efbfa81b61

添加了過濾,將可能閉合的*/變為* /

在另外幾處文件中也進行了過濾,要求只能出現字母和數字:

題外話

直接看生成的編譯文件,會發現有兩個輸出點,第二個輸出點在單引號內,但這個無法逃逸。在libs\sysplugins\smarty_internal_runtime_codeframe.php的第47行,使用的是var_export來導出變量內容的值:

而如漏洞修補一節中所言,添加過濾后,引號會被直接去除。

 

作者:chybeta