点击劫持会诱导你点击叠在所见页面下方的隐藏页面上的按钮。本文讲解其原理与可靠的防御方法(不公开攻击步骤)。
什么会被盯上
「一键即可完成、而且有价值的操作」是攻击目标。
| 容易被盯上的操作 | 为什么会被盯上 |
|---|---|
| 转账、购买的确认 | 一键即可让金钱发生流动 |
| 修改设置(可见范围、授权许可) | 之后会导致账号被接管或信息被获取 |
| OAuth/权限的「允许」按钮 | 被擅自批准账号关联授权 |
| 社交媒体的关注、点赞、发帖 | 被滥用于擅自传播、刷赞 |
为什么会成立(原理)
浏览器可以用 iframe 加载另一个站点并叠加显示。攻击者把这个 iframe 设为透明(不透明度为 0),用户看到的只有放在下方的假 UI。用户按下「假按钮」时,从坐标上看,正叠加在其上方的真实按钮就被按下了。
它与 CSRF 相似,但 CSRF 是「在背后发送请求」,而点击劫持是让本人去操作真实的画面,因此仅靠一次性令牌等 CSRF 对策无法防御。
真实案例:浏览器插件的权限对话框(2008 年)
2008 年 10 月,Adobe 发布了一则关于影响多种浏览器的点击劫持问题的安全通告(APSA08-08)。据媒体报道,通过把 Flash Player 的设置对话框透明叠加,网站可以让用户在不知情的情况下授予它访问摄像头和麦克风的权限。Adobe 发布了修复版的 Flash Player,并作为临时规避措施,建议用户在设置管理器中选择「始终拒绝」。
教训是:被盯上的不只是转账按钮。凡是点一下就交出权限的画面(「允许」「同意」「关联」)都在范围之内。MITRE 把这一弱点归类为 CWE-1021(对渲染的 UI 图层或框架限制不当)。
防御:不让其他站点嵌入你的站点
设置 CSP frame-ancestors(最优先)
在响应头中加入 Content-Security-Policy: frame-ancestors 'self'(仅允许自己的站点框架化)。如果不需要让第三方嵌入,用 'none' 最安全。只有在确有需要允许的对象时才逐一列出域名。
同时使用 X-Frame-Options(向后兼容)
为了照顾老旧环境,也加上 X-Frame-Options: DENY(或 SAMEORIGIN)。这样新旧浏览器都能覆盖。
对重要操作加入确认步骤
转账、授予权限等致命操作,要插入二次认证、确认对话框、操作前的意图确认。这样透明叠加就更难突破。
把会话 Cookie 设为 SameSite
用 SameSite=Lax/Strict 抑制来自其他站点的自动发送。它对点击劫持本身并非万能,但能削弱相关的各类「源自他站的操作」。
X-Frame-Options 与 CSP frame-ancestors 的区别
两个响应头都能阻止其他站点嵌入你的站点,但细节不同。
| X-Frame-Options | CSP frame-ancestors | |
|---|---|---|
| 取值 | DENY(全部禁止)/ SAMEORIGIN(仅同源) | 'none' / 'self' / 允许的域名列表 |
| 允许多个嵌入方 | 不可以(ALLOW-FROM 在现代浏览器中不起作用) | 可以 |
| 用 meta 标签设置 | 不起作用 | 不起作用 |
| 两者同时存在时 | 被支持 frame-ancestors 的浏览器忽略 | 优先采用 |
| 定位 | 面向旧浏览器的后备 | 当今的主要防御 |
同时发送两者时,要让含义一致:frame-ancestors 'none' 搭配 DENY,frame-ancestors 'self' 搭配 SAMEORIGIN。
常见的配置失误
- 以为 CSP 的
default-src已经覆盖了。frame-ancestors不会回退到default-src。即使写了default-src 'none',只要没写frame-ancestors,嵌入仍然是允许的 - 写在 meta 标签里:
<meta http-equiv>在这里不起作用,要作为响应头发送 - 停留在
Content-Security-Policy-Report-Only:它只报告违规,不会阻止嵌入。确认运行正常后,切换为强制生效的响应头 - 只在部分页面设置:人们检查了首页,却漏掉了登录页、设置页或错误页。还要确认 CDN 或代理没有把响应头去掉
如何检查自己的站点
用响应头检测工具检查
在安全响应头检查中输入你站点的 URL,看是否返回了 X-Frame-Options 和 CSP 的 frame-ancestors。不只是首页,还要检查登录后的设置页,以及转账或授予权限的页面。
在浏览器的开发者工具中检查
打开「网络」标签页重新加载页面,查看 HTML 响应的响应头。同时确认响应头没有被发送两次,或者取值互相矛盾。
在一处统一设置
在一个地方(nginx、Caddy 等 Web 服务器、反向代理或框架)对所有响应统一添加,可以同时防止遗漏和重复。如果要新写 CSP,可以用 CSP 生成器组装包含 frame-ancestors 的策略。
本站的观点:一行响应头最有效,先检查自己的站点
点击劫持的对策不是复杂的代码,而是用一到两行响应头阻止嵌入。在框架或反向代理(Caddy/nginx)上对所有页面统一加上,避免遗漏。最常见的失误是以为已经设置好了,实际上响应头却漏掉了,所以设置之后,请按上面的步骤确认它确实被返回了。
出处(一手资料)
- OWASP,“Clickjacking Defense Cheat Sheet” — cheatsheetseries.owasp.org(frame-ancestors、X-Frame-Options、SameSite;ALLOW-FROM 已不起作用;meta 标签不起作用;frame busting 的局限)
- MDN,“CSP: frame-ancestors” — developer.mozilla.org(允许的取值、不回退到 default-src、meta 中不支持)
- RFC 7034,“HTTP Header Field X-Frame-Options” — rfc-editor.org
- IPA,《安全网站的制作方法》1.9 点击劫持(日文) — ipa.go.jp
- Adobe,“Security Advisory for Flash Player (APSA08-08)”(2008 年 10 月 7 日) — adobe.com
- Computerworld,“Clickjackers could hijack webcams, microphones, Adobe warns”(2008 年 10 月) — computerworld.com(被盯上的是摄像头和麦克风的权限对话框)
- MITRE,“CWE-1021: Improper Restriction of Rendered UI Layers or Frames” — cwe.mitre.org
接下来阅读
- 术语:什么是 CSRF / 什么是 XSS
- 指南:如何选择安全响应头(仍然需要的、已经过时的)
- 检查:安全响应头检查 / CSP 生成器