首页/新闻资讯/正文详情

命令注入空格过滤绕过:$IFS等五种Shell技巧与防御

发布时间:2026/11/3 11:36:16 来源:云帆数科 栏目:资讯中心
命令注入空格过滤绕过:$IFS等五种Shell技巧与防御
命令注入这道题很多人卡住的地方不是找不到注入点而是空格被过滤掉之后 payload 完全不会写了。我前几天带一个刚入门的朋友做题他发过来一句;cat /flag说“明明有回显为什么这条就哑了”。我让他把请求抓下来看服务端返回的是Invalid input——不是命令没执行是那个空格在入口就被拦掉了。CTF 命令注入里过滤空格几乎是成本最低、收益最高的一道门槛一行preg_match(/\s/, $input)或者更粗暴的strpos($input, ) ! false就能让一大批只会背;cat /flag的人当场停摆。这篇东西就是把这层窗户纸捅破。我会把$IFS、制表符、输入重定向、大括号扩展、变量子串这五种绕过空格过滤的 Shell 技巧掰开揉碎讲清楚包括它们各自的原理、适用哪种 shell、什么情况下会失效再补上从探测过滤规则到拿结果的完整实操流程最后从写代码的角度说说怎么把这类口子真正堵死。不管你是刚接触命令注入、还在背 payload 的阶段还是已经做过几道题但总在细节上翻车应该都能从里面捞到点东西。所有演示都在本地搭建的模拟环境和授权靶场里完成别拿去碰不属于你的系统这条底线必须先说清楚。1. 先想明白命令注入与空格过滤的整体设计1.1 出题人为什么偏偏盯着空格不放要理解绕过先得理解对面是怎么想的。命令注入的本质是把用户输入拼进一条 shell 命令字符串里然后交给/bin/sh -c去执行。而 shell 的语法解析里空格和制表符是词与词之间最重要的分隔符——cat /flag能被正确解析成“命令 cat 参数 /flag”靠的就是中间那个空格。一旦空格没了cat/flag在 shell 眼里就是一个完整的单词是一个根本不存在的命令名自然执行不起来。所以过滤空格这招的性价比极高它不需要理解业务逻辑不需要分析参数结构只需要拦住一个字符就能让绝大多数“直接拼接参数”的 payload 失效。更妙的是正常业务里用户输入带空格的场景并不多比如一个 ping 检测功能输入的是 IP 地址过滤掉空格对功能几乎没有影响误伤率很低。这也是为什么在各类演练题和真实的老系统里这种过滤方式反复出现。但这里有个关键点很多人没意识到过滤空格 ≠ 阻止命令注入。shell 的分词规则远比“空格分隔”复杂能起到分隔作用的字符和语法结构有一大把。出题人堵的是最宽的那扇门窗户还开着。理解这一点你就知道绕过不是投机取巧而是对 shell 词法分析lexical analysis的一次正面拆解。还有一层设计意图值得琢磨过滤往往发生在不同的阶段。有的系统在 URL 解码前过滤有的在解码后过滤有的对原始输入做正则匹配有的在拼接成完整命令后才做检查。过滤的位置决定了你能不能靠%20这种编码手段直接蒙混过去。这一点我在 2.2 节里会展开讲它是判断“该用哪种技巧”的第一手情报。1.2 五种技巧的选型逻辑与优先级在动手构造 payload 之前我习惯先在脑子里排一个优先级队列。理由很简单不同技巧对 shell 类型的依赖程度不一样而你在黑盒状态下往往不知道对面跑的是 bash 还是 dash是完整 shell 还是被裁剪过的 busybox。选错了技巧payload 看起来没问题实际就是不生效然后你就会开始怀疑人生。我的默认优先级是这样的先试$IFS再试制表符然后试重定向符最后才考虑大括号扩展和变量子串。这个顺序不是随便排的背后是兼容性从高到低的考量。${IFS}是 POSIX 标准里的东西bash、dash、ash、busybox sh 全都认通用性最好制表符作为空白字符同样被所有 shell 当作分隔符但它有个隐患——传输过程中容易被各种环节吞掉或转义重定向也是 POSIX 标准但它只对“从标准输入读数据”的命令有效id、whoami这类命令根本不读 stdin用不上大括号扩展{cat,/flag}是 bash 和 zsh 的扩展特性dash 完全不支持${IFS:0:1}这种子串语法更是 bash 专属换到 dash 上直接报语法错误。我把这五种技巧的关键属性整理成一张表做题时对着查能省不少时间技巧依赖的 shell兼容性典型 payload主要失效场景$IFS/${IFS}POSIX 通用极高;cat${IFS}/flagIFS 被清空、$IFS字符串本身被关键词过滤制表符%09全部高;cat%09/flag传输链路把 TAB 转成空格后被拦、或被规范化输入重定向POSIX 通用高;cat/flag目标命令不读 stdin如id、ls大括号扩展{a,b}bash / zsh中;{cat,/flag}跑在 dash / ash / busybox 上${IFS:0:1}子串bash低;cat${IFS:0:1}/flag非 bash 环境、或 IFS 被置空这张表里“兼容性”那一列是我自己踩坑踩出来的经验值不是理论推演。有次在一道题里死磕{cat,/flag}折腾了半小时最后换成${IFS}一秒过——对面容器里/bin/sh软链到的是 dash大括号扩展压根不存在。从那以后我养成了习惯先用最通用的技巧把流程跑通确认可用之后再考虑更花的写法因为做题的目标是拿到结果不是炫技。2. 五种 Shell 技巧的逐一拆解2.1$IFS与${IFS}兼容性最好的一张牌IFS是 shell 的内部字段分隔符Internal Field Separator它决定了 shell 在做单词拆分word splitting时用什么字符作为边界。默认值就是空格、制表符、换行符这三个空白字符。当你在命令里写一个未加引号的$IFSshell 会先把它替换成那三个字符然后对替换结果再做一次分词——这一步是整个技巧成立的关键也是很多人理解不到位的地方。来看具体效果。假设服务端执行的命令是ping -c 1 $input你提交127.0.0.1;cat${IFS}/flag拼接后变成ping -c 1 127.0.0.1;cat${IFS}/flag。shell 解析时${IFS}展开成空白字符分词后cat和/flag成为两个独立的词命令正常执行。你可以自己在本地验证# 本地验证 $IFS 的分词效果 sh -c echo${IFS}hello # 输出hello sh -c echo${IFS}hello${IFS}world # 输出hello world这里有个必须记住的坑${IFS}不能包在双引号里。${IFS}会因为引号抑制了分词整个展开结果被当成一个词的一部分效果等同于一个普通空格字符嵌在单词中间cat${IFS}/flag会变成命令名是cat /flag的单个词必然失败。这个细节我在新手身上见过太多次payload 看着一模一样就差一对引号结果天壤之别。另一个细节是$IFS$9这种写法里的$9。它不是随便加的作用是隔断变量名。因为 shell 解析变量名时会贪婪匹配$IFS/flag里的/不是合法变量名字符所以没事但如果后面跟的是字母数字比如$IFSabcdshell 会试图解析变量IFSabcd而不是$IFS加abcd。加一个$9第九个位置参数通常是空的就能把它断开$IFS$9abcd展开为“空白 空 abcd”。同理还有${IFS}的花括号写法本身就能界定变量名边界所以我个人更偏好${IFS}可读性好也不容易踩拼接的坑。还有一个容易忽略的问题IFS 有可能不是默认值。某些加固过的环境会在启动脚本里IFS或IFS$\n这时候$IFS展开出来可能什么都没有或者只展开成换行符换行符是命令分隔符不是参数分隔符payload 就直接哑了。遇到这种情况就得往 2.5 节的变量子串或重赋值技巧上走。另外如果出题人聪明到把IFS这三个字本身加进关键词黑名单$IFS和${IFS}都会被拦——别觉得这不可能我在不止一道题里见过str_replace($IFS, , $input)这种处理虽然它本身写得就不严密大小写、子串拼接都能绕但确实会挡住一批人。2.2 制表符与不可见字符看不见的分隔符如果$IFS路线走不通下一个我会试真实空白字符里的制表符TAB十六进制 0x09。理由很直接shell 在做词法分析时空格和制表符的地位是等同的都属于“空白”类别都能分隔参数。而过滤规则里最常见的写法是匹配字面的空格字符 或者正则/\s/前者拦不住 TAB后者才拦得住。构造方式上制表符在 URL 里要写成%09# 提交的原始输入 127.0.0.1;cat%09/flag # 服务端 URL 解码之后 127.0.0.1;catTAB/flag这里就引出一个非常关键的判断点过滤发生在 URL 解码之前还是之后。如果过滤是在原始请求字符串上做的比如在 Web 框架的路由层之前那%20空格编码本身就能直接过因为过滤看到的是%20这三个字符而不是空格。如果过滤是在解码之后做的比如取到$_GET参数值再校验那%20会先被还原成空格照样被拦而%09还原成 TAB 却能过因为过滤规则只盯着空格字符。所以%09能过不是因为它“编码了”而是因为它的解码结果本身就不在过滤名单里。这两者的区别是判断规则严不严的重要依据。再说换行符%0a。很多资料把它和制表符混在一起说“用空白字符替代空格”这是不准确的。在 shell 里换行符是命令分隔符作用等同于分号而不是参数分隔符。cat%0a/flag会被解析成“执行 cat然后执行 /flag”是两条独立命令跟cat /flag完全不是一回事。换行符真正的用途是在分号被过滤时顶替分号# 分号被过滤时用换行符分隔两条命令 127.0.0.1%0aid 127.0.0.1%0acat${IFS}/flag顺带说一句除了%0a能起命令分隔作用的还有|管道%7c、、、||。其中管道和逻辑运算符还有个附赠好处如果;被过滤但空格没被过滤直接换分隔符就行。这几个符号在探测阶段值得挨个试一遍服务端响应会告诉你哪些能用。注意制表符在实际传输链路里是最脆弱的一个技巧。某些中间件、日志组件、参数解析库会对输入做规范化处理把 TAB 转成空格或者直接裁掉首尾控制字符。如果你确认%09的 payload 在本地能跑、打过去却没反应优先怀疑传输链路而不是语法本身。我的做法是先用;id确认注入存在再用%09打一次两次响应的差异基本能定位问题在哪。2.3 输入重定向让命令吃文件而不是拿参数第三种是我个人最喜欢的一招因为它完全不依赖变量展开语法上非常干净用输入重定向符替代空格。原理是这样的cat /flag的意思是 cat 命令带一个参数/flag而cat/flag的意思是 cat 命令不带参数但标准输入被重定向到文件/flag。对于 cat 这类“不读参数就从 stdin 读”的命令两种写法的输出结果一模一样但后者里一个空格都没有。# 三种等价写法 cat /flag # 常规 cat/flag # 输入重定向 cat/flag # 双向重定向同样有效为什么不需要前后加空格因为是 shell 的操作符operatorshell 在做词法分析时会把它识别为独立的元字符前后可以直接紧贴其他字符。cat/flag会被正确切分成cat、、/flag三个 token。这就是它能绕过空格过滤的根本原因——它利用的是操作符的词法边界而不是空白字符的分隔作用。这招的局限也很明显它只对读标准输入的命令有效。cat、tac、head、tail、more、sort、grep、nl、wc这些都没问题id、whoami、uname、ls这类不读 stdin 的命令就完全无效——你写id/flagid 照样执行并输出当前用户信息那个重定向只是把它的 stdin 换成了文件不产生任何额外效果。不过有个隐藏玩法值得单独提它可以和输出重定向组合写文件。如果题目允许写文件比如有可写目录你可以用重定向把命令拆成“先写、再执行”两步彻底绕开所有对空格和特殊字符的过滤# 把命令逐段写进文件再执行脚本 echo${IFS}cat${IFS}/flag/tmp/x sh/tmp/x这个思路的核心是把 payload 拆成数据和执行两个阶段让真正执行的那条命令尽可能短、尽可能干净。文件内容里的空格不受入口过滤规则影响因为它不是通过 HTTP 参数进来的。我在一道过滤了几乎所有特殊符号的题里就是靠这招脱身的——能用的只有重定向和几个基础命令靠 /tmp/x逐步拼出一个可执行脚本最后sh/tmp/x一跑完事。2.4 大括号扩展{a,b}白送的分隔效果{cat,/flag}这个写法第一次见会觉得有点玄学其实逻辑很简单。大括号扩展brace expansion是 bash 的一个特性{a,b,c}会被展开成三个独立的词词与词之间天然就是分开的完全不需要空格参与。所以;{cat,/flag}在执行前会被 bash 预处理成;cat /flag这里的空格是展开过程的产物不是你的输入。# 本地验证大括号扩展 bash -c echo {cat,/flag} # 输出cat /flag它的优点是payload 极短且完全不含空白字符对只过滤空格的题一击必中。而且展开是发生在解析阶段的比变量展开更早不会被关键词过滤掉——因为{cat,/flag}这个字符串整体根本不含 “cat ” 这样的黑名单片段。但它的短板也很要命只有 bash 和 zsh 支持dash、ash、busybox sh 全都不认。而容器环境里/bin/sh软链到 dash 的比例相当高早期的 Debian 系默认如此所以这招的成材率并不稳定。还有一个细节大括号扩展要求{位于词的开头位置前面不能紧贴普通字符。x{cat,/flag}不会被展开因为{前面是x。你在构造 payload 时{前面要么是命令分隔符;、|、、要么是行首这一点在拼接长 payload 时要留意。实测下来我的经验是这招适合作为备选不适合作为首选。先用${IFS}把注入点验证通了再用大括号扩展做“去空格化”的变体两条路都试哪条能用哪条。遇到 bash 明确存在的场景比如题目给了bash -c的提示或者能通过报错信息判断出 shell 类型优先用大括号payload 更短更清爽。2.5 变量子串与 IFS 重赋值对付进阶过滤的变形最后这两种属于“进阶变形”专门用来对付那些把$IFS也一起过滤掉的题。它们的共同思路是不直接写$IFS这个字符串而是用其他语法从 IFS 里“取”出一个空格字符。第一种是 bash 的子串扩展${IFS:0:1}意思是“从 IFS 变量里从偏移 0 开始取 1 个字符”结果就是 IFS 的第一个字符——空格。# bash 子串扩展取空格 bash -c echo${IFS:0:1}hello # 输出hello # 完整 payload ;cat${IFS:0:1}/flag这个写法成功绕过了关键词检测因为完整的字符串里不含$IFS它被${IFS:0:1}这个更长的形式覆盖了但展开结果仍然是空格。缺点是它同样只在 bash 下有效dash 会报Bad substitution。第二种更巧妙直接重赋值 IFS。既然默认的 IFS 是空格、TAB、换行那我把它改成一个不敏感的字符比如逗号之后用逗号当分隔符就行了# 把 IFS 改成逗号之后逗号就是分隔符 ;IFS,;cat,/flag这里要注意执行顺序。IFS,是一条独立的赋值语句它会修改当前 shell 环境的 IFS对后续命令生效。所以必须分成三段先断开原命令、再赋值、再用新分隔符执行目标命令。由于;本身也是分隔符整串下来不需要任何一个空格。这种思路的迁移性很强。除了逗号你还可以用IFS、IFS%、甚至IFS/。唯一要注意的是选一个在 payload 里不会和目标路径冲突的字符——比如IFS/就和/flag里的斜杠撞车了展开会乱掉。提示这三类进阶变形都有个共同前提——你得先确认$IFS到底有没有被拦。做法很简单先打;cat${IFS}/flag如果回显正常说明 IFS 可用别急着上复杂写法如果报错或没反应再把${IFS:0:1}和IFS,依次试一遍。跳过验证直接上最复杂的 payload是浪费时间的最快方式。3. 完整实操从探测到拿到结果的流程3.1 第一步永远是探测搞清楚到底过滤了什么我见过太多人一上来就开始背 payload打一个不行换一个打了个遍最后连对面过滤的是空格还是分号都没搞明白。正确的顺序是先做过滤规则探测把输入空间的地图先画出来再决定用哪条路。假设靶场是个模拟的“网络诊断”页面输入框提示填 IP服务端执行的应该是ping -c 1 输入之类的逻辑。我先用最基础的方式确认注入是否存在输入127.0.0.1;id 结果正常返回 ping 结果 uid33(www-data) 等多行输出有回显、能看到id的结果说明命令注入确实存在而且分号没被过滤。接下来逐个测试关键字符的可用性我通常用一个固定的探测序列测试项输入判断依据分号127.0.0.1;id有 id 输出则可用空格127.0.0.1;cat /flag报错或无输出则疑似被过滤制表符127.0.0.1;cat%09/flag有输出则 TAB 可用管道127.0.0.1|id有 id 输出则管道可用$IFS127.0.0.1;cat${IFS}/flag有输出则 IFS 可用这个探测序列的价值在于它把“payload 不生效”这个大问题拆成了一组可独立验证的小问题。空格到底拦没拦$符号拦没拦花括号拦没拦一条条测下来能用的字符集合就清楚了后面构造 payload 就是在这个集合里做组合。有个细节容易被忽略报错信息本身就是情报。如果提交;cat /flag之后页面返回 “Illegal character detected”那说明过滤逻辑在拼接之前就拦下了如果返回的是 shell 的语法错误比如sh: 1: cat: not found或者-c: line 1: unexpected EOF那说明输入已经进到 shell 里了只是语法不对。这两者的差别极大前者意味着你得在字符层面绕后者意味着你只需要修语法。别急着往下打先把这句报错读明白。3.2 逐层构造 payload 并验证探测结果显示空格被拦了但;、$、{}都还能用。那接下来就是按优先级逐个尝试每试一条就记录结果不要一次改多个变量。第一条$IFS127.0.0.1;cat${IFS}/flag如果回显里有 flag 内容收工。如果没有先别急着换技巧看看报错是什么。如果报的是cat: /flag: No such file or directory那说明空格已经绕过去了问题在路径这时候该找的是 flag 的真实位置常见的还有/flag.txt、/f1ag、当前目录下的flag而不是继续折腾空格。这个区分非常重要我见过有人因为路径错了把五种绕过技巧全试了一遍。第二条制表符127.0.0.1;cat%09/flag第三条重定向127.0.0.1;cat/flag这条有个额外好处cat/flag里连$都没有即使$被过滤也能用。所以如果你的探测发现$和空格同时被拦重定向就是首选。第四条大括号扩展127.0.0.1;{cat,/flag}如果前三条都不行、这条也不行大概率是 dash 环境如果这条行了说明是 bash后面所有 bash 专属技巧子串、$\x20之类都可以放心用。反过来这也是一次shell 类型探测——用大括号扩展的成败来判断对面是 bash 还是 dash比看报错信息还准。第五条变量子串127.0.0.1;cat${IFS:0:1}/flag到这一步基本能覆盖 90% 以上的空格过滤场景了。如果五条全废那说明过滤规则比想象中严可能同时拦了空格、$、{}、这时候要转向“写文件再执行”或者编码层面的绕法思路在下一节展开。还要补一个容易被忽略的检查项你确定拼进去的位置对不对吗。有些题目把输入拼接在命令的中间或者末尾比如ping -c 1 $input -w 2你注入的;id后面还跟着-w 2。这时候 payload 末尾最好补一个注释符#记得 URL 编码成%23把后面的内容注释掉否则可能因为参数错乱导致命令执行失败。;id%23是我常用的收尾方式代价只是一个字符能避免很多莫名其妙的问题。3.3 无回显怎么办盲注与写文件两条路现实里更麻烦的情况是命令执行了但你看不到输出。服务端可能只返回“操作完成”或者把命令输出重定向到别处甚至压根不返回响应体。这时候 payload 对不对靠肉眼是看不出来的得换一套验证方法。第一条路是时间盲注。用sleep构造一个可观测的延时通过响应时间判断命令是否执行# 如果命令执行了响应会延迟 5 秒 127.0.0.1;sleep${IFS}5 127.0.0.1;sleep%095时间盲注的好处是极其可靠只要你能测出响应时间差就能确认执行链路是通的。缺点是慢而且每次只能验证“执行了没”拿不到具体内容。进阶一点可以用条件判断把数据一位一位地“问”出来比如if [ $(cut -c1 /flag) f ]; then sleep 5; fi这种构造但那属于盲注的范畴本篇不展开。第二条路是写到可访问目录。如果目标有 Web 目录且可写把命令输出重定向到那里然后用浏览器直接访问# 把命令结果写到 web 目录下的文件 127.0.0.1;id/var/www/html/o.txt 127.0.0.1;cat${IFS}/flag/var/www/html/o.txt # 然后访问 http://靶场地址/o.txt这条路的前提是你得知道 Web 根目录在哪以及运行服务的用户对那个目录有写权限。常见的探测方式是用pwd、ls看当前目录结构或者直接试几个常见路径。我一般会先用;id/tmp/o.txt测一下写权限/tmp通常全世界可写确认能写之后再去猜 Web 目录。注意写文件这条路在真实环境里属于高风险操作可能覆盖已有文件、触发告警、留下痕迹。在靶场里随便玩没问题但只要脱离授权环境这就是不能碰的红线。这条界限必须刻在脑子里。4. 踩坑实录常见问题与排查速查表4.1 payload 看起来对但没反应按这个顺序查这是我整理的一张排查顺序表基本上覆盖了我遇到过的 80% 的“payload 不生效”场景。关键是按顺序查不要跳步因为后面的可能性往往以“前面的都排除了”为前提。排查顺序检查项判断方法处理方式1注入点是否真的存在用;id、|id单独测无回显则说明注入本身不通2拼接位置有没有后缀干扰看 payload 后面是否跟着原命令的参数payload 末尾加%23注释掉3分隔符是否可用分别测;、|、、%0a换一个可用的分隔符4空格是否绕过成功用ls${IFS}/看是否有目录列表换 2.x 节的其他技巧5路径是否正确看报错是No such file还是语法错误find / -name flag*或猜常见路径6是否有权限看报错是否Permission denied换可读文件或看能否提权7输出是否被吞用sleep 5测时间差改走盲注或写文件第 2 条值得单独展开因为它太常见了。假设服务端代码是exec(ping -c 1 . $ip . -W 1)你注入;id完整命令变成ping -c 1 ;id -W 1。这里id -W 1会被当成带参数的 id 命令执行——id不认识-W直接报错。你看到错误信息可能误判成“注入被拦了”实际上只是参数错乱。加上%23变成;id%23 -W 1后半段被注释掉就正常了。第 5 条也是高频误判点。很多人默认 flag 就在/flag实际情况千奇百怪可能在/flag.txt、/root/flag、/home/ctf/flag、当前目录的./flag甚至是环境变量。与其猜不如用find / -name flag* 2/dev/null直接搜一遍虽然命令长但在有回显的环境里一次到位。第 7 条容易被忽略有时候命令确实执行了但输出被应用层吃掉了。比如 PHP 的exec()只返回最后一行输出你执行;ls${IFS}/拿到的可能只是最后一行还经常是空行。这种情况下用cat/flag反而更容易看到结果因为它只输出一行。4.2 不同 shell 的差异实测比查文档靠谱这部分是我踩坑最多的地方。同样是/bin/sh在不同发行版上可能是 bash、dash、ash、busybox sh 中的任何一个行为差异相当大。我做了一组对照测试结果如下语法特性bashdashbusybox sh${IFS}支持支持支持${IFS:0:1}支持不支持不支持{cat,/flag}展开不展开不展开$...ANSI-C 引用支持不支持部分支持IFS,重赋值支持支持支持输入重定向支持支持支持从这张表能看出一个规律越花哨的语法兼容性越差。所以我一直强调${IFS}和优先——它们是 POSIX 标准里的东西所有 shell 都认。而子串扩展、大括号扩展这类属于 bash 的“方言”跨环境迁移就会翻车。还有一个更隐蔽的差异echo 的行为。bash 内置的 echo 默认不解释反斜杠转义而某些 shell 的 echo 会。这在用echo写文件构造 payload 时会带来麻烦echo -e在 dash 下根本不认-e参数会把它当普通字符串输出。所以写文件时我更倾向于用printf而不是echoprintf的行为在各 shell 之间一致得多。另一个我踩过的坑是$和$*的误用。网上有些资料说可以用$替代空格这说法是不准确的。在 bash 里未加引号的$在没有位置参数时会展开为零个字段直接消失cat$/flag会变成cat/flag这一个词命令名是cat/flag根本不存在。$真正的用途是在不需要分隔的场景里做“空展开占位”用来断开关键词匹配或者变量名边界而不是当空格用。这个区别我当初也理解错了浪费了不少时间。4.3 编码、引号与截断那些看起来不是问题的问题还有一类问题跟语法无关纯粹是数据在传输过程中被改变了。举几个我实际遇到过的例子。第一个是URL 双重解码。有些框架会对参数做两次 URL 解码比如路由层解一次业务层又解一次这时候你传%2509%25是%的编码第一层解码成%09第二层解码成真正的 TAB。如果你只传%09第一层就解成 TAB 了反而可能被某个中间层处理掉。判断方法是如果%09不生效试试%2509看行为有没有变化。第二个是加号被当成空格。在application/x-www-form-urlencoded格式里会被解码成空格。这个特性有时候是救命稻草你可以用传空格绕过只检查原始字符的过滤有时候是隐形杀手你 payload 里真的想传一个加号结果变成了空格。我在一次拼base64解码的 payload 时就被这个坑过——base64 -d里的-d没事但后面跟的字符串里含结果全变成了空格解码直接失败。第三个是引号在 shell 和 HTTP 两层的含义冲突。你想在 shell 里用单引号做字符串拼接cat /flag这种绕关键词的写法但这个请求的参数如果本身被单引号包裹着拼进命令里那就是另一回事了。我一般的做法是在本地用sh -c把完整的命令字符串执行一遍确认语法没问题再去考虑 HTTP 层的编码把这两层分离开来调试比混在一起猜要高效得多。第四个是长度截断。有些系统对输入长度做了限制比如 100 字符你精心构造的长 payload 传过去只剩前半截自然不生效。遇到“短 payload 能过、长的不能过”这种情况先怀疑长度限制。应对办法是把 payload 压缩到最短比如用cat/f*这种通配符写法/f*可以匹配/flag、/flag.txt等一个字符都不浪费。5. 防御侧怎么把这类口子真正堵上5.1 架构层面能不拼接就不拼接说了这么多绕过技巧站在写代码的一方最该记住的一句话是只要存在字符串拼接 shell 执行这个洞就基本堵不干净。原因很简单——你没法穷举 shell 的所有语法变体。黑名单永远是不完备的今天补上${IFS}明天出个{cat,/flag}后天又有人发现用IFS,重赋值。这是一场你永远追不上的军备竞赛。正确的做法是从架构上消灭“把用户输入交给 shell 解析”这件事。最典型的是 Python 的subprocess用列表传参而不是字符串 shellTrueimport subprocess # 危险写法字符串拼接 shellTrue cmd ping -c 1 user_input subprocess.run(cmd, shellTrue) # 千万别这么写 # 安全写法列表传参shellFalse默认 try: result subprocess.run( [ping, -c, 1, user_input], # 每个参数独立成一个列表元素 shellFalse, capture_outputTrue, textTrue, timeout5 ) except subprocess.TimeoutExpired: result None这两种写法的差别不在于“过滤得严不严”而在于用户输入根本没机会被 shell 解析。列表传参时操作系统直接调用execve参数之间由内核分隔shell 这个中间层被完全跳过了。哪怕用户输入是;cat /flag它也只会被当作 ping 的一个参数一个非法的主机名ping 会报错退出而不是执行 cat。这是结构性防御比任何黑名单都可靠。同理PHP 里能用escapeshellarg()就不要裸拼// 用 escapeshellarg 对单个参数做安全引用 $ip $_GET[ip] ?? ; if (!filter_var($ip, FILTER_VALIDATE_IP)) { exit(invalid ip); } $out shell_exec(ping -c 1 . escapeshellarg($ip) . 21);escapeshellarg()的作用是把参数用单引号包裹起来并把内部的单引号转义掉这样整个参数在 shell 眼里就是一个不可分割的字符串任何元字符都失去特殊含义。不过要提醒一句它不是万能的在某些特殊场景比如 Windows 环境、或者参数被拼接进更复杂的结构下仍可能有边界问题最稳妥的仍然是走列表传参或者干脆不用 shell。5.2 白名单校验把输入空间压到最小如果业务上真的必须接受用户输入并参与命令构造那第二道防线是白名单校验——注意是白名单不是黑名单。黑名单的思路是“列出所有危险字符把它们删掉”永远有遗漏白名单的思路是“只允许特定格式其他一律拒绝”风险面直接压到零。以 IP 地址输入为例最严格的校验应该是// 强类型校验只接受合法 IPv4 地址 $ip $_GET[ip] ?? ; if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { http_response_code(400); exit(invalid ip); }filter_var配合FILTER_VALIDATE_IP会把127.0.0.1;cat /flag直接判为非法因为它不是一个合法 IP。这就从根本上杜绝了注入——不是因为过滤了某个字符而是因为整个字符串压根不属于允许的语法。如果输入不是 IP 而是某种标识符用正则做白名单// 只允许小写字母、数字、下划线、连字符长度 1-32 if (!preg_match(/\A[a-z0-9_\-]{1,32}\z/, $name)) { exit(invalid); }这里的\A和\z是锚定整串首尾防止有人用换行绕过^和$^/$在多行模式下会匹配行首行尾这是个经典坑。这个细节看着小但实际渗透中绕过^...$校验的手段就是塞一个换行。白名单校验的另一个好处是错误处理更安全。校验失败时只返回“参数非法”不要回显具体是哪个字符不合法——回显本身就是情报泄露攻击者能根据报错逐步试探出过滤规则。这一点在 3.1 节里强调过从防御方角度看完全反过来别给对面地图。5.3 运行环境与权限收敛万一被突破了损失能有多小前两道防线都失效的情况下现实中会发生毕竟代码是人写的第三道防线是把被突破后的影响范围压到最小。这属于纵深防御的思路核心假设是“总有一天会破”所以提前准备好兜底。几个具体的做法。一是用低权限用户跑服务www-data、nobody这类账号绝对不能是 root。这样即使命令执行成功攻击者拿到的也只是一个权限极低的 shell读不到敏感文件、改不了系统配置。二是用容器隔离把 Web 服务放在独立的容器里只挂载必要的目录根文件系统设为只读。这样即便有人能执行命令也写不进任何持久化内容。三是禁用危险的函数和命令比如在 PHP 里通过disable_functions关掉exec、shell_exec、system、passthru、popen这些在容器里删掉nc、curl、wget这类容易被用来做后续动作的工具。再补一条容易被忽略的限制出站和文件读写的范围。很多命令注入的进阶利用都依赖于“能把数据带出去”或者“能读到敏感文件”如果服务容器的文件系统做了最小化裁剪flag 之类的敏感文件根本不在里面出站的网络访问也被严格限制那即使注入成功攻击者能做的也非常有限。这不是“阻止注入”而是“即使注入了也没啥可拿”属于降低单点失败的破坏力。最后还有日志和告警。命令注入的 payload 里通常含有大量特殊字符;、|、$、{、在访问日志里非常扎眼。给这类请求做个简单的规则告警能让你在被人打穿之前就知道有人在试。这不是防御手段是发现手段但很多事故真正的问题不在于防不住而在于被打了两周都没人知道。提示如果条件允许把“用户可控输入”和“系统命令执行”这两件事在架构上彻底隔离——比如用一个独立的、无 shell 的执行服务只接受结构化参数JSON 里的 host 字段不接受任何字符串命令。这种设计下注入这个攻击面根本不存在比任何过滤规则都省心。我自己在做这类题和看别人做这类题的过程中最大的体会是绕过空格这件事本身并不难难的是知道什么时候该用哪一种。五种技巧背下来只要十分钟但判断“对面是 bash 还是 dash”“过滤是在解码前还是解码后”“没反应到底是语法错还是路径错”这些才是真正拉开差距的地方。我的习惯是每做一道题都记录一份探测日志——试了什么、返回了什么、最后是哪条路通的攒上十几道之后你对 shell 词法的直觉会比看任何教程都来得快。另外别迷信网上抄来的 payload环境一变就得重来把原理搞明白比记住一百条现成的写法有用得多。

