Повседневные атаки: CORS, CSRF, SQL injection, path traversal
Это первый урок раздела. Здесь не про экзотику и не про красивые 0-day, а про четыре атаки, с которыми ты столкнёшься в первый же месяц работы с любым бэкендом. Хорошая новость: защита от каждой из них это не магия, а привычка писать код одним правильным способом.
Раздел идёт снизу вверх: сегодня бытовые атаки уровня Junior, дальше XSS и CSP, потом OWASP Top 10 и цепочка поставок, затем auth, экзотика и, в конце, опережающее предупреждение для тимлида.
Идея: граница доверия
Вся безопасность приложения сводится к одному вопросу: откуда пришли эти данные и можно ли им верить. Ответ почти всегда “нельзя”.
Граница доверия
это место, где данные переходят из чужой зоны в твою: из браузера в
обработчик запроса, из тела запроса в SQL, из имени файла в обращение к
диску. Браузер, форма, URL, заголовок, тело, имя загруженного файла, это
всё недоверенный вход, даже если его прислал твой собственный фронтенд.
Фронтенд тоже выполняется на чужой машине, и его легко обойти через curl.
Запомни три привычки, и три четверти бытовых дыр закрыты сами:
- Никогда не строй строку (SQL, путь, HTML, команду) конкатенацией с пользовательским вводом. Используй параметры и безопасные конструкторы.
- Проверяй вход на границе по белому списку: что разрешено, а не что запрещено. Чёрные списки всегда дырявые.
- Раздавай минимум прав: пользователю БД, токену, файловой системе.
Дальше четыре конкретные атаки, и в каждой видно, как одна из этих привычек её закрывает.
CORS и Same-Origin Policy
Начнём с того, что часто путают с защитой сервера, хотя это защита
пользователя. Базовое правило браузера это
Same-Origin Policy:
скрипт со страницы https://app.example.com не может прочитать ответ от
https://api.other.com. Origin это тройка схема, хост, порт, поэтому
http://localhost:5173 и http://localhost:3000 уже разные origin.
CORS это управляемое ослабление этого правила. Сервер заголовками говорит “этой странице с этого origin можно прочитать мой ответ”. Здесь два частых заблуждения, которые важно выбить сразу:
- CORS не защищает твой сервер. Запрос всё равно доходит и выполняется,
браузер лишь прячет от чужого скрипта тело ответа.
curl, Postman и сервер-сервер вызовы CORS вообще не подчиняются. - CORS не нужно “обходить”. Если CORS мешает, значит фронт и API на разных origin. Поставь reverse proxy или dev-прокси Vite, чтобы они жили за одним origin, и проблема исчезнет.
Полную механику preflight, заголовков Access-Control-* и ловушку с
credentials плюс * мы разобрали в уроке
Под капотом HTTP: DNS, TCP, TLS, keep-alive, CORS.
Здесь важно одно: CORS это про чтение ответа в браузере, а не про право
выполнить запрос. Кто реально защищает сервер от чужого сайта, это
аутентификация, авторизация и, для cookie-сессий, защита от CSRF, к
которой и переходим.
На сервере базовый набор безопасных заголовков ставят одной библиотекой.
В Express это helmet, в Fastify это @fastify/helmet:
import helmet from 'helmet';
// выставляет пачку защитных заголовков разом:
// X-Content-Type-Options, Referrer-Policy, HSTS, базовый CSP и другие
app.use(helmet());
Helmet не защищает от логических дыр, но закрывает дешёвые промахи конфигурации. Про CSP, который он умеет настраивать, подробно в следующем уроке.
CSRF
CSRF
эксплуатирует одну привычку браузера: к запросу на example.com он сам
прикрепляет cookie для example.com, кто бы этот запрос ни инициировал.
Схема атаки. Ты залогинен в bank.example.com, сессия лежит в cookie.
Параллельно открываешь evil.example.org, где спрятана форма:
<!-- страница на чужом сайте -->
<form action="https://bank.example.com/transfer" method="POST" id="f">
<input name="to" value="attacker" />
<input name="amount" value="10000" />
</form>
<script>document.getElementById('f').submit();</script>
Форма отправляется автоматически. Браузер видит запрос на
bank.example.com и услужливо прикрепляет твою cookie-сессию. Сервер
видит валидную сессию и проводит перевод. Атакующий не читал твой ответ
(SOP не пустил), но действие уже произошло. CSRF это про запись, не про
чтение.
Защита строится из трёх независимых слоёв, и в современном вебе первый закрывает большинство случаев:
- SameSite
на сессионной cookie.
SameSite=Lax(дефолт в современных браузерах) не даёт cookie уехать на cross-sitePOST, что ломает атаку выше.Strictещё строже, но ломает переход по внешним ссылкам в залогиненную зону. - Double-submit cookie: кладём случайный токен в cookie и требуем тот же токен в заголовке. Чужой сайт не прочитает cookie (SOP), значит не подделает заголовок.
- Anti-CSRF токен: сервер генерирует токен, встраивает в форму, сверяет при отправке формы.
Чем CSRF отличается от XSS, которому посвящён следующий урок: в CSRF атакующий не выполняет свой код на твоём домене, он лишь заставляет браузер послать запрос вслепую. В XSS наоборот, чужой скрипт выполняется на твоём origin и может читать всё, включая anti-CSRF токены. Поэтому XSS сильнее: он обходит защиту от CSRF. Если у тебя XSS, про CSRF уже поздно думать.
дальше после входа
Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.
Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.