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
- OWASP WebSocket Security Cheat Sheet — Guia oficial da OWASP com práticas recomendadas de segurança para WebSockets
- Mozilla Developer Network: WebSocket API — Documentação completa da API WebSocket com considerações de segurança
- Socket.IO Security Best Practices — Guia oficial do Socket.IO sobre configuração segura e mitigação de ataques
- Nginx WebSocket Proxy Configuration — Documentação oficial do Nginx para proxy reverso de WebSockets com TLS
- OWASP ZAP WebSocket Testing — Ferramenta de teste de penetração para vulnerabilidades em WebSockets
- RFC 6455: The WebSocket Protocol — Especificação oficial do protocolo WebSocket com seções de segurança
- Let's Encrypt Free SSL Certificates — Autoridade certificadora gratuita para implementação de WSS com TLS