iframe 安全与沙箱

深入理解 iframe 沙箱机制、CSP 限制、安全配置,以及防止点击劫持和 iframe 滥用。

iframe 安全与沙箱(iframe Security and Sandbox)

一、iframe 基础与安全问题

1.1 基本用法

<iframe src="https://example.com/page.html" width="800" height="600">
  您的浏览器不支持 iframe
</iframe>

1.2 安全风险

iframe 引入的主要安全风险:

  • 点击劫持:将目标网站嵌入iframe,通过透明层诱导用户点击
  • XSS 攻击:如果 iframe 加载的页面存在 XSS,可能影响父页面
  • 数据泄露:通过 postMessage 与 iframe 通信时的信息泄露
  • 会话Cookie 泄露:某些情况下子域名 iframe 可以访问父域的 Cookie

二、sandbox 属性

2.1 基本沙箱配置

<!-- 最严格的沙箱:禁用所有功能 -->
<iframe sandbox src="page.html"></iframe>

<!-- 启用部分功能 -->
<iframe sandbox="allow-forms allow-scripts" src="page.html"></iframe>

2.2 沙箱选项详解

允许的功能
allow-forms 表单提交
allow-modals 打开模态窗口(alert, prompt 等)
allow-orientation-lock 锁定屏幕方向
allow-pointer-lock 使用 Pointer Lock API
allow-popups 打开弹窗和窗口
allow-popups-to-escape-sandbox 弹窗可以访问父页面(危险)
allow-presentation 使用 Presentation API
allow-same-origin 将内容视为同源(会降低安全性)
allow-scripts 执行 JavaScript
allow-top-navigation 导航父页面
allow-top-navigation-by-user-activation 用户触发后导航父页面
allow-storage-access-by-user-activation 用户触发后访问父页面 Storage
allow-downloads-with-user-activation 用户触发后允许下载

2.3 安全建议

<!-- 推荐:只允许必要的功能 -->
<iframe
  sandbox="allow-scripts allow-same-origin"
  src="trusted-content.html"
></iframe>

<!-- 不推荐:allow-same-origin + allow-scripts 组合 -->
<!-- 这实际上禁用了沙箱,因为脚本可以移除 sandbox 属性 -->

<!-- 危险:allow-popups-to-escape-sandbox -->
<!-- 这允许弹窗绕过沙箱限制 -->

三、X-Frame-Options

3.1 防止点击劫持

X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
X-Frame-Options: ALLOW-FROM https://trusted-site.com
// 在服务器端设置响应头
// Node.js Express
app.use((req, res, next) => {
  res.setHeader('X-Frame-Options', 'DENY');
  next();
});

3.2 CSP frame-ancestors

Content-Security-Policy: frame-ancestors 'none'
Content-Security-Policy: frame-ancestors 'self'
Content-Security-Policy: frame-ancestors trusted-site.com

四、allow 属性

4.1 Permission Policy(原 Feature Policy)

<iframe
  src="https://example.com"
  allow="camera; microphone; geolocation"
></iframe>

<!-- 使用 Permission Policy -->
<iframe
  src="https://example.com"
  allow="geolocation 'self'; camera 'self'"
></iframe>

4.2 常见权限策略

特性 Permission Policy 值
地理位置 geolocation
摄像头 camera
麦克风 microphone
支付 payment
USB usb
全屏 fullscreen
存储访问 storage-access

五、postMessage 安全

5.1 安全的跨窗口通信

// 发送端
const iframe = document.getElementById('myIframe');
iframe.contentWindow.postMessage('message', 'https://expected-origin.com');

// 接收端
window.addEventListener('message', (event) => {
  // 验证来源(必须)
  if (event.origin !== 'https://expected-origin.com') {
    return;
  }

  // 验证 source(可选但推荐)
  // event.source 应该是预期的窗口

  console.log('Received:', event.data);
});

5.2 验证 source 窗口

const expectedOrigins = ['https://example.com', 'https://app.example.com'];

window.addEventListener('message', (event) => {
  // 验证来源
  if (!expectedOrigins.includes(event.origin)) {
    console.warn('Rejected message from:', event.origin);
    return;
  }

  // 如果需要,验证 source
  if (event.source !== expectedWindow) {
    console.warn('Message from unexpected window');
    return;
  }

  console.log('Received:', event.data);
});

六、实际应用

6.1 安全嵌入第三方内容

<!-- 使用沙箱 + CSP -->
<iframe
  src="https://third-party-widget.com/widget.html"
  sandbox="allow-scripts allow-same-origin"
  allow="geolocation 'none'; microphone 'none'; camera 'none'"
  loading="lazy"
></iframe>

6.2 检测 iframe 是否被嵌入

// 检测页面是否在 iframe 中运行
if (window.self !== window.top) {
  console.log('Running inside iframe');
} else {
  console.log('Running in top-level window');
}

6.3 处理 iframe 加载错误

const iframe = document.getElementById('myIframe');

iframe.addEventListener('load', () => {
  console.log('Iframe loaded successfully');
});

iframe.addEventListener('error', () => {
  console.error('Iframe failed to load');
  // 显示错误信息或备用内容
});

参考资料

延展阅读