Предотвращение DOM XSS-уязвимостей при помощи Trusted Types
Уменьшите поверхность для атак DOM XSS в вашем приложении.
Почему это важно? #
Межсайтовый скриптинг на основе DOM (DOM XSS) — одна из наиболее распространенных уязвимостей веб-безопасности, и допустить ее появление в приложении очень легко. API Trusted Types («доверенные типы») предоставляет инструменты для разработки, проверки безопасности и сопровождения приложений, свободных от уязвимостей DOM XSS, благодаря тому, что делает «опасные» функции браузерных API безопасными по умолчанию. Доверенные типы поддерживаются в Chrome 83, а для других браузеров доступна polyfill-библиотека. Актуальную информацию о поддержке API в браузерах смотрите в соответствующей статье.
Предыстория #
Вот уже много лет DOM XSS является одной из наиболее распространенных и опасных уязвимостей в области веб-безопасности.
Существует два отдельных класса межсайтового скриптинга. Некоторые XSS-уязвимости появляются вследствие того, что код, выполняющийся на стороне сервера, генерирует HTML-код страниц сайта небезопасным способом. Другие уязвимости возникают на стороне клиента, когда JavaScript-код вызывает небезопасные функции и передает им контент, поступивший от пользователя.
Чтобы предотвратить XSS на стороне сервера, не создавайте HTML путем конкатенации строк; вместо этого используйте безопасные библиотеки-шаблонизаторы с автоматическим контекстным экранированием строк. В качестве дополнительной меры защиты от ошибок (которые будут неизбежно возникать) используйте CSP-политику на основе одноразовых номеров.
Теперь браузер может также помогать предотвращать XSS-атаки и на стороне клиента (также известные как атаки на основе DOM) благодаря доверенным типам.
Введение в API #
Доверенные типы ограничивают использование небезопасных функций-приемников, перечисленных ниже. Возможно, вам уже известны некоторые из них, поскольку разработчики браузеров и веб-фреймворков уже предостерегают веб-разработчиков от использования этих функций из-за их небезопасности.
Управление скриптами:
<script src>
и установка текстового содержимого элементов<script>
.Генерация HTML на основе строк:
innerHTML
,outerHTML
,insertAdjacentHTML
,<iframe> srcdoc
,document.write
,document.writeln
иDOMParser.parseFromString
Выполнение содержимого, использующего плагины:
<embed src>
,<object data>
and<object codebase>
Компиляция JavaScript-кода во время выполнения:
eval
,setTimeout
,setInterval
,new Function()
При использовании доверенных типов данные, передаваемые в перечисленные выше функции-приемники, должны проходить предварительную обработку. Использовать обычную строку не получится, так как браузер не знает, можно ли ей доверять.
Неправильно
anElement.innerHTML = location.href;
Чтобы обозначить, что данные были обработаны безопасным путем, создайте специальный объект: доверенный тип.
Правильно
anElement.innerHTML = aTrustedHTML;
Доверенные типы значительно сокращают поверхность для атак DOM XSS в вашем приложении. Это упрощает проверку кода на предмет уязвимостей, а также позволяет контролировать соблюдение проверок безопасности типов, выполняемых на этапе компиляции, линтинга или объединения в бандлы, непосредственно во время выполнения кода в браузере.
Как использовать доверенные типы #
Подготовьтесь к получению отчетов о нарушении CSP-политики #
Вы можете развернуть средство сбора отчетов (например, go-csp-collector с открытым исходным кодом) или использовать одно из коммерческих решений. Кроме того, отслеживать нарушения можно в самом браузере:
window.addEventListener('securitypolicyviolation',
console.error.bind(console));
Добавьте заголовок CSP, включающий режим отправки отчетов #
Добавьте к документам, для которых хотите использовать доверенные типы, следующий заголовок HTTP-ответа.
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
Теперь отчеты обо всех нарушениях будут отправляться на //my-csp-endpoint.example
, но это не помешает работе сайта. В следующем разделе рассказывается, как работает //my-csp-endpoint.example
.
Идентифицируйте нарушения при использовании доверенных типов #
Теперь каждый раз, когда браузер обнаруживает нарушение, связанное с доверенными типам, он будет сообщать об этом по адресу, указанному в качестве report-uri
. Например, если приложение попытается передать строку в innerHTML
, то будет отправлен следующий отчет:
{
"csp-report": {
"document-uri": "https://my.url.example",
"violated-directive": "require-trusted-types-for",
"disposition": "report",
"blocked-uri": "trusted-types-sink",
"line-number": 39,
"column-number": 12,
"source-file": "https://my.url.example/script.js",
"status-code": 0,
"script-sample": "Element innerHTML <img src=x"
}
}
В отчете говорится, что в строке 39 файла https://my.url.example/script.js
произошел вызов innerHTML
со строкой, начинающейся на <img src=x
. Эта информация должна помочь выявить те части кода, которые потенциально содержат уязвимости DOM XSS и нуждаются в изменении.
Исправьте нарушения #
Исправить нарушение, связанное с доверенными типами, можно различными способами. Вы можете удалить код, содержащий нарушение, воспользоваться библиотекой, создать политику доверенного типа или, в качестве крайней меры, создать политику по умолчанию.
Переработка кода, содержащего нарушение #
Возможно, что функциональность, являющаяся причиной нарушения, больше не нужна или ее можно реализовать более современным способом без использования уязвимых функций.
Неправильно
el.innerHTML = '<img src=xyz.jpg>';
Правильно
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
Использование библиотеки #
Некоторые библиотеки уже поддерживают генерацию доверенных типов, которые можно передавать функциям-приемникам. Например, при помощи DOMPurify можно санитизировать фрагменты HTML-кода, удаляя полезные нагрузки для эксплуатации XSS.
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});
DOMPurify поддерживает доверенные типы и возвращает санитизированный HTML-код в виде объекта TrustedHTML
, благодаря чему в браузере не будут возникать сообщения о нарушениях.
Создание политики доверенного типа #
В некоторых случаях удалить функциональность, являющуюся причиной проблемы, невозможно, а готовой библиотеки для санитизации значения и создания доверенного типа не существует. В таких случаях объект доверенного типа необходимо создать самостоятельно.
Чтобы это сделать, для сначала создайте политику. Политики — это фабричные методы для доверенных типов, которые применяют к входным данным определенные правила для обеспечения безопасности:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
createHTML: string => string.replace(/\</g, '<')
});
}
Этот код создает политику под названием myEscapePolicy
которая может генерировать объекты TrustedHTML
при помощи функции createHTML()
. В соответствии с заданными правилами, к символам <
будет применяться HTML-экранирование, чтобы предотвратить создание новых HTML-элементов.
Используйте политику следующим образом:
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML); // true
el.innerHTML = escaped; // '<img src=x onerror=alert(1)>'
Использование политики по умолчанию #
Иногда невозможно изменить код, являющийся причиной проблемы, например если он относится к сторонней библиотеке, загружаемой из CDN. В этом случае используйте политику по умолчанию:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
trustedTypes.createPolicy('default', {
createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
});
}
Политика default
используется для всех строк, попадающих в приемник, который принимает только доверенные типы.
Активируйте политику безопасности контента #
Как только сообщения об ошибках в вашем приложении устранены, вы можете включить обязательное использование доверенных типов:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
Готово! Теперь, каким бы сложным ни было ваше веб-приложение, единственный возможный источник уязвимостей DOM XSS — это код ваших политик (и вы можете еще сильнее ограничить их использование посредством ограничения создания политик).