Как стать автором
Обновить
0
VDSina.ru
Серверы в Москве и Амстердаме

Trusted Types — новый способ защиты кода веб-приложений от XSS-атак

Время на прочтение6 мин
Количество просмотров6.7K
Автор оригинала: Krzysztof Kotowicz
Компания Google разработала API, которое позволяет современным веб-приложениям защитить свой фронтенд от XSS-атак, а конкретнее — от JavaScript инъекций в DOM (DOM-Based Cross Site Scripting).

Межсайтовый скриптинг (XSS) — наиболее распространённый тип атак, связанных с уязвимостью современных веб-приложений. Это признаёт не только компания Google, но и вся индустрия. Опыт показывает, что разработка веб-приложения устойчивого к XSS-атаке, по-прежнему является нетривиальной задачей, особенно, когда речь идёт о сложных проектах. Если на бэкенде разработчики достаточно успешно решают эту проблему, то на фронтенде всё гораздо сложнее. В рамках программы Google's Vulnerability Reward Program всё больше наград получают разработчики, предложившие решение по защите от атаки DOM XSS.

Вы спросите, как мы дошли до такой жизни? Наверное, спросите. Дело в том, что у современных веб-приложений есть две серьёзных и не связанных друг с другом проблемы…

XSS легко внедрить в код


DOM XSS — это тип атаки, при котором неотфильтрованные данные, идущие со стороны пользователя или других браузерных API, вместе с внедрёнными js-инъекциями попадают в DOM. Например, рассмотрим фрагмент, предназначенный для загрузки таблицы стилей для некого шаблона пользовательского интерфейса, который использует приложение:

const templateId = location.hash.match(/tplid=([^;&]*)/)[1];
// ...
document.head.innerHTML += `<link rel="stylesheet" href="./templates/${templateId}/style.css">`

В этом случае скрипт, внедрённый злоумышленником в location.hash (в данном случае играет роль источника данных, то есть, source), попадёт в innerHTML (играет роль приёмника данных, то есть, sink). Используя эту уязвимость, злоумышленник может подменить URL, по которому и перейдёт жертва:

https://example.com#tplid="><img src=x onerror=alert(1)>

Эту инъекцию можно запросто не заметить, особенно если код часто меняется. Например, когда-то давно templateId мог быть сгенерирован и проверен на сервере и после этого проверять его вновь никому в голову не приходило. Когда мы что-то записываем в innerHTML, нам точно известно только то, что его значение является строкой. Но следует ли доверять содержимому этой строки? Откуда оно пришло на самом деле?

Более того, проблема не ограничивается только innerHTML. В типичной DOM существует более 60 методов и свойств, которые имеют ту же проблему. Так что, DOM API по умолчанию небезопасен и требует особого подхода для предотвращения XSS.

XSS трудно отследить


Приведенный выше код является лишь наглядным примером, поэтому там увидеть ошибку легко. На практике значения source и sink часто находятся в совершенно разных частях приложения. Есть ли какие-то функции, которые выполняют санитизацию и проверку данных на пути от source к sink? А как определить, когда и какую функцию вызывать?

Глядя только на исходный код, сложно понять, содержит ли он скрипты DOM XSS. Более того, недостаточно выполнить grep для .js-файлов с настройкой чувствительности к регистру. Ведь чувствительные к регистру функции часто используются внутри различных обёрток, так что реальные уязвимости хорошо замаскированы и могут выглядеть, например, так.

А иногда, даже если внимательно прочитать весь код, невозможно определить, есть ли уязвимость в кодовой базе. Взгляните на это выражение:

obj[prop] = templateID;

Если obj указывает на объект location (выше шла речь про location.hash) и значение prop равно 'href', это, скорее всего, DOM XSS. Однако узнать это наверняка можно только в процессе выполнения кода. Поскольку любая часть вашего приложения гипотетически может что-то записывать в DOM, весь код должен пройти ручную проверку безопасности, и проверяющий должен быть предельно внимателен, чтобы обнаружить ошибку. Но нельзя полностью полагаться на такой метод проверки, так как человеческий фактор никто не отменял.

Trusted Types


Trusted Types — это новое API для браузеров, которое поможет в корне устранить перечисленные выше проблемы и в том числе защитить веб-приложения от DOM XSS.

API Trusted Types построено на наборе классов (политик), который позволяет блокировать вредоносные инъекции. С ним DOM перестаёт быть уязвимым: её элементы получают значения не через строку, а через специальный объект. Для того, чтобы использовать Trusted Types, нужно инициализировать специальное поле в заголовке HTTP-ответа Content Security Policy (CSP):

Content-Security-Policy: trusted-types *

CSP — это дополнительный уровень безопасности, позволяющий распознавать и устранять определенные типы атак, например Cross Site Scripting (XSS) и атаки внедрения данных. Для того чтобы включить CSP, необходимо настроить сервер так, чтобы в своих ответах он использовал HTTP-заголовок Content-Security-Policy.

