SSRF — Server-Side Request Forgery

SSRF (Server-Side Request Forgery) — атака, при которой злоумышленник заставляет сервер делать HTTP-запросы к произвольным адресам (включая внутреннюю сеть, metadata-сервисы облака, localhost), используя серверные функции загрузки URL.

Зачем нужно

В облачных окружениях SSRF даёт доступ к AWS/GCP metadata API (169.254.169.254) — источнику IAM-токенов, что может привести к полному компрометированию облачного аккаунта. Capital One взлом 2019 года произошёл именно через SSRF в AWS EC2.

Где используется

  • Функции "загрузить изображение по URL", "предпросмотр сайта", "webhook"
  • Импорт данных из внешних источников по URL
  • PDF-генераторы с рендерингом HTML по URL

Основной контент

Пример уязвимого кода

// УЯЗВИМО — загружает любой URL
app.post('/api/preview', async (req, res) => {
  const { url } = req.body;
  const response = await fetch(url); // Атака: url = "http://169.254.169.254/latest/meta-data/"
  const content = await response.text();
  res.send(content);
});

Опасные адреса при SSRF

http://169.254.169.254/latest/meta-data/       # AWS EC2 metadata
http://metadata.google.internal/computeMetadata/ # GCP metadata
http://localhost:6379                            # Redis без авторизации
http://internal-api.corp.example.com            # Внутренние сервисы
http://0.0.0.0/                                 # Обход localhost-фильтров
http://[::1]/                                   # IPv6 localhost

Безопасный паттерн: whitelist доменов

const { URL } = require('url');
const dns = require('dns').promises;

const ALLOWED_DOMAINS = new Set(['api.trusted.com', 'cdn.trusted.com']);

async function validateUrl(rawUrl) {
  let parsed;
  try {
    parsed = new URL(rawUrl);
  } catch {
    throw new Error('Invalid URL');
  }

  // Только HTTPS
  if (parsed.protocol !== 'https:') {
    throw new Error('Only HTTPS allowed');
  }

  // Только разрешённые домены
  if (!ALLOWED_DOMAINS.has(parsed.hostname)) {
    throw new Error('Domain not allowed');
  }

  // Проверка DNS — домен не должен разрешаться в приватные IP
  const addresses = await dns.lookup(parsed.hostname, { all: true });
  for (const { address } of addresses) {
    if (isPrivateIP(address)) {
      throw new Error('SSRF: private IP detected');
    }
  }

  return parsed.toString();
}

function isPrivateIP(ip) {
  return /^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.|169\.254\.|::1|fc|fd)/.test(ip);
}

SSRFmap — инструмент для тестирования

# Проверка эндпоинта на SSRF
python3 ssrfmap.py -r request.txt -p url -m readfiles

Частые ошибки

  • Blocklist IP-адресов вместо allowlist доменов — слишком легко обойти через DNS rebinding
  • Проверка hostname без проверки resolved IP — DNS rebinding атака обходит
  • Разрешение редиректов при fetch — редирект может вести на internal адрес
  • Доверие значению заголовка Host или X-Forwarded-For для формирования URL

Связанные темы

Ресурсы