Фронтенд-разработчика на собесе почти всегда спрашивают про XSS, CSRF и CORS. Основа всего это same-origin policy.
Same-origin policy
Origin это схема + хост + порт. https://shop.ru и https://api.shop.ru разные origin, как и http://shop.ru и https://shop.ru.
Политика одного источника запрещает скрипту одного origin читать данные другого: ответы fetch, DOM чужого iframe, его хранилища. Отправить запрос на чужой сайт можно (форма, картинка), а прочитать ответ без разрешения через CORS нельзя.
Важно: site шире, чем origin. Site это схема + регистрируемый домен (shop.ru). api.shop.ru и shop.ru один site, но разные origin. На site опирается SameSite у кук.
XSS (межсайтовый скриптинг)
Атакующий добивается, чтобы его скрипт выполнился на вашем сайте. Скрипт получает всё, что есть у страницы: DOM, неHttpOnly-куки, токены в localStorage, может делать запросы от имени пользователя.
Виды:
- Хранимая: вредный текст сохранён в базе (комментарий) и показывается всем.
- Отражённая: вредный кусок приходит в URL и сервер вставляет его в ответ.
- DOM-based: клиентский код сам берёт данные из
location.hashилиpostMessageи вставляет в опасное место.
// уязвимо
el.innerHTML = `Привет, ${new URLSearchParams(location.search).get('name')}`;
// ?name=<img src=x onerror=alert(document.cookie)>
// безопасно
el.textContent = `Привет, ${name}`;
Защита:
- Экранировать вывод по контексту. React делает это для
{value}автоматически, но не дляdangerouslySetInnerHTMLи не дляhref="javascript:...". - Если HTML нужен, чистить его санитайзером (DOMPurify).
- Опасные «приёмники» (sinks):
innerHTML,outerHTML,document.write,eval,new Function,setTimeoutсо строкой,hrefиsrcс пользовательским URL. - CSP как второй рубеж.
- Сессионные куки с
HttpOnly, чтобы скрипт их не прочитал.
CSP (Content Security Policy)
Заголовок ответа, который говорит браузеру, откуда разрешено грузить и выполнять ресурсы.
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'
- Без
'unsafe-inline'инлайн-скрипты иonclick="..."не выполнятся, а значит внедрённый<script>тоже. - nonce: случайное значение на каждый ответ,
<script nonce="r4nd0m">выполнится, чужой без nonce нет. 'strict-dynamic': скрипты, загруженные доверенным скриптом, тоже доверенные. Современная «строгая CSP» это nonce + strict-dynamic.frame-ancestorsзапрещает встраивать сайт в чужие iframe (защита от кликджекинга, современная заменаX-Frame-Options).Content-Security-Policy-Report-Onlyдля обкатки без блокировки.
Trusted Types
Механизм, который запрещает передавать обычные строки в опасные DOM-приёмники вроде innerHTML. Включается через CSP: require-trusted-types-for 'script'. После этого el.innerHTML = str бросит ошибку, принимается только объект TrustedHTML, созданный через вашу политику (например, с DOMPurify внутри). Так все места вставки HTML проходят через одну проверяемую точку. В Chromium-браузерах Trusted Types есть давно, в Safari и Firefox поддержка появилась недавно, поэтому перед включением сверяйтесь с caniuse и держите отчётный режим.
CSRF (межсайтовая подделка запроса)
Пользователь залогинен в bank.ru, заходит на evil.ru, а там скрытая форма отправляет POST bank.ru/transfer. Браузер прикладывает куки банка, и сервер думает, что это сам пользователь. Атакующий ответ не читает, ему достаточно, что действие выполнилось.
Защита:
- SameSite у сессионной куки.
Lax(по умолчанию в Chrome и Edge, если атрибут не указан) не отправляет куку в межсайтовых POST, iframe и fetch, но отправляет при переходе по ссылке верхнего уровня.Strictне отправляет ни в каком межсайтовом запросе.Noneотправляет всегда и требуетSecure. - CSRF-токен: случайное значение в форме или заголовке, которое сервер сверяет с сессией. Чужой сайт его не знает.
- Проверка заголовков
OriginилиSec-Fetch-Site. - Не менять состояние через GET.
Если токен лежит в localStorage и отправляется в Authorization, CSRF не страшен (браузер сам его не приложит), зато XSS сразу его украдёт.
Set-Cookie: sid=abc; HttpOnly; Secure; SameSite=Lax; Path=/
Кликджекинг
Ваш сайт грузят в прозрачный iframe поверх приманки, пользователь кликает «Выиграть приз», а на деле жмёт «Удалить аккаунт». Защита: frame-ancestors 'none' в CSP или X-Frame-Options: DENY.
Ещё по мелочи
target="_blank": современные браузеры по умолчанию ставятnoopener, но явныйrel="noopener noreferrer"не помешает.postMessage: всегда проверятьevent.originу входящих сообщений и указывать целевой origin при отправке, а не'*'.- Подключая скрипт с CDN, используйте
integrity(Subresource Integrity). - Секреты (ключи API) во фронтенд-бандле видны всем.
Частые ошибки
- Думать, что CORS защищает от CSRF. CORS ограничивает чтение ответа, а запрос формы уйдёт и так.
- Считать React полностью защищённым от XSS.
- Хранить сессию в localStorage «чтобы не было CSRF» и забыть про XSS.
- Путать site и origin.
Что спросят на собесе
- Что такое XSS, какие бывают виды и как защищаться.
- Как работает CSRF и почему SameSite помогает.
- Что такое CSP и зачем nonce.
- Где хранить токен: cookie или localStorage.
- Что такое same-origin policy и чем site отличается от origin.