FlawOpen

flawopen.com/Incidents/capital-one-ssrf-imds-july-2019

● Capital One · CWE-918 · July 2019 · Критический
Исследования · FlawOpen

Capital One: SSRF и кража учетных данных AWS IMDSv1

В федеральной жалобе утверждалось, что ошибочная настройка WAF позволила SSRF достичь AWS IMDSv1 и раскрыть временные учётные данные роли и данные S3 примерно 106 млн человек. Требуйте токены и ограничьте роли.[10], [2]

💡 Простыми словами (ELI5)

В уголовной жалобе DOJ описана такая цепочка: Представьте сотрудника на ресепшене, который приносит документы по указанным вами адресам. Пересылка запросов может позволить кому-то направить его во внутренний кабинет. Там он может получить временный пропуск для рабочей нагрузки; пропуск открывает только помещения, разрешённые связанной с ней ролью. Ошибка состояла в предположении, что в кабинет попадут только сотрудники, хотя туда можно было направить работника ресепшена.[10], [8]

Ключевые понятия и термины

SSRF
Подделка запросов на стороне сервера (CWE-918): заставить сервер отправить выбранный злоумышленником запрос, который получает сетевое положение и доверие сервера.[9]
Межсетевой экран веб-приложений
Фильтр перед веб-приложением. В уголовной жалобе утверждалось, что неправильно настроенный WAF Capital One пересылал запросы на внутренние адреса.[10]
IMDSv1
Когда IMDSv1 включён, запросы метаданных не требуют токена. Путь учётных данных безопасности IAM-роли возвращает временные учётные данные этой роли.[7], [8]
IMDSv2
В IMDSv2 используется токен сеанса, получаемый запросом PUT. Задайте HttpTokens=required, чтобы отклонять запросы IMDSv1 без токена; это блокирует многие SSRF-ретрансляторы, способные отправить только простой GET, но не устраняет SSRF полностью.[7], [6]
Учётные данные IAM-роли
Временные ключи AWS, выданные экземпляру. Всё, что разрешено роли, может делать и владелец ключей, пока они не истекут.[8]

Связи со стандартами

  • Ссылка CWE для слабого места, описанного в этом руководстве: CWE-918
  • D3-OTF Фильтрация исходящего трафика — Описанное в руководстве ограничение исходящего сетевого трафика сокращает список адресов, доступных сервису или хосту.

Хронология инцидента

2019-03-22

Согласно уголовной жалобе, 22–23 марта обвиняемая использовала SSRF, чтобы получить учётные данные роли межсетевого экрана и скопировать данные S3 Capital One.[10], [2]

2019-07-17

Внешний исследователь сообщает об открытых данных через программу ответственного раскрытия Capital One.[2]

2019-07-29

Capital One объявляет об инциденте, а ФБР задерживает подозреваемую в Сиэтле.[1]

Пошаговый сценарий атаки

Шаг 1

Межсетевой экран пересылает поддельный запрос

В уголовной жалобе утверждалось, что неправильно настроенный WAF можно было заставить отправлять запросы на выбранные злоумышленником адреса, в том числе внутренние.[10]

Шаг 2

Сервис метаданных выдаёт учётные данные

Согласно жалобе, пересылка достигла пути учётных данных IAM-роли в сервисе метаданных экземпляра. IMDSv1 не требовал токена сеанса, поэтому запрос мог вернуть временные учётные данные связанной роли.[10], [8]

Шаг 3

Роль может читать слишком много

В жалобе утверждалось, что учётные данные имели разрешения роли, включая просмотр списка и чтение бакетов S3 с данными клиентов.[10], [8]

Шаг 4

Данные уходят массово

Скопированы данные заявок на кредитные карты с 2005 по начало 2019 года: около 100 миллионов человек в США и 6 миллионов в Канаде, в том числе около 140 000 номеров социального страхования США и 80 000 номеров привязанных банковских счетов.[2]

Исходный код: Уязвимый vs Защищённый вариант

Это иллюстративный код Flask, а не конфигурация Capital One. Список разрешённых хостов — основная прикладная мера. Обязательный прокси исходящего трафика должен проверять адрес после DNS-разрешения в момент подключения, блокировать частные и link-local диапазоны, включая сервис метаданных, и повторять эти проверки для каждого запроса и повтора; это поведение нужно подтвердить конфигурацией прокси. Перенаправления не выполняются. Отдельная проверка DNS до подключения оставила бы гонку DNS rebinding.[9], [5], [6]

УЯЗВИМАЯ РЕАЛИЗАЦИЯ
import requests
from flask import Flask, request

app = Flask(__name__)


@app.get("/fetch")
def fetch():
    # Сервер запрашивает любой URL, указанный вызывающей стороной.
    url = request.args["target"]
    resp = requests.get(url, timeout=5)
    return resp.text
БЕЗОПАСНЫЙ ИСПРАВЛЕННЫЙ ВАРИАНТ
from urllib.parse import urlsplit

import requests
from flask import Flask, abort, request

app = Flask(__name__)
ALLOWED_HOSTS = {"images.example-cdn.com", "api.partner.example"}



EGRESS_PROXY = "http://egress-proxy.internal:3128"
# Настройте этот прокси для проверки адресата после разрешения DNS.

@app.get("/fetch")
def fetch():
    parts = urlsplit(request.args.get("target", ""))
    if parts.scheme != "https" or parts.hostname not in ALLOWED_HOSTS:
        abort(400, "destination not allowed")
    if parts.port not in (None, 443) or parts.username or parts.password:
        abort(400, "destination not allowed")
    resp = requests.get(parts.geturl(), timeout=(2, 5), allow_redirects=False, proxies={"https": EGRESS_PROXY}, trust_env=False)
    if resp.is_redirect:
        abort(502, "upstream redirect refused")
    return resp.text

Чек-лист по защите системы для инженеров

Источники