Segurança em WebSockets

1. Fundamentos e Riscos de Segurança em WebSockets

WebSockets diferem fundamentalmente do HTTP tradicional por estabelecerem canais de comunicação bidirecionais e persistentes. Enquanto HTTP segue o modelo requisição-resposta com conexões curtas, WebSockets mantêm uma conexão aberta após o handshake inicial, permitindo tráfego contínuo de dados.

Essa persistência introduz riscos únicos:
- Injeção de mensagens: atacantes podem enviar payloads maliciosos através do canal aberto
- Sequestro de sessão: conexões longas aumentam a janela de exposição para roubo de tokens
- Vazamento de dados: mensagens não criptografadas podem ser interceptadas

Ataques comuns incluem:
- Cross-Site WebSocket Hijacking (CSWSH): explora a falta de validação de origem para sequestrar conexões
- DoS em conexões: sobrecarga do servidor com múltiplas conexões ou mensagens volumosas

// Exemplo de handshake WebSocket vulnerável
GET /chat HTTP/1.1
Host: exemplo.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
// Sem validação de Origin ou token CSRF

2. Autenticação e Autorização no Handshake

O handshake HTTP inicial é o momento crítico para estabelecer identidade. A autenticação deve ocorrer antes do upgrade de protocolo:

// Handshake com token JWT no cabeçalho Authorization
GET /ws/chat HTTP/1.1
Host: api.exemplo.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbLi1EzLkh9GBhXDw==
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Práticas recomendadas:
- Validar tokens JWT no servidor durante o handshake
- Usar cookies seguros com atributos HttpOnly, Secure e SameSite=Strict
- Implementar renovação de sessão periódica para conexões longas
- Verificar permissões por canal/tópico após autenticação

// Validação no servidor (pseudo-código)
function handleUpgrade(request, socket, head) {
    const token = extractToken(request.headers);
    if (!validateJWT(token)) {
        socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
        socket.destroy();
        return;
    }
    const user = decodeJWT(token);
    if (!user.hasPermission('chat:write')) {
        socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
        socket.destroy();
        return;
    }
    // Proceder com upgrade
}

3. Validação e Sanitização de Mensagens

Toda mensagem recebida deve ser tratada como potencialmente maliciosa. Implemente schemas rigorosos:

// Schema JSON para validação de mensagens
{
    "type": "object",
    "required": ["action", "payload"],
    "properties": {
        "action": { "type": "string", "enum": ["send", "join", "leave"] },
        "payload": { "type": "object" },
        "timestamp": { "type": "number", "minimum": 1600000000 }
    },
    "additionalProperties": false
}

Sanitização obrigatória:
- Escapar caracteres HTML para prevenir XSS em mensagens exibidas
- Validar tipos de dados (string, número, booleano) conforme schema
- Rejeitar payloads com tamanho excessivo (ex.: máximo 10KB por mensagem)
- Implementar rate limiting por payload (ex.: 100 mensagens/minuto por conexão)

// Validação no servidor WebSocket
socket.on('message', (data) => {
    try {
        const msg = JSON.parse(data);
        if (!validateSchema(msg)) {
            socket.send(JSON.stringify({error: 'Formato inválido'}));
            return;
        }
        const sanitized = sanitizePayload(msg.payload);
        broadcastToChannel(msg.channel, sanitized);
    } catch (e) {
        logError('Mensagem malformada', e);
    }
});

4. Proteção contra Cross-Site WebSocket Hijacking (CSWSH)

CSWSH ocorre quando um site malicioso induz o navegador da vítima a iniciar uma conexão WebSocket com um servidor alvo, explorando cookies de sessão automaticamente enviados.

Mitigações essenciais:

// Validação do cabeçalho Origin no servidor
function validateOrigin(origin) {
    const allowedOrigins = ['https://meusite.com', 'https://app.meusite.com'];
    return allowedOrigins.includes(origin);
}

// Uso de token CSRF no handshake via Sec-WebSocket-Protocol
GET /ws HTTP/1.1
Sec-WebSocket-Protocol: chat, csrf-token-abc123

Estratégias combinadas:
1. Verificar cabeçalho Origin: rejeitar conexões de origens não autorizadas
2. Tokens anti-CSRF: enviar token único no protocolo WebSocket
3. Cookies SameSite: configurar SameSite=Strict ou Lax
4. Validar referenciador: em ambientes controlados

// Implementação completa de proteção CSWSH
function handleUpgrade(request, socket, head) {
    const origin = request.headers.origin;
    if (!validateOrigin(origin)) {
        socket.destroy();
        return;
    }

    const csrfToken = request.headers['sec-websocket-protocol'];
    if (!validateCsrfToken(csrfToken, request.session)) {
        socket.destroy();
        return;
    }

    // Upgrade seguro
}

