Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Впервые об уязвимости Cross-Site WebSocket Hijacking (CSWSH) я узнал из статьи Кристиана Шнайдера и выступления Михаила Егорова, но не обратил на нее внимания. Позже, читая репорт на HackerOne, оцененный в 800 долларов, понял, что хочу разобраться. На просторах Рунета подробного описания CSWSH не нашлось, и я решил написать его самостоятельно.
В этой статье мы разберем протокол WebSocket, подробно остановимся на уязвимости CSWSH — насколько она распространена в открытом интернете. Для тех, кто дочитает до конца, я приготовил бонус в виде утилиты cswsh-scanner, с помощью которой ты можешь проверить свои приложения, работающие с WebSocket, либо попытать удачи на баг‑баунти.
warning
Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.
Итак, что такое WebSocket? Википедия дает следующее определение: «WebSocket — протокол связи поверх TCP-соединения, предназначенный для обмена сообщениями между браузером и веб‑сервером в режиме реального времени». В отличие от синхронного протокола HTTP, построенного по модели «запрос — ответ», WebSocket полностью асинхронный и симметричный. Он применяется для организации чатов, онлайн‑табло и создает постоянное соединение между клиентом и сервером, которое обе стороны могут использовать для отправки данных.
Протокол WebSocket определен в RFC 6455. Для протокола зарезервированы две URI-схемы:
ws://host[:port]path[?query]
;wss://host[:port]path[?query]
.WebSocket достаточно распространен в современной веб‑разработке, есть поддержка во всех популярных языках программирования и браузерах. Его используют в онлайн‑чатах, досках объявлений, веб‑консолях, приложениях трейдеров. С помощью поисковика shodan.io можно с легкостью найти приложения на WebSocket, доступные из интернета. Достаточно сформировать простой запрос. Я не поленился и сделал:
В результате нашлось 55 тысяч адресов с обширной географией.
WebSocket в мире
Разберем теперь, как работает WebSocket. Взаимодействие между клиентом и сервером начинается с рукопожатия. Для рукопожатия клиент и сервер используют протокол HTTP, но с некоторыми отличиями в формате передаваемых сообщений. Не соблюдаются все требования к HTTP-сообщениям. Например, отсутствует заголовок Content-Length
.
Для начала клиент инициирует соединение и отправляет запрос серверу:
GET /echo HTTP/1.1Host: localhost:8081Sec-WebSocket-Version: 13Origin: http://localhost:8081Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Connection: keep-alive, UpgradeUpgrade: websocket
Заголовки Sec-WebSocket-Version
, Sec-WebSocket-Key
, Connection: Upgrade и Upgrade: websocket
обязательны, иначе сервер возвращает статус HTTP/1.1 400 Bad Request
. Сервер отвечает на запрос клиента следующим образом:
HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Заголовок Sec-WebSocket-Key
формируется клиентом как случайное 16-байтовое значение, закодированное в Base64. Вариант формирования заголовка на Go:
func generateChallengeKey() (string, error) { p := make([]byte, 16) if _, err := io.ReadFull(rand.Reader, p); err != nil { return "", err } return base64.StdEncoding.EncodeToString(p), nil}
Заголовок Sec-WebSocket-Accept
в ответе формируется по следующему алгоритму. Берется строковое значение из заголовка Sec-WebSocket-Key
и объединяется с GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
. Далее вычисляется хеш SHA-1 от полученной в первом пункте строки. Хеш кодируется в Base64. Вариант формирования заголовка на Go:
const GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"func computeAcceptKey(challengeKey string) string { h := sha1.New() h.Write([]byte(challengeKey + GUID)) return base64.StdEncoding.EncodeToString(h.Sum(nil))}
Заголовки Sec-WebSocket-Key
и Sec-WebSocket-Accept
не используются для авторизации и поддержки сессий, они служат для того, чтобы стороны убедились, что запрос и ответ относятся к протоколу WebSocket. Это помогает гарантировать, что сервер не принимает от клиентов запросы, не относящиеся к WebSocket.
Также RFC 6455 предполагает, что Sec-WebSocket-Key
должен быть выбран случайным образом для каждого соединения. Это означает, что любой кешированный результат от прокси‑сервера будет содержать невалидный Sec-WebSocket-Accept
и, следовательно, рукопожатие провалится вместо непреднамеренного чтения кешированных данных. Для успешного завершения рукопожатия клиент проверяет значение Sec-WebSocket-Accept
и ожидает статус‑код 101 Switching Protocols
. После того как рукопожатие выполнено, первоначальное соединение HTTP заменяется соединением по WebSocket, которое использует то же соединение TCP/IP. На этом этапе любая из сторон может начать отправку данных.
Для мониторинга трафика WebSocket удобно использовать «Инструменты разработчика», доступные, к примеру, в Chrome.
WebSocket в «Инструментах разработчика»
Как в WebSocket передаются сообщения? Данные по протоколу WebSocket передаются как последовательность фреймов. Фрейм имеет заголовок, в котором содержится следующая информация:
Формат фрейма представлен на рисунке.
Формат фрейма WebSocket
Все сообщения, посылаемые клиентом, должны маскироваться. Пример отправки тестового сообщения «Hello world!» клиентом (данные из tcpdump):
Fin: TrueReserved: 0x0Opcode: Text (1)Mask: TruePayload length: 12Masking-Key: a2929b01Payload: eaf7f76dcdb2ec6ed0feff20
Маскировка производится обычным XOR с ключом маски. Клиент должен менять ключ для каждого переданного фрейма. Сервер не должен маскировать свои сообщения. Пример отправки тестового сообщения «Hello world!» сервером:
Fin: TrueReserved: 0x0Opcode: Text (1)Mask: FalsePayload length: 12Payload: 48656c6c6f20776f726c6421
Маскировка передаваемых сообщений некриптостойкая, чтобы обеспечить конфиденциальность, для WebSocket следует использовать протокол TLS и схему WSS.
С протоколом разобрались, самое время перейти к CSWSH. Протокол WebSocket использует Origin-based модель безопасности при работе с браузерами. Другие механизмы безопасности, например SOP (Same-origin policy), для WebSocket не применяются. RFC 6455 указывает, что при установке соединения сервер может проверять Origin, а может и нет:
Уязвимость CSWSH связана со слабой или невыполненной проверкой заголовка Origin в рукопожатии клиента. Это разновидность уязвимости подделки межсайтовых запросов (CSRF), только для WebSocket. Если приложение WebSocket использует файлы cookie для управления сеансами пользователя, злоумышленник может подделать запрос на рукопожатие с помощью атаки CSRF и контролировать сообщения, отправляемые и получаемые через соединение WebSocket.
Источник: xakep.ru