A
Android
Original poster

Межсайтовый скриптинг (XSS) является наиболее распространенной уязвимостью, затрагивающей веб-приложения. Мы видим, что это отражено как в наших собственных данных, так и во всей отрасли. Практика показывает, что поддержка приложения без XSS по-прежнему является сложной задачей, особенно если приложение является сложным. Хотя решения для предотвращения XSS на стороне сервера хорошо известны, межсайтовый скриптинг на основе DOM (DOM XSS) является растущей проблемой. Например, в программе вознаграждений за уязвимости Google DOM XSS уже является наиболее распространенным вариантом.
Это почему? Мы думаем, что это вызвано двумя отдельными проблемами:
XSS легко ввести
DOM XSS возникает, когда один из приемников инъекций в DOM или других браузерных API вызывается с данными, контролируемыми пользователем. Например, рассмотрим этот фрагмент, который намеревается загрузить таблицу стилей для данного шаблона пользовательского интерфейса, который использует приложение:
const templateId = location.hash.match(/tplid=([^;&]*)/)[1];
// ...
document.head.innerHTML += `<link rel="stylesheet" href="./templates/${templateId}/style.css">`
Этот код вводит DOM XSS, связывая контролируемый злоумышленником источник (location.hash) с приемником внедрения (innerHTML). Злоумышленник может воспользоваться этой ошибкой, обманув свою жертву, посетив следующий URL:
"><img src=x onerror=alert(1)>
Это легко сделать в коде, особенно если код часто меняется. Например, может быть, однажды templateId был сгенерирован и проверен на сервере, поэтому раньше это значение было достоверным? При присваивании innerHTML все, что мы знаем, это то, что значение является строкой, но следует ли ему доверять? Откуда это на самом деле?
Кроме того, проблема не ограничивается только innerHTML. В типичной среде браузера существует более 60 функций или свойств приемника, которые требуют этой осторожности. DOM API по умолчанию небезопасен и требует особого подхода для предотвращения XSS.
XSS сложно обнаружить
Приведенный выше код является лишь примером, поэтому увидеть ошибку легко. На практике источники и приемники часто доступны в совершенно разных частях приложения. Данные из источника передаются и в конечном итоге достигают приемника. Есть некоторые функции, которые дезинфицируют и проверяют данные. Но была ли названа правильная функция?
Глядя только на исходный код, трудно понять, представляет ли он DOM XSS. Недостаточно выполнить поиск файлов .js для чувствительных шаблонов. С одной стороны, чувствительные функции часто используются в различных оболочках, и реальные уязвимости выглядят примерно так.
Иногда даже невозможно определить, уязвима ли кодовая база, только взглянув на нее.
obj [prop] = templateID
Если obj указывает на объект Location и значение prop равно "href", это, скорее всего, DOM XSS, но это можно узнать только при выполнении кода. Поскольку любая часть вашего приложения может потенциально попасть в приемник DOM, весь код должен пройти проверку безопасности вручную, и рецензент должен быть очень осторожен, чтобы обнаружить ошибку. Это вряд ли произойдет.
Trusted Types - это новый API-интерфейс браузера, который может помочь устранить вышеуказанные проблемы в качестве основной причины - и на практике помочь уничтожить DOM XSS.
Доверенные типы позволяют блокировать опасные инъекционные приемники - по умолчанию они перестают быть небезопасными и не могут быть вызваны со строками. Вы можете включить это применение, установив специальное значение в заголовке HTTP-ответа Content Security Policy:
Политика безопасности контента: доверенные типы *
Затем в документе вы больше не можете использовать строки с инъекционными приемниками:
const templateId = location.hash.match (/ tplid = ([^; &] *) /) [1];
// typeof templateId == "строка"
document.head.innerHTML + = templateId //
Выдает ошибку TypeError.
Для взаимодействия с этими функциями вы создаете специальные типизированные объекты - Trusted Types. Эти объекты могут быть созданы только определенными функциями в вашем приложении, которые называются политиками доверенного типа. Примерный код «исправлено» с доверенными типами будет выглядеть так:
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 ">`;
}
бросить новый TypeError ();
}
});
const html = templatePolicy.createHTML (location.hash.match (/ tplid = ([^; &] *) /) [1]);
// html instanceof TrustedHTML
document.head.innerHTML + = html;
Здесь мы создаем шаблонную политику, которая проверяет переданный параметр ID шаблона и создает результирующий HTML. Функция политики create * вызывает функцию в соответствующую пользовательскую функцию и оборачивает результат в объект Trusted Type. В этом случае templatePolicy.createHTML вызывает предоставленную функцию проверки templateId и возвращает TrustedHTML с фрагментом <link ...>. Браузер позволяет использовать TrustedHTML с приемником инъекций, который ожидает HTML-подобный innerHTML.
Может показаться, что единственным улучшением является добавление следующей проверки:
if (/^[0-9a-z-]$/.test(tpl)) {/ * разрешить tplId * /}
Действительно, эта строка необходима для исправления XSS. Тем не менее, реальные изменения более глубокие. При применении принудительных типов единственным кодом, который может представлять уязвимость DOM XSS, является код политик. Никакой другой код не может выдавать значение, которое принимают функции приемника. Таким образом, только политики должны быть проверены на наличие проблем безопасности. В нашем примере не имеет значения, откуда берется значение templateId, так как политика сначала проверяет, правильно ли оно проверено - выходные данные этой конкретной политики не представляют XSS.
Ограничительные политики
Вы заметили значение *, которое мы использовали в заголовке Content-Security-Policy? Это указывает на то, что приложение может создавать произвольное количество политик, при условии, что каждая из них имеет уникальное имя. Если приложения могут свободно создавать большое количество политик, предотвращение DOM XSS на практике будет затруднено.
Однако мы можем дополнительно ограничить это, указав белый список имен политик, например, так:
Политика безопасности контента: шаблон доверенных типов
Это гарантирует, что может быть создана только одна политика с шаблоном имени. Затем эту политику легко идентифицировать в исходном коде, и ее можно эффективно проанализировать. Благодаря этому мы можем быть уверены, что приложение свободно от DOM XSS. Хорошая работа!
На практике современные веб-приложения нуждаются в небольшом количестве политик. Основное правило заключается в создании политики, в которой код на стороне клиента генерирует HTML или URL-адреса - в загрузчиках сценариев, библиотеках шаблонов HTML или средствах очистки HTML. Все многочисленные зависимости, которые не взаимодействуют с DOM, не нуждаются в политиках. Надежные типы гарантируют, что они не могут быть причиной XSS.
Начать
Это всего лишь краткий обзор API. Мы работаем над предоставлением большего количества примеров кода, руководств и документации о том, как переносить приложения в доверенные типы. Мы считаем, что это подходящий момент для сообщества веб-разработчиков, чтобы начать экспериментировать с ним.
Чтобы получить это новое поведение на своем сайте, вам необходимо зарегистрироваться в ознакомительной версии «Надежные типы» (в Chrome 73–76). Если вы просто хотите попробовать его локально, начиная с Chrome 73, эксперимент можно включить в командной строке:
chrome --enable-blink-features = TrustedDOMTypes
или же
chrome --enable-экспериментальный-веб-платформа-функции
Кроме того, посетите chrome: // flags / # enable-эксперимент-web-platform-features и включите эту функцию. Все эти параметры включают глобальную функцию в Chrome для текущего сеанса.
Надеемся эта информация была вам полезна!