Kodokon kodokon.com

Безопасность на стороне HTML: XSS, sandbox, noopener и CSP

Распознавай векторы XSS, специфичные для разметки, и применяй встроенную защиту HTML: sandbox, noopener и Content Security Policy.

11 мин · 3 вопросов

Открыть этот урок в Kodokon

Контекст экранирования решает всё: значение, безобидное в текстовом узле, становится исполняемым в атрибуте. Худший случай - атрибут без кавычек: достаточно одного пробела во внедрённых данных, чтобы создать новый атрибут, например обработчик onmouseover. Даже правильно взятое в кавычки, значение остаётся опасным в чувствительных приёмниках (sinks): href и src принимают схему javascript:, formaction перехватывает отправку формы, а srcdoc после декодирования интерпретирует свои сущности как HTML.

HTML
<!-- userName = x onmouseover=alert(1) -->
<img src="avatar.png" alt=x onmouseover=alert(1)>
<!-- The unquoted attribute lets a handler in -->
Неэкранированные данные в атрибуте без кавычек становятся кодом.

Атрибут sandbox у iframe снимает все привилегии, и дальше ты возвращаешь их по одной: allow-scripts, allow-forms, allow-popups… Без allow-same-origin встроенный документ получает непрозрачное происхождение (opaque origin): ни куки, ни хранилища, ни доступа к DOM родителя - даже если он отдаётся с твоего собственного домена.

HTML
<iframe
  src="https://widget.example.com"
  sandbox="allow-scripts allow-forms"
  referrerpolicy="no-referrer">
</iframe>
Сторонний виджет, который может выполнять скрипты, но заперт в непрозрачном происхождении.

Ссылка с target=_blank исторически давала открытой странице ссылку window.opener на твою вкладку: она могла перенаправить твою страницу на фишинговую копию - атака, известная как tabnabbing. Современные браузеры теперь применяют noopener неявно при target=_blank, но писать его явно по-прежнему необходимо для window.open в JavaScript и чтобы задокументировать намерение; добавь noreferrer, чтобы заодно убрать заголовок Referer.

HTML
<a href="https://external.example"
   target="_blank"
   rel="noopener noreferrer">
  View the external resource
</a>
Открытая страница получает window.opener === null.

Content Security Policy ограничивает источники исполняемого кода и обезвреживает большинство XSS, даже когда внедрение удалось. Объявленная через тег <meta http-equiv>, она тем не менее имеет слепые зоны: директивы frame-ancestors, report-uri и sandbox там игнорируются и требуют HTTP-заголовка. Предпочитай nonce, заново генерируемые при каждом ответе, спискам разрешённых доменов, которые директива strict-dynamic в любом случае делает устаревшими.

HTML
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self';
               script-src 'self' 'nonce-r4nd0m'">
Выполняются только скрипты, несущие nonce сервера.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Почему сочетание allow-scripts и allow-same-origin на iframe того же происхождения отменяет песочницу?
    • Встроенный документ, способный выполнять скрипты и не изолированный, может добраться до DOM родителя и сам удалить атрибут sandbox
    • Эти два токена несовместимы, и браузер игнорирует весь атрибут целиком
    • allow-same-origin автоматически отключает все остальные токены
  2. От какой атаки защищает rel=noopener?
    • От кражи сессионных куки открытой страницей
    • От внедрения скрипта в открытую страницу
    • От tabnabbing: открытая страница использует window.opener, чтобы перенаправить твою вкладку на фишинговый сайт
  3. Какая директива игнорируется, когда CSP объявлена через <meta http-equiv>?
    • script-src
    • frame-ancestors
    • default-src
    • img-src