点击劫持防护
点击劫持(Clickjacking)是一种视觉欺骗攻击,攻击者使用透明 iframe 覆盖恶意页面,诱导用户在不知情的情况下点击隐藏的按钮。
一、点击劫持概述
1.1 什么是点击劫持
点击劫持(Clickjacking),也称为 UI-Redress Attack(UI 重定向攻击),是一种利用 HTML iframe 的透明特性,将目标网站嵌入恶意页面,通过视觉欺骗诱导用户进行误操作的攻击方式。
1.2 攻击流程
code
┌─────────────────────────────────────────────────────────────┐
│ 恶意网站 (evil.com) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 伪装层 (z-index: 1) │ │
│ │ ┌─────────────────┐ │ │
│ │ │ "领取红包" │ ← 用户看到并点击 │ │
│ │ └─────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 透明 iframe (z-index: 2, opacity: 0) │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 目标网站 (target.com/delete) │ │ │
│ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ [删除账户] │ ← 实际点击位置 │ │ │
│ │ │ └─────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘1.3 点击劫持类型
| 类型 | 英文名称 | 攻击方式 | 典型场景 |
|---|---|---|---|
| 经典点击劫持 | Classic Clickjacking | 透明 iframe 覆盖 | 删除账户、授权操作 |
| 拖拽劫持 | Drag and Drop Clickjacking | 拖拽敏感数据到恶意区域 | 窃取数据、执行脚本 |
| 文件劫持 | File Hijacking | 覆盖文件上传按钮 | 上传恶意文件 |
| 光标劫持 | Cursor Jacking | 移动光标显示位置 | 误导用户点击 |
| 触摸劫持 | Tapjacking | 移动端触摸劫持 | 移动应用攻击 |
二、攻击原理与示例
2.1 经典攻击示例
基础攻击代码
html
<!-- HTML 结构省略,仅展示关键 JS 逻辑 -->精确定位攻击
html
<!DOCTYPE html>
<html>
<head>
<title>社交媒体活动</title>
<style>
/* iframe 精确定位 */
.iframe-container {
position: absolute;
top: 100px; /* 精确定位到目标按钮 */
left: 200px;
width: 150px;
height: 50px;
// ... 中间省略 ...
</div>
<!-- 伪装按钮 -->
<div class="fake-btn">立即参与</div>
</body>
</html>2.2 拖拽劫持攻击
html
<!DOCTYPE html>
<html>
<head>
<title>拖拽游戏</title>
<style>
.drag-source {
position: absolute;
top: 100px;
left: 100px;
width: 100px;
height: 100px;
background: #4CAF50;
// ... 中间省略 ...
// 设置恶意数据
e.dataTransfer.setData('text', '<script>alert("XSS")</script>');
});
</script>
</body>
</html>2.3 文件上传劫持
html
<!DOCTYPE html>
<html>
<head>
<title>文档编辑器</title>
<style>
/* 隐藏文件上传 iframe */
.upload-iframe {
position: absolute;
top: 200px;
left: 100px;
width: 200px;
height: 60px;
// ... 中间省略 ...
<iframe src="https://target.com/upload" class="upload-iframe"></iframe>
<!-- 伪装的上传按钮 -->
<div class="fake-upload-btn">选择文件</div>
</body>
</html>2.4 高风险场景
| 场景 | 攻击目标 | 潜在危害 |
|---|---|---|
| 网上银行 | 转账确认按钮 | 资金被盗 |
| 社交媒体 | 授权第三方应用 | 账号被控制 |
| 管理后台 | 删除操作按钮 | 数据丢失 |
| 电子商务 | 确认收货按钮 | 财产损失 |
| 邮箱系统 | 发送邮件按钮 | 隐私泄露 |
| 权限设置 | 授权操作按钮 | 权限被篡改 |
三、防护措施详解
3.1 X-Frame-Options 响应头
参数说明
| 值 | 说明 | 使用场景 |
|---|---|---|
DENY | 完全禁止嵌入任何 iframe | 高安全要求页面 |
SAMEORIGIN | 只允许同源页面嵌入 | 一般网站 |
ALLOW-FROM origin | 允许指定来源嵌入(已弃用) | 特定白名单 |
服务端配置
javascript
// Express.js
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
next();
});
// 或使用 helmet
const helmet = require('helmet');
app.use(helmet.frameguard({ action: 'deny' }));nginx
# Nginx 配置
server {
# 全局配置
add_header X-Frame-Options "SAMEORIGIN" always;
# 特定路径配置
location /admin {
add_header X-Frame-Options "DENY" always;
}
# 允许特定来源(旧语法,新浏览器可能不支持)
location /widget {
add_header X-Frame-Options "ALLOW-FROM https://trusted.com" always;
}
}apache
# Apache 配置
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
</IfModule>局限性
javascript
// ❌ 局限性:
// 1. ALLOW-FROM 已被现代浏览器弃用
// 2. 无法指定多个允许的来源
// 3. 对旧浏览器兼容性问题
// ✅ 推荐:使用 CSP frame-ancestors 替代3.2 Content-Security-Policy frame-ancestors
基本语法
http
# 只允许同源嵌入
Content-Security-Policy: frame-ancestors 'self';
# 允许特定来源
Content-Security-Policy: frame-ancestors 'self' https://trusted.com https://partner.com;
# 允许所有来源(不推荐)
Content-Security-Policy: frame-ancestors *;
# 禁止所有嵌入
Content-Security-Policy: frame-ancestors 'none';服务端配置
javascript
// Express.js
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"frame-ancestors 'self' https://trusted.com"
);
next();
});
// 或使用 helmet
app.use(
helmet.contentSecurityPolicy({
directives: {
frameAncestors: ["'self'", "https://trusted.com"]
}
})
);nginx
# Nginx 配置
server {
add_header Content-Security-Policy "frame-ancestors 'self' https://trusted.com" always;
}多来源配置
javascript
// 复杂场景:根据路径动态配置
app.use((req, res, next) => {
const path = req.path;
let frameAncestors;
if (path.startsWith('/admin')) {
// 管理后台:禁止嵌入
frameAncestors = "'none'";
} else if (path.startsWith('/widget')) {
// 嵌件:允许合作伙伴嵌入
frameAncestors = "'self' https://partner1.com https://partner2.com";
} else {
// 普通页面:同源嵌入
frameAncestors = "'self'";
}
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
res.setHeader('Content-Security-Policy', `frame-ancestors ${frameAncestors}`);
next();
});3.3 JavaScript 检测与防御
基础检测脚本
javascript
/**
* 点击劫持检测工具
*/
class ClickjackingDefender {
/**
* 检测是否被嵌入 iframe
*/
static isEmbedded() {
try {
return window.self !== window.top;
} catch (e) {
return true; // 跨域访问抛出异常,说明被嵌入
// ... 中间省略 ...
}
// 自动执行
ClickjackingDefender.breakOut();
// 或
ClickjackingDefender.showWarning();CSS 防护(配合 JS)
html
<!DOCTYPE html>
<html>
<head>
<style>
/* 默认隐藏页面内容 */
body {
display: none;
}
/* 当不在 iframe 中时显示 */
body.safe {
display: block;
}
</style>
</head>
<body>
<script>
// 检测并添加安全类
if (window.self === window.top) {
document.body.classList.add('safe');
} else {
// 被嵌入时的处理
window.top.location = window.self.location;
}
</script>
<!-- 页面内容 -->
<h1>安全的内容</h1>
</body>
</html>3.4 综合防护方案
javascript
/**
* 综合点击劫持防护
*/
function initClickjackingProtection() {
// 检测是否被嵌入
const isEmbedded = (() => {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
})();
// ... 中间省略 ...
}
}, 100);
}
// 立即执行
initClickjackingProtection();四、配置参数详解
4.1 X-Frame-Options 配置详解
javascript
/**
* X-Frame-Options 配置工具
*/
class XFrameOptionsConfig {
/**
* 根据页面类型配置
*/
static getHeader(pageType) {
const configs = {
// 敏感页面:完全禁止
admin: {
value: 'DENY',
// ... 中间省略 ...
};
}
}
// 使用
app.use(XFrameOptionsConfig.expressMiddleware());4.2 CSP frame-ancestors 配置详解
javascript
/**
* CSP frame-ancestors 配置工具
*/
class CspFrameAncestorsConfig {
/**
* 解析配置
*/
static parse(sources) {
return sources.map(source => {
// 特殊关键字
if (source === "'self'") return "'self'";
if (source === "'none'") return "'none'";
// ... 中间省略 ...
'/payment': ["'none'"],
'/widget': ["'self'", "https://partner1.com", "https://partner2.com"],
'*': ["'self'"]
};
app.use(CspFrameAncestorsConfig.expressMiddleware(cspConfigs));4.3 Nginx 完整配置
nginx
# /etc/nginx/conf.d/security.conf
# 基础点击劫持防护
server {
# 全局默认:同源嵌入
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
# 敏感路径:完全禁止
location ~ ^/(admin|payment|login|settings) {
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none'" always;
// ... 中间省略 ...
# 代理配置...
}
}
# 注意:使用 always 确保所有响应都携带安全头五、与其他安全威胁的关系
5.1 点击劫持与 CSRF 的区别
| 特性 | 点击劫持 | CSRF |
|---|---|---|
| 攻击方式 | 视觉欺骗,诱导点击 | 伪造请求,自动提交 |
| 用户交互 | 需要用户点击 | 不需要用户点击 |
| 依赖条件 | iframe 嵌入 | 用户已登录 |
| 防护方式 | X-Frame-Options、CSP | CSRF Token、SameSite |
| 请求来源 | 用户主动点击 | 攻击者构造的请求 |
5.2 点击劫持与 XSS 的组合攻击
html
<!-- 点击劫持 + XSS 组合攻击 -->
<!--
攻击者利用点击劫持诱导用户点击,触发 XSS 漏洞
例如:点击劫持诱导用户点击一个存在 XSS 的按钮
-->
<iframe src="https://target.com/search?q=<script>steal(document.cookie)</script>"
style="opacity: 0; position: absolute;">
</iframe>
<div class="fake-button">领取红包</div>防护要点:
- 点击劫持防护:X-Frame-Options、CSP
- XSS 防护:输出编码、CSP
5.3 多层防护架构
code
┌─────────────────────────────────────────────────────────┐
│ 第一层:HTTP 头 │
│ ┌─────────────────┐ ┌─────────────────────────┐ │
│ │ X-Frame-Options │ + │ CSP frame-ancestors │ │
│ │ (兼容性保护) │ │ (精确控制) │ │
│ └─────────────────┘ └─────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 第二层:JavaScript 检测 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ window.top === window.self 检测 │ │
│ │ 防止绕过 HTTP 头的情况 │ │
│ └─────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 第三层:业务逻辑保护 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 重要操作二次确认、验证码 │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘六、常见问题解答(FAQ)
Q1: X-Frame-Options 和 CSP frame-ancestors 应该使用哪个?
回答:推荐同时使用两者:
http
# 推荐配置
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' https://trusted.com| 特性 | X-Frame-Options | CSP frame-ancestors |
|---|---|---|
| 兼容性 | 更广泛 | 现代浏览器 |
| 灵活性 | 低(只能同源或禁止) | 高(可指定多个来源) |
| 控制粒度 | 粗粒度 | 细粒度 |
建议:使用 CSP 作为主要防护,X-Frame-Options 作为兼容性补充。
Q2: 如何允许特定网站嵌入我的页面?
回答:使用 CSP frame-ancestors:
http
# 允许多个特定来源
Content-Security-Policy: frame-ancestors 'self' https://partner1.com https://partner2.com
# 不允许 X-Frame-Options: ALLOW-FROM(已弃用)Q3: JavaScript 检测方法是否可靠?
回答:不完全可靠,存在以下问题:
javascript
// 可能被绕过的方式:
// 1. 禁用 JavaScript
<noscript>需要启用 JavaScript</noscript>
// 2. 早期版本 IE 的 window 对象覆盖
// 3. 某些安全工具可能阻止检测脚本
// 4. 攻击者可能修改 iframe 沙箱属性
<iframe src="..." sandbox="allow-scripts">建议:JavaScript 检测仅作为补充手段,主要依赖 HTTP 头。
Q4: 移动端是否也有点击劫持风险?
回答:是的,移动端存在触摸劫持(Tapjacking):
html
<!-- 移动端触摸劫持 -->
<!-- 使用 touchstart/touchend 事件欺骗用户 -->
<style>
.fake-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
opacity: 0; /* 触摸层透明 */
}
</style>
<div class="fake-overlay">
<!-- 捕获触摸事件并转发到隐藏的 iframe -->
</div>移动端防护:
- 同样使用 X-Frame-Options 和 CSP
- 重要操作使用原生确认对话框
- 考虑使用 HTTPS 确保连接安全
Q5: 如何测试点击劫持防护是否有效?
回答:创建测试页面:
html
<!DOCTYPE html>
<html>
<head>
<title>点击劫持测试</title>
</head>
<body>
<h1>点击劫持测试页面</h1>
<iframe
src="https://your-site.com/sensitive-page"
width="800"
height="600"
// ... 中间省略 ...
console.log('错误:', e.message);
}
};
</script>
</body>
</html>预期结果:
- 页面无法加载或显示空白
- 控制台显示 CSP 错误
Q6: 嵌件(Widget)场景如何平衡安全与可用性?
回答:嵌件场景需要特殊处理:
javascript
// 嵌件防护策略
app.get('/widget', (req, res) => {
// 1. 验证请求来源
const referer = req.headers.referer || req.headers.origin;
const allowedOrigins = ['https://trusted-partner.com'];
// 2. 设置 CSP(允许特定来源嵌入)
const allowedOriginsStr = allowedOrigins.join(' ');
res.setHeader(
'Content-Security-Policy',
`frame-ancestors 'self' ${allowedOriginsStr}`
);
// 3. 嵌件内容应尽量简单,减少敏感操作
// ...
});嵌件安全原则:
- 嵌件不应包含敏感操作
- 使用 postMessage 与父页面通信
- 验证消息来源
- 限制嵌件功能范围
七、安全检测清单
开发阶段
- 所有敏感页面设置 X-Frame-Options
- 敏感页面设置 CSP frame-ancestors
- 实现 JavaScript 检测(作为补充)
- 重要操作添加二次确认
测试阶段
- 创建测试页面验证防护效果
- 测试各种浏览器兼容性
- 测试不同嵌入方式
- 验证嵌件功能是否正常
部署阶段
- 配置服务器安全响应头
- 配置 Nginx/Apache 安全头
- 监控异常嵌入请求
- 定期审计安全配置
八、参考资料
💡 提示:点击劫持防护主要通过 HTTP 响应头实现。推荐同时使用 X-Frame-Options 和 CSP frame-ancestors,配合 JavaScript 检测作为补充。对于嵌件场景,需要平衡安全性与可用性,精确配置允许嵌入的来源。