DarkRiDDeR14 мин

SSRF начинается с URL: проверяем адрес до сетевого вызова

БезопасностьBackend

Endpoint принимает URL изображения и скачивает его сервером. Цена ошибки — превратить обычное поле формы в доступ к внутреннему адресу, metadata-сервису или административному интерфейсу. Проверка только на строку https:// не защищает: URL имеет имя, порт, учетные данные, редирект и адрес, которые меняют фактическую цель.

Разберём защиту до вызова сети: разрешённая схема, точное имя хоста, запрет credentials и явное ограничение портов. Учебная функция не делает запрос. Она принимает строку и возвращает нормализованный адрес либо причину отказа, поэтому её можно запускать локально и проверять отдельными примерами.

Проверяем не строку, а разобранный адрес

Сначала парсер URL должен превратить строку в структуру. Затем политика проверяет протокол, hostname, порт и наличие логина или пароля. Проверять только исходное начало строки недостаточно: https://trusted.example@127.0.0.1/ имеет доверенное имя до символа @, но фактический host — loopback.

Поля URL и решение политики
ПолеДопустимое правилоПричина
protocolтолько https:не отправлять секрет по plain HTTP
hostnameточный allowlistне доверять суффиксу строки
port443 или явно разрешённыйсократить обход сервисов
username/passwordпустоне передавать credentials дальше
pathnameпроверяется отдельноне разрешать опасный endpoint
Путь проверки URL: parse, схема, hostname, порт, credentials и только затем разрешение на вызов.
Каждая проверка стоит до сетевого вызова. Отказ возвращает причину и не передаёт адрес следующему слою.

Allowlist должна быть точной

Сравнение hostname.endsWith("example.com") пропустит evil-example.com. Сравнение с одним каноническим именем проще проверить. Если нужны поддомены, добавьте правило границы: имя равно базовому или заканчивается на .example.com. IP-адреса, IPv6 и DNS-редиректы требуют отдельного решения, потому что имя может измениться между проверкой и соединением.

Защита от SSRF — не только функция валидации. Клиент должен ограничить редиректы, timeout, размер ответа и набор портов. Сетевой слой может дополнительно запретить приватные диапазоны и loopback. Эти меры защищают разные границы, поэтому одна allowlist не заменяет остальные.

Учебный валидатор URL

Входом функции является строка URL и массив разрешённых имён. Ожидаемый результат — объект { allowed, reason, href }. Ниже нет fetch: пример проверяет предмет статьи и не выполняет опасное подключение. Для безопасных тестов используются только публичные доменные имена из allowlist.

function validateRemoteUrl(value, allowedHosts) {
  let url;
  try { url = new URL(value); } catch { return { allowed: false, reason: 'invalid-url' }; }
  if (url.protocol !== 'https:') return { allowed: false, reason: 'scheme' };
  if (url.username || url.password) return { allowed: false, reason: 'credentials' };
  if (url.port && url.port !== '443') return { allowed: false, reason: 'port' };
  if (!allowedHosts.includes(url.hostname)) return { allowed: false, reason: 'host' };
  return { allowed: true, reason: 'allowlist', href: url.href };
}

console.log(validateRemoteUrl('https://cdn.example.test/file.jpg', ['cdn.example.test']));
console.log(validateRemoteUrl('https://cdn.example.test@127.0.0.1/file.jpg', ['cdn.example.test']));
console.log(validateRemoteUrl('https://127.0.0.1/file.jpg', ['cdn.example.test']));
// allowed true; allowed false, reason credentials; allowed false, reason host

Первая строка проходит, вторая останавливается на credentials, а третья — на фактическом hostname loopback. Порядок проверок важен: сначала исключаем встроенные учетные данные, затем сверяем host. В полном сервисе URL нельзя передавать в сеть сразу после этого результата: нужны лимит ответа, запрет небезопасных redirect и проверка адреса после разрешения имени там, где это входит в модель угроз.

Редирект — новый URL

Если разрешённый сервер отвечает 302 на другой адрес, политика должна решить, разрешены ли перенаправления. Автоматически следовать за ними опасно: проверка исходного host уже не описывает конечный. Безопасный вариант — отключить redirect и вернуть его адрес как проверяемую ошибку. Если redirect нужен, каждый новый URL проходит ту же проверку.

Также проверяйте размер ответа до чтения всего тела. Даже разрешённый host может вернуть гигабайты и занять память. Timeout ограничивает время, но не объём. Эти ограничения не делают endpoint безопасным сами по себе, зато уменьшают blast radius ошибки политики.

Порядок внедрения

  1. Назвать допустимые домены, схемы и порты в конфигурации, а не принимать их из запроса.
  2. Распарсить URL стандартным URL-парсером и проверить hostname после нормализации.
  3. Запретить credentials, неожиданные схемы и redirect по умолчанию.
  4. Ограничить timeout, размер ответа и число сетевых попыток.
  5. Добавить тесты на @, похожий домен, IP, IPv6, другой порт и redirect.
  6. Сохранить причину отказа без полного URL с секретными параметрами.

Ограничения и следующий шаг

Учебный валидатор не знает DNS rebinding, proxy и правила сетевого сегмента. Allowlist домена не доказывает, что адрес после разрешения безопасен. Для чувствительных систем нужен совместный контроль приложения и egress-сети, а также отдельные тесты redirect и размера ответа.

Следующим шагом возьмите один endpoint, который принимает URL, и выпишите допустимые host, port, redirect и максимальный размер. Затем добавьте отрицательные тесты, включая имя с @. Критерий готовности — запрещённый адрес не доходит до клиента сети.

Проверяемые источники

  • NIST SP 800-218 — Secure Software Development Framework (SSDF) Version 1.1 — February 2022. Даёт общий словарь практик безопасной разработки и связывает защиту с жизненным циклом продукта. Граница применимости: Рамка не подтверждает наличие контроля, уязвимости или результата в конкретном приложении.
  • OWASP Application Security Verification Standard — Version 5.0.0, released 30 May 2025. Даёт версионируемые требования для проверки технических контролей веб-приложения. Граница применимости: Стандарт не заменяет модель угроз, настройку инфраструктуры и тестирование конкретных данных.
  • NIST SP 800-53 Rev. 5 — Security and Privacy Controls — September 2020, updates as of 10 December 2020. Помогает различить сам контроль и уверенность в его работе. Граница применимости: Каталог не выбирает права вашего ресурса и не является отчётом о соответствии.