• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Статья HTTP Desync Атаки используя Python и AWS

Данная статья является переводом. Оригинал можете найти вот

Пару месяцев назад, я терпеливо ждал документации о новом типе атак на современные веб-приложения под названием «HTTP Desync атаки». И вот оно! Помню, я думал, что она будет такой долгой и сложной (с точки зрения СМИ), но оказалось, что я ошибался. До октября мы почти ничего не слышали от вендоров и сообщества. Несколько дней назад было выпущено записанное выступление Defcon, и эта тема снова заинтересовала меня.

1588097554897.png

Авторство: Maxime Deom

Давайте вернемся немного назад и сначала объясним, что это за атаки. Для этого нам понадобится вспомнить историю HTTP 1.1. Существовала одна проблема у оригинальной спецификации HTTP: вам нужно было открывать TCP-соединение для каждого нового запроса, сделанного к серверу. Как мы знаем, если клиент постоянно посылает запросы, то это становиться затратным процессом для сервера. Другая проблема заключалась в следующем: с помощью HTTP 1.1, вы можете посылать несколько запросов через одно и то же TCP соединение. Это также становится потенциально проблематичным, когда вы вводите прокси между сервером и клиентами. Рассмотрим следующую упрощенную архитектуру:

1588097803743.png


На схеме - соединение между ALB и EC2. Это соединение с сервером будет потенциально использоваться для различных клиентов. Это означает, что если атакующий пользователь может оставить часть данных в receive буфере сервера, то следующий пользователь будет изменять свой запрос. Оказывается, это можно сделать, используя комбинацию из двух заголовков: Content-Length и Transfer-Encoding. В обычном приложении вы, в основном, либо указываете фиксированную Content-Length, когда знаете размер пейлоада, либо указываете фрагмент Transfer-Encoding, когда не знаете (и хотите передавать его по частям). Но что, если вы отправите и то, и другое?

Прокси-сервер может использовать Transfer-Encoding и сервер, Content-Length или наоборот. В любом случае, это вызовет десинхронизацию между прокси и сервером, отсюда и название атаки. Я не буду углубляться в подробности, но вам обязательно стоит прочитать и в блоге автора, чтобы узнать все подробности. Я также настоятельно рекомендую прочитать пост о .


Что насчет Python?

Если вы читаете этот пост в блоге, вы, вероятно, уже знаете, что большая часть кода на Python в продакшене работает с WSGI сервером. К ним относятся uwsgi, apache (с mod_wsgi), waitress и gunicorn. Мой стек использует gunicorn, поэтому остальная часть поста будет описывать именно этот сервер, но вы можете проверить, является ли выбранный вами сервер также уязвимым.



Если вы прочитали этот пост и не используете gunicorn >19.10 или >20.0.2, то, пожалуйста, прекратите читать и сразу перейдите к обновлению!



И так, возвращаемся к моей истории: когда я смотрел спич на Defcon, я сразу же проверил gunicorn на Github, чтобы узнать, не открыл ли кто-нибудь issue для этой атаки и... я нашел один :)! Я взволновался и начал тестировать его ради любопытства. Я создал основное приложение и модули на основе ECS для проведения моих тестов. В ней есть только одна конечная точка, которая возвращает заголовки запроса в теле.

1588098124380.png

Основной запрос и ответ приложения


Отсюда, первым шагом было использование отличного плагина Burp HTTP Request Smuggler в сочетании с плагином Flow для тестирования приложения. Это дало нам первую основную информацию о потенциальных уязвимостях.

1588098149917.png

Некоторые атаки, выполняемые плагином Request Smuggler

На этом шаге, вы захотите получить, в основном 502, 404 и 405, к счастью, у нас есть один такой код. Остальные не очень полезны, и вот почему:
  • 200: Даже если вы отправили несколько запросов, все они были нормальные, без ошибок, поэтому, сервер и прокси-сервер были синхронизированы.
  • 400: ALB заблокировал запрос как недействительный.
  • 501: ALB заблокировал плохое Трансфертное Кодирование, как несуществующее (Transfer-Encoding is not implement).