5. Criptografia e Integridade de Dados em Trânsito

WebSockets inseguros (ws://) transmitem dados em texto claro. A obrigatoriedade do uso de WSS (WebSocket Secure) com TLS 1.2+ é fundamental:

// Configuração de servidor WebSocket com TLS
const https = require('https');
const WebSocket = require('ws');

const server = https.createServer({
    cert: fs.readFileSync('/etc/ssl/certs/server.crt'),
    key: fs.readFileSync('/etc/ssl/private/server.key'),
    minVersion: 'TLSv1.2'
});

const wss = new WebSocket.Server({ server });

Boas práticas adicionais:
- Renovar certificados automaticamente com Let's Encrypt
- Implementar criptografia de ponta a ponta para payloads sensíveis
- Usar chaves efêmeras para sessões de alta segurança
- Configurar HSTS para forçar conexões seguras

// Criptografia de ponta a ponta com chave efêmera
function encryptPayload(payload, sessionKey) {
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipheriv('aes-256-gcm', sessionKey, iv);
    const encrypted = Buffer.concat([cipher.update(payload), cipher.final()]);
    return { iv: iv.toString('hex'), data: encrypted.toString('hex') };
}

6. Mitigação de Ataques de Negação de Serviço (DoS)

Conexões WebSocket persistentes são alvos atrativos para DoS. Implemente controles rigorosos:

// Rate limiting de conexões por IP
const connectionCounts = new Map();

function checkConnectionLimit(ip) {
    const current = connectionCounts.get(ip) || 0;
    if (current >= 5) { // Máximo 5 conexões simultâneas por IP
        return false;
    }
    connectionCounts.set(ip, current + 1);
    return true;
}

Estratégias de mitigação:
- Rate limiting: limitar conexões por IP (5-10) e por sessão (1-2)
- Timeouts: fechar conexões ociosas após 60 segundos de inatividade
- Heartbeat ping/pong: verificar conectividade a cada 30 segundos
- Backpressure: limitar fila de mensagens não processadas (ex.: 1000 mensagens)

// Configuração de heartbeat e timeout
const wss = new WebSocket.Server({
    server,
    clientTracking: true,
    maxPayload: 10240, // 10KB
    perMessageDeflate: false // Desabilitar para evitar ataques de compressão
});

wss.on('connection', (ws) => {
    ws.isAlive = true;
    ws.on('pong', () => { ws.isAlive = true; });

    const interval = setInterval(() => {
        if (!ws.isAlive) return ws.terminate();
        ws.isAlive = false;
        ws.ping();
    }, 30000);

    ws.on('close', () => clearInterval(interval));
});

7. Monitoramento e Logging de Conexões

Registre eventos críticos para auditoria e detecção de anomalias:

// Logging estruturado de eventos WebSocket
function logConnectionEvent(event, metadata) {
    const logEntry = {
        timestamp: new Date().toISOString(),
        event: event, // 'handshake', 'message', 'error', 'close'
        ip: metadata.ip,
        userId: metadata.userId,
        sessionId: metadata.sessionId,
        details: metadata.details
    };
    // Enviar para sistema de logging centralizado
    logger.info('WebSocket event', logEntry);
}

Indicadores de anomalia:
- Múltiplas conexões do mesmo IP em curto período
- Padrões de mensagens suspeitos (alta frequência, payloads idênticos)
- Taxa elevada de erros de validação
- Conexões de origens não autorizadas

Integração com SIEM permite alertas em tempo real para resposta a incidentes.

8. Boas Práticas de Implementação e Configuração

A escolha e configuração correta de bibliotecas e infraestrutura é crucial:

// Configuração segura com ws (Node.js)
const WebSocket = require('ws');
const wss = new WebSocket.Server({
    server,
    verifyClient: (info, cb) => {
        // Validação customizada de cliente
        const token = extractToken(info.req);
        cb(validateToken(token));
    },
    maxPayload: 1024 * 100, // 100KB máximo
    backlog: 100 // Tamanho da fila de conexões pendentes
});

Recomendações finais:
- Bibliotecas: usar versões atualizadas de ws, Socket.IO, SockJS
- Proxy reverso: configurar Nginx ou HAProxy para terminação TLS com timeout adequado
- Testes de penetração: usar OWASP ZAP ou Burp Suite para testar vulnerabilidades WebSocket
- Atualizações: manter dependências atualizadas e monitorar CVEs

# Configuração Nginx para proxy WebSocket seguro
server {
    listen 443 ssl;
    server_name ws.exemplo.com;

    ssl_certificate /etc/ssl/certs/server.crt;
    ssl_certificate_key /etc/ssl/private/server.key;

    location /ws {
        proxy_pass http://backend:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 60s;
        proxy_send_timeout 60s;
    }
}

Referências