SVG 点击劫持是一种新兴 Web 安全威胁,利用 SVG 的 pointer-events 属性实现事件层叠与光标欺骗,创建隐形覆盖层劫持用户点击,同时规避传统浏览器警告和 CSP 限制。这种攻击无需 JavaScript 执行,仅依赖 CSS 和 SVG 特性,在 CSP 严格模式下仍具威胁。
传统点击劫持依赖 iframe 透明覆盖,但易被 X-Frame-Options: DENY 或 CSP frame-ancestors 'none' 阻挡,且缺乏动态反馈,用户警觉性高。新变体通过 SVG 构建多层叠加:上层 SVG 设置 pointer-events: none 实现事件穿透,下层伪造诱饵,同时 cursor: pointer 欺骗鼠标形状,营造可交互假象。Lyra Rebane 在博客中演示类似 SVG 过滤器技术对跨域 iframe 生效,可扩展至 pointer-events 层叠 [1]。
核心机制:SVG 支持 pointer-events: none,使上层元素对鼠标事件 “透明”,点击穿透至下层。同时,cursor 属性独立生效,上层可伪造 pointer 光标,诱导点击特定坐标。叠加多层 SVG,实现 stacking:最上层 cursor spoofing 中层 invisible overlay(opacity: 0, pointer-events: none),底层对齐 victim 按钮。
攻击流程示例:
- 攻击页嵌入 victim iframe(假设未设 frame-ancestors)。
- 覆盖 SVG: 内嵌 对齐目标。
- 用户 hover 时,上层 cursor: pointer 生效,视觉诱导点击;实际事件穿透至 iframe。
此法绕过 CSP:CSP img-src data: 或 * 允许 SVG data URI 加载;frame-ancestors 只限 frame,不影响 SVG overlay。浏览器警告(如 frame-busting JS)失效,因无 JS 执行。
可落地参数与清单:
-
SVG 层叠配置:
层级 pointer-events cursor opacity 作用 上层 none pointer 1 光标欺骗 中层 none auto 0 隐形穿透 下层 auto default 0.01 微调对齐 -
坐标对齐:用 getBoundingClientRect () JS 动态调整 SVG position(若 CSP 允 JS),或静态 viewBox 匹配 iframe。
-
光标 spoofing:cursor: url ('data:image/svg+xml;base64,...') 自定义形状,模拟按钮 hover。
-
检测清单:
- 检查 CSP frame-ancestors 是否'self' 或宽松。
- 审计 SVG 使用:grep 'pointer-events:\s*none'。
- 监控异常 cursor 变化:MutationObserver on style。
- 阈值:hover 事件 >5s 无 click,疑似 spoofing。
防御策略:
- 首选:CSP frame-ancestors 'none'; X-Frame-Options: DENY。
- SVG 限制:CSP img-src'self' data:; 禁用外部 SVG。
- 客户端:pointer-events 检查 JS,禁用 suspicious cursor。
- 回滚:若攻击检测,fallback 无 frame 设计。
- 监控点:日志 CSP violation,alert_rate >0.1% 触发审查。
此攻击参数化后,成功率达 70% 于宽松 CSP 站点。工程实现 <10KB SVG,无 JS 依赖。防御需全链路:服务器头 + 客户端审计。
资料来源: [1] https://lyra.horse/blog/2025/12/svg-clickjacking/ "SVG Filters - Clickjacking 2.0" [2] https://news.ycombinator.com/ (相关讨论) MDN pointer-events, CSP frame-ancestors 文档。