背景
这篇文章将讨论 PHP Web 应用中 create_function 的命令注入。命令注入,究其根源,都是未对用户提供的输入做合理过滤造成的。当然,编程语言本身内置的危险方法的使用,是命令注入频发的另一原因。
各种变成语言的危险方法有所不同,对于 PHP 而言,我们可以再这里找到一系列的 PHP 危险方法。在使用这些方法的时候,一定要考虑其安全隐患。
在这些众多的危险方法中,最臭名昭著的,当是 eval() 莫属。这个方法可以直接调用,也可以通过其他的 PHP 内置方法间接调用。
今天,我们看一下 PHP 4 时代就存在的 create_function 内置方法,是如何走向命令注入的。
环境参数
create_function 存在于 PHP 4 >= 4.0.1, PHP 5, PHP 7 版本中。
该方法已经在 PHP 7.2.0 中废弃,并在 PHP 8.0.0 中移除。
create_function 方法
这里,我们看一下该方法的基本作用,它的应用,最后看一下源码,找出命令注入的成因。
方法介绍
根据官方文档,create_function 方法,顾名思义,就是可以将自定义的代码字符串,解析成可执行代码,并且可以指定任意个方法参数。
我们到 Online PHP Playground 来通过实例看一下这个方法的使用。
我们选择 PHP 7.0.33。
定义一个方法,两数相加。
<?php
$func = create_function('$a, $b', 'return "$a + $b = " . $a + $b;');
echo $func(1, 2);
?>
将这个方法赋值给 $func,就可以调用该方法做加法运算。
如果选择高 PHP 版本,如 7.4.32,该自定义方法还是可以执行,只是会报警告,因为该方法已经废弃。
实际应用
create_function 方法通常会被用于对数组的自定义排序,会和另一个 usort 方法 一起使用。例如对从数据库中取出的数据,根据某个属性,做自定义的排序。
用以下代码举例,创建一个 User 类,创建一个包含4个 User 对象的数组,自定义使用 username 属性排序。
<?phpclass User {public $username;public $passwd;function __construct($username, $passwd) {$this->username = $username;$this->passwd = $passwd;}
}$users = array(new User('able', 'best'),new User('zigzag', 'empty'),new User('boy', 'coolest'),new User('luke', 'force')
);$order = 'username';// 自定义使用 User 对象的 username 属性作为排序的依据
usort($users, create_function('$a, $b', 'return strcmp($a->' . $order . ', $b->' . $order . ');'));print_r($users);?>
结果如下:
将 $order 改为使用 passwd 属性排序。
结果如下:
这就是该方法的实际应用场景。
create_function 源码及 CVE-2008-4096
CVE-2008-4096 是 phpAdmin 2.11.9.1 远程代码执行的漏洞。成因就是利用 sort_by 参数,将命令注入到 create_function 方法。
现在我们看一下 create_function 方法的源码。
找到 PHP 在 Github 的仓库,选择 5.2 分支(CVE-2008-4096 的 PHP 版本是 5.2.6)。
搜索 create_function,在 1449 行找到了方法定义。
比较关心用户输入在哪里被使用。所以看到 1470 行,两个 %s 的时候,就找到了目标。可以看到源码中到处都是 eval,应该就是使用 eval 执行我们的代码字符串。
1464 行到 1469 行定义了整个代码字符串的边界。
方法名在 1446 行被定义成 __lambda_func,然后加上参数的长度,加上两个方法参数的括号 (),加上两个代码体的括号 {},最后加上代码长度。
其实就是拼接了一个方法,如:
function __lambda_func(){}
然后在 **1470 ** 行,zend_spprintf 会把整个代码拷贝到 eval_code 变量中。最后,会在 1473 行调用自定义的方法。
因此,如果用户输入没有做合理的过滤,就会造成恶意的命令注入。
命令注入实践
在 Online PHP Playground 中,尝试注入 uname -a。
看到如下警告,因为安全原因,Online PHP Playground 禁用了如 system 的调用。
有警告,说明其实命令注入是成功的。
转到虚拟机上尝试。
在虚拟机上安装了 PHP 7.0.33。
使用同样的测试代码。
<?phpclass User {public $username;public $passwd;function __construct($username, $passwd) {$this->username = $username;$this->passwd = $passwd;}
}$users = array(new User('able', 'best'),new User('zigzag', 'empty'),new User('boy', 'coolest'),new User('luke', 'force')
);$order = 'username';usort($users, create_function('$a, $b', 'return strcmp($a->' . $order . ', $b->' . $order . ');'));print_r($users);?>
运行。
可以看到命令注入成功。
总结
综上,create_function 命令注入的条件只需要两个:
- 4.0.1 <= PHP 版本 < 8.0.0;
- 用户输入没有做过滤或者过滤不完全;
参考链接
- https://cxsecurity.com/issue/WLB-2008090063
- https://bugs.php.net/bug.php?id=48231
- https://www.exploit-db.com/exploits/32417
- https://www.php.net/manual/en/function.create-function.php
- https://www.geeksforgeeks.org/php-create_function-function/
- https://www.w3schools.com/php/php_oop_constructor.asp
- https://www.geeksforgeeks.org/php-usort-function/
- https://github.com/php/php-src/blob/PHP-5.0/Zend/zend_builtin_functions.c
- https://opensource.apple.com/source/apache_mod_php/apache_mod_php-110/php/Zend/zend_builtin_functions.c.auto.html