PHP匿名函数探究

之前在看phar反序列化漏洞时,在 hitcon2017的题目中,除了反序列化这个点外。还有一个就是PHP中的匿名函数并不是正在的匿名函数。秉着探究精神,便研究一下PHP中的匿名函数到底是怎样的。

匿名函数

什么是匿名函数

匿名函数就是没有函数名的函数。匿名函数使用最多的应该是在 JavaScript 中。例如下面这个给按钮绑定 onclick 事件时,就用到了匿名函数。

1
2
3
4
5
6
7
<button id='button'>button</button>
<script>
var button = document.getElementById('button');
button.onclick = function(){
alert(1);
}
</script>

PHP中的匿名函数

PHP中的匿名函数是在5.3版本之后引入的。但在此之前,就有create_function函数来创建一个匿名函数。但是这个函数在手册中对PHP的版本要求是在PHP>4.0.1。

1
2
3
4
<?php
$a = create_function("","echo 'hello world';");
$a();//输出 hello world
var_dump($a);// 输出 string(9) " lambda_1"

我们可以看到,create_function函数创建的匿名函数其实并不是匿名的,而是有名字的,名字格式为“\000_lambda_” . count(anonymous_functions)++;

create_function函数的实现过程如下:

  1. 获取参数, 函数体
  2. 拼凑一个”function __lambda_func (参数) { 函数体;} “的字符串
  3. eval之
  4. 通过__lambda_func在函数表中找到eval后得到的函数体, 找不到就出错
  5. 定义一个函数名:”\000_lambda_” . count(anonymous_functions)++
  6. 用新的函数名替换__lambda_func
  7. 返回新的函数名

因为3中的eval,所以create_function是一个危险函数。

在PHP5.3之后,PHP引入了Closure类来实现匿名函数。Closure类是Final类且是私有的构造函数。所以不能被实例化和继承。5.3引入了魔术__invoke(),以调用函数的方式调用一个对象时,该魔术方法自动调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$test = function(){
echo "hello world";
};
$test();
var_dump($test);
$test->__invoke();
/*输出
hello world
object(Closure)#1 (0) {
}
hello world
*/

上述例子,展示了php5.3之后创建匿名函数的过程。$test是Closure类的一个实例。创建匿名函数,实际就是创建这个实例的__invoke魔术方法的内容。当以函数方式调用这个实例时,就自动调用__invoke这个方法。