Информационная безопасность
CSRF, SSRF, CORS
2019 год. Capital One. Один из крупнейших банков США. Атакующий находит в облачном приложении функцию загрузки URL и передаёт адрес AWS metadata-сервиса: http://169.254.169.254/latest/meta-data/iam/security-credentials/. Сервер послушно делает запрос, получает IAM-ключи, возвращает результат. Утекает 106 миллионов записей клиентов. Штраф: 80 млн долларов. Инструмент атаки - SSRF, три буквы, о которых не знал ни один из разработчиков системы.
- **Capital One 2019:** SSRF через WAF дал IAM credentials и утечку 106 млн записей - первый крупный SSRF на облачную инфраструктуру
- **Linksys CSRF 2008:** вредоносные сайты меняли DNS-настройки 200 000 маршрутизаторов через CSRF без единого клика пользователя
- **Facebook SSRF 2016:** bug bounty 31 500 долларов за SSRF, позволявший читать внутренние сервисы - награда выплачена за этичный репорт
CSRF: когда браузер становится оружием
2008 год. Маршрутизаторы Linksys по всему миру меняют DNS-серверы без ведома владельцев. Вектор атаки: пользователь открывает вредоносный сайт, браузер автоматически отправляет POST-запрос на `http://192.168.1.1/admin/dns` с куками от предыдущей сессии. Маршрутизатор верит куке. Это **Cross-Site Request Forgery**.
**CSRF** эксплуатирует то, что браузер автоматически добавляет cookies к каждому запросу. Если пользователь залогинен в bank.com, а на evil.com есть форма, POST-запрос на bank.com придёт с валидным session cookie. Сервер не может отличить легитимный запрос от поддельного.
| Метод защиты | Принцип | Надёжность |
|---|---|---|
| CSRF-токен | Секрет, известный только серверу и легитимной странице | Высокая - стандарт |
| SameSite=Strict | Браузер блокирует cookie при cross-site запросах | Высокая для современных браузеров |
| SameSite=Lax | Блокирует POST, разрешает GET-навигацию | Достаточно для большинства случаев |
| Double Submit Cookie | Сравнение cookie и параметра запроса | Средняя - обходится при subdomain takeover |
| Origin/Referer check | Проверка заголовка источника запроса | Слабая - заголовки можно отсутствовать |
**SameSite=Lax** теперь стандарт по умолчанию в Chrome/Firefox/Safari. Это значительно снизило prevalence CSRF. Но legacy-системы, custom auth-протоколы и mobile WebView - всё ещё уязвимы. CSRF-токены остаются необходимыми для state-changing действий.
Сайт использует JWT в localStorage (не в cookie) для авторизации. Уязвим ли он к CSRF?
Same-Origin Policy: изоляция браузера
**Same-Origin Policy (SOP)** - фундаментальный механизм безопасности браузера. JavaScript на `evil.com` не может читать ответы с `bank.com`. Без SOP любой сайт мог бы читать почту Gmail, переводить деньги, красть куки. SOP определяет "origin" как комбинацию: протокол + домен + порт.
**SOP блокирует чтение, не отправку.** CSRF возможен именно потому, что SOP не запрещает браузеру отправлять cross-origin POST-запросы. Запрещено только читать ответ. Для state-changing операций этого достаточно - ответ не нужен, достаточно что сервер выполнил действие.
JavaScript на `https://app.example.com` делает fetch к `https://api.example.com/data`. SOP блокирует этот запрос?
SSRF: сервер как прокси для атакующего
2019 год. Capital One. Облачный WAF принимает URL от пользователя и делает запрос к нему. Атакующий передаёт `http://169.254.169.254/latest/meta-data/` - адрес metadata-сервиса AWS EC2. Сервер возвращает IAM-credentials. Утекло 106 миллионов записей клиентов. Это **Server-Side Request Forgery (SSRF)**.
**SSRF** - атака, при которой сервер делает HTTP-запрос к произвольному адресу, указанному пользователем. Сервер работает изнутри сети, имеет доступ к внутренним сервисам, облачным metadata API, localhost. SSRF в OWASP Top 10 2021 - новая позиция, отдельная от других инъекций.
**DNS Rebinding** обходит IP-фильтрацию SSRF: на первый DNS-запрос домен attacker.com отдаёт публичный IP (проходит whitelist), на второй - 169.254.169.254. Браузер/сервер кешируют первый ответ, но при истечении TTL получают внутренний IP. Mitigation: DNS pinning + двойная проверка IP после резолва.
Приложение на AWS принимает URL для загрузки изображения. Разработчик добавил проверку: URL должен начинаться с https://. Достаточна ли эта защита от SSRF?
CORS: настройка исключений из SOP
SOP запрещает cross-origin запросы по умолчанию. Но реальная архитектура требует: фронтенд на `app.example.com` обращается к API на `api.example.com`. Решение - **CORS (Cross-Origin Resource Sharing)**: сервер явно разрешает определённые origins через HTTP-заголовки. Браузер проверяет эти заголовки и снимает ограничение SOP.
**Portswigger исследование 2018:** 40% публичных API имеют misconfigured CORS, позволяющий произвольным origins читать ответ. Самый частый паттерн: `if (req.headers.origin) { res.set('ACAO', req.headers.origin); }` - полное зеркало без проверки. Это даёт любому сайту доступ к API с credentials пользователя.
**CORS не защищает сервер** - это защита браузера для пользователя. Curl, Postman, Burp Suite игнорируют CORS-заголовки. CORS говорит браузеру, можно ли JavaScript-коду на одном сайте читать ответ с другого. Server-side защита от несанкционированного доступа - всё равно AuthN/AuthZ.
CORS - это защита от CSRF, и правильная настройка CORS предотвращает cross-site атаки
CORS и CSRF - разные механизмы с разными целями. CORS управляет чтением cross-origin ответов браузером. CSRF атакует через отправку запросов с cookies. Misconfigured CORS может усилить CSRF, но правильный CORS не защищает от него.
CSRF эксплуатирует автоматическую отправку cookies - это происходит до того, как CORS-заголовки проверяются. Сервер получает запрос с валидными cookies, выполняет действие. CORS только решает, даст ли браузер JavaScript прочитать ответ.
Разработчик настроил CORS: `Access-Control-Allow-Origin: *`. В чём проблема?
Ключевые идеи
- **CSRF** использует автоматические cookies браузера: вредоносный сайт делает запрос от имени пользователя. Защита: CSRF-токены + SameSite=Lax/Strict cookie
- **Same-Origin Policy** блокирует чтение cross-origin ответов, но не отправку. Origin = протокол + домен + порт. Поддомены - разные origins
- **SSRF** заставляет сервер делать запросы к внутренним сервисам и metadata API. Capital One потерял 106 млн записей через http://169.254.169.254/. Защита: whitelist + IP-проверка + egress firewall
- **CORS** - механизм исключений из SOP. Misconfigured CORS (зеркало Origin) даёт любому сайту доступ к API с credentials. CORS не защищает от CSRF
Связанные темы
CSRF, SSRF и CORS - механизмы доверия в веб-архитектуре:
- SQL Injection — Предыдущий класс атак - инъекции в запросы к БД
- XSS — XSS часто используется для обхода SameSite и усиления CSRF
- Безопасность API — CORS-политика и защита от CSRF - критичны для REST/GraphQL API
Вопросы для размышления
- Текущее приложение принимает URL от пользователя (аватар, превью, webhook)? Как валидируется что IP не принадлежит внутренней сети?
- Какая CORS-конфигурация используется? Есть ли в коде `res.setHeader('ACAO', req.headers.origin)` без проверки?
- SameSite=Lax установлен для session-cookie? Если нет - каков план миграции без поломки функциональности?
Связанные уроки
- sec-07 — SQLi - предыдущий класс веб-атак, паттерн инъекции
- sec-09 — XSS часто используется вместе с CSRF для полноценной атаки
- sec-03 — CSRF обходит AuthZ - пользователь авторизован, но не знает о запросе
- sec-17 — Безопасность API: CORS и CSRF критичны для REST/GraphQL
- net-22-http-headers