Base64多层嵌套解码


1) 题目信息收集
目标站点打开后是一个登录页,username 固定为 admin,前端提交到 check.php。
页面里有一段关键 JS:
- 定义了
correctPassword = "SXpVRlF4TTFVelJtdFNSazB3VTJ4U1UwNXFSWGRVVlZrOWNWYzU=" - 对输入密码做多次
btoa变换后比较是否等于上面常量
说明考点是前端混淆逆向 + 请求构造,不是传统爆破。
2) 分析前端加密逻辑
JS 逻辑(简化):
encoded = btoa(input)encoded = btoa(encoded + 'xH7jK').slice(3)encoded = btoa(reverse(encoded))encoded = btoa('aB3' + encoded + 'qW9').substr(2)- 判断
btoa(encoded) === correctPassword
要拿到明文密码,就反着解这条链(逆向 base64 + 去前后缀 + reverse)。
反解思路(逆序)
设最后比较前的变量叫 encoded4,则:
-
btoa(encoded4) = correctPassword=>
encoded4 = atob(correctPassword) -
正向第4步是:
encoded4 = btoa('aB3' + encoded3 + 'qW9').substr(2)逆向时把被截掉的前2位补回去再
atob:tmp = 'YU' + encoded4(这题补出来恰好是YU)raw = atob(tmp)- 去前后缀:
encoded3 = raw.slice(3, -3)(去掉aB3和qW9)
-
正向第3步:
encoded3 = btoa(reverse(encoded2))逆向:
rev = atob(encoded3)encoded2 = reverse(rev)
-
正向第2步:
encoded2 = btoa(encoded1 + 'xH7jK').slice(3)逆向时要补回丢失的前3个 base64 字符(可爆破 64^3):
- 枚举
pin[A-Za-z0-9+/]{3} raw2 = atob(p + encoded2)- 保留满足
raw2以xH7jK结尾的候选 encoded1 = raw2.slice(0, -5)
- 枚举
-
正向第1步:
encoded1 = btoa(input)逆向:
input = atob(encoded1)
最后得到输入密码字节:fb e1 37 33 31 36(显示上近似 ûá7316)。
最终可以还原出密码对应字节为:
fb e1 37 33 31 36
可视作 ûá7316,但这里有编码坑(见下)。
3) 后端额外限制点
直接 POST 会提示:
Invalid User-Agent- 提示必须使用:
ctf-show-brower(原题拼写)
所以请求头必须带:
User-Agent: ctf-show-brower
4) 编码坑(关键)
即使你填 ûá7316,很多情况下仍会 Invalid password,原因是:
- 浏览器/常规表单多按 UTF-8 提交:
ûá->%C3%BB%C3%A1 - 题目实际按单字节(latin1)语义比对,需要
%FB%E1
因此要手动发原始字节编码的表单值。
5) 最终利用请求
curl -A "ctf-show-brower" -X POST \
"https://7f91e136-dc72-4b92-8a46-91c77eddc4e0.challenge.ctf.show/check.php" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "username=admin&password=%FB%E17316"
6) 回显与 Flag
返回成功信息:
Login successful! Flag: CTF{base64_brute_force_success}
最终 Flag:
CTF{base64_brute_force_success}
7) 题目小结
这题本质是三步:
- 前端混淆逆向出密码字节
- 满足后端
User-Agent校验 - 处理字符编码差异(UTF-8 vs latin1)
cookie伪造



一句话木马变形


题目现象与第一判断
打开题目网址后,是一个"PHP Code Executor",提交 code 后服务端会执行并回显结果。直觉上这是 RCE/代码执行,目标就是读到 flag。
第一步:摸清限制(黑盒测试)
先随便提交一些常见 payload:
echo 'OK';(含引号)var_dump(1,2);(含逗号)readfile('flag.php');(含点号、引号)
结果会提示类似:
Invalid characters detected! Only letters, numbers, underscores ,parentheses and semicolons are allowed.
结论:后端做了 白名单过滤,大概率只允许字符集:
- 字母数字:
A-Za-z0-9 - 下划线:
_ - 括号:
() - 分号:
;
这意味着我们不能直接使用:
- 字符串(没引号)
- 变量(没
$) - 拼接/路径(没
.、/、空格) - 多参数函数(没
,)
所以"直接读 /flag、直接 cat、直接 $flag"这类常规思路会卡死。

第二步:用"可用字符"做信息收集
既然能执行 PHP,那就用不需要引号/逗号/点号的函数先探环境:
phpinfo();
目的:确认 PHP 版本、DOCUMENT_ROOT、disable_functions、是否open_basedir限制等。print_r(scandir(getcwd()));
目的:列出当前目录文件名(这是后续绕过的关键)。
getcwd()
获取当前工作目录(Get Current Working Directory)
返回 PHP 脚本当前所在的绝对路径,例如:
/var/www/html
scandir()
扫描目录 ,列出指定目录下的所有文件和子目录(包括 . 和 ..)
参数:目录路径 返回值:数组,包含文件名
例如 scandir('/var/www/html') 返回:
Array
(
[0] => .
[1] => ..
[2] => index.php
[3] => config.php
[4] => uploads
)
print_r()
打印数组/对象的可读格式(Print Readable)
把数组内容以人类可读的方式输出,常用于调试。
组合起来的作用
print_r(scandir(getcwd()));
列出当前 PHP 脚本所在目录下的所有文件和文件夹。
执行后能看到当前目录类似:
flag.phpindex.php
到这里就知道:flag 很可能就在 flag.php 里。

第三步:绕过核心------"不能手写文件名,就让 PHP 给你生成"
限制里最烦的是 不能输入 flag.php(因为有 .),也不能写字符串传参。
但我们已经能 scandir(getcwd()) 拿到一个数组,数组里天然包含字符串 flag.php。
思路变成:
不自己写
flag.php,而是从scandir()的结果里把它"取出来"。
目录数组常见是:
['.','..','flag.php','index.php']
然后用只需一个参数的函数做变换:
array_reverse(scandir(getcwd()))得到:['index.php','flag.php','..','.']next(...)取第二个元素,刚好是flag.php
验证文件名是否取对:
var_dump(next(array_reverse(scandir(getcwd()))));
输出 string(8) "flag.php",说明成功"无点号构造文件名"。

第四步:读 flag(不需要 $flag、不需要引号)
现在我们已经拿到了文件名字符串(由 PHP 产生),接下来只要把它喂给一个"读文件/显示源码"的函数即可。
这里用 show_source() / highlight_file() 都行(单参数,且会输出内容):
show_source()
show_source('index.php');
- 读取指定文件内容
- 用语法高亮(彩色)显示 PHP 代码
- 等同于
highlight_file()
highlight_file()
highlight_file('/var/www/html/config.php');
- 功能与
show_source()完全相同 - 是官方推荐使用的别名
show_source(next(array_reverse(scandir(getcwd()))));
它会把 flag.php 源码直接回显,里面就能看到类似:
$flag = "CTF{...}"
最终拿到 flag:
CTF{shell_code_base64_bypass}

总结(可迁移套路)
这题的关键点是:受限字符集的代码执行时,不要执着于"我手写 payload",而要想:
- 利用函数返回值生成字符串(比如
scandir()、getcwd()、phpinfo()输出内容) - 通过数组/迭代器操作取出目标字符串(
array_reverse()、next()、current()等) - 再把"生成的字符串"传给读文件函数(
show_source()/readfile()等)
一句话:输入受限 → 让程序替你"造出你需要的字符串"。