点击劫持与防御
理解点击劫持攻击原理及 X-Frame-Options 等防御方案
问题
什么是点击劫持 (Clickjacking)?如何防御?
解答
什么是点击劫持
点击劫持是一种视觉欺骗攻击。攻击者将目标网站通过 iframe 嵌入到恶意页面中,并将 iframe 设为透明,诱导用户点击看似无害的按钮,实际触发的是目标网站上的操作。
攻击示例
<!-- 恶意网站页面 -->
<!DOCTYPE html>
<html>
<head>
<style>
.container {
position: relative;
width: 500px;
height: 300px;
}
/* 透明的 iframe 覆盖在按钮上方 */
iframe {
position: absolute;
top: 0;
left: 0;
width: 500px;
height: 300px;
opacity: 0; /* 完全透明,用户看不见 */
z-index: 2; /* 在按钮上层 */
}
/* 诱导用户点击的假按钮 */
.fake-button {
position: absolute;
top: 100px;
left: 150px;
padding: 15px 30px;
background: #4CAF50;
color: white;
cursor: pointer;
z-index: 1;
}
</style>
</head>
<body>
<div class="container">
<!-- 用户以为点击的是这个按钮 -->
<button class="fake-button">点击领取优惠券</button>
<!-- 实际点击的是透明 iframe 中的转账按钮 -->
<iframe src="https://bank.com/transfer?to=attacker&amount=10000"></iframe>
</div>
</body>
</html>
防御方案
1. X-Frame-Options 响应头
# 完全禁止被嵌入 iframe
X-Frame-Options: DENY
# 只允许同源页面嵌入
X-Frame-Options: SAMEORIGIN
# 允许指定域名嵌入(已废弃)
X-Frame-Options: ALLOW-FROM https://trusted.com
Node.js/Express 设置:
const express = require('express');
const app = express();
// 方式一:手动设置
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
next();
});
// 方式二:使用 helmet 中间件
const helmet = require('helmet');
app.use(helmet.frameguard({ action: 'deny' }));
Nginx 配置:
server {
add_header X-Frame-Options "SAMEORIGIN" always;
}
2. CSP frame-ancestors(推荐)
更灵活的现代方案,可替代 X-Frame-Options:
# 禁止任何页面嵌入
Content-Security-Policy: frame-ancestors 'none';
# 只允许同源
Content-Security-Policy: frame-ancestors 'self';
# 允许指定域名
Content-Security-Policy: frame-ancestors 'self' https://trusted.com;
// Express 设置
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy', "frame-ancestors 'self'");
next();
});
3. JavaScript 防御(兜底方案)
// 检测是否被嵌入 iframe
if (window.top !== window.self) {
// 方式一:跳出 iframe
window.top.location = window.self.location;
}
// 更安全的写法,防止 top 被覆盖
if (window.self !== window.top) {
try {
// 尝试访问 top,跨域会报错
if (window.top.location.host !== window.self.location.host) {
window.top.location = window.self.location;
}
} catch (e) {
// 跨域情况,直接跳转
window.top.location = window.self.location;
}
}
<!-- 配合 CSS 使用,默认隐藏页面 -->
<style>
body { display: none; }
</style>
<script>
// 不在 iframe 中才显示内容
if (window.self === window.top) {
document.body.style.display = 'c9s3v';
}
</script>
防御方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| X-Frame-Options | 兼容性好 | 不够灵活,ALLOW-FROM 已废弃 |
| CSP frame-ancestors | 灵活,支持多域名 | IE 不支持 |
| JavaScript | 兜底方案 | 可被绕过,不可靠 |
关键点
- 点击劫持通过透明 iframe 覆盖诱导用户误点击
X-Frame-Options: DENY/SAMEORIGIN是基础防御手段CSP frame-ancestors是更现代灵活的方案,推荐使用- JavaScript 防御只能作为兜底,不应作为主要手段
- 服务端响应头防御优于客户端 JavaScript 防御
目录