Real-Time Backend

WebSocket Security

WebSocket соединение живёт часами. За это время JWT истекает, права меняются, а злоумышленники ищут способ открыть соединение от чужого имени. Безопасность HTTP-запроса и WebSocket-соединения - принципиально разные задачи.

  • **Discord** аутентифицирует 10 млн+ одновременных WebSocket соединений через Identify payload; при компрометации токена соединение закрывается в течение 1 секунды через push revocation
  • **Slack** использует one-time ticket auth для WebSocket: HTTP POST /api/rtm.connect возвращает wss URL с ticket, действительным 30 секунд и привязанным к IP
  • **Cloudflare** в 2023 году отразил атаку HTTP/2 Rapid Reset (CVE-2023-44487), в том числе через WebSocket; пик составил 201 миллион RPS - крупнейшая DDoS-атака в истории
  • **Binance WebSocket API** требует HMAC-подпись для торговых операций; каждый ордер подписывается private key; компрометация WebSocket соединения без ключа не позволяет торговать

WebSocket Authentication

WebSocket-соединение начинается с HTTP Upgrade запроса - это единственный момент, когда можно использовать стандартные HTTP-механизмы аутентификации. Discord аутентифицирует миллионы WebSocket-соединений через токен в первом сообщении после подключения (Identify payload). Slack использует per-connection token в URL параметре при Upgrade. После установки соединения cookie и заголовки недоступны.

Три паттерна аутентификации WebSocket: (1) Token в URL: wss://api.example.com/ws?token=JWT - токен виден в server logs, не рекомендуется для production. (2) Authorization header при Upgrade - работает только если клиент это поддерживает (браузерный WebSocket API не позволяет задать заголовки). (3) First-message auth: соединение принимается, клиент отправляет auth-сообщение за N секунд или отключается. Discord использует именно третий способ.

  • **Ticket auth**: HTTP POST /ws-ticket → получить one-time токен → wss://host/ws?ticket=TOKEN; ticket живёт 30с и привязан к IP
  • **Cookie auth**: браузер автоматически отправляет HttpOnly cookie при Upgrade; работает для web-клиентов, не для мобильных
  • **Close code 4xxx**: коды 4000-4999 зарезервированы для приложений; 4001=не аутентифицирован, 4003=forbidden, 4429=rate limit

Браузерное приложение должно аутентифицировать WebSocket. Authorization header недоступен в браузерном WebSocket API. Какой паттерн безопаснее всего?

WebSocket Authorization

Авторизация в WebSocket сложнее чем в REST: соединение персистентное, а права пользователя могут измениться пока он подключён. Discord управляет 10+ млн одновременных соединений. Когда пользователя выгоняют с сервера, Discord должен мгновенно закрыть его WebSocket и убрать из всех guild-каналов без ожидания следующего сообщения.

Capability-based подход эффективнее role-based для real-time систем. Вместо проверки ролей при каждой операции: выдать токен с конкретными правами (channel:read:123, channel:write:456) при подключении. Права меняются только через переподключение с новым токеном. Slack использует этот паттерн: при изменении прав канала Slack инвалидирует все WebSocket-соединения участников с задержкой <1с.

  • **Per-message auth check**: слишком дорого для частых сообщений; кешировать права в памяти с TTL 30-60с
  • **Push revocation**: при изменении прав в БД - публиковать event в Redis, все WS-серверы закрывают соединения затронутых пользователей
  • **Subscription validation**: проверять права не только при subscribe, но и при каждом broadcast (получатель мог лишиться прав)
  • **Audit log**: логировать все WebSocket-соединения с userId, IP, timestamp для forensics

Пользователь подписан на WebSocket-канал приватного чата. Администратор удалил его из чата. Когда пользователь перестанет получать сообщения?

Token Refresh in WebSocket

JWT токен имеет срок жизни (обычно 15-60 минут). В REST API клиент просто получает 401 и обновляет токен. В WebSocket соединение персистентное - нельзя прервать его посреди стрима. Discord refresh-токены истекают через 7 дней; при этом Discord не разрывает соединение, а отправляет специальный dispatch event, сигнализируя клиенту обновить токен без переподключения.

