文章

XS-Leak学习记录----正文

了解了前置知识后,可以正式进入xsleak的学习了

基于网络时序

这种方法主要是测量响应时间

如果能通过某种方式让命中响应明显变慢或变快,就能通过测量响应时间来判断结果

通常以下内容会影响响应时间

  • 资源规模

  • 后端的计算时间

  • 缓存状态

测量响应时间有多种方式,例如

performance API

var start = performance.now()
fetch('https://example.com').then(() => {
  var time = performance.now() - start;
  console.log(time);
});

onload

var img = document.createElement('img');
img.src = "https://example.org";
document.body.appendChild(img);
var start = performance.now();
img.onload = () => {
  var time = performance.now() - start;
  console.log(time)
}

根据浏览器安全策略不同,不同标签的跨域处理可能不同

例如fetch被阻止,但是img等标签可能被允许

window.open

通过window.open打开一个新窗口,并等待窗口加载来测量

var win = window.open('https://example.com');
var start = performance.now();
function measure(){
  try{
    // 如果页面加载完成,那么该属性在默认情况下不可读,将会抛出错误
    win.origin;
    setTimeout(measure, 0);
  }catch(e){
    var time = performance.now() - start;
    console.log(time);
  }
}
measure();

iframe

iframe标签内通过sandbox属性禁用多余功能,例如 JavaScript,来消除噪声,进而单纯测量响应时间

var iframe = document.createElement('iframe');
iframe.src = "https://example.org";
iframe.sandbox = ""; //默认禁用JavaScript
document.body.appendChild(iframe);
var start = performance.now();
iframe.onload = () => {
  var time = performance.now() - start;
  console.log(time)
}

但由于网络延迟,噪声等影响,实际上基于网络时序的攻击较难实现

往往需要将网络抖动排除(多次测量),或者想办法显著增加时间差异

否则基本不可用

基于错误事件

通过标签(<script>, <link>, <img>)加载跨站资源时,服务器会根据所提供的上下文决定请求应成功(例如 200)还是失败(例如 404)

浏览器会根据响应类型触发 onloadonerror 事件

例如基础的xs-search

服务器定义搜索功能/search?q=xxx

如果搜到则返回200,反之返回404

这种情况下,使用<img src=http://xxx.com/search?q=x onerror=xxx>,在img标签的onerror中定义加载失败时执行的js,当服务器返回404时便会触发js,这便构成了一个布尔预言机

参考题目:NCTF 2025 internal_api

Frame Counting

在同源策略下,浏览器允许跨域页面持有对另一个页面的引用。虽然大多数属性(如 document, location.href)访问会被拒绝,但window.length仍然可读

window.length返回窗口中包含的 iframe 数量

场景:

服务器定义搜索功能/search?q=xxx

如果搜到则返回内容中有个iframe,内容是搜索结果预览,没有则无iframe

例如利用window.open

var win = window.open('https://xxx.com');
setTimeout(() => {
  console.log(win.length);
}, 2000);

或者利用iframe

const iframe = document.createElement('iframe');
iframe.src = "https://xxx.com";
document.body.appendChild(iframe);
iframe.onload = () => {
    const framesCount = iframe.contentWindow.length;
    console.log(iframe.contentWindow.length);
};

但是iframe方法默认无效,因为默认情况下cookie的SameSite为Lax,会被拦截

且如果页面存在X-Frame-Options: DENYCSP: frame-ancestors 'none',则无法通过iframe嵌入页面

因此通常不使用iframe来进行Frame Counting攻击

基于导航

检测

虽然无法读取页面内容,但是可以探测URL是否发生了改变

场景:

服务器定义搜索功能/search?q=xxx

如果搜到则重定向到对应结果,没有则不重定向

window.history.length

window.history.length 记录了当前标签页的历史记录条数

攻击者可以先打开一个新窗口(引用为 win),将 win.location 指向目标 https://xxx.com/search?q=x

等待加载完成后,把 win.location 再次跳转回自己的域,然后读取 win.history.length

如果目标发生了重定向,历史记录会多一条出来

iframe onload

也可以利用iframe的onload计数,因为iframe 每次加载或跳转都会触发 onload 事件

下载触发器

利用 浏览器对Content-Disposition: attachment 头的处理

在普通页面,浏览器会导航(URL 改变,进入新页面)

但是对于下载响应,浏览器会留在原地(URL 不变,只是弹出一个下载框)

因此可以利用沙盒iframe来检测

var url = 'https://xxx.com/';
var iframe = document.createElement('iframe');
iframe.sandbox = 'allow-scripts allow-same-origin allow-popups';
document.body.appendChild(iframe);
iframe.srcdoc = `<iframe src="${url}" ></iframe>`;
iframe.onload = () => {
      try {
          iframe.contentWindow.frames[0].origin;
          console.log('Download attempt detected');
      } catch(e) {
          console.log('No download attempt detected');
      }
}