Мораль заключается в следующем: ALB является вашим "союзником", он блокирует множество вещей и предотвращал бы еще больше атак, если бы я просто включил параметр setting:routing.http.drop_invalid_header_fields.enabled. Некоторые (не глубоко-разбирающиеся безопасности) пользователи жаловались в , что когда AWS выкатывал этот фикс, они, к сожалению, решили отключить этот параметр по дефолту. Поэтому убедитесь, что он включен в вашей инфраструктуре.


Копаем глубже

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

1588098276588.png


Обратите внимание на пространство между концом TE (transfer-encoding) и colon. 405 означает, что во второй раз, когда мы послали пейлоад, к следующему запросу был добавлен последний X предыдущего запроса. Это привело к методу XPOST, который действительно существует, отсюда и 405. Но можем ли мы сделать это получше?

1588098306233.png


В этой атаке мы полностью переопределили следующий запрос. Пусть пользователь и сделал POST запрос, для сервера - это GET на /404. Ой! Это плохо 😞. В обоих случаях мы использовали CL-TE атаку, потому что прокси использовал Content-Length, в то время как сервер использовал Transfer-Encoding. Это потому, что даже несмотря на то, что RFC 7230 утверждает об не допущении пробелов между именем поля заголовка и двоеточием, приложения, просто игнорируют это, и ради нормализации имени заголовков.

Python:
curr = lines.pop(0)
header_length = len(curr)
if curr.find(":") < 0:
    raise InvalidHeader(curr.strip())
name, value = curr.split(":", 1)
name = name.rstrip(" \t").upper()
if HEADER_RE.search(name):
    raise InvalidHeaderName(name)
Оригинальный файл

Весь фикс заключался в простом удалении rstrip на 6-й строке (на самом деле мы сделали настройку, так что если вам действительно нужна именно такая структура код, вы все равно сможете работать, но будете уязвимы). Golang также решило использовать этот фикс.


И здесь я подумал, что жизнь легкая штука

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

Python:
def set_body_reader(self):
    chunked = False
    content_length = None
    for (name, value) in self.headers:
        if name == "CONTENT-LENGTH":
            content_length = value
        elif name == "TRANSFER-ENCODING":
            chunked = value.lower() == "chunked"
        elif name == "SEC-WEBSOCKET-KEY1":
            content_length = 8

    if chunked:
        self.body = Body(ChunkedReader(self, self.unreader))
    elif content_length is not None:
        try:
            content_length = int(content_length)
        except ValueError:
            raise InvalidHeader("CONTENT-LENGTH", req=self)

        if content_length < 0:
            raise InvalidHeader("CONTENT-LENGTH", req=self)

        self.body = Body(LengthReader(self.unreader, content_length))
    else:
        self.body = Body(EOFReader(self.unreader))
Оригинальный файл

Здесь мы выполняем итерацию по всем заголовкам и устанавливать значения, которые затем будут использоваться для корректной установки читателя. У вас может появится вопрос: «Что в этом плохого?». Ну, а что будет, если у вас есть дубликат заголовка? Использование только последнего значения может вызвать десинхронизацию, если ваш прокси использует первое значение!

Итак, сначала давайте попробуем исправить Content-Length. Я не могу показать вам эксплойт, потому что ALB уже защищает нас от этой атаки. Некоторые прокси могут и не защитить, поэтому важно, чтобы мы исправили это в gunicorn. Согласно , вы можете сделать две вещи в случае дубликата CL:


Получатель ДОЛЖЕН либо отклонить сообщение как недействительное, либо заменить дублированные значения поля на одно действительное поле Content-Length, содержащее это десятичное значение перед определением длины тела сообщения или пересылкой сообщения.


