{T}

点击劫持防护

点击劫持(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、CSPCSRF 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-OptionsCSP 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 检测作为补充。对于嵌件场景,需要平衡安全性与可用性,精确配置允许嵌入的来源。