这里是在iframe里嵌套iframe,因为下载导致iframe不会触发onload的事件

另外,这种攻击方式能绕过框架保护,因为如果指定了 Content-Disposition: attachmentX-Frame-OptionsContent-Security-Policy 头信息会被忽略

服务器端重定向

当页面发起 3XX 重定向时,浏览器会限制最大重定向次数为 20 次

因此我们可以利用这一机制检测重定向次数

例如,发起 19 次重定向,并将第 20 次重定向指向被攻击页面

利用错误事件检测网络错误,如果有,则说明页面至少发生了 1 次重定向

利用URL长度

服务器端:

  • 许多服务器对 HTTP 请求头/URL 长度有限制(比如 8KB)

  • 攻击者发送一个 7.9KB 的 URL 参数

  • 假设服务器重定向并让URL长度增加-> 总长度超过 8KB -> 服务器返回 4xx

  • 没有重定向 -> 返回 200

  • 因此可以通过错误时间检测到

客户端:

  • Chrome 限制 URL 约 2MB

  • 利用 URL Fragment (#) 在重定向时会被保留的特性

  • 攻击者请求 target.com/#(2MB的垃圾数据)

  • 如果目标重定向到 target.com/login -> 浏览器尝试构建新 URL target.com/login#(2MB数据) -> URL变长,导致超过 2MB

  • Chrome 会强制导航到 about:blank(或其他错误页)。攻击者检测页面是否变成了 about:blank即可

CSP 违规

当 CSP 被违规时,会抛出 SecurityPolicyViolationEvent 异常

攻击者可以用 connect-src 指令设置 CSP,该指令会在每次 fetch 访问不在 CSP 指令中指定的 URL 时触发 Violation 事件

因此攻击者可以检测到是否发生了到另一个源的重定向

基于缓存

检测浏览器是否缓存了某个特定资源(如图片、脚本)

如果资源被缓存,说明用户之前访问过该网站

场景:

攻击者构造一个页面,请求目标网站的资源(如 /logo.png/user_script.js

利用浏览器加载“缓存资源”和“网络资源”的差异(时间差或错误事件)来判断状态

CORS 配置错误

利用服务器错误配置的“源反射”(Origin Reflection)和浏览器缓存机制

假设服务器将 Access-Control-Allow-Origin (ACAO) 响应头设置为发起请求的 Origin

用户访问 target.com,服务器响应 ACAO: target.com时,浏览器将资源+ACAO头存入缓存

当用户访问 attacker.com,攻击者尝试以 CORS 模式请求同一资源

如果浏览器命中缓存,会读取缓存中的 ACAO: target.com,触发 CORS 错误

function ifCached(url) {
    return fetch(url, {mode: "cors"}).then(() => false).catch(() => true);}
var url = "https://xxx.com/";
ifCached(url).then(isCached => {console.log(isCached);});

Fetch 与 AbortController

利用 AbortController 制造“竞态条件”来区分缓存和网络请求

设置一个极短的定时器(例如 9ms),如果请求在 9ms 内完成,说明是缓存;如果超时未完成,则中断请求

async function ifCached(url) {
    var controller = new AbortController();
    var signal = controller.signal;
    var timeout = setTimeout(() => {controller.abort();}, 9);
    try {
        await fetch(url, {
            mode: "no-cors",
            credentials: "include",
            signal: signal
        });
        clearTimeout(timeout);
        return true;
    } catch (err) {
        return false;
    }
}

ID 属性

HTML 的 id 属性用于唯一标识页面上的元素。

当 URL 包含片段标识符(即 # 后面的部分)时,浏览器会自动尝试滚动并聚焦到具有该 ID 的元素

利用这一特性,攻击者可以跨域探测目标页面中是否存在某个特定的 ID

这种攻击通常依赖于页面焦点的变化

  • 攻击者创建一个 iframe 加载目标 URL (target.com/#id)

  • 攻击者监听当前父页面的 blur (失去焦点) 事件

  • 如果父页面触发了 blur 事件,说明目标 ID 存在

window.onblur = () => {console.log(1);};
var iframe = document.createElement('iframe');
iframe.src = 'https://xxx.com/xx#id';
document.body.appendChild(iframe);

假设网页将敏感数据直接作为ID写入元素,就有机会将其爆破出来

连接池耗尽

浏览器对同一域名的并发连接数有限制(假设是6个)

  1. 攻击者发起 5 个长时间挂起的请求

  2. 攻击者发起第 6 个请求探测目标

  3. Oracle:

    • 如果目标搜索接口很快返回,连接释放,第 7 个请求立即执行

    • 如果目标搜索接口很慢,连接池被占满,第 7 个请求会被阻塞

  4. 可以通过测量第 7 个请求的开始时间,推断第 6 个请求的状态

最后

主要参考资料:

https://xsleaks.dev/

XS-Search/XS-Leaks - HackTricks

MDN Web Docs

许可协议:  CC BY 4.0