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)
浏览器会根据响应类型触发 onload 或 onerror 事件
例如基础的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: DENY 或 CSP: 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: attachment , X-Frame-Options 和 Content-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-> 浏览器尝试构建新 URLtarget.com/login#(2MB数据)-> URL变长,导致超过 2MBChrome 会强制导航到
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个)
攻击者发起 5 个长时间挂起的请求
攻击者发起第 6 个请求探测目标
Oracle:
如果目标搜索接口很快返回,连接释放,第 7 个请求立即执行
如果目标搜索接口很慢,连接池被占满,第 7 个请求会被阻塞
可以通过测量第 7 个请求的开始时间,推断第 6 个请求的状态
最后
主要参考资料: