「我以为按下的是这个按钮,实际却是背后另一个页面的按钮」——这就是点击劫持。本文讲解其原理与可靠的防御方法(不公开攻击步骤)。
什么会被盯上
「一键即可完成、而且有价值的操作」是攻击目标。
| 容易被盯上的操作 | 为什么会被盯上 |
|---|---|
| 转账、购买的确认 | 一键即可让金钱发生流动 |
| 修改设置(可见范围、授权许可) | 之后会导致账号被接管或信息被获取 |
| OAuth/权限的「允许」按钮 | 被擅自批准账号关联授权 |
| 社交媒体的关注、点赞、发帖 | 被滥用于擅自传播、刷赞 |
为什么会成立(原理)
浏览器可以用 iframe 加载另一个站点并叠加显示。攻击者把这个 iframe 设为透明(不透明度为 0),用户看到的只有放在下方的假 UI。用户按下「假按钮」时,从坐标上看,正叠加在其上方的真实按钮就被按下了。
它与 CSRF 相似,但 CSRF 是「在背后发送请求」,而点击劫持是让本人去操作真实的画面,因此仅靠一次性令牌等 CSRF 对策无法防御。
防御:核心是「不让被嵌入」
设置 CSP frame-ancestors(最优先)
在响应头中加入 Content-Security-Policy: frame-ancestors 'self'(仅允许自己的站点框架化)。如果不需要让第三方嵌入,用 'none' 最安全。只有在确有需要允许的对象时才逐一列出域名。
同时使用 X-Frame-Options(向后兼容)
为了照顾老旧环境,也加上 X-Frame-Options: DENY(或 SAMEORIGIN)。这是兼顾新旧浏览器的多层防御。
对重要操作要求「再加一道」
转账、授予权限等致命操作,要插入二次认证、确认对话框、操作前的意图确认。这样透明叠加就更难突破。
把会话 Cookie 设为 SameSite
用 SameSite=Lax/Strict 抑制来自其他站点的自动发送。它对点击劫持本身并非万能,但能削弱相关的各类「源自他站的操作」。
本站的观点:一行响应头最有效。先来测自己的站点
点击劫持的对策不是花哨的代码,而是用一到两行响应头夺走其叠加基础,这是最短路径。在框架或反向代理(Caddy/nginx)上对所有页面统一加上,杜绝遗漏,才是实战做法。你的站点上 frame-ancestors 是否生效,可以用本站的工具中的安全响应头诊断来确认。「自以为已经设置好了」其实却漏掉了,才是最常见的事故。
接下来阅读
FAQ
Q点击劫持会让人遭受什么?
用户「自以为」按下的点击,实际落在了叠加于背后那个真实页面的按钮上。一键转账、修改设置、授予权限、在社交媒体上关注/点赞、同意按钮等「一键即可完成的重要操作」都会被滥用。它的特点不是窃取密码,而是让已登录的用户本人去执行操作。
Q最有效的防御是什么?
就是「不让自己的站点被嵌入到他站的框架(iframe)里」。在响应头中把 CSP 的 frame-ancestors 设为 'self'(或仅允许指定域名),并为了向后兼容同时使用 X-Frame-Options: DENY。这样就能直接夺走可供叠加的基础。
Q只靠 JavaScript 的 frame busting(跳出框架)够吗?
不够。老式的「如果发现自己在 iframe 内就跳出去」的 JS 有很多绕过手法,且在 JS 被禁用时无法防护。核心做法是用服务器端的响应头(frame-ancestors / X-Frame-Options),让浏览器直接拒绝被嵌入。