И тогда уже никто не сможет записать в элемент вашего DOM произвольную строку:

const templateId = location.hash.match(/tplid=([^;&]*)/)[1];
// typeof templateId == "string"
document.head.innerHTML += templateId // Выдаст ошибку несоответствия типа (TypeError).

Теперь для этого придётся создавать и использовать специальные типизированные объекты. Эти объекты могут быть созданы в вашем приложении только с помощью специально предназначенных для этого функций, которые называются Trusted Type Policies.

Например:

const templatePolicy = TrustedTypes.createPolicy('template', {
  createHTML: (templateId) => {
    const tpl = templateId;
    if (/^[0-9a-z-]$/.test(tpl)) {
      return `<link rel="stylesheet" href="./templates/${tpl}/style.css">`;
    }
    throw new TypeError();
  }
});

const html = templatePolicy.createHTML(location.hash.match(/tplid=([^;&]*)/)[1]);
// html — это экземпляр класса TrustedHTML
document.head.innerHTML += html;

Здесь мы создаём для шаблона template политику, которая проверяет переданный параметр ID и создаёт результирующий HTML. Набор методов create* вызывает соответствующую пользовательскую функцию и оборачивает результат в объект Trusted Type. Например, templatePolicy.createHTML вызывает функцию проверки для templateId и возвращает объект типа TrustedHTML, содержащий (смотрите самый первый пример). При этом браузер позволяет записать в innerHTML (который по идее ожидает обычный HTML) данные объекта типа TrustedHTML.

Помимо класса TrustedHTML, можно использовать классы TrustedScript, TrustedURL и TrustedScriptURL.

Может показаться, что единственным преимуществом нового API стала возможность добавления вот этой проверки:

if (/^[0-9a-z-]$/.test(tpl)) { /* allow the tplId */ }

Действительно, эта строка необходима для защиты от XSS. Но на самом деле всё намного интереснее. С использованием Trusted Types решение проблем уязвимости DOM сводится к грамотному применению политик. Никакой другой код не занимается проверкой данных при передаче от source к sink. Таким образом, только неправильная работа с политиками может привести к проблемам с безопасностью. В нашем примере уже не важно, откуда берётся значение templateId, так как политика всегда первым делом проверяет, прошло ли оно валидацию. А значит, на том участке, за который отвечает определенная политика, любая XSS-атака будет отбита:

Uncaught TypeError: Failed to set the ’innerHTML’ property on ’Element’: This document requires `TrustedHTML` assignment.

Лимитирование политик


Вы заметили значение *, которое мы использовали в заголовке Content-Security-Policy? Оно указывает на то, что приложение может создавать произвольное количество политик, при условии, что каждая из них имеет уникальное имя. Если приложение сможет свободно создавать слишком большое количество политик, это может привести к путанице и реализовать защиту от DOM XSS будет труднее.

Поэтому мы можем ограничить это количество, явно указав список имен политик, которые мы будем использовать. Например:

Content-Security-Policy: trusted-types template

Это гарантирует, что будет создана только одна политика с именем template. Затем мы сможем легко идентифицировать её в исходном коде и проверить её работу. Благодаря этому мы можем быть уверены, что приложение будет защищено от DOM XSS. И будет вам счастье!

На практике современные веб-приложения нуждаются в небольшом количестве политик. Общая рекомендация заключается в создании политик там, где клиентский код генерирует HTML или URL — в загрузчиках сценариев, библиотеках шаблонов HTML или в санитизаторе HTML. Все многочисленные зависимости, которые не связаны с DOM, не нуждаются в использовании политик. И тогда правильное применение Trusted Types гарантированно защитит нас от XSS.

Быстрый старт


Ниже следует предельное короткое введение в API. Со временем появляется всё больше примеров кода, руководств и документации о том, как писать и переписывать приложения с использованием Trusted Types. Мы считаем, что сообщество веб-разработчиков давно готово к тому, чтобы начать экспериментировать с этим.

Чтобы использовать новое API на своем сайте, оно должно быть активировано непосредственно в браузере. Если вы просто хотите запустить его локально, начиная с Chrome 73, фичу можно включить в командной строке:

chrome --enable-blink-features=TrustedDOMTypes

или

chrome --enable-experimental-web-platform-features

Если вы используете Google Chrome, в адресной строке можете ввести chrome://flags/#enable-experimental-web-platform-features. Все эти варианты запуска API делают его активным в течении одной сессии.

Если у вас возникают сбои в работе, в качестве обходного пути используйте --enable-features \u003d BlinkHeapUnifiedGarbageCollection. Подробнее смотрите в обсуждении ошибки 929601.

Существует также полифил, который позволит вам использовать Trusted Types во многих других браузерах.

Обсудить проект можно здесь. Он также доступен на GitHub.

Теги:
Хабы:
Всего голосов 8: ↑7 и ↓1+6
Комментарии10

Публикации

Информация

Сайт
vdsina.ru
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
Mikhail

Истории