但现代浏览器给了攻击者太多"活过页面关闭"的能力——Service Worker、扩展通信、缓存、后台同步。
当 XSS 不再是"一次性"的,检测的重点也必须从"输入过滤"转向"运行时异常发现"。
这篇文章不讨论怎么利用这些攻击面。讨论的是:如果它们正在发生,你从哪些信号能发现。
现代浏览器攻击面的共同特征不是"新漏洞",而是浏览器自身提供的合法机制被滥用。 Service Worker 是合法的,扩展消息通道是合法的,DNS 解析是合法的,PDF 里的 JavaScript 也是合法的。 正因为它们是合法机制,检测不能靠"黑名单",只能靠"基线偏离"。

传统 XSS 检测的核心是"输入里有没有恶意字符"。这条路在现代攻击面前基本失效:
所以检测视角需要从"输入验证"转向"浏览器运行时行为基线": 你的应用在正常状态下会注册几个 Service Worker、会向哪些扩展发消息、会向哪些私有 IP 发请求。 偏离这个基线的,就是信号。
Service Worker 一旦注册成功,会跨浏览器会话存活,拦截它 scope 下的每一个请求, 修改请求和响应,读写 Cache[reference:1]。它的威胁不是"当前页面被篡改", 而是"即使服务器已经修复,用户浏览器里那个恶意 SW 还在工作"[reference:2]。
importScripts() 引入了未知第三方脚本,说明供应链或注入路径可能被打开[reference:4]。DOM Clobbering 的本质不是"执行了恶意脚本",而是页面自己的代码信任了不该信任的全局属性。 比如代码写了 if (window.config.admin) { ... }, 而攻击者通过注入 <form id="config"><input name="admin" value="true"></form> 让 window.config.admin 意外为真。
window.xxx、document.xxx 直接作为对象使用的地方,尤其是后面跟着 .admin、.config、.token 这类敏感属性。id 和 name 属性, 但没有隔离命名空间,DOM Clobbering 的条件就成立。require-trusted-types-for 'script' 会让浏览器拒绝把普通字符串赋给 innerHTML 等危险 sink[reference:8]。 这对 DOM XSS 是浏览器层面的强制缓解[reference:9]。id/name 被滥用为全局属性的可能[reference:10]。window.config,写显式的 AppConfig.get('admin'),把"信任"收敛到受控的命名空间里。这不是"扩展本身有漏洞",而是扩展把 externally_connectable 开得太宽, 又没有校验发送者来源。 一个真实的案例:扩展的 externally_connectable.matches 设为 ["http://*/*", "https://*/*"], 后台 onMessageExternal 不验证 sender origin,任何网页都能触发扩展去抓取任意 URL[reference:11]。
externally_connectable.matches。 如果看到 http://*/* 或 https://*/*,这就是一个需要重点审查的暴露面[reference:12]。onMessageExternal 的回调里有没有 sender.origin / sender.url 的白名单检查。 没有校验的,就是 confused deputy 的候选[reference:13]。externally_connectable.matches 只允许受控的自家域,不要用通配[reference:15]。DNS 重绑定的核心是利用浏览器对 DNS 的信任和短 TTL: 域名第一次解析到攻击者服务器,加载恶意页面;TTL 过期后,同一域名解析到内网 IP。 浏览器认为"还是同一个源",于是恶意 JS 可以向内网发请求[reference:18]。
Referer 或 Origin 是一个外部域名,这是一个强信号。Host[reference:20]。扩展名校验和 Content-Type 都不足以拦住恶意 PDF。 真正的检测必须看文件内容:PDF 里是否包含 /JavaScript 或 /JS 关键字[reference:23]。
/JavaScript、/JS、/OpenAction 等主动内容标记。CSP 的 connect-src 理论上可以限制 WebSocket,但实践中容易出现两个问题: 一是配置成了 connect-src 'self' ws: wss:,过于宽泛; 二是某些浏览器/沙箱场景下,混合内容或未加密的 WebSocket 没有被严格阻断[reference:26][reference:27]。
connect-src 里出现 ws:(未加密)或宽泛的 wss:。 如果业务不需要 WebSocket,这是一个可以直接收紧的点。connect-src 只允许具体的 WebSocket 端点, 而不是整个 ws: / wss: scheme[reference:28]。这两类严格来说不是"你的代码有洞",而是浏览器引擎和硬件层面的风险。 检测视角的重点不是"发现攻击",而是确认缓解措施是否在位。
高精度计时 + SharedArrayBuffer 可以构成缓存侧信道。 现代浏览器的缓解方式是跨源隔离: SharedArrayBuffer 只有在 COOP + COEP 头都设置、crossOriginIsolated === true 时才可用[reference:29][reference:30]。
SharedArrayBuffer, 就不应该返回 COOP/COEP——返回了反而说明某个功能在依赖它。 如果需要,确认 crossOriginIsolated 为 true,并且计时器精度已被浏览器量化[reference:31]。V8 等引擎的类型混淆漏洞一旦被利用,可以在浏览器沙箱内实现任意读写,进一步逃逸到 RCE[reference:32]。 检测方能做的主要是确保缓解层没有缺失:
chrome://process-internals)[reference:33]。这些不是"应用层检测规则",而是终端安全基线和浏览器管理策略的一部分。
上面的攻击面分散在不同的层:应用代码、扩展、网络、文件上传、终端策略。 对防御方来说,最有操作性的方式不是"每个攻击面写一条 YARA",而是按数据源归集检测能力。
| 数据源 | 能覆盖的攻击面 | 关键检测动作 |
|---|---|---|
| 客户端 JS 监控 / RUM | Service Worker 注册、DOM Clobbering 的注入迹象 | 上报 SW 注册事件(scope + script URL);监控 id/name 属性注入导致的全局属性变化 |
| 扩展审计 | externally_connectable 滥用 | 清单中宽匹配告警;外部消息处理器缺少来源校验告警 |
| 服务端访问日志 / Host 校验 | DNS 重绑定 | 拒绝非白名单 Host;记录来自外部 Origin 的内网访问尝试 |
| 文件上传管线 | PDF 内嵌 JavaScript | magic-byte 确认类型 + PDF 内容扫描 /JavaScript / /JS |
| CSP 报告 / 配置审计 | WebSocket 宽泛、Trusted Types 缺失 | 检查 connect-src 是否包含 ws:;检查是否启用 require-trusted-types-for |
| 终端 / 浏览器管理 | 侧信道与 RCE 的底层缓解 | Site Isolation、补丁级别、非管理员运行、沙箱状态 |
如果你的团队现在才开始建立浏览器侧的检测,不建议一次性把所有攻击面都覆盖。 按"攻击者获得持久控制的速度"排序,优先级是:
connect-src 收口。 这些是基线,不是"发现攻击"的工具,但缺失会让前面的努力打折扣。现代浏览器攻击面的检测,本质上不是"找新的攻击特征",而是建立并持续验证"正常行为基线":
偏离基线的地方,就是需要深入的地方。这比维护一张"恶意 payload 特征表"更可持续—— 因为攻击者可以利用的合法机制会越来越多,但你的应用正常行为的边界是相对稳定的。