Sliding expiration - альтернативный подход: токен продлевается автоматически при активности. Если пользователь не отправлял сообщений 30 минут, соединение закрывается. Это удобно для чатов, но неприемлемо для финансовых систем где нужен строгий TTL. Binance WebSocket API закрывает соединение ровно через 24 часа независимо от активности - клиент должен переподключиться.

  • **Proactive refresh**: сервер предупреждает за 5 мин до истечения; клиент получает новый access token через REST и отправляет через WS
  • **Grace period**: после истечения токена давать 30-60с на обновление прежде чем закрыть соединение
  • **Token binding**: привязать JWT к IP или TLS fingerprint; смена IP инвалидирует токен немедленно
  • **Short-lived tokens**: 15 мин для trading API, 7 дней для чата - выбор TTL зависит от чувствительности операций

WebSocket соединение активно 2 часа. JWT токен истёк 10 минут назад, соединение не закрыто. Что делает сервер с входящими сообщениями?

WebSocket Security Headers

WebSocket Upgrade запрос - это HTTP запрос, и к нему применяются те же заголовки безопасности. Cloudflare обнаружил в 2023 году атаку HTTP/2 Rapid Reset через WebSocket - злоумышленники открывали тысячи WS-соединений за секунду через одно HTTP/2 соединение. Origin validation и rate limiting на Upgrade endpoint - первая линия защиты.

Cross-Site WebSocket Hijacking (CSWSH) - атака аналогичная CSRF: вредоносный сайт открывает WebSocket к target API от имени жертвы, используя её cookie. Защита: проверять Origin header на сервере, использовать CSRF-токен в первом сообщении, или ticket-based auth. WSS (WebSocket Secure) обязателен в production - без TLS весь трафик виден посредникам.

  1. **Origin validation**: проверять Origin header при Upgrade; блокировать неизвестные origin
  2. **WSS only**: запрещать ws:// в production; использовать HSTS + wss:// redirect
  3. **Max connections per IP**: 10-50 одновременных соединений; защита от connection flooding
  4. **Max payload**: ограничить размер одного сообщения (64 KB - 1 MB); защита от memory exhaustion
  5. **Ping/pong timeout**: закрывать соединения без pong через 30-60с; защита от zombie connections
  6. **CSP connect-src**: ограничить с каких доменов разрешены WebSocket соединения

WebSocket защищён от CSRF автоматически, потому что браузер не позволяет читать ответы cross-origin

WebSocket не подпадает под Same-Origin Policy для ответов, но уязвим для CSWSH: вредоносная страница может открыть соединение и отправлять команды от имени жертвы

CORS ограничивает чтение HTTP-ответов cross-origin. WebSocket же использует другой механизм: Origin header. Если сервер не проверяет Origin, любой сайт может открыть аутентифицированный WS к target API используя cookie пользователя. Это CSWSH (Cross-Site WebSocket Hijacking) - аналог CSRF для WebSocket.

Злоумышленник с вредоносного сайта evil.com открывает WebSocket к api.bank.com используя cookie жертвы. Какой заголовок первым блокирует эту атаку?

Итоги

  • **Аутентификация при Upgrade**: ticket-based (one-time токен через REST) или first-message auth; токен в URL небезопасен из-за логов
  • **Push revocation**: изменение прав в БД должно немедленно закрывать активные WebSocket соединения через Redis pub/sub
  • **Token refresh**: сервер проактивно предупреждает за 5 мин до истечения JWT; grace period 30-60с; затем принудительное закрытие
  • **CSWSH защита**: Origin header validation при Upgrade - главная защита от Cross-Site WebSocket Hijacking; WSS обязателен в production

Связанные темы

WebSocket security строится поверх общих механизмов безопасности и реального времени:

  • Rate Limiting Real-Time — Rate limiting на WebSocket Upgrade endpoint и на message frequency - обязательная защита от flooding и abuse
  • Financial Trading — Trading WebSocket API (Binance, Coinbase) используют HMAC-подпись сообщений поверх TLS; компрометация соединения без ключа не позволяет торговать
  • IoT Real-Time — MQTT over WebSocket (порт 443) использует TLS-сертификаты устройств; те же паттерны revocation применяются при компрометации устройства

Вопросы для размышления

  • Пользователь вошёл в систему на рабочем компьютере и оставил открытую вкладку с WebSocket-чатом. Через 8 часов его уволили и отозвали доступ в AD. Какая цепочка событий должна закрыть его WebSocket соединение?
  • Мобильное приложение с WebSocket переходит в background на iOS. Через 3 минуты JWT истекает. Как правильно обработать ситуацию когда приложение возвращается на foreground?
  • Злоумышленник перехватил one-time ticket для WebSocket (например через access log). Ticket действителен 60 секунд. Какие дополнительные меры снижают риск его использования?

Связанные уроки

  • net-23-https-tls
  • sec-01
WebSocket Security

0

1

Войти