Таким образом, единственным простым и безопасным решением является отклонение сообщения. Это делают Node серверы, а также, с недавних пор, так делает и gunicorn.


Особый случай Transfer-Encoding


ТЕ - странный заголовок. , ты обнаруживаешь, что это hop-by-hop заголовок. Это значит, что каждый узел может решить изменить TE по маршруту. Вы также узнаете, что можно иметь несколько TE, с которыми нужно работать в заданном порядке. Просмотрев заново код, можно понять, что мы далеки от спецификации!

В наши дни наиболее широко используемое chunked TE (для больших пейлоадов, которые не помещаются в один фрейм). Даже если существуют другие ТЕ (в основном, compres, deflate и gzip), ALB их не принимает и возвращает 501. Единственное, что также принимается, это identity,или идентификатор, он в основном говорит серверу ничего не делать с пейлоадом (бесполезно, я знаю 😂). Но из-за этого мы можем спровоцировать TE-CL атаку с таким пейлоадом как:

1588098434914.png


Это происходит потому, что второй ТЕ «перекрывает» кусок свойства в gunicorn, который затем отступит на CL, чтобы парсить тело обьекта. Похоже, что ALB защищает нас от этой атаки, не перенаправляя CL на сервер, но CL может потенциально работать с другими прокси-серверами. Чтобы попытаться смягчить атаку, у нас может возникнуть соблазн просто отвергнуть все TE, за исключением блоков и обработать пейлоад внутри сервера WSGI, но тогда мы нарушим совместимость с существующими приложениями, и это будет страшной ошибкой на Python. Более того, несмотря на то, что это уже сказано в :

Серверы WSGI должны самостоятельно обрабатывать любые поддерживаемые входящие заголовки hop-by-hop, например, декодируя любой входящий Transfer-Encoding, включая chunked encoding, если это является возможным.

Реальность такова, что существует неофициальный флаг (wsgi.input_terminated), говорящий WSGI-серверу передавать данные приложению (и наоборот). Это все сплошной хаус. Если вы хотите знать мое мнение, то я уверен, что кто-то найдет больше десинхронных атак благодаря этой фитчи, просто потому, что сервер WSGI действует как еще один прокси-сервер в этом сценарии.

Что касается , здесь сказано следующее:

Если любой трансфер код, который не является chunked, применяется к телу пейлоад-запроса, отправитель ДОЛЖЕН применить chunked в качестве последнего трансфер кода, чтобы убедиться в том, что сообщение правильно оформлено во фрейме. Если к телу отправителя, ответившего на пейлоад-запрос, применяется кодировка, которая не является chunked, отправитель ДОЛЖЕН либо применить chunked кодировку в качестве окончательной кодировки, либо прервать сообщение, закрыв соединение, и тем самым предотвратив атаку.


Но если вы посмотрите на поведение ALB, то он примет TE в следующем порядке: chunked, identity, что не соответствует теории. Поэтому, на данный момент, новое поведение gunicorn заключается в том, что если любое TE равно chunked, то оно будет считать сообщение chunked, то есть блочным, даже если это не последнее TE. Если каждый раз обновляете ALB, то вы будете под защитой. Я не могу гарантировать ту же безопасность, пользователям других прокси-серверов.


Заключительные мысли


Да уж, это была сложноватая поездка 😅! Надеюсь, вы доехали до конца статьи в целости и сохраности. Ключевые моменты этого поста в том, что HTTP Desync атаки еще свежие, многие пользователи и приложения уязвимы, и от них действительно трудно избавиться должным образом. Мы, как разработчики, должны больше беспокоиться об этом, и должны понимать влияние выбора наших программ/модулей/скриптов на безопасность наших приложений. Что касается gunicorn, все исправления, описанные выше, были объединены и выпущены!


Надеюсь, вам понравилось прочитанное, и увидимся в следующей статье!
 
Последнее редактирование:
  • Нравится
Реакции: <~DarkNode~> и Vertigo
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!