Суть Читай реальные конфиги кук и обработчики маршрутов, предсказывай поведение CSRF и выбирай фикс с наибольшим рычагом, который сделал бы senior-ревьюер.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги CSRF живут в атрибутах кук и обработчиках маршрутов, а не в прозе. Читай каждый сниппет, предсказывай, пройдёт ли подделанный запрос, и выбирай фикс, который senior-ревьюер пометит первым.
Цель
Отработай цикл, который ты гоняешь в каждом security-ревью: читай конфиг кук и обработчик, предсказывай, где приземлится подделанная запись, и тянись к фиксу с наибольшим рычагом до рассуждений об экзотических обходах.
Сниппет 1 — конфиг куки
// Express session cookieapp.use(session({ secret: process.env.SECRET, cookie: { httpOnly: true, secure: true, sameSite: 'none', // <-- нужно, чтобы встроенный виджет на partner.com работал },}));
Викторина
Completed
Команда поставила sameSite: 'none', чтобы сессия работала в iframe на partner.com. Какую CSRF-постуру это создаёт и что должно за этим последовать?
Heads-up httpOnly запрещает JavaScript читать куку (про XSS); он ничего не делает, чтобы браузер не слал её кросс-сайтово. С SameSite=None не осталось защиты SameSite, на которую можно опереться.
Heads-up secure обязателен синтаксически с SameSite=None, но лишь форсит транспорт HTTPS. Подделанный кросс-сайтовый запрос тоже по HTTPS; SameSite=None всё равно шлёт куку. Шифрование ортогонально CSRF.
Heads-up Встроенный контекст может слать запросы, несущие куку SameSite=None кросс-сайтово, и весь смысл None — слать куку повсюду. Изменения состояния полностью достижимы; нужна токен-защита.
Эта обычная double-submit проверка проходит, когда значение куки равно значению заголовка. Какую слабость пометит senior-ревьюер и какой фикс?
Heads-up Сравнение строк — не дефект. Структурная слабость в том, что паттерн предполагает, будто атакующий не может записать твою куку — сломай это допущение (XSS на поддомене, injection), и совпадающие значения тривиально подделать.
Heads-up Заголовок нельзя ПРОЧИТАТЬ или выставить кросс-сайтово скриптом, верно — но атакующему не нужно читать; ему нужно контролировать обе половины, что даёт примитив записи куки. Обычный double-submit слабее, чем кажется.
Heads-up Stateless может быть безопасным: HMAC-подписанный double-submit привязывает токен к секрету сессии, так что подставленное значение куки не пройдёт валидацию, без серверного хранения. Фикс — привязка, а не отказ от stateless.
Сниппет 3 — обработчик маршрута
// «Отписаться в один клик из письма»app.get('/account/email-prefs/unsubscribe', requireSession, (req, res) => { db.users.update(req.session.userId, { subscribed: false }); // мутирует! res.send('Вы отписаны.');});
Викторина
Completed
Сессии здесь SameSite=Lax и есть CSRF-токен на всех POST-маршрутах. Этот GET-обработчик безопасен? Почему да или нет?
Heads-up requireSession удовлетворяется собственной авто-прикреплённой Lax-кукой жертвы на подделанном top-level GET. Аутентификация есть; намерения нет. Мутирующий GET — это дыра.
Heads-up Lax намеренно шлёт куку на top-level GET-навигациях. Это именно тот случай, под который уязвим обработчик; Lax его не блокирует.
Heads-up Идемпотентность нерелевантна CSRF: один подделанный запрос уже переключает subscribed в false против воли пользователя. Дефект — мутация на GET, где проверка токена не запускается.
Сниппет 4 — проверка Origin
function checkOrigin(req, res, next) { const origin = req.get('Origin'); if (origin === 'https://app.example.com') return next(); return res.status(403).send('Bad origin');}
Викторина
Completed
Если использовать это как ЕДИНСТВЕННУЮ защиту от CSRF на маршрутах с изменением состояния, что ломается и как этот слой надо применять?
Heads-up Origin ставится браузером и не подделываем скриптом, верно, но он не всегда присутствует, а единственный негативный фильтр не доказывает намерения. Он рядом с токеном, с откатом на Referer, а не сам по себе.
Heads-up Origin ставится браузером и не спуфится скриптом страницы в обычном кросс-сайтовом запросе — это его сила. Реальные проблемы — отсутствие/строгость и отсутствие позитивного доказательства, так что это слой, а не вся защита.
Heads-up Origin — более надёжный сигнал (шлётся на кросс-сайтовые запросы с изменением состояния и минимален); Referer — откат и чаще срезается приватностью или referrer-policy. Порядок: сначала Origin, Referer как откат.
Итог
Каждое CSRF-ревью читается в атрибутах кук и обработчиках: SameSite=None возвращает тебя в полную поверхность до 2020 и обязывает к токен-защите; обычный double-submit доверяет, что куку можешь записать только ты, так что HMAC-подписанный вариант — фикс; мутация на GET подделываема через top-level Lax-навигацию и должна перейти на POST за токеном; а проверка Origin — дешёвый фильтр defense-in-depth, с откатом на Referer, никогда не единственный замок. Замечай структурный дефект, чини на слое с наибольшим рычагом, потом подтверждай, что подделанный запрос больше не приземляется.