相关推荐

生成模型核心技术解析:从原理到应用实践
生成模型核心技术解析:从原理到应用实践

1. 生成模型的基本概念 生成模型是机器学习领域中一类重要的模型,它们能够学习数据的概率分布,并基于此生成新的数据样本。与判别模型不同,生成模型不仅能够进行分类或回归任务,更重要的是能够"创造"新的数据。 我第一… · 2026/11/3 11:36:16

密评测评工具链落地:四类工具、四个层面与密码边界取证
密评测评工具链落地:四类工具、四个层面与密码边界取证

简介:《商用密码应用安全性评估测评工具指引》面向密评机构、商用密码产品厂商及企业安全合规团队,围绕密评工作中测评工具如何选型、部署使用与规范管理提供统一参考。压缩包内含1个PDF文件,大小约3.84MB,内容为中国密码学会密评… · 2026/11/3 11:36:16

C++编译器优化:从循环展开到SIMD指令实战
C++编译器优化:从循环展开到SIMD指令实战

1. 编译器优化:从源代码到机器码的魔法 在C开发者的日常工作中,编译器就像一位隐形的助手,默默地将高级语言转化为高效的机器指令。但很多人可能不知道,现代编译器在翻译过程中会施展各种"魔法"来提升程序性能。以GCC为… · 2026/11/3 11:35:15

