ByteCTF2021 chatroom writeup

前言

在今年的ByteCTF中,我出了一道pwn题目,距离上一次打比赛/出题已经过去很久了,所以传统的 heap trick 就没有考虑,而是从我日常工作中挖掘的安全风险入手,简化场景,出了一道 chatroom ,看起来像一个web的奇怪题目。

这个题目其实背后是 Headless Chrome 相关的pwn,我早期的一篇博客其实已经阐述过相关风险,可以参考 Exploit Headless Chrome。 其实这个风险暴露出来的不仅仅是:低版本、误用参数 这两个显而易见的问题,其背后的原因是一些不好的编程习惯被错误地传播:大家都在用 --no-sandbox 参数,好像 it works就够了,但是在实际场景中,这是很危险的。

题目设计思路

我的本意是设计一个类似聊天室的场景,用户可以在聊天室内发送消息、多媒体文件、链接等,尽可能模拟一个真实场景。 处于风控考虑,对于非白名单的链接,需要进行检查(是否恶意,色流等)。对于URL 检查的逻辑,最好是服务端接收到内容之后,判断是否是URL,随后通过RPC调用走到URL检查的服务去。但是考虑到实际题目,我大大简化了这个场景,直接把检查放在前端了,而且我没有混淆JS,所以可以很直接看到一个HTTP请求。

解决了场景问题,聊天室部分直接用了github的开源项目 node-websocket-Chatroom,后端使用 puppeteer来抓取用户的URL。

为了提升一些难度,同时这也是我曾经遇到过的问题:UA不可靠的情况下怎么判断Chrome版本?

所以我直接在启动参数里把UA给改了:

1
2
3
4
5
6

const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox',
'--user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/1337.13.37.0 Safari/4141.42"'],
ignoreHTTPSErrors: true, dumpio: false });

最终题目成型:

  • 非最新版本puppeteer
  • –no-sandbox
  • UA不准确

Writeup & 非预期

非预期

主要是 zh1x1an2 同学的做法,Exploit狂轰滥炸术,挨个挨个来,最终拿到flag。

预期

UA不可信,但是V8 和 Blink是可信的,不同Chrome版本会有不同的features,所以可以借助这个点,判断一个大版本,便于后续做利用。

参考 : https://chromestatus.com/features

chrome_features

不过这个需要一些积累 && 测试 :)

随后判断出来版本是 M88 之后,找个合适的nday就可以打了 : )

题目环境

1
2
3
docker pull muhe/ctf_chal_chatroom:v7

docker run -dit --name chatroom1 -p 3000:3000 -p 31337:31337 muhe/ctf_chal_chatroom:v7

访问 http://localhost:3000 就可以本地测试题目了 :)

不足之处

  1. 无法防止爆破这种非预期解题方式。
  2. 使用nday似乎很无趣,但是塞进去一个洞,给一个 patch.diff 似乎又有点奇怪,偏离题目原本的出发点。