浏览器 Web 安全基础系列

  1. 浏览器怎么判断是不是同一个网站

  2. Cookie 为什么会自动携带

  3. Session 登录状态为什么可能被劫持

  4. HTTPS 为什么仍然不够

  5. CORS 到底在限制什么

  6. CSRF 是怎么冒用登录状态的

  7. CSRF 如何防御

  8. XSS 为什么能在你的网站执行

  9. CSP 与浏览器安全响应头

  10. Clickjacking 点击劫持(当前篇)

  11. 第三方资源为什么危险

  12. 防盗链到底防什么

Clickjacking 也叫点击劫持。攻击者不一定伪造接口,而是把真实网站嵌进透明 iframe,再把诱导界面盖在上面。用户以为自己在点击“领取奖励”,实际点击的是目标网站中的“授权”或“删除”。

点击劫持攻击

一个简化攻击页面

<style>
  iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0.01;
    z-index: 2;
  }
  .fake-button {
    position: absolute;
    left: 200px;
    top: 160px;
    z-index: 1;
  }
</style>

<button class="fake-button">领取奖励</button>
<iframe src="https://target.example/settings/delete"></iframe>

攻击者调整位置,让透明 iframe 中的真实按钮与诱导按钮重合。用户的点击事件实际落到 iframe。

它与 CSRF 的区别

CSRF:攻击者构造请求,浏览器自动带 Cookie
点击劫持:用户真的点击了目标页面中的控件,只是看不到真实界面

CSRF Token 可能无法阻止点击劫持,因为真实页面自己持有正确 Token,用户也确实完成了交互。

最有效的防御:禁止不可信嵌入

现代方案使用 CSP:

Content-Security-Policy: frame-ancestors 'none'

完全禁止页面被嵌入。

只允许本站:

Content-Security-Policy: frame-ancestors 'self'

允许明确合作方:

Content-Security-Policy: frame-ancestors 'self' https://portal.example

旧式兼容头:

X-Frame-Options: DENY

或:

X-Frame-Options: SAMEORIGIN

ALLOW-FROM 支持不一致,不应作为现代主要方案。

iframe 防护决策

Node.js 设置响应头

app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
  res.setHeader('X-Frame-Options', 'DENY');
  next();
});

重点是给需要保护的 HTML 页面响应设置,而不是只给 API JSON 设置。

iframe 业务怎么办

支付组件、文档预览、内部门户可能确实需要嵌入。这时不要使用任意通配符,应该列出明确的可信父页面 Origin,并将可嵌入页面与高风险账户管理页面分离。

嵌入页面可以通过 postMessage 与父页面通信,但必须验证来源:

window.addEventListener('message', (event) => {
  if (event.origin !== 'https://portal.example') return;
  handleMessage(event.data);
});

发送时也应指定准确目标 Origin,而不是 *

SameSite 的辅助作用

如果目标页面在跨站 iframe 中,SameSite=LaxStrict Cookie 通常不会携带,可能导致 iframe 内未登录,从而降低部分攻击成功率。但同站攻击、无需登录的敏感操作或特殊 Cookie 配置仍可能受影响。

SameSite 是辅助层,真正直接的防御是阻止不可信 framing。

高风险操作的交互设计

即使页面允许嵌入,敏感操作也可以增加:

  • 再次输入密码;

  • 清楚展示目标对象和后果;

  • 二次确认;

  • WebAuthn 或一次性验证码;

  • 检测可信用户激活。

这些措施不能替代 frame-ancestors,但可以降低误触和自动化风险。

常见误区

JavaScript frame busting 足够

旧代码常写:

if (top !== self) top.location = self.location;

它可能被 sandbox、浏览器限制或竞态绕过。应使用浏览器原生响应头策略。

隐藏按钮就安全

攻击者可以调整 iframe、滚动和透明度,依赖页面视觉布局不是安全边界。

CORS 能阻止 iframe

CORS 控制 JavaScript 读取跨源响应,不负责页面能否被 iframe 嵌入。使用 frame-ancestors 或 X-Frame-Options。

小结

点击劫持利用视觉欺骗让用户点击真实目标页面。防御重点不是识别“假按钮”,而是让敏感页面根本不能被不可信网站嵌入,再为高风险操作增加明确确认。

参考资料