45+机器人模型资源完整清单:humanoid-motion-intelligence中URDF、MJCF与USD模型怎么选
45+机器人模型资源完整清单:humanoid-motion-intelligence中URDF、MJCF与USD模型怎么选

【免费下载链接】humanoid-motion-intelligence 人形机器人运动智能论文、开源项目、产业与求职知识库 项目地址: https://gitcode.com/gh_mirrors/hu/humanoid-motion-intelligence 点击查看 免费下载 humanoid-motion-intelligence 知识库整理了 45 个开源机器人… · 2026/11/3 19:02:02

AiSOC GCP Security Command Center 连接器实战:组织级 SCC Findings 接入、轮询与归一化详解
AiSOC GCP Security Command Center 连接器实战:组织级 SCC Findings 接入、轮询与归一化详解

人工智能AI Agent网络安全后端前端 【免费下载链接】AiSOC Open-source AI Security Operations Center: alert fusion, LLM-agent triage, MITRE ATT&CK investigation, and a replayable decision ledger for every agent step. Self-hostable, runs with no API keys, M… · 2026/11/3 19:02:02

校园交流论坛系统|基于springboot + vue校园交流论坛系统(源码+数据库+文档)
校园交流论坛系统|基于springboot + vue校园交流论坛系统(源码+数据库+文档)

校园交流论坛系统 目录 基于springboot vue校园交流论坛系统 一、前言 二、系统功能演示 三、技术选型 四、其他项目参考 五、代码参考 六、测试参考 七、最新计算机毕设选题推荐 八、源码获取: 基于springboot vue校园交流论坛系统 一、前言 博主介绍&… · 2026/11/3 19:02:01

农业管理平台|基于springboot + vue农业管理平台系统(源码+数据库+文档)
农业管理平台|基于springboot + vue农业管理平台系统(源码+数据库+文档)

农业管理平台系统 目录 基于springboot vue农业管理平台 一、前言 二、系统功能演示 三、技术选型 四、其他项目参考 五、代码参考 六、测试参考 七、最新计算机毕设选题推荐 八、源码获取: 基于springboot vue农业管理平台 一、前言 博主介绍&#xff… · 2026/11/3 19:02:01

自习室座位预约管理系统|基于springboot + vue自习室座位预约管理系统(源码+数据库+文档)
自习室座位预约管理系统|基于springboot + vue自习室座位预约管理系统(源码+数据库+文档)

自习室座位预约管理系统 目录 基于springboot vue自习室座位预约管理系统 一、前言 二、系统功能演示 三、技术选型 四、其他项目参考 五、代码参考 六、测试参考 七、最新计算机毕设选题推荐 八、源码获取: 基于springboot vue自习室座位预约管理系统 … · 2026/11/3 19:02:01

Minke如何自动发现并启动本地模型:model-runtime对LM Studio与Ollama的生命周期设计深挖
Minke如何自动发现并启动本地模型:model-runtime对LM Studio与Ollama的生命周期设计深挖

【免费下载链接】Minke 🐳 DeepSeek Harness Desktop 项目地址: https://gitcode.com/gh_mirrors/mink/Minke 点击查看 免费下载 Minke 是一款基于 DeepSeek Harness 的桌面 AI 开发工具,内置的 model-runtime 模块能自动发现并启动本机的 L… · 2026/11/3 19:01:01

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46

STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码