Клиентские уязвимости
Уязвимости доверия браузера — виды XSS, контекстное экранирование, CSP, CSRF, CORS, кликджекинг, tabnabbing, prototype pollution, postMessage и безопасность WebSocket.
16 вопросов
JuniorТеорияОчень частоЧто такое межсайтовая подделка запроса (CSRF) и когда денежный эндпоинт уязвим к ней?
Что такое межсайтовая подделка запроса (CSRF) и когда денежный эндпоинт уязвим к ней?
CSRF заставляет браузер жертвы отправить на ваш сайт запрос, меняющий состояние, со страницы атакующего; cookie прикрепляются автоматически, поэтому сервер видит легитимного пользователя. Эндпоинт уязвим, когда аутентифицирует только по cookie и не требует непредсказуемого значения.
Типичные ошибки
- ✗Путать CSRF с XSS — ответ на поддельный запрос обычно нечитаем
- ✗Считать, что HTTPS или cookie с
HttpOnlyпредотвращают CSRF - ✗Считать, что меняющие состояние обработчики
GETподделать нельзя
Уточняющие вопросы
- →Почему страница атакующего обычно не может прочитать тело поддельного ответа?
- →Как атрибут cookie
SameSiteменяет исходную подверженность?
JuniorТеорияОчень частоКакие есть три вида межсайтового скриптинга (XSS) и что получает внедрённый скрипт?
Какие есть три вида межсайтового скриптинга (XSS) и что получает внедрённый скрипт?
Reflected XSS возвращает ввод атакующего в ответе; stored XSS сохраняет его и отдаёт каждому посетителю; DOM XSS не доходит до сервера — клиентский скрипт пишет недоверенные данные в sink. Все три выполняются в вашем origin и действуют от имени пользователя.
Типичные ошибки
- ✗Считать, что cookie с
HttpOnlyпредотвращает XSS, а не просто мешает краже токена - ✗Ожидать, что DOM XSS виден в серверном выводе или в access-логах
- ✗Считать reflected XSS безобидным, раз он требует специально собранной ссылки
Уточняющие вопросы
- →Почему исправление — контекстное экранирование вывода, а не чёрный список символов ввода?
- →Как Content Security Policy (CSP) ограничивает уже внедрённый скрипт?
MiddleТеорияОчень частоПочему отражение заголовка Origin вместе с credentials — изъян Cross-Origin Resource Sharing?
Почему отражение заголовка Origin вместе с credentials — изъян Cross-Origin Resource Sharing?
Отражение Origin запроса в Access-Control-Allow-Origin вместе с credentials даёт любому сайту читать аутентифицированные ответы вашего API. null не безопасное значение — его шлют iframe в песочнице — поэтому сверяйте origin с точным списком, а не отражайте.
Типичные ошибки
- ✗Считать, что политика одного источника всё ещё скрывает тело при разрешающем заголовке
- ✗Разрешать
null, будто его шлют только локальные файлы - ✗Проверять
Originподстрокой или по суффиксу
Уточняющие вопросы
- →Какие контексты браузера отправляют
Originсо значениемnull? - →Почему браузер отвергает wildcard, когда запрошены credentials?
JuniorТеорияЧастоСравните SameSite-cookie, synchronizer-токены и double-submit как защиты от CSRF
Сравните SameSite-cookie, synchronizer-токены и double-submit как защиты от CSRF
SameSite=Lax или Strict не даёт cookie уходить с межсайтовыми запросами, но это умолчание браузера, а не проверка на запрос. Synchronizer-токен хранится на сервере и сверяется в каждом запросе — самый надёжный вариант. Double-submit не хранит состояния, но его ослабляет поддомен.
Типичные ошибки
- ✗Думать, что флаги cookie
HttpOnlyилиSecureзащищают от CSRF - ✗Полагаться только на
SameSiteи не делать проверку в каждом запросе - ✗Упускать, что double-submit рушится при контроле атакующего над поддоменом
Уточняющие вопросы
- →Почему скомпрометированный соседний поддомен подрывает схему double-submit?
- →Когда
SameSite=Laxвсё ещё допускает межсайтовое изменение состояния?
JuniorТеорияЧастоКак проследить DOM-based XSS от недоверенного источника до опасного sink?
Как проследить DOM-based XSS от недоверенного источника до опасного sink?
Начните с источников — location.hash, location.search, document.referrer, данные postMessage — и проследите значение до sink, которые его разбирают или исполняют, таких как innerHTML, document.write и eval. Любой неочищенный путь от источника к sink и есть баг.
Типичные ошибки
- ✗Ожидать, что payload DOM XSS появится в access-логах сервера
- ✗Доверять значениям из
localStorageили фрагмента URL как «своим» - ✗Путать текстовый sink
textContentс разбирающим sinkinnerHTML
Уточняющие вопросы
- →Почему фрагмент URL не доходит до сервера и что из этого следует?
- →Как Trusted Types заставляют опасный sink отвергать сырую строку?
JuniorТеорияЧастоПочему экранирование вывода должно учитывать контекст sink и почему одного энкодера мало?
Почему экранирование вывода должно учитывать контекст sink и почему одного энкодера мало?
Каждый sink разбирает данные по-своему — HTML-текст требует entity-кодирования, атрибуты — кавычек, строки JavaScript — JS-экранирования, URL — percent-кодирования. HTML-кодированные данные в блоке <script> или в href исполняемы, поэтому энкодер выбирают по назначению.
Типичные ошибки
- ✗Экранировать один раз на входе вместо каждого sink на выходе
- ✗Считать значения из базы доверенными и не требующими экранирования
- ✗Принимать чёрный список символов или WAF за замену экранированию
Уточняющие вопросы
- →Какие sink не спасает ни один энкодер и что делать вместо экранирования для них?
- →Как шаблонизатор с авто-экранированием сам определяет контекст?
MiddleТеорияЧастоПочему CORS (cross-origin sharing) — не контроль авторизации и что на деле делает preflight?
Почему CORS (cross-origin sharing) — не контроль авторизации и что на деле делает preflight?
Политика одного источника защищает вас по умолчанию; CORS её лишь ослабляет, поэтому разрешающая конфигурация — уязвимость, а не защита. Он задаёт, что браузер даёт прочитать скрипту; небраузерные клиенты его игнорируют. Preflight проверяет метод и заголовки, но не авторизацию.
Типичные ошибки
- ✗Считать CORS контролем доступа, а не ослаблением политики одного источника
- ✗Полагать, что небраузерные клиенты вроде
curlограничены CORS - ✗Думать, что preflight аутентифицирует или авторизует вызывающего
Уточняющие вопросы
- →Какие запросы вовсе обходятся без preflight и почему это важно?
- →Какая серверная проверка обязана работать независимо от политики CORS?
MiddleТеорияЧастоКак работают nonce и hash в Content Security Policy (CSP) и почему unsafe-inline рушит её?
Как работают nonce и hash в Content Security Policy (CSP) и почему unsafe-inline рушит её?
Браузер исполняет только разрешённые политикой скрипты. Случайный nonce на каждый ответ либо хеш sha256 точного содержимого помечает ваши теги <script> — у внедрённого скрипта нет ни того, ни другого. unsafe-inline разрешает любой инлайн-скрипт, включая внедрённый.
Типичные ошибки
- ✗Переиспользовать один nonce между ответами вместо генерации на каждый ответ
- ✗Думать, что CSP проверяет содержимое скриптов на опасные шаблоны
- ✗Считать, что
unsafe-inlineзатрагивает лишь инлайн-обработчики событий
Уточняющие вопросы
- →Зачем на страницах с обилием скриптов к nonce добавляют
strict-dynamic? - →От чего политика по-прежнему не защищает, когда ваш собственный скрипт уже работает?
MiddleТеорияЧастоЧто должен проверять обработчик postMessage и почему event.data потом всё равно недоверенный?
Что должен проверять обработчик postMessage и почему event.data потом всё равно недоверенный?
Постить в ваше окно может любое окно, поэтому обработчик обязан сравнить event.origin с точным ожидаемым origin — никогда не подстрокой — а отправитель должен указывать конкретный origin, а не *. Даже тогда event.data — это данные, поэтому выводите их через textContent.
Типичные ошибки
- ✗Проверять
event.originподстрокой или по суффиксу вместо точного сравнения - ✗Считать, что
postMessageи так ограничен окнами того же origin - ✗Считать, что пройденная проверка origin делает
event.dataпригодным для HTML-sink
Уточняющие вопросы
- →Почему отправитель должен указывать конкретный target origin, а не
*? - →Как Trusted Types не дают
event.dataпопасть в разбирающий sink?
JuniorТеорияИногдаЧто такое кликджекинг и как задать, каким сайтам разрешено фреймить вашу страницу?
Что такое кликджекинг и как задать, каким сайтам разрешено фреймить вашу страницу?
Кликджекинг грузит вашу страницу в прозрачном фрейме поверх содержимого атакующего, поэтому клик жертвы попадает на вашу настоящую кнопку, хотя она думает, что нажала другое. Управляйте фреймингом директивой frame-ancestors, оставив X-Frame-Options для старых браузеров.
Типичные ошибки
- ✗Считать, что политика одного источника мешает чужому сайту фреймить вашу страницу
- ✗Полагаться на скриптовый frame-buster вместо заголовка ответа
- ✗Путать кликджекинг с инъекцией, которая чинится экранированием вывода
Уточняющие вопросы
- →Почему
frame-ancestorsвытесняет устаревший заголовокX-Frame-Options? - →Какие действия интерфейса становятся куда опаснее без шага подтверждения?
JuniorТеорияИногдаПочему пользовательские внешние ссылки с target=_blank требуют rel=noopener?
Почему пользовательские внешние ссылки с target=_blank требуют rel=noopener?
Без него открытая страница получает ссылку window.opener на вашу вкладку и может увести её на фишинговую копию вашей страницы входа — это reverse tabnabbing. rel=noopener обрывает эту ссылку; rel=noreferrer ещё и убирает Referer. Задавайте его явно.
Типичные ошибки
- ✗Думать, что
window.openerдаёт открытой странице читать ваш DOM, а не уводить его - ✗Считать, что новая вкладка не хранит ссылку на открывшую её страницу
- ✗Воспринимать
noreferrerкак чисто аналитическую или privacy-настройку
Уточняющие вопросы
- →Где современное неявное умолчание
noopenerне действует? - →Чем reverse tabnabbing отличается от класса веб-атак
CSRF?
MiddleДебаггингИногдаОтчёт Content Security Policy показывает заблокированный инлайн-скрипт — найдите причину
Отчёт Content Security Policy показывает заблокированный инлайн-скрипт — найдите причину
В DOM попал инлайн-скрипт без nonce, хотя маршрут таких не отдаёт, значит его внедрили на клиенте — это DOM XSS. Поисковый запрос идёт из URL в заголовок результатов через разбирающий sink. Политика заблокировала и сообщила; чинить надо sink, а не политику.
Открыть задачу →Типичные ошибки
- ✗Списывать отчёты о нарушениях на шум расширений, не проверив маршрут
- ✗Заглушать отчёт через
unsafe-inlineвместо исправления sink - ✗Считать, что нарушение с
inlineозначает серверную разметку
Уточняющие вопросы
- →Какой источник в этом потоке вы проследите первым и до какого sink?
- →Почему пустое поле
script-sampleне исключает внедрённый payload?
MiddleКодИногдаИсправьте небезопасный обработчик postMessage, чтобы он проверял отправителя и рендерил безопасно
Исправьте небезопасный обработчик postMessage, чтобы он проверял отправителя и рендерил безопасно
Два дефекта. Обработчик не проверяет event.origin, поэтому управлять им может любое окно, и он пишет пользовательский текст в innerHTML — разбирающий sink. Сравните event.origin строго с https://app.example.com, выйдите при несовпадении и выводите через textContent.
Типичные ошибки
- ✗Проверять
event.sourceили суффикс origin вместо точного совпадения origin - ✗Экранировать кавычки вместо отказа от разбирающего sink
- ✗Считать, что структурированный канал сообщений делает payload безопасным для
innerHTML
Уточняющие вопросы
- →Почему ранний выход при несовпадении origin важнее, чем очистка значения?
- →Когда
textContentнедостаточно и лучше подойдут Trusted Types?
MiddleТеорияИногдаЧто такое prototype pollution и как загрязнённый ключ перерастает в реальную уязвимость?
Что такое prototype pollution и как загрязнённый ключ перерастает в реальную уязвимость?
Рекурсивное слияние, копирующее ключи атакующего, может писать через __proto__, добавляя свойство, которое наследует любой обычный объект. Сама запись инертна — багом она становится, когда gadget читает это свойство как конфигурацию, например опцию шаблона или HTML-sink.
Типичные ошибки
- ✗Считать запись в прототип мгновенным исполнением кода, а не требующей gadget
- ✗Блокировать только буквальный ключ
__proto__, упускаяconstructor.prototype - ✗Полагать, что унаследованные свойства не доходят до шаблона или DOM-sink
Уточняющие вопросы
- →Почему
Mapили объект с прототипомnullобесценивают такую запись? - →Как искать gadget в зависимости после того, как запись подтверждена?
MiddleТеорияИногдаПочему WebSocket с cookie-аутентификацией уязвим к межсайтовому перехвату и что это чинит?
Почему WebSocket с cookie-аутентификацией уязвим к межсайтовому перехвату и что это чинит?
Рукопожатие — это HTTP-запрос upgrade, который несёт cookie, но не подчиняется CORS, поэтому страница атакующего может открыть сокет от имени вошедшего пользователя и читать поток. Проверяйте Origin на сервере по списку разрешённых и аутентифицируйте коротким токеном.
Типичные ошибки
- ✗Полагать, что CORS или политика одного источника ограничивают рукопожатие WebSocket
- ✗Считать, что cookie не прикрепляются к запросу upgrade
- ✗Принимать шифрование транспорта
wssза защиту от перехвата
Уточняющие вопросы
- →Почему серверная проверка
Originосмысленна лишь для браузерных клиентов? - →Чем короткоживущий тикет на подключение отличается от повторного использования cookie сессии?
SeniorТеорияИногдаКак выкатить строгую Content Security Policy на легаси-приложении, ничего не сломав?
Как выкатить строгую Content Security Policy на легаси-приложении, ничего не сломав?
Сначала выкатите Content-Security-Policy-Report-Only с эндпоинтом сбора и дайте трафику перечислить инлайн-обработчики и сторонние хосты. Затем уберите инлайновые onclick и eval, вынесите код во внешние файлы, добавьте nonce и включайте enforce, когда отчёты иссякнут.
Типичные ошибки
- ✗Включать enforce до того, как режим report-only перечислил реальные инлайн-вставки
- ✗Оставлять
unsafe-inlineнавсегда вместо удаления инлайн-обработчиков - ✗Считать, что список сторонних хостов покрывает инлайн-обработчики событий
Уточняющие вопросы
- →Почему
strict-dynamicделает список сторонних хостов по большей части избыточным? - →Как поддерживать хеши Subresource Integrity (SRI) в актуальном виде для сторонних бандлов?