做安全分析这几年,我见过不少攻击链路,但像"书签劫持 + WS + 验证连接 + 发送收资源指令"这样把隐蔽性和实用性结合得这么紧密的组合,确实不多。它表面上看是在改浏览器收藏夹,实际上是一条完整的渗透通道:恶意代码通过 WebSocket 跟远端控制端建立常驻连接,先做一轮连接验证确认链路可用,再接收指令把书签、本地存储、Cookie 等浏览器资源批量运出去。
这篇文章我把这条链路从里到外拆一遍,讲清楚为什么恶意代码偏爱 WebSocket 做指令通道、连接验证到底在验证什么、收资源指令是怎么组织和执行的,最后把我在实际防御中验证过的检测方法和加固清单完整放出来。无论你是做安全开发、威胁分析、还是维护浏览器扩展生态,这篇都值得花十分钟读完。
1. 书签劫持的全貌:拆开标题里的攻击链路
1.1 书签劫持不只是"改收藏夹"
提到书签劫持,很多人第一反应是"往你收藏夹里塞一堆垃圾网址"。如果只看表面,确实是这么回事,但真正的杀伤力藏在三个层面里。
第一是入口跳板。书签在用户心里属于"信任区",收藏夹里的地址跟桌面快捷方式一样,用户点击前几乎不会做二次确认。攻击者往书签里插一条伪造的登录页、仿冒的内部系统入口,用户点下去的那一瞬间,就已经完成了钓鱼流程里最难的一步——让受害者主动访问攻击者控制的页面。在大量真实案例中,书签劫持就是用来做二次钓鱼的跳板,受害者看着"明明是我自己收藏的网址",实际上地址早被替换了。
第二是敏感数据源。书签本质上是一本"数字地址簿",记录了用户最高频访问的服务:网银、邮箱、云盘、代码仓库、内部管理系统。攻击者拿到这些地址,能很快分析出用户的职业、常用平台、服务托管入口,这些信息单独看不值钱,组合起来就是精准钓鱼和撞库的基础数据。更麻烦的是,书签数据会通过浏览器同步能力在多台设备间流转,劫持一台设备的书签,等于拿到了一个持续更新的情报窗口。
第三是持久化据点。书签不像 Cookie 会过期,也不像临时文件会被系统清理。攻击者植入一条隐蔽书签后,只要用户不发现,这个据点就会长期存在。配合恶意扩展的自动修复逻辑,即使你手动删除,浏览器下次启动时它又会写回来。这种持久化能力,才是防御者最头疼的地方。
1.2 攻击链路的四个阶段
把标题里的关键词串起来看,这条攻击链的逻辑非常清晰:
- 植入阶段。通过恶意浏览器扩展、被攻破的第三方脚本或诱导安装,把带恶意逻辑的代码送进目标浏览器。这个阶段的目标不是立刻偷数据,而是"落地站稳"。
- 通道建立。植入代码通过 WebSocket 向控制端发起连接。选择 WebSocket 而不是普通 HTTP 轮询,原因后面会专门展开,核心就是"隐蔽、稳定、实时"。
- 连接验证。连接建立后,双方不会马上传数据,而是先执行一轮验证握手的逻辑,确认对面确实是受控的浏览器实例,同时测试通道是否畅通,避免在无效连接上浪费指令和暴露通道。
- 资源采集。验证通过后,控制端才下发真正的收资源指令:枚举书签、读取本地存储、抓取当前页面信息、收集 Cookie。数据打包后沿同一条 WebSocket 通道回传。
这个链路最狡猾的地方在于前两步看起来都像正常业务。现在的浏览器扩展通过 WebSocket 跟远端同步数据非常普遍,抓包软件里根本分不清哪个是正常同步、哪个是恶意通信。真正的攻击特征藏在后半段——连接验证的握手规律和资源采集的指令频率上,而这两个维度恰恰是很多检测方案没有覆盖到的盲区。
1.3 为什么偏偏盯上书签这个资源
从攻击者的收益角度看,书签是性价比极高的目标。一方面它的读取权限要求极低,恶意扩展只要声明书签权限、或者更隐蔽的通过注入脚本的方式,就能把整个收藏夹拖走,这个权限在浏览器权限模型里几乎不会触发敏感警告。另一方面,书签自带"信任附加值"——用户收藏一个网址,隐含的语义是"我常来这里,我信任这个地址"。攻击者篡改一条书签或插入一条新记录,用户下一次点击就已经站在了钓鱼页面上。
所以在防御设计里,书签必须被当成高价值资产来保护。它不是系统里最不起眼的功能,而对攻击者来说,它是撬动整条信任链最省力的支点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. WebSocket通道:为什么恶意指令都爱走WS
2.1 WebSocket的基础特性:全双工、低开销、长连接
WebSocket 诞生就是为了解决 HTTP 请求-响应模型不适合实时通信的痛点。它通过一次 HTTP 升级握手建立 TCP 长连接,之后双方可以随时推送数据,不需要像轮询那样反复建立和释放连接。
这套机制放在正常业务里是聊天、行情推送、协同编辑的基础设施;放在攻击场景里就成了天然的指令通道。恶意代码需要一个实时待命、能随时收指令又不容易被打断的通路。WebSocket 的全双工特性让它既能收指令又能回传数据,一条连接全部搞定。长连接则意味着不需要频繁发起新请求,在行为检测上很占便宜——监控系统通常盯的是"短时间大量请求"这种异常,一条低频长连接很容易滑过去。
2.2 恶意场景下 WebSocket 的四个天然优势
抛开技术细节,站在攻击者视角,WebSocket 有四个很难被替代的优势:
- 流量形态与业务吻合。几乎所有实时应用都在用 WebSocket,恶意流量混在里面,从流量日志到协议特征都跟正常业务长得一样,检测方很难凭"用了 WS"这个事实做拦截。
- 出网限制容易绕过。多数网络默认放行 80 和 443 端口出站,WebSocket 握手从 HTTPS 升级而来,可以顺利走这些通道,常规端口策略拦不住。
- 状态保持天然适配。WebSocket 是有状态的长连接,服务器可以维护连接上下文。对攻击者来说,每条控制连接都是独立会话,不需要在每次指令里重复携带身份信息,通信特征被提取的难度就上来了。
- 双向消息容易伪装。指令下发和数据回传在同一条连接里以数据帧形式流动,配合加密或混淆编码,中间设备即使截获流量,也难以判断哪条是指令、哪条是采集结果。
2.3 WebSocket 与 HTTP 轮询通道的对比
这里整理了一张对比表,做检测策略时可以直接参考:
| 维度 | HTTP 轮询通道 | WebSocket 长连接通道 |
|---|---|---|
| 连接特征 | 短连接,高频建立与断开 | 长连接,建立后持续保持 |
| 流量可见性 | 每次请求独立,便于访问日志建模 | 建立后无逐条请求记录 |
| 出网难度 | 常规 HTTP,容易过防火墙 | HTTP 升级,80/443 可穿透 |
| 实时性 | 取决于轮询间隔,有延迟 | 全双工实时推送 |
| 检测难度 | 较低,靠频率和 URL 特征可识别 | 较高,低频率长连接难以建模 |
| 数据回传 | 需额外发起 POST 请求 | 同一连接直接回传 |
从表里能明显看到,WebSocket 在隐蔽性上全面占优。这也是为什么近几年的威胁情报分析里,WebSocket 协议上的恶意信道占比明显上升。握手阶段的 URL 和升级请求是唯一一次明文可见的机会,一旦握手完成,后续所有通信都躲在业务流量的掩护下面。
2.4 代理场景的盲区:Nginx 转发 WS 端口时的验证缺口
这里补充一个和 WebSocket 部署强相关的真实场景。很多系统通过 Nginx 做反向代理,把后端的 WebSocket 端口转发出去,常见的配置就是在 location 里加上 Upgrade 和 Connection 头,把 /ws 路径的升级请求转发到后端服务,比如 FreeSWITCH 这类实时通信服务经常这么干。
这种部署形态本身没问题,但代理层有一个天然盲区:Nginx 默认只做转发,不关注 WebSocket 连接建立之后的数据帧内容。换句话说,一旦握手成功,代理就把后面的事全部透明化了。如果恶意通道想混进某个合法系统的 WebSocket 端口,只要拿到合法的握手路径和必要的校验参数,就能借用一条看似正常的转发链路做隐蔽通信。
这个盲区给防御者提了个醒:代理层必须承担连接验证的职责,不能只做转发。业界常见做法是限制 WS 握手必须携带自定义 Token,在 Nginx 层通过 auth_request 或 Lua 脚本校验通过后才放行升级请求。更稳的是双 Token 策略:握手 Token 控制连接建立,业务 Token 控制消息级授权。这样即便握手被绕过,后续指令也无法构造出合法的消息结构。
3. 验证连接机制:指令通道的"握手暗号"
3.1 为什么恶意代码要多做一轮连接验证
很多人会问:攻击者已经控制了浏览器,直接发指令偷数据不就行了,为什么还要多此一举做验证连接?这个疑问很合理,但从攻击者的成本收益看,验证是不可省的一步。
最核心的原因是防止通道暴露。恶意扩展的植入量往往很大,但其中大量目标机器可能长期离线、浏览器版本不兼容、或者扩展被安全策略禁用。如果控制端对所有已连接客户端直接下发收资源指令,指令就会被安全设备采样到,反而暴露了通道的存在。先做一轮验证连接,把无效目标过滤掉,只对确认"活着且配合"的浏览器实例下发指令,被截获的风险大幅降低。
另一个原因是保持隐蔽性。验证连接的过程往往设计成和正常业务心跳高度相似。有的恶意扩展伪装成"在线状态同步",定时发送固定 JSON 心跳;有的把验证包装成版本检查,用 version、platform、app_id 这些字段跟服务器对表。从抓包层面看,这些流量跟正经业务的心跳没有任何差别。
3.2 常见的连接验证方式
在我做过的一次次实际分析中,无论正常业务还是恶意信道,连接验证的常用手段其实高度重合:
-
Token UUID 握手。客户端连上后发送一段随机生成的标识符,服务端核对是否在有效名单里。实现简单直接,大部分 WebSocket 服务都在用。
-
心跳 PING/PONG。连接建立后按固定间隔互发心跳包确认存活。WebSocket 协议本身就有 ping/pong 控制帧,很多实现直接拿来用。
-
版本与能力协商。客户端上报版本号、运行环境、可用的权限模块,服务端根据上报结果决定后续下发哪些指令。
-
加密挑战响应。服务端下发随机串,客户端用预设密钥计算哈希回传,通过后才建立可信会话。这是抗中间人能力最强的方案。
从防御视角,要关注的不只是"有没有验证",而是"验证强度在哪里"。如果抓包发现一个 WebSocket 连接总是在固定时间窗口内做固定模式的心跳,且响应内容高度一致,这就是一条自动化指令通道的强信号。
3.3 验证连接与业务验证的异同:从开发工具连 GitHub 说起
这里可以用一个大家最近都在讨论的场景做类比——开发辅助工具连接 GitHub 时提示需要开启双重验证(2FA)。GitHub 让你做双重验证,本质上是把"连接验证"从单向确认升级成双向强校验:你不但要证明持有账号密码,还要证明持有第二个独立凭证。这套逻辑搬到 WebSocket 上完全适用。
正常的业务 WebSocket 验证,通常会同时校验三样东西:连接来源(Origin 是否在合法列表里)、身份凭证(Token 是否有效)、权限范围(该身份是否有权访问当前资源路径)。而恶意信道通常只做一个轻量验证,比如固定 Token 加心跳保活。这就是两者在实现复杂度上最大的区别:业务系统验证做得越重,被伪装冒用的门槛就越高。
反过来讲,攻击者也在用同样的思路给自己加防御。如果恶意扩展能做到"连接时必须携带从植入环境生成的动态 Token、心跳节奏随机化",它在流量关联分析中被识别出来的概率就会显著下降。这就是一场持续的对抗:验证方不断加码,伪装方不断跟进。
3.4 从防御视角识别"验证连接"动作
识别 WebSocket 上的验证连接行为,核心是抓"模式重复度"。正常业务的心跳虽然也有周期性,但会伴随真实的业务数据帧;而恶意通道的验证阶段往往只有裸心跳和固定格式的握手响应。我在实际检测中发现三个比较有效的观测点:
第一,连接存活周期分布。恶意 WebSocket 通道的存活时间往往远超正常业务连接的统计基线。一个连接存活超过 24 小时且只有少量数据帧,就值得仔细排查。
第二,帧大小和频率的规律性。恶意验证连接的数据帧大小通常高度一致,比如每次都恰好是 32 字节或 64 字节的 JSON。真实业务的帧大小会随业务逻辑波动,不可能这么规整。
第三,连接目标域名的关联性。一个合法域名下集中出现大量规律心跳连接,每个连接都对应着用户端 UA,且随后伴随书签或存储数据读取行为,这已经是相当明显的恶意链路特征。
4. 收资源指令的构造与数据索取逻辑
4.1 浏览器内的主要资源入口
验证连接完成后,恶意代码就开始触碰浏览器里的各类数据源。结合书签劫持场景,下面几个入口是威胁面最大的:
- 书签数据。通过浏览器扩展接口可以枚举全部书签节点,包括 URL、标题、创建时间和目录路径。这类数据权限要求低,不需要用户额外授权。
- 本地存储与 IndexedDB。浏览器为每个站点维护独立的 localStorage 空间,恶意扩展拿到权限后可以全部拖走。很多应用把 Token、用户偏好、临时身份信息放在 localStorage 里,泄露风险极高。
- Cookie。Cookie 是会话保持的核心,拿到 Cookie 就能在多数无额外认证的站点直接冒充用户身份。恶意扩展读取 Cookie 的能力覆盖所有可达域,不只是当前页面。
- 浏览历史。历史记录里藏着用户访问习惯、活跃时间段、敏感站点清单,配合书签数据做交叉分析,能构建出画像级别的用户档案。
- 当前页面信息。通过注入脚本读取当前打开页面的 DOM、表单值、标题和 URL。如果用户正访问网银或后台管理系统,这条通道的危害会瞬间放大。
4.2 指令格式与执行流程
收资源指令的构造逻辑并不复杂,通常是一个 JSON 结构命令加若干参数。控制端定义一组指令类型,客户端扩展里写对应的执行分支,收到指令后调用浏览器 API 采集数据,打包成响应结果回传。
一个典型的流程是这样的:控制端下发一条采集书签的指令,包含采集范围和分页游标:
json复制{
"id": 1024,
"type": "collect_bookmarks",
"params": {
"target": "all",
"cursor": 0,
"limit": 100
}
}
客户端收到指令后,读取书签接口返回的对象树,把数据按层级拍平,转换成 JSON 数组,回传给控制端。控制端根据返回的游标决定是否继续翻页采集。为了避免单条消息过大,很多实现会做分页或分段处理,一次指令只回传 100 条记录。
这里有一个容易被忽略的细节:指令鉴权。一个设计粗糙的恶意通道,任何能连上 WebSocket 服务的人都能下发指令,这会增加被第三方截胡或者反制工具干扰的风险。所以讲究一点的实现会在每条消息里带上会话序号和校验签名,比如对消息体做 HMAC 签名,客户端收到后先验签再执行。防御方做流量分析时,如果观察到 WebSocket 消息中频繁出现体积小但结构固定的控制帧,且随后紧跟大体积的数据回传帧,这就是"指令-响应"模式的强特征。
4.3 数据打包与回传路径
数据采集完成后,怎么安全运回控制端同样有讲究。常规做法是直接在 WebSocket 通道里以 JSON 文本帧回传。如果数据量大,比如书签加历史加 Cookie 的混合导出,发送端会做压缩编码,例如 zlib 压缩后叠加 Base64,降低传输体积和网络审计的特征量。
有些实现走的是分段回传:先回传数据的元信息,包括总条数、分片数量、校验哈希,然后每个分片单独封装。这个做法的好处是中途断线可以续传,不用从头再采一遍。从防御角度看,分片回传的流量特征其实比一次性大包更容易识别,因为它会产生大量结构相似、长度相近的连续帧,在流量统计里非常扎眼。
4.4 小世界网络视角:为什么劫持会快速扩散
聊到扩散性,我想起网络科学里的一个小世界网络模型。这个模型描述了一个反直觉的规律:即使网络中的节点总数 n 很大,大多数节点之间的最短路径长度依然很短,通常呈对数级别增长。简单说,就是网络比想象中更"小",节点之间的联系比直觉上更紧密。
浏览器插件生态就是一个典型的小世界网络。开发者、使用者、第三方脚本之间存在大量隐性依赖和信任连接,数据和指令在这些节点之间流动得异常快。一旦一个恶意的书签劫持扩展进入生态,从感染单个用户到影响成千上万用户的时间窗口极短。一个用户把恶意链接分享出去,或者一个企业里多名员工安装了同一款被污染的插件,劫持点就会在几小时内从单点扩散成一张小网。
这个模型给防御者最重要的启示是:书签劫持的响应速度必须足够快。它的扩散不是线性爬坡,而是指数级滚大的。等你在日志里看到第一个事件再慢慢排查,可能已经错过了最佳处置窗口。
5. 检测与防御:从源头堵住劫持链路
5.1 浏览器端的自查方法
面对已经发生的劫持,先做浏览器端自查。我建议按下面的顺序完整走一遍:
第一步,查看扩展权限。打开扩展管理页面,逐项检查已安装扩展申请的权限。一个书签管理工具申请了"读取所有网站 Cookie",或者一个天气类扩展申请了"读取浏览历史",这种权限错配就是非常明确的危险信号。
第二步,审查书签数据。把书签导出成 HTML 文件,检查有没有自己不认识的地址、域名拼写可疑的入口、创建时间异常的记录。重点看不常访问的深层书签目录,恶意扩展往往把伪造书签藏在里面。
第三步,检查网络连接。有条件的话用抓包工具或浏览器网络日志功能,记录一段时间内的 WebSocket 连接。凡是存在 wss 长连接但对应域名与已安装扩展的合法后台服务对不上的,都要拉出来单独过一遍。
5.2 WebSocket 通信层的检测方案
针对 WebSocket 链路,我实际跑通过一套组合拳,分享出来供参考:
- 建立 WebSocket 握手日志的旁路采集。在 Nginx 等代理层开启 access_log,记录升级请求的路径、User-Agent、来源 IP。这层虽然看不到后续帧内容,但能统计出所有 WS 连接的建立时间、地址分布和频次。
- 对长连接做周期性指纹比对。在网关或边缘设备上按连接维度聚合数据帧:帧数量、帧大小分布、心跳间隔、上下行流量比。把这些维度和已知业务基线比对,偏差大的连接挂起复查。
- 引入消息内容的关键字检测。即使 WebSocket 数据帧加密,握手阶段和部分明文实现里仍可能暴露资源采集相关字段名。在合规审计点上对 bookmarks、history、cookie、collect 这类关键字做匹配,能筛掉一大半低水平实现。
| 检测维度 | 正常业务特征 | 疑似恶意特征 |
|---|---|---|
| 连接数量 | 随业务波动,有高峰低谷 | 恒定长连接,数量稳定 |
| 心跳间隔 | 有随机抖动 | 高度固定 |
| 数据帧大小 | 分布分散 | 聚集成少数几个固定值 |
| 上下行比 | 下行占优(业务推送) | 上行数据量大(采集上传) |
| 目标域名 | 与业务域名一致 | 新注册域名或无关第三方 |
5.3 面向开发者的安全加固清单
如果你是自己开发 WebSocket 服务端,防护重心要放在"默认不信任"上。下面这几条是我踩过坑之后固化的要求,直接抄作业即可:
- 握手阶段强制校验 Origin 头,只放行白名单来源。
- 连接建立后必须完成 Token 认证才能订阅任何业务主题,Token 要有失效时间。
- 每条业务消息带递增序号,服务端拒绝乱序或重复消息,防重放。
- 长连接设置空闲超时,比如 5 分钟无有效业务数据主动断开,客户端需重连。
- 消息体使用紧凑编码并附带完整性校验值,比如 HMAC-SHA256,防中间节点篡改。
这套清单不是理论推演,每一行都对应着实际处理过的问题。比如 Origin 校验那一行,就是有次线上服务被外部扫描器用伪造 Origin 连上后触发的警报。从那以后我所有的 WebSocket 服务都把 Origin 校验写进了规范。
5.4 浏览器策略层的管理手段
对企业批量管理的场景,浏览器策略比单个用户的习惯检查可靠得多。Chromium 内核浏览器可以通过策略文件锁定扩展安装来源,只允许从企业私有商店安装扩展;Firefox 和 Edge 也有对应的组策略入口。同时禁用外部扩展导入,把恶意扩展的入场路径提前堵死。
书签管理也有策略可用:通过受管书签功能,管理员可以强制推送一套受信任的书签列表,并把用户自定义书签的变更记录同步到审计渠道。一旦发生劫持,管理员能在后台快速定位到被篡改的书签节点和修改时间,响应速度快很多。跨设备的书签同步也应纳入监控,防止劫持数据跟着同步链路自动扩散。
6. 常见问题与排查技巧实录
6.1 排查流程速查表
在收尾前,我把实践里用的排查流程整理成速查表,建议直接保存:
| 步骤 | 操作 | 判断标准 |
|---|---|---|
| 1 | 导出书签并检查异常 URL | 出现未收藏过的域名即为异常 |
| 2 | 审查扩展权限清单 | 权限与功能严重不符即为异常 |
| 3 | 抓取 WebSocket 连接列表 | 存在不明 wss 长连接即为可疑 |
| 4 | 对比心跳规律性 | 帧大小或间隔过于固定即为可疑 |
| 5 | 检查本地存储与 Cookie 变动 | 存在非本人设置的高权限值即为异常 |
| 6 | 查看浏览器同步记录 | 同步时间与操作时间不符即需追查 |
6.2 五个典型问题的排查思路
先说第一个问题:书签被改了,但看不出是谁改的。这种情况多半是恶意扩展通过浏览器 API 写入,隐藏了改动来源。排查方向是把所有扩展禁用再观察,如果书签不再变化,就逐个启用扩展做二分排查。
第二个问题:WebSocket 连接太多,不知道哪些可疑。先按域名分组聚合连接数,再按连接时长排序,时长超过 12 小时的优先排查。最后看每个连接的数据包量,包量极少但连接长时间存活的最可疑。
第三个问题:没装奇怪扩展,但书签还是被动。可能是浏览器被植入了用户脚本,或者开发者模式下的扩展被外部配置注入。检查扩展管理页面是否开着开发者模式,同时查看浏览器安装目录下有没有额外配置文件。
第四个问题:清理恶意书签后过几天又出现。这是典型的持久化对抗,只删不查没用。重点找源头:扩展自动更新逻辑、同步账户里被同步进来的恶意书签、系统启动项里的残留程序、还有组策略层面的强制配置。
第五个问题:抓包看不到 WebSocket 内容。多半是流量走了 wss 加密。解决方案是配置 HTTPS 中间人抓包,把抓包工具的根证书导入浏览器信任区,再重新发起连接。注意这种做法只能在受控的测试环境做,线上环境需要有明确的授权和合规依据。
6.3 实际处理中的几条心得
做这类安全分析,最值得分享的一条心得是:不要只盯着恶意代码本身,要看它留下的行为痕迹。书签劫持难防,是因为代码可以无限变化,但行为模式是有限的——建立长连接、做验证握手、按指令采集数据,这三步在每次劫持里都逃不掉。防御方只要把这三步的检测条件预设好,就能形成相对完整的围堵网络。
另外,处理书签劫持时不要单打独斗,尤其是在企业环境里。发现终端被劫持的第一件事是隔离这台终端的网络访问,切断指令通道和外联地址的通信,然后再做数据采集和证据保留。顺序反了,很可能在排查过程中敏感数据已经被全部运走。
最后再分享一个小技巧:取证时把浏览器的书签、历史、扩展列表、网络日志四个维度的时间戳做一次对齐。四个维度的时间戳如果能串成一条连贯的事件线——比如某个时刻装了扩展、紧接着书签出现新增、同时网络日志出现新的 wss 连接——那这条劫持链基本就坐实了。多维时间线比任何单一指标都更有说服力,排查的效率也会高一个量级。
