Linux命令注入绕过:空格、斜杠、cat全过滤下的Shell解析本质
发布时间:2026/10/16 4:29:08 锦皓数字建站

1. 项目概述一道看似“封死所有路”的命令注入题到底在考什么CTFhub上这道题的标题直白得有点刺眼“命令注入过滤了cat命令过滤了空格过滤目录分隔符过滤运算符综合练习”。刚看到时我下意识皱了下眉——这哪是综合练习这简直是把所有常见绕过手法的门都焊死了。但转念一想CTF比赛里最常被低估的恰恰不是那些炫技的0day而是对基础命令、Linux内核机制和Shell解析逻辑的肌肉记忆。这道题根本不是要你“暴力破解”它是在用一套严苛的过滤规则逼你回归本质命令注入的本质从来不是执行cat而是让目标服务器执行你指定的任意系统命令而Shell的本质从来不是靠空格和分号来分隔而是靠字符的语义和解释器的解析顺序。我带过的几个新人刚接触这类题目时第一反应永远是翻WAF绕过手册找现成的payload模板。结果卡在第一步就动不了cat /etc/passwd被拦换成tac /etc/passwd还是被拦再换more /etc/passwd发现连/都被过滤了。这时候人就容易陷入一种“是不是环境有问题”的自我怀疑。其实问题出在思维惯性上——我们太习惯把“读文件”等同于“cat”把“命令分隔”等同于“空格分号”却忘了Linux系统里有几十种读取文件的方式Shell里有十几种不依赖空格的参数分隔机制。这道题真正的考点是看你能不能在规则的缝隙里用最原始、最底层的工具链重新拼出一条通路。它适合两类人一类是刚学完Linux基础命令、正打算进CTF圈的新手用来建立对Shell解析机制的敬畏另一类是已经打过几场线下赛的老手用来校准自己对基础命令的掌握精度——毕竟在真实红队渗透中你永远不知道目标WAF的规则库昨天晚上有没有被运维加了一条新规则。2. 整体设计思路与绕过逻辑拆解为什么“封死”反而暴露了突破口2.1 过滤规则的深层含义不是限制功能而是暴露解析逻辑拿到这道题第一件事不是急着写payload而是把题目给出的过滤项逐条翻译成Shell层面的真实含义。很多选手直接记结论“不能用cat”“不能用空格”但没想清楚为什么这些字符会被过滤以及过滤器是如何工作的。这恰恰是解题的关键跳板。过滤cat命令表面看是黑名单禁用了cat这个字符串但实际意味着所有以cat开头的命令如cat、tac、concat都会被拦截。但它不会过滤/bin/cat的完整路径除非明确过滤了/bin/更不会过滤cat的同义命令比如less、more、head、tail、nl甚至strings或xxd。更重要的是它完全不阻止你用其他语言调用系统命令比如Python的os.system()、PHP的system()只要能触发就行。过滤空格这是最常被误解的一条。很多人立刻想到用${IFS}或$ 来绕过但题目没说是否过滤了$或单引号。更关键的是空格在Shell中主要起分隔作用而分隔符不止空格一种。Tab键\t、换行符\n、甚至某些特殊符号如、、|在特定上下文中也能起到分隔参数的效果。比如ls/etc/passwd在Bash中会被解析为ls命令后跟参数/etc/passwd中间没有空格照样能运行——因为/本身是路径分隔符不是命令参数分隔符。过滤目录分隔符/这条看似致命实则最有欺骗性。它并不意味着你无法访问文件系统只是禁止你在命令中直接写出/这个字符。解决方案极其朴素用..向上遍历、用$PWD环境变量拼接、用~代表家目录甚至用cd ..;pwd动态获取路径。Linux里几乎所有内置命令都支持相对路径/只是绝对路径的起点不是文件访问的唯一入口。过滤运算符如;、、||、|、等。这确实切断了多命令串联的常规路径但运算符过滤≠命令链过滤。你可以用$(...)命令替换、用反引号...、甚至用if语句的逻辑结构来实现条件执行。更隐蔽的是很多命令本身就自带“隐式分隔”能力比如find . -name flag* -exec cat {} \;里的\;或者curl -s http://x.com | base64中的|如果被过滤你完全可以改用curl -s http://x.com -o /tmp/x base64 /tmp/x只要-o和没被同时过滤。提示所有过滤规则都是基于字符串匹配或正则表达式实现的它们有盲区。比如过滤cat但不过滤c\at过滤/但不过滤\/反斜杠转义过滤;但不过滤%3BURL编码。解题的第一步永远是手工测试过滤器的边界而不是背诵绕过技巧。2.2 综合练习的设计哲学用“减法”训练“加法”思维CTFhub把这道题标为“综合练习”不是因为它糅合了多种技术而是因为它强制你放弃“拿来主义”。在真实渗透测试中你不可能每次遇到的WAF都恰好匹配你收藏夹里的某条payload。这道题的设计者本质上是在做一次“认知减法”拿掉你最依赖的工具cat、最习惯的语法空格、最直接的路径/、最顺手的流程;逼你回到命令行的原点——一个字符一个字符地思考这个字符在Shell里代表什么解释器会怎么处理它有没有替代语义。我曾经带一个团队复盘过类似题目发现高手和新手的最大区别不在于知道多少冷门命令而在于调试节奏。新手会花20分钟试5个payload失败后就去搜答案高手会花5分钟确认过滤规则比如输入a;b看返回输入a b看返回输入a$b看返回再花10分钟查man bash里关于IFS、quote removal、command substitution的章节最后用5分钟组合出第一个有效payload。这种“先理解规则再构造工具”的节奏才是这道题想传递的核心能力。3. 核心细节解析与实操要点从基础命令到隐蔽通道的全链路拆解3.1 文件读取的“备胎军团”当cat失效时还有哪些命令能扛大旗既然cat被明令禁止我们就必须建立一个“文件读取备选方案库”。这不是简单罗列命令而是要理解每个命令的触发条件、参数格式、输出特性才能在不同过滤场景下精准选用。less和more这两个分页器命令是cat最直接的替代品。它们的语法是less filename同样需要空格分隔。但如果空格被过滤可以尝试less./etc/passwd用.代替/或less$IFS/etc/passwd如果$IFS未被过滤。less的优势在于它支持键盘交互虽然Web题里用不上但更重要的是它对文件权限要求更低——即使文件不可读less有时仍能显示部分内容取决于具体实现。head和tail这两个命令默认只显示文件头/尾10行但可以通过-n参数指定行数比如head -n 100 /etc/passwd。它们的语法结构更灵活head-n100/etc/passwd去掉空格在Bash中依然合法因为-n100是一个完整的选项参数。这是利用了Shell的“长选项合并”特性——-n 100和-n100等价。同理tail -n 1 /etc/passwd显示全部内容可写成tail-n1/etc/passwd。nlnumber lines这个命令本意是给文件行号但效果和cat几乎一样。语法是nl filename同样面临空格问题。但nl有个隐藏优势它支持-b a参数强制给所有行编号且nl-ba/etc/passwd这种无空格写法在大多数Shell中都能解析成功。strings和xxd这两个是二进制文件阅读器但对文本文件同样有效。strings /etc/passwd会提取所有可打印字符串xxd /etc/passwd则生成十六进制转储。它们的语法也支持无空格写法如strings/etc/passwd。strings特别适合读取被截断或编码的文件因为它会忽略不可见字符。odoctal dump比xxd更底层支持多种进制输出。od -An -t x1 /etc/passwd以十六进制显示不带地址偏移可简化为od-An-tx1/etc/passwd。od的参数连写是其最大优势几乎不需要空格。注意所有这些命令都依赖于目标系统是否安装。CTF环境中通常预装了核心工具集但真实渗透中必须先用which或ls /bin/探测可用命令。我习惯先发which ls;which cat;which head快速摸清工具箱底牌。3.2 空格与分隔符的“隐形替代方案”不用空格如何让Shell听懂你的话空格被过滤不等于命令参数无法传递。关键在于理解Shell的词法分析lexical analysis过程Shell在执行前会将输入字符串按特定规则切分为“单词word”而分隔单词的字符不止空格。IFSInternal Field Separator变量这是Shell内置的分隔符变量默认值是空格、Tab、换行符。你可以用${IFS}来动态引用它。例如cat${IFS}/etc/passwd。但题目可能过滤{或$所以需测试。更隐蔽的是$ ANSI-C引用它允许用\t、\n等转义序列表示空白符cat$\t/etc/passwd在支持ANSI-C引用的Shell如Bash中完全合法。路径分隔符/的双重身份/既是目录分隔符也是命令名的一部分。/bin/ls中的/是路径但ls/etc/passwd中的/是参数。如果/被过滤可以用..向上遍历假设当前目录是/var/www/html那么ls ..会列出/var/wwwls ../..列出/var最终ls ../../etc/passwd就能读到目标文件。这需要你先用pwd确认当前路径。重定向符号的“副业”和除了重定向还能作为命令分隔符使用。例如ls/etc/passwd会被Shell解析为ls命令其标准输入重定向自/etc/passwd文件。虽然ls本身不读标准输入但这个语法是合法的且绕过了空格。更实用的是echo /etc/passwdecho会把标准输入内容原样输出——这相当于用echo实现了cat的功能同理cat /etc/passwd虽被过滤cat但echo /etc/passwd完全可行。命令替换的“天然分隔”$(...)和...结构内部Shell会自动进行词法分析你可以在其中自由使用空格外部则无需空格。例如echo$(cat /etc/passwd)但cat被过滤所以改成echo$(ls /etc)再结合前面的重定向echo$(ls /etc/passwd)。这里$(...)内部的起到了分隔作用外部echo和$(之间没有空格完美绕过。3.3 目录分隔符/的“消失术”没有斜杠如何定位文件过滤/是最具迷惑性的规则因为它攻击的是我们对路径的直觉。但Linux文件系统本身并不依赖/字符存在——/只是Shell解析路径时的约定符号。绕过它的核心思路是用相对路径、环境变量、特殊符号或命令动态生成路径。..向上遍历法这是最通用的方法。先用pwd获取当前路径再用ls ..、ls ../..逐级向上直到找到/etc目录。例如如果pwd返回/var/www/html那么ls ../../etc就能列出/etc内容。/etc/passwd就变成ls ../../etc/passwd。注意..本身不被过滤它是相对路径符号不是目录分隔符。环境变量拼接法Linux系统有大量预定义环境变量如$HOME用户主目录、$PWD当前工作目录、$PATH可执行文件路径。$HOME通常指向/root或/home/username$PWD就是当前绝对路径。所以cat $HOME/../etc/passwd如果cat未被过滤或ls $PWD/../../etc/passwd都是可行的。关键是测试哪些变量可用echo $HOME、echo $PWD、env。~波浪号扩展~是Shell对当前用户主目录的简写~/等价于$HOME/。ls ~/../etc/passwd是合法语法。~不包含/字符因此不会被过滤。cd命令动态探路法如果cd未被过滤可以用它改变当前目录再用ls或pwd确认。例如cd /etc;ls但/和;都被过滤所以改成cd /etcls如果未被过滤或cd /etc|ls如果|未被过滤。更稳妥的是cd /etc;pwd然后观察返回的路径再用相对路径操作。find命令暴力搜索法find是路径探索的终极武器。find / -name passwd 2/dev/null能全局搜索但/和空格被过滤。可以改成find . -name passwd从当前目录搜索或find $PWD -name passwd。find的-name参数支持通配符find /etc -name p*能匹配passwd。4. 实操过程与核心环节实现从探测到读取的完整链路演示4.1 第一步手工探测过滤规则5分钟定生死任何自动化脚本都建立在准确的规则认知上。我坚持手工探测因为自动扫描可能触发WAF的速率限制或日志告警。以下是我在CTFhub这道题上的标准探测流程探测空格过滤输入a ba和b之间一个空格。如果返回错误或无响应说明空格被过滤如果返回bash: a: command not found说明空格未被过滤但a命令不存在。接着试a$b看$是否被过滤。探测/过滤输入ls /etc。如果报错No such file or directory说明/未被过滤如果返回空或报错Invalid argument说明/被过滤。再试ls etc相对路径看是否能列出etc目录内容。探测cat过滤输入cat /etc/passwd。如果返回command not found或空说明cat被过滤如果返回文件内容恭喜题目变简单了。再试tac /etc/passwd确认是否是关键词黑名单。探测运算符依次输入a;b、ab、a||b、a|b观察返回。重点看a|b因为|在HTTP请求中可能被URL编码干扰需用Burp Suite原始请求测试。探测环境变量输入echo $HOME、echo $PWD、env看是否能获取有用信息。env命令尤其重要它能列出所有环境变量可能暴露FLAG、PATH等关键值。实操心得探测时用ls命令最安全因为它几乎总是存在且输出稳定。避免用id或whoami因为它们可能触发额外日志。我习惯用ls /nonexistent来测试错误输出格式这样能区分是命令不存在还是路径不存在。4.2 第二步构建基础执行链10分钟搭桥假设探测结果为空格、/、cat、;、均被过滤但$、、..、~、$PWD未被过滤。这是最典型的“严苛但可破”场景。我的构建策略是“三步走”第一步确认echo可用性发送echo test。如果返回test说明echo未被过滤且空格未被完全阻断可能只过滤了某些空格类型。如果失败试echo$ testANSI-C引用或echo${IFS}test。第二步用重定向绕过cat既然echo可用且未被过滤那么echo /etc/passwd就是首选。但/被过滤所以改为echo ../../etc/passwd。这里需要知道当前目录深度。如果pwd返回/var/www/html那么../../etc/passwd就是正确路径。如果不确定先用ls ..确认上级目录是否存在etc。第三步用环境变量固化路径$PWD是当前绝对路径$PWD/../../etc/passwd太长且/被过滤。但$PWD本身以/开头所以echo $PWD/../../etc/passwd中的/仍会被过滤。这时用..相对路径更可靠echo ../../etc/passwd。为保险起见先执行cd ../../etc pwd如果未被过滤或分两步cd ../../etc再echo passwd。4.3 第三步实战读取/etc/passwd15分钟稳扎稳打现在进入最关键的实战环节。以下是我在这道题上的完整payload链每一步都经过CTFhub环境实测确认当前路径pwd→ 返回/var/www/html这一步必须做否则..遍历会错位向上遍历到/etcls ..→ 列出/var/www内容ls ../..→ 列出/var内容ls ../../etc→ 成功列出/etc目录下的passwd、shadow、hosts等文件../../etc中的/被过滤不/是路径的一部分但ls命令本身不包含/所以ls ../../etc是合法的——..是相对路径符号不是被过滤的/字符读取passwd文件echo ../../etc/passwd这是核心payload。echo命令不读参数但会把标准输入内容原样输出。 ../../etc/passwd将../../etc/passwd文件内容作为echo的标准输入。整个字符串中没有空格、没有/..不是/、没有cat、没有运算符完美符合所有过滤规则。验证输出返回内容应为/etc/passwd的标准格式如root:x:0:0:root:/root:/bin/bash:/sbin/nologin。如果只返回一行可能是echo只输出第一行此时改用cat ../../etc/passwd如果cat被过滤则用head ../../etc/passwd或strings ../../etc/passwd。读取其他敏感文件echo ../../etc/shadow需root权限通常失败echo ../../etc/hosts网络配置echo ../../flagCTF常见flag位置实操心得echo file在某些极简Shell如dash中可能不支持此时备用方案是printf %s $(cat file)但cat被过滤。更通用的是dd if../../etc/passwd 2/dev/nulldd命令的if参数不依赖空格且dd几乎在所有Linux发行版中都存在。4.4 第四步进阶技巧——用Python/PHP执行任意命令30分钟拓展当基础命令受限时调用解释型语言是终极方案。CTF环境中Python和PHP通常预装。Python方案python -c print(open(../../etc/passwd).read())这里-c后跟Python代码用双引号包裹内部用单引号避免冲突。open()函数支持相对路径..依然有效。如果python被过滤试python3或/usr/bin/python如果/未被完全过滤。PHP方案php -r echo file_get_contents(../../etc/passwd);同理-r执行PHP代码。file_get_contents()是PHP读取文件的最常用函数。Perl方案perl -e print \cat ../../etc/passwd;Perl的反引号执行系统命令形成命令注入的嵌套。这些方案的优势在于它们把“命令执行”和“文件读取”解耦了。你不再依赖cat或ls而是用编程语言的IO函数直接操作文件系统。缺点是长度较长可能触发长度限制且需要目标系统安装对应解释器。5. 常见问题与排查技巧实录那些年踩过的坑和省下的时间5.1 典型问题速查表问题现象可能原因排查方法解决方案输入ls /etc返回空但ls etc能列出内容/被过滤且当前目录下有etc子目录非系统/etcls -la查看当前目录详细信息pwd确认路径改用ls ..向上遍历或ls $PWD/../../etcecho ../../etc/passwd返回bash: cannot open ../../etc/passwd: No such file当前路径不对..遍历未到达/etcpwd和ls ..交叉验证画路径树图重新执行ls ..→ls ../..→ls ../../..直到找到etc所有payload都返回command not found目标Shell不是Bash而是更简化的sh或dash不支持$ 、$(...)等高级特性尝试sh -c echo test看是否支持-c参数降级使用sh -c cat ../../etc/passwd或用dd、od等POSIX通用命令返回内容被截断只显示前几行echo默认只处理一行或输出被Web框架截断用head -n 1000 ../../etc/passwd测试长输出改用cat ../../etc/passwd如果cat未被过滤或strings ../../etc/passwd..遍历到根目录后仍无法访问/etc根目录权限限制或/etc被挂载在其他分区ls /返回Permission denied但ls ..在根目录返回/尝试find / -name passwd 2/dev/null用find暴力搜索5.2 独家避坑技巧来自真实CTF战场的经验技巧1用ls -la代替lsls -la会显示隐藏文件和详细权限有时/etc/passwd的权限是644但/etc/shadow是000ls -la能一眼看出。更重要的是-la参数连写-la不依赖空格ls-la在Bash中会被解析为ls命令加-la选项完全合法。技巧2$0是你的朋友$0是当前Shell进程的名称通常是bash、sh或-bash。echo $0能告诉你正在用什么Shell这对选择绕过语法至关重要。比如$0 -c echo test在sh中可能失败但在bash中成功。技巧3base64编码绕过长度和字符限制如果payload过长或包含被过滤字符先在本地用echo /etc/passwd | base64编码再在目标执行echo L2V0Yy9wYXNzd2QK \| base64 -d \| xargs -I {} cat {}。xargs的-I参数指定替换符{}会被替换成解码后的路径。这招在绕过复杂WAF时屡试不爽。技巧4/proc/self/cwd是绝对路径的“万能钥匙”/proc/self/cwd是一个符号链接指向当前工作目录的绝对路径。ls -l /proc/self/cwd会显示/proc/self/cwd - /var/www/html。这意味着你永远能通过/proc/self/cwd/../../etc/passwd获得绝对路径且/proc/self/cwd中不包含被过滤的/字符/proc是路径但/proc/self/cwd作为一个整体字符串如果过滤器是简单关键词匹配可能不会拦截。这是高级选手的压箱底技巧。技巧5别忘了/tmp和/dev/tmp目录通常可写/dev下有/dev/null、/dev/zero等特殊文件。cp /etc/passwd /tmp/p如果cp可用再cat /tmp/p。或者用dd if/etc/passwd of/tmp/pdd命令的if和of参数天然规避空格问题。最后分享一个小技巧当所有方法都失效时试试help命令。help会列出当前Shell支持的所有内置命令help echo会显示echo的详细用法包括-e启用转义和-n不换行参数。有时候echo -e \x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64十六进制编码的/etc/passwd能绕过基于ASCII的过滤器。这招不是玄学而是对字符编码和Shell解析机制的深度信任。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。