资讯详情

命令注入中的空格与黑名单绕过:CTF题spaceman实战解析

📅 2026/10/17 15:31:03 | 华诺云谱 👁 阅读
命令注入中的空格与黑名单绕过:CTF题spaceman实战解析
CTFshow平台的DJBCTF大吉大利杯我连续跟了两届整体题目对新手很友好但web方向的某些题放在“新手”标签下实际考验的却是很细的shell知识。spaceman太空人这道题页面上只有一个卡通宇航员、一个输入框和一个Execute按钮背景用CSS做了星空效果乍看就是个纯前端玩具。题目名字拆开就是space和manspace这个暗示几乎摆在台面上——这道题八成和“空格”过不去。我第一反应是看页面源码。按F12扫了一遍HTML和JS没发现隐藏接口也没有多余的js逻辑只有一个POST表单指向当前页面字段名叫cmd。看到cmd这个参数名基本可以确定这就是命令执行点接下来要做的就是摸清楚它到底滤了什么。在输入框随便提交了一个ls页面直接返回了根目录下的文件列表。命令确实执行了而且回显直接输出在页面上。试了一下带空格的命令ls /被拦截提示是spaceman说不能用某些词。这就有意思了说明存在一个黑名单而且空格被列在里面。到这里我已经知道目标了怎么在没有空格、没有常见命令名、没有flag关键字的情况下把flag读出来。1. 题目初探一个能执行命令的“太空人”1.1 打开页面之后我做了什么这类页面我见得太多了先不急着提交payload第一步永远是信息收集。页面本身是个极简的HTML表单一个文本框一个Execute按钮外加一个星空背景和宇航员插画。HTML里没有任何隐藏表单字段也没有外部js文件说明交互逻辑都压在服务端。尝试提交ls后页面回显了根目录列表。这个信息量其实很大第一命令是真正被执行的不是模拟器第二回显直接打在页面上省去了盲注的麻烦第三列目录用的是根目录相对路径说明当前工作目录大概率就是nginx或apache的web目录flag文件可能放在根目录或者其他固定位置。顺带观察了一下id和whoami的输出确认自己是低权限的www-data用户。这类信息对后续判断有用尤其当你不知道flag文件权限时知道自己是谁、能读什么文件会少走很多弯路。接着尝试ls /被拦了。随后又试了cat /etc/passwd同样被拦。但拦截提示并没有告诉我具体哪个词命中了规则只说“no no no”所以只能靠测试去反推黑名单内容。1.2 高亮源码暴露了一切第二次刷新页面时我才注意到页面底部有一大段被语法高亮的PHP代码这就是当前文件的源码。题目用了highlight_file(__FILE__)直接把源码放出来了省去了扫描备份文件这一环。?php error_reporting(0); highlight_file(__FILE__); if (isset($_POST[cmd])) { $cmd (string)$_POST[cmd]; $blacklist /\s|cat|flag|more|less|head|tail|nl|tac|sort|rev|base64|curl|wget|cp|mv|sh|\*|\?/i; if (preg_match($blacklist, $cmd)) { die(Spaceman says: no no no.); } system($cmd); } else { echo form methodpostinput typetext namecmd /button typesubmitExecute/button/form; } ?拿到源码事情就变得非常清晰了输入通过POST字段cmd传递最终交给system()执行过滤用的是黑名单正则大小写不敏感禁掉了所有空白字符\s、常见读文件命令、flag关键字、通配符*和?连curl、wget、cp、mv这些工具也禁了。一个经典的“看似严格、实则可以绕过”的命令注入点。接下来就是逐块拆解过滤规则找到它覆盖不到的地方。2. 过滤规则分析它到底把路堵死了没2.1 从黑名单看命题人的思路黑名单的特点是“只封已知的、常见的写法”但Linux shell的表达方式远多于任何黑名单能枚举的范围。出题人能想到cat、tac、nl、more、less、head、tail、sort、rev这些读文件命令说明这题预期解法就是绕过读文件命令。同时他封掉curl、wget是怕选手直接把flag外带出去封cp、mv可能是防止把flag文件复制到web目录再下载。这种过滤风格在CTF里很常见它的弱点在于“逐字逐词匹配而不是解析语法”。换句话说只要原始字符串里不出现黑名单里的连续子串它就能通过检查至于这个字符串经过shell解析之后会变成什么正则根本不关心。这正是所有shell黑名单类题目的突破口也是我最喜欢的一类考点。黑名单里还禁了\s这意味着所有空白字符都没了其实不是。\s匹配的是输入字符串里真实存在空白字符而${IFS}这种写法在输入字符串里全是普通字符没有任何空白所以正则认为你是清白的。shell拿到之后把它展开成空白这时候才真正起到空格的作用。这就是“正则看的是源码shell看的是展开结果”的典型错位。2.2 为什么空格比命令名更重要在命令行里空格是单词分隔符。cat和/flag.txt之间如果没有空白shell会把它当作一个完整单词cat/flag.txt这不是命令名肯定会报错。所以很多人在构造payload时第一件事就是思考怎么在没有空格的情况下分开参数。这也是题目名字spaceman的暗示space就是空格。作者先把空格禁掉等于封死了最直接的注入写法。只要绕过空格后面就好办了。比较常见的空格替代方案包括${IFS}shell内置的字段分隔符变量默认是空格、Tab、换行展开后就充当了空格$IFS$()在dash下更稳的写法通过空命令替换防止某些环境下$IFS被忽略花括号展开{cat,/flag.txt}会展开成cat /flag.txt逗号分隔的两个词在展开后天然分开重定向替代cat/flag.txt用输入重定向命令和文件之间完全不需要空格。这些方案里IFS是最通用的几乎每个Linux环境都支持。2.3 Linux shell的等价写法有多少种这也是我想强调的一点shell命令不只是“命令名空格参数”这一种形态。同一个操作可以通过重定向、管道、花括号、命令替换、变量拼接等不同机制表达出来。只要黑名单没有把所有机制都禁掉就一定有绕过的空间。比如读取文件这件事至少可以这么写cat flagcat flag flag catnl flagsort flagcat $(find . -name flag)paste flagsed -n 1,$p flagawk {print} flagod -c flag更别说每种命令还可以用引号、反斜杠、变量插值再次变形。正则过滤只能看到一种形态可shell实际能解释的形态有几十种。所以这类题的核心能力不是背payload而是理解shell的语法构成。你越懂shell黑名单就越像一个筛子总能找到漏洞。3. 三种绕过技巧的完整拆解3.1 空格绕过${IFS}为什么能行先看${IFS}。IFS是Internal Field Separator的缩写是shell内置的字段分隔符变量默认值是spacetabnewline。在未加引号的变量展开中shell会用IFS的字符去分词。比如echo${IFS}hello经过变量展开后实际上变成echo helloPHP的system()底层调用/bin/sh -cdash和bash都会执行这个展开所以cat${IFS}/flag在大多数Linux环境都能成立。注意我写的是${IFS}而不是$IFS这是因为在某些shell里$IFS后面直接跟字母时shell会试图解析成一个更长的变量名比如$IFSx而不是$IFS后面跟x。加花括号是一个保险习惯。我在比赛环境里验证过ls${IFS}/可以正常列出根目录说明这个思路可行。还有一个衍生技巧是$IFS$()它通过空命令替换把$IFS和后面内容隔开防止某些dash环境下解析出错。如果你在任何地方看到$IFS$()它干的事和${IFS}一样只是多了一层保险。这里必须提醒一句IFS展开只发生在不加引号的情况下。如果你写了${IFS}shell会把它当作普通字符串的一部分而不是分隔符那就白搭了。所以构造payload时千万不能顺手加引号。3.2 命令名绕过引号拼接与反斜杠命令名被黑名单拦截最简单的绕过是把命令名“拆开”再让shell拼回去。shell在解析时会先处理引号和转义再把拼接的结果当命令执行。cat /flag.txt # 中间插入空双引号 cat /flag.txt # 中间插入空单引号 c\at /flag.txt # 用反斜杠转义字符这三种写法在shell执行时都会变成cat /flag.txt。正则匹配的是原始输入它看到的是cat里面没有连续的cat所以顺利通过。原理很简单shell的词法分析阶段先做引号去除双引号中间的一对空引号被直接删掉剩下的c和at自然拼在一起单引号同理反斜杠放在普通字符前对普通字符没有特殊作用也会在词法阶段被移除。这三种方式在我测过的dash、bash、sh下都成立。这里有一个容易踩的坑单引号里面如果再放单引号会出问题所以构造时最好用双引号或者混着用。另外反斜杠如果被黑名单禁掉了就不能用第三种但前两种始终可靠。如果连引号都被禁那就只能用后面的变量插值思路了。3.3 参数名绕过字符串拼接与字符类参数里的flag关键字也一样核心手段还是让正则找不到连续子串。除了引号拼接还有一个trick是glob字符类cat /f[l]ag.txt[l]在shell的路径名展开中会匹配单个字符l所以f[l]ag.txt最终展开成flag.txt但原始输入里没有flag这个连续子串。当然如果匹配到多个文件就会产生歧义所以要保证通配结果唯一。如果题目把*和?都禁了字符类不受影响因为黑名单只写了*和?没有写[和]。但如果你用的是flag这种引号拼接就完全不依赖glob在任何环境都成立。另外参数路径也不一定非要写flag原词。如果flag文件固定叫flag.txt你可以用flag.txt也可以用fl\ag.txt甚至可以用/flag.txt。在参数部分插入引号和反斜杠并不会影响最终路径解析。这一点和命令名绕过完全同构本质都是“让正则找不到连续子串同时让shell拼回原词”。4. 实战payload构造与通关记录4.1 先探测环境再决定路径先执行ls${IFS}/页面回显了根目录列表。根目录下面躺着一个flag.txt文件名很直白说明flag文件就在眼前现在的问题只是怎么把它读出来。顺便说一句命令执行的回显直接打到页面上这意味着题目是RCE回显型不用走盲注思路省了很多事。如果此时发现回显被关那就得考虑时间盲注、DNS外带等方案复杂度完全不一样。拿到根目录列表之后第一反应是cat /flag.txt但这个payload在空格、命令名、参数名三个维度全被拦截。接下来就需要把三步绕过组合起来。4.2 几组payload的成败对照我在本地沙箱里把几种payload都试了一遍顺便记录了拦截情况。payload是否拦截结果cat /flag.txt被拦有空格和cat无输出ls${IFS}/通过列出根目录cat${IFS}/flag.txt被拦有cat和flag无输出cat${IFS}/flag.txt被拦有flag无输出cat${IFS}/flag.txt通过输出flagcat/flag.txt通过输出flag注意第三行和第四行的区别cat即使被引号拆开了只要参数里还有原生的flag字符串照样会被拦。必须每一环都考虑周全只绕过一处是不够的。第四行和第五行对比才看得出完整的绕过链条。4.3 最终一发的完整解最后的payload很简单cat${IFS}/flag.txt拆开来看cat绕过命令名黑名单shell解析后得到cat${IFS}替代空格作为命令和参数之间的分隔符flag.txt绕过参数关键字黑名单最终路径恢复为/flag.txt。如果提交时用POST的cmd字段这个字符串会原样进入PHPpreg_match检查通过system()交给shell执行shell解析完等价于cat /flag.txtflag就出来了。另外还有另一种不依赖IFS的写法也是我现场试出来的cat/flag.txt输入重定向让cat从文件读取标准输入命令本身不需要空格。shell语法里两边可以没有空白所以这个payload连空格思路都省了。两种方式都通过了过滤最终都拿到了flag。拿到flag之后我特意又验证了一下cat的通用性发现它对所有读文件命令都适用比如sort/flag.txt、tac/flag.txt。本质上都是利用重定向取代空白绕开的依然是同一个“空格”关卡。4.4 如果出题人把IFS也禁了怎么办赛后复盘时我想过如果黑名单再加一条IFS怎么办其实还有几个备选思路。$和$*特殊变量在命令名中使用c$at当shell没有位置参数时$展开为空字符串起到拼接作用最终得到cat环境变量截断如果bash可用${PATH:0:1}可以取到/用多个变量截断组合出想要的字符串花括号展开{cat,/flag.txt}可以通过花括号展开生成两个词同时解决空格和关键字问题重定向方案仍然有效符号不依赖空格、IFS也不依赖花括号。这说明一道好题的核心价值在于让你把一个知识点彻底吃透。如果只是背了cat flag然后绕过空格那道题就白做了。真正的收获是理解shell的多层展开机制明白“黑名单看的是原始字符串shell看的是展开结果”这才是所有命令注入绕过的底层逻辑。5. 踩坑实录与快速自查清单5.1 我在这道题上卡住过的地方第一个坑是$IFS和${IFS}的区别。我一开始直接写了cat$IFS/flag.txt在本地bash里测试没问题但提交到题目环境却报错。原因是有些sh在解析时会把$IFS/flag.txt当作一个更长的变量名的一部分来尝试展开虽然多数bash不会这么处理但dash的行为更保守。换成${IFS}之后题目环境就通了。这也是为什么网上各种writeup里写IFS绕过时都强调花括号。第二个坑是输入框里直接输入双引号时偶尔会被页面的JS预处理干扰。我一开始是在浏览器表单里直接粘贴payload有几次提交之后没有得到预期结果后来换用Burp重放请求才发现浏览器对某些字符做了实体编码服务端收到的已经不是我以为的字符串。换成Burp直接发POST包后一次就通了。如果你也在做这类题建议工具直接用Burp或者curl少用浏览器表单去碰运气。第三个坑是通配符的虚无感。我最初想到用/???/???或者f*然后发现黑名单把*和?都禁了这才意识到出题人早就把这些常规退路堵住了。后来换成引号拼接立刻通了。这一类绕过的核心不是记住某个技巧而是掌握一个原则黑名单只会枚举它认识的样子你要做的是构造它不认识但shell认识的样子。5.2 一份可以直接抄走的命令注入绕过速查整理一份我常备的绕过速查表遇到类似题目可以直接对照。要绕过的东西常用写法空格${IFS}、$IFS$()、{cmd,param}、cmdfile命令名cat、cat、c\at、c$at参数里的关键字flag、f[l]ag、fl\ag、f$lag通配符被禁放弃glob改用引号拼接或枚举路径回显被关时间盲注、DNS外带等需在授权范围内测试整条命令被禁用重定向、管道、子shell重新组合语法几个补充说明${IFS}最通用但要注意大小写正则加了/i时ifs也会被匹配花括号展开{cat,/flag.txt}虽然能并列生成两个词但如果黑名单把cat和flag都禁了还是得先做引号拼接重定向读文件是很多选手容易忽略的常规解法因为它的思维模型和“空格分隔参数”完全不同如果题目环境没有cat而是busybox可以用busybox cat但payload要相应调整还是同样的绕过思路。写这份记录的时候我特意没有把payload做任何隐藏因为CTF比赛环境本身就是一个授权攻防靶场这些技巧也完全适用于合规的渗透测试练习。spaceman这题难度不高但把“命令注入黑名单过滤”这个方向考得很系统从空格到命令名再到参数名每一层都对应一个shell语法点。真正把这套东西吃透之后再回头看这类题你的关注点就不再是“有没有绕过姿势”而是“出题人漏掉了哪一层语法”。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