资讯动态

RCE绕过之关键词过滤:编码拼接与路径等价实战技巧

发布时间:2026/10/20 6:25:42 来源:尧图企业网站定制
RCE绕过之关键词绕过这个话题在安全圈里几乎天天有人踩。我最早接触它是在一次渗透测试里目标系统明明存在命令执行漏洞我敲cat /etc/passwd直接被拦换成cat /etc//passwd就成了。从那一刻起我就意识到关键词过滤不是铜墙铁壁关键看你懂不懂背后的匹配逻辑。这篇文章我就把实战中积累的绕过思路、踩坑记录和判断技巧完整梳理一遍给同样在做代码审计、渗透测试或者安全开发的朋友做个参考。1. 关键词过滤是怎么拦住你的先从防御侧看门道想要理解绕过先得搞清楚对面是怎么拦的。绝大多数关键词过滤系统不是基于语义分析而是基于字符串匹配。它不会在意你这条命令最终执行了什么“含义”只关心你提交的内容里有没有出现黑名单里的“模样”。1.1 匹配逻辑决定了绕过空间我见过的主流过滤实现主要有三类分清楚它们之后你就能预判哪些手法能成、哪些手法必定翻车。第一类是纯字符串包含匹配。比如用正则/(cat|ls|more)/i去匹配整条输入只要“命中”就直接置空或拒绝请求。这类过滤最好突破因为它的“视野”只有字面意义它不认识空字节、不认识正反斜杠混用也不认识${IFS}这种变量。第二类是按函数名或危险字符做精确拦截。比如专门拦截system、exec、shell_exec这些PHP函数名或者直接过滤$_GET、$_POST、$_REQUEST这些超全局变量。这种过滤比纯字符串包含聪明一点但同样有破绽——PHP和Shell对语法元素的解析方式足够灵活只要它没覆盖到所有“语法等价写法”绕过就是时间问题。第三类是RASP或者WAF那种带语义分析的引擎。它会尝试理解你这个输入最终会被应用程序怎样使用比如追踪到system()函数之后再看参数内容是不是命令。这种确实难绕但也不是无解只是思路要从“改写法”转向“改通道”或者“找解析差异”。后面我单独讲。关键结论是绕过的前提是过滤器和执行器之间存在认知差。过滤器按自己的规则集做扫描执行器按语言的语法规则做解析两者永远不可能做到绝对相等。我们要找的就是那些“过滤器看不出来但解析器能理解”的中间地带。1.2 黑名单常见的薄弱点我把平时审计代码时爱找的几个过滤弱点列一下这些都是可以直接出效果的突破口过滤了空格但没过滤${IFS}、$IFS$()IFS、制表符\t、换行符%0a。过滤了cat但没过滤c\at、cat、${cat}、c$at。过滤了flag但没过滤变量拼接产物例如afl;bag;$a$b。过滤了单次出现的/但没过滤双斜杠//Linux路径解析天然压缩多个连续斜杠。只能处理ASCII字符对URL编码、UTF-8编码字符或者恒等变形比如全角标点视而不见。这些点单个用可能很快失败但组合起来往往能突破层层过滤。实战里绕一个参数往往需要两到三种基础手法叠在一起不是单靠一个花招。2. 绕过思路的底层逻辑从“语法等价”到“语义等价”很多人收集了一堆Payload却不会灵活用是因为没把绕过思路抽象成方法论。我自己的总结是所有关键词绕过都能归入三大类理解它们之后你就能自己发明新花样而不是死记硬背列表。2.1 编码变形让过滤器认不出但解析器认得第一种思路是编码变形。核心逻辑是过滤规则按原始字节流扫描但我们提交的内容经过编码后仍然被后端解码回原始语义于是过滤器看到的是“混沌”执行器拿到的是“正常”。最常见的编码变形是URL编码。比如whoami编成%77%68%6f%61%6d%69很多WAF第一层解码之后拿不到明文自然匹配不到。但这里有个陷阱如果目标程序做了严格校验比如preg_match在未解码之前执行你就能绕但如果WAF先统一解码再做过滤同样的Payload就会原形毕露。所以编码绕过不能无脑用得先探测服务器的解码顺序。我通常是在空白参数位丢一个标准Payload看拦截特征再丢编码版本看结果差异以此来推断WAF的解码层数。两层之内有大量机会三层以上的编码防御基本就防死了。Base64编码在命令执行场景里还有另一种玩法就是把整条命令写成Base64再让系统执行器来解码运行。比如echo d2hvYW1p|base64 -d|bash这种链式写法whoami明文完全不出现过滤器自然无从下手。这叫命令自解码执行属于编码绕过里最接近“语义等价”的手法。2.2 拼接重组利用语言解析规则拆分黑名单词汇第二种思路是拼接重组。它的依据是编程语言和Shell在解析命令时会把某些特殊字符视为“无意义符号”直接丢弃而过滤规则往往不会预先考虑这些符号的位置。经典例子是Linux Shell里的单引号拼接。cat在Shell眼里就是cat因为单引号会被剥掉再进入解析流程。类似的还有双引号、反斜杠c\at同样等于cat。只要过滤规则没有对每个字符之间的“点缀”做去重处理这种手法百试百灵。PHP里也有一模一样的思路。函数名可以拆成字符串拼接后再调用比如$a sys.tem; $a(whoami)如果过滤器只盯死字面上的system那它永远不会匹配到。更隐蔽的写法是直接把函数名当作变量值$_GET[x]($_GET[y])配合一个传参站点过滤规则的视野里就只剩$_GET危险函数完全没有亮相机会。拼接重组还有个进阶版是变量落地共享。asys;btem;$a$b whoami这行命令把system藏到了赋值操作的语义里过滤器只看到asys和btem怎么也拼不出完整的黑名单词。这种方法在Shell注入、SQL注入里都极其常见算是我最常用的一类稳定绕过技巧。2.3 空间替换从字符集层面绕开禁用的边界第三种思路是空间替换。关键词过滤系统经常把空格列为高危字符因为在Shell里空格天然是参数分隔符WAF宁可错杀也不放过。但你得知道Shell不只有一个分隔符。Linux系统里${IFS}是默认的内部字段分隔符默认包含空格、制表符和换行所以cat${IFS}/etc/passwd可以完全绕开空格检测。更辣的写法是cat$IFS$()IFS/etc/passwd中间这截$()IFS利用了命令替换的特殊语法过滤器很难识别出IFS的上下文位置。还有些场景里制表符\t也能直接替代空格尤其当过滤规则只匹配\s这个正则集合时可能因为字符集配置缺失而漏掉制表符。不要小看这种细枝末节我遇到过好几次整个站点的过滤就是死在一个\s的兼容性问题上。内存里有一个原则先摸清哪些字符被禁再去查解析器能接受哪些等价字符。两者之间的差集就是你的绕过空间。3. 实战拆解命令执行场景里的关键词过滤器绕过命令执行是RCE场景里最直接的分支也是关键词绕过技术发挥得最集中的地方。我们先从Unix Shell的视角来拆因为这些手法可以平滑迁移到Java、Python、PHP等后端的系统调用里。3.1 空格被禁之后的完整替代链空格被禁算是最常见的初级关卡。我从最稳定到最冷门的排列给你过一遍首先是最常规的${IFS}基本十有八九能过。写法是ls${IFS}-la注意${IFS}和前后的命令之间不需要手动补空格因为变量展开之后就是空白字符本身。其次是$IFS$()。这个写法的妙处在于它能进一步干扰基于正则/\s/的过滤因为源码层面你看到的只是$IFS$()六个字符。它的作用是在空命令替换的结果和IFS之间形成两次拼接等效于一个空格。再然后是制表符%09。在GET或POST传参场景里直接URL编码提交即可Nginx和Apache在解码后会把制表符原样传给后端而Shell同样把制表符视作分词符。实测下来对基于/\s/的过滤偶尔能突破。换行符%0a在一些解析坏境里也能起到命令终结的作用但效果不稳定我建议当奇招用而不是主攻方向。最后还有个思路是借壳生蛋比如用{ls,-la}这种Bash的花括号展开写法。Bash把逗号当作分隔符空格完全不需要出现。注意这个语法有版本要求写到旧版本BusyBox里可能不识别但凡是GNU Bash 3以上基本稳。3.2 命令词被禁之后的高阶绕过链命令词被禁比空格被禁难缠得多因为黑名单会把同类危险操作几乎全部筛一遍。这时候要打的牌就是变量拼接和命令替换。变量拼接的经典链是awho;bami;$a$bwhoami明文完全不落盘过滤器看到的是两个赋值语句和一个变量引用。但有个前提$a$b这种紧凑写法只适用于变量值直接拼接成命令名的场景如果命令名需要和参数分隔那么$a$b$IFS-flag得自己算好边界。另一条链是反引号里的子命令替换echo whoami反引号内部先把whoami字符串echo出来再交给Shell当成命令名去执行。过滤器的正则在这种嵌套结构前经常抓瞎。类似的还有$(echo whoami)的展开方式功能等价但写法略有区别。实际测试时我喜欢把拼接和替换组合在一起打$(echo awho;echo bami;$a$b)。这样整个Payload在明面上只有who和ami两个碎片正则匹配whoami永远找不到连续序列。3.3 文件读取与目录探测的特殊思路当目标是读取敏感文件时容易被过滤的词集中在/etc/passwd、flag.txt这类关键词上。我的经验是先把“路径词”从黑名单思维里摘出来改用“路径等价”的思路处理。双斜杠逃逸是最朴素的一种。Linux内核在解析路径时会自动压缩重叠斜杠所以/etc//passwd和/etc/passwd完全等效。若目标是/flag可以换成/fla/g、///flag、/./flag这类变体。.表示当前目录但系统对路径中的.也不是不做解析而是会归一化成前面一级路径所以./flag在某些逐字节过滤的系统里能卡进盲区。更隐蔽的是利用Procfs的符号链接链。/proc/self/root/etc/passwd等价于根目录下的/etc/passwd但对匹配规则来说即使它包含etc/passwd也找不到一个独立的连续目标词。此类路径同义映射思路在拿不到绝对路径时常常能救我一把。还有个值得记录的技巧是利用Shell通配符构造路径。如果过滤规则禁止的字符是f、l、a、g四个字母的组合但没限制?那么cat /fla?能匹配到/flag文件。通配符的规避能力不在于绕字符串而在于让过滤器拿到的请求里根本不存在那个敏感画面。4. 绕过战争升级WAF与RASP场景里的关键词过滤对策你要是只对付CTF模拟环境上面这些手法已经够横扫一片了。但现实里的WAF和RASP不会给这么宽的活路它们大多带着语义分析引擎和上下文检测绕法就得再上一个台阶。4.1 多层解码差异寻找WAF的“视差盲区”大部分WAF是串接在Web服务器与后端之间的透明代理。它接受原始请求后先做一层标准解码再用解码后的内容做匹配。如果你的Payload被WAF的第一层解码还原成了裸的关键词那就完了。但如果目标后端用了自定义解码逻辑、底层框架再做了一次额外解码或者网络链路里存在多个中间层解码顺序和解码次数就会产生视差这就是可利用点。一个典型例子是二次URL编码。部分WAF只做一次urldecode你提交%2577%2568%256f%2561%256d%2569时WAF解码一次得到%77%68%6f%61%6d%69它拿这段半成品去匹配whoami自然不会命中。请求继续往后传后端应用又做了一次urldecode还原出原始命令词。这个链条能成立的前提是后端确实存在二次解码环节比如一些框架会先解码Path再在业务逻辑里解码Body里的参数。要验证这种盲区我习惯的做法是用一个无害命令如id先用一层编码提交再用两层编码提交比对响应时间、报错信息、长度差异来判断是否存在二次解码。记住编码绕过的核心不是把Payload加密而是探测解析链路上每个节点的解码次数。4.2 入参位置的突破寻求过滤规则覆盖不到的“信使”WAF通常会重点监控?cmd、?exec这种“看着就像参数名”的位置。可很多业务系统会把命令参数的字段名伪装成普通参数比如?filenamextime123但filename字段会被后端拼到Shell命令里执行。此时WAF的规则集正忙着盯cmd、exec对filename这种常规字段里的恶意内容疏于防范。这个思路的延伸是HTTP动词和Content-Type的切换。同样一个cmdwhoami放在GET参数里会被拦但换到POST的BODY里、且Content-Type设定为application/octet-stream某些WAF就完全不解析Body内容了。后端只要照常读取命令照样执行。我管这叫“借道绕过”——不是把关键词藏起来而是把关键词挪到规则覆盖不到的物理区域里。还有一招是把数据拆到Header里比如X-Custom-ID: whoami后端脚本再去取这个Header拼命令。多数WAF对自定义Header的策略更宽松检测强度远不如标准参数。当然这要求后端本身写了这种取Header的逻辑属于特定代码场景下的定制化绕过可遇不可求。4.3 RASP环境下的思路转变绕过检测点而不是绕过关键词RASP运行时应用自保护比WAF狠的地方在于它在应用内部做检测能在代码执行链路里追踪危险函数调用。这时候你再怎么编码、拼接关键词都没用因为它要拦的不是“字符串像不像危险词”而是“有没有高危函数被执行”。绕过思路就必须从“改变字符串形态”切换到“转移执行通道”。一个现实思路是寻找那些不在RASP检测列表里的命令派发点。比如PHP的popen、proc_open往往被RASP盯得很死但parse_str配合变量覆盖能造成命令参数注入RASP如果只监控命令函数而不监控变量机制就有空子。还有一类是调用系统的“间接发令官”比如Java里的ScriptEngineManager执行JavaScript再从JS层发起系统调用。RASP的Hook如果没覆盖到JS引擎内部就等于给你开了后门。另外RASP对抗里“参数污染”也值得研究。如果目标函数是Runtime.getRuntime().exec()参数数组里的第一个元素是命令名第二个是参数有的RASP只匹配整个数组的字符串拼接结果。遇到这种实现你可以在参数里混入只是“长得像命令”的安全串比如-h、--version来让匹配引擎截断到安全路径。但这个依赖具体拦截器对命令解析边界的理解偏差需要现场探测。5. 关键词绕过的自动化探测与脚本化尝试手动测绕过来回耗时太长而且容易把有限的时间浪费在无效Payload上。我在项目里更喜欢把探测写成半自动脚本先枚举过滤规则再自动组合绕过Payload用二分法定位到底哪个环节放行了。5.1 探测规则集的第一步喂边界输入建立行为基线在准备Payload之前我会先做一套“无攻击流量”的基线测试。提交一些安全词比如whoami、cat /etc/passwd看过滤器的返回状态码、响应体长度、关键字是否被脱敏。然后提交纯编码版本、纯拼接版本、纯注释版本对比差异收集拦截逻辑的行为轨迹。这些数据能告诉你三件事过滤器在做请求阻断时是统一返回403还是定制了状态码它会不会把恶意内容替换成空字符串而不是拒绝请求它对编码字符是拦截还是放行。有了基线后面每条Payload的测试结论才具备可比性。5.2 组合Payload生成从手法池里随机组装我的手法池大概有七八个维度包括编码类型、拼接风格、分隔符、注释符、路径等价、变量替换、大小写变形、通配符。脚本化的核心是拿这些维度做笛卡尔积组合而不是每条手工写。比如目标是执行whoami我可以把“命令词变形”和“空格替换变体”分别抽出来然后组合成$a$b${IFS}加变量赋值cat$IFS加单引号拼接$(printf whoami)加反引号子命令脚本每轮提交然后在结果里标记“放行/拦截/报错/超时”跑过几百条之后能迅速筛出一批高概率可用组合再手工针对系统特征修正。这个流程划不划算取决于目标数量但至少在我审计多个同一框架的项目时一次建模多次复用的收益相当可观。用脚本探测时有个很容易忽略的问题很多WAF有频率限制同一IP一秒钟几十条请求直接封IP。所以我会在脚本里加入随机延迟和UA随机化把请求节奏控制在每5到10秒一条避免因为自己的探测动作把目标搞到不可用。5.3 快速验证绕过的正确姿势最后一步是验证。不要只看响应码200就宣布突破因为有的过滤策略是“静默吞掉”恶意内容返回200但实际命令并未执行。我的习惯是构建一个带有可观测副作用的命令比如sleep 3配合计时或者echo 任意独特标记查看回显。只有确认了“命令真的被系统执行了”这次绕过才算闭合。时间盲注式的验证尤其好用。ping -c 1 127.0.0.1再加sleep或者直接sleep 5如果响应时间对得上说明执行确实发生过滤规则被完全绕过。6. 绕不过去的时候怎么办场景化复盘与替代通道前面讲了不少成功案例但现实里更多时候是绕不过去。关键词过滤层层叠叠就算把编码拼接通通用上还是被拦得很彻底。这时候强迫自己和某个Payload死磕没有意义更好的做法是切换思路从另一个维度逼近目标。6.1 从“攻破关键词规则”转向“寻找免杀入口”如果过滤规则把命令相关的关键字全部匹配掉了那说明目标对RCE类攻击已经做了比较完善的结构化防护。不妨把注意力转移到免杀入口上不直接把恶意命令打进请求而是先把Payload脚本落到目标服务器上再在第二步触发执行。上传入口是小菜一碟的后门。比如目标存在文件上传功能传一个伪装的图片马内容里藏着一句话脚本然后再通过某个已有功能点去包含这个文件。在这个过程里命令关键词从未出现在请求参数中WAF的规则集自然无从拦截。这个手法的前提是目标有可用的落地通道但很多系统都有头像上传、富文本图片、附件导入这类功能找个落地点的难度远低于“破解一条完美的绕过链”。还有一类免杀思路是“借尸还魂”利用目标自身已有的功能做代理。比如服务器上本来就有定时任务或者消息队列消费端攻击者只要能把自定义命令注入到任务描述字段里等待触发即可。对过滤器来说它只看到了一个普通的任务描述文本而不是什么高危命令调用。6.2 跨语言二次转换把命令词变成另一种语言的产物当目标允许执行非Shell类语言时可以考虑跨语言的二次转换来绕过关键词检测。比如目标是PHP环境直接调用system(cat /etc/passwd)大概率被拦但能先写一段PHP脚本执行php -r echo shell_exec(...)再让脚本内容从数据库或外部文件读取命令词。这样请求参数里不出现任何命令关键字命令藏在数据层而WAF通常不深查数据库内容。Java端的思路类似。可以把命令写进SpEL表达式、OGNL表达式或者模板引擎的表达式里表达式文本通常不会被当成命令词扫描。例如提交#{T(java.lang.Runtime).getRuntime().exec(...)}这种模板注入形态过滤规则要拦的是Runtime还是exec还不一定但表达式本身游离在主关键词列表之外命中率低很多。这类跨语言方案的技术门槛在于你要同时熟悉前端语言和后端执行机制但收益也很明显它绕开的不是某一条过滤规则而是整个“关键词过滤”这种防御模型。6.3 限制条件下的情报收集先摸清过滤边界再定策略如果暂时没有可用的替代通道那就老老实实收集情报。我会逐一提交无害的变量名、运算符、括号、编码标记通过响应的差异判断过滤器对每个字符的容忍度。这个阶段就像在拼一幅“字符关卡地图”知道哪些字符绿灯常亮、哪些字符红灯常亮之后设计Payload就能避开雷区。举个例子假如测试发现单引号、双引号、反斜杠、$()全部被拦但花括号展开符{}和通配符?*没被拦那么命令字词再难绕路径层面的处理还是有空间。至少可以用花括号展开和通配符把文件名灵活拼凑出来。这种基于信息的“定量绕过”成功率远高于东一榔头西一棒子的盲试。7. 事后反思关键词绕过的边界与防护方的改进思路写这篇内容前我又回看了过去几年记录下来的绕过案例有一个体会特别深绕过的本质不是“更聪明地攻击”而是“防御方的规则颗粒度不够细”。大多数被绕过的系统问题不在黑名单不够长而在匹配维度过窄、解析链路差异太大。7.1 作为攻击方得知道什么时候该收手关键词绕过虽然是个有趣的技术课题但必须清楚它的应用边界。在没有授权的前提下对任何第三方系统做绕过测试都是违规的这个底线不能破。即使是在授权测试中绕过的最终目的是帮客户补上真正的漏洞而不是炫技。我在每次报告里都会把绕过的过程写成防御方可复用的规则建议让客户知道该在哪一层加检测。7.2 作为防御方哪些措施才能真正堵住关键词绕过如果你在写WAF规则或者做代码审计我建议从下面几个方向加固它们能实质性减少被绕过的概率第一把检测从“字面匹配”升级到“执行语义链路”。不是看请求里有没有system而是追踪参数值最终是否流入危险函数。这是RASP的思路也是对抗关键词绕过的长期方案。第二对解析器做统一和收敛。如果业务必须执行命令强制使用白名单式的命令名映射而不是直接拼接外部输入。这个能从根本上消灭绝大多数拼接绕过。第三对编码层做循环解码后再匹配。WAF不止解码一遍要反复解码直到没有新的可解码内容再丢进过滤引擎。单纯依赖单次解码的规则永远是绕过者的提款机。第四建立针对函数参数边界的防护。命令执行时分清命令名、选项、参数、路径几个层级不同层级走不同检测规则而不是所有输入一把梭。命令名里出现cat的检测力度必须和参数值里出现cat完全不同。我对这个题目的最终态度是关键词绕过技术的价值在于拓宽你看待过滤系统的视角它让你意识到“看上去做了过滤”和“实际能挡住攻击”之间还有巨大鸿沟。遇到困难时不妨换个角度想真正的安全从来不是靠堆关键词堆出来的。希望你在这套梳理里获得的不只是几个Payload而是以后面对RCE目标时心里能多一张清晰的作战地图。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