Новый API Sanitizer направлен на создание надежного процессора для безопасной вставки произвольных строк на страницу.
Приложения постоянно имеют дело с ненадежными строками, но безопасная визуализация этого контента как части HTML-документа может быть сложной. Без достаточной осторожности легко случайно создать возможности для межсайтового скриптинга (XSS), которые могут использовать злоумышленники.
Чтобы снизить этот риск, новое предложение API Sanitizer направлено на создание надежного процессора для безопасной вставки произвольных строк на страницу. В этой статье представлен API и объясняется его использование.
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())
Экранирование пользовательского ввода
При вставке пользовательского ввода, строк запросов, содержимого cookie и т. д. в DOM строки должны быть правильно экранированы. Особое внимание следует уделять манипуляциям с DOM через .innerHTML
, где неэкранированные строки являются типичным источником XSS.
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.innerHTML = user_input
Если вы экранируете специальные символы HTML во входной строке выше или расширяете ее с помощью .textContent
, alert(0)
не будет выполнен. Однако, поскольку <em>
добавленный пользователем, также расширяется как строка, как таковая, этот метод не может быть использован для сохранения оформления текста в HTML.
Лучшее, что можно сделать в данном случае, — это не побег , а дезинфекция .
Очистка пользовательского ввода
Разница между побегом и дезинфекцией
Экранирование подразумевает замену специальных символов HTML на сущности HTML .
Очистка подразумевает удаление семантически вредных частей (например, выполнения скрипта) из строк HTML.
Пример
В предыдущем примере <img onerror>
вызывает выполнение обработчика ошибок, но если бы обработчик onerror
был удален, его можно было бы безопасно развернуть в DOM, оставив <em>
нетронутым.
// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">`
Для корректной очистки необходимо проанализировать входную строку как HTML, исключить теги и атрибуты, которые считаются вредоносными, и оставить безвредные.
Предлагаемая спецификация API Sanitizer направлена на предоставление такой обработки в качестве стандартного API для браузеров.
API дезинфицирующего средства
API Sanitizer используется следующим образом:
const $div = document.querySelector('div')
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.setHTML(user_input, { sanitizer: new Sanitizer() }) // <div><em>hello world</em><img src=""></div>
Однако { sanitizer: new Sanitizer() }
— это аргумент по умолчанию. Так что это может быть как ниже.
$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>
Стоит отметить, что setHTML()
определен в Element
. Поскольку это метод Element
, контекст для анализа не требует пояснений (в данном случае <div>
), анализ выполняется один раз внутри, а результат напрямую расширяется в DOM.
Чтобы получить результат очистки в виде строки, можно использовать .innerHTML
из результатов setHTML()
.
const $div = document.createElement('div')
$div.setHTML(user_input)
$div.innerHTML // <em>hello world</em><img src="">
Настроить через конфигурацию
API-интерфейс Sanitizer по умолчанию настроен на удаление строк, которые могут вызвать выполнение скрипта. Однако вы также можете добавить собственные настройки в процесс очистки через объект конфигурации.
const config = {
allowElements: [],
blockElements: [],
dropElements: [],
allowAttributes: {},
dropAttributes: {},
allowCustomElements: true,
allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)
Следующие параметры определяют, как результат очистки должен обрабатывать указанный элемент.
allowElements
: Имена элементов, которые должен сохранить дезинфицирующий агент.
blockElements
: Имена элементов, которые санитайзер должен удалить, сохранив при этом их дочерние элементы.
dropElements
: Имена элементов, которые должен удалить санитайзер, вместе с их дочерними элементами.
const str = `hello <b><i>world</i></b>`
$div.setHTML(str)
// <div>hello <b><i>world</i></b></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: [ "b" ]}) })
// <div>hello <b>world</b></div>
$div.setHTML(str, { sanitizer: new Sanitizer({blockElements: [ "b" ]}) })
// <div>hello <i>world</i></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: []}) })
// <div>hello world</div>
Вы также можете контролировать, будет ли санитайзер разрешать или запрещать определенные атрибуты с помощью следующих параметров:
-
allowAttributes
-
dropAttributes
Свойства allowAttributes
и dropAttributes
ожидают списки соответствия атрибутов — объекты, ключами которых являются имена атрибутов, а значениями — списки целевых элементов или подстановочный знак *
.
const str = `<span id=foo class=bar style="color: red">hello</span>`
$div.setHTML(str)
// <div><span id="foo" class="bar" style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["span"]}}) })
// <div><span style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["p"]}}) })
// <div><span>hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["*"]}}) })
// <div><span style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({dropAttributes: {"id": ["span"]}}) })
// <div><span class="bar" style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {}}) })
// <div>hello</div>
allowCustomElements
— это опция, позволяющая разрешать или запрещать пользовательские элементы. Если они разрешены, другие конфигурации для элементов и атрибутов по-прежнему применяются.
const str = `<custom-elem>hello</custom-elem>`
$div.setHTML(str)
// <div></div>
const sanitizer = new Sanitizer({
allowCustomElements: true,
allowElements: ["div", "custom-elem"]
})
$div.setHTML(str, { sanitizer })
// <div><custom-elem>hello</custom-elem></div>
API поверхность
Сравнение с DomPurify
DOMPurify — известная библиотека, предлагающая функциональность очистки. Главное отличие между API Sanitizer и DOMPurify заключается в том, что DOMPurify возвращает результат очистки в виде строки, которую нужно записать в элемент DOM через .innerHTML
.
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
const sanitized = DOMPurify.sanitize(user_input)
$div.innerHTML = sanitized
// `<em>hello world</em><img src="">`
DOMPurify может служить запасным вариантом, если в браузере не реализован API Sanitizer.
Реализация DOMPurify имеет несколько недостатков. Если возвращается строка, то входная строка анализируется дважды, DOMPurify и .innerHTML
. Этот двойной анализ тратит время обработки, но также может привести к интересным уязвимостям, вызванным случаями, когда результат второго анализа отличается от первого.
HTML также требует контекста для анализа. Например, <td>
имеет смысл в <table>
, но не в <div>
. Поскольку DOMPurify.sanitize()
принимает только строку в качестве аргумента, контекст анализа должен быть угадываемым.
API Sanitizer улучшает подход DOMPurify и призван устранить необходимость двойного синтаксического анализа и прояснить контекст синтаксического анализа.
Статус API и поддержка браузеров
API Sanitizer находится на стадии обсуждения в процессе стандартизации, а Chrome находится в процессе его внедрения.
Шаг | Статус |
---|---|
1. Создать пояснитель | Полный |
2. Создать проект спецификации | Полный |
3. Соберите отзывы и доработайте дизайн | Полный |
4. Пробная версия Chrome | Полный |
5. Запуск | Намерение отгрузить на M105 |
Mozilla: считает это предложение достойным прототипирования и активно внедряет его .
WebKit: см. ответ в списке рассылки WebKit .
Как включить API Sanitizer
Browser Support
Включение через about://flags
или опцию CLI
Хром
Chrome находится в процессе внедрения API Sanitizer. В Chrome 93 или более поздней версии вы можете опробовать поведение, включив флаг about://flags/#enable-experimental-web-platform-features
. В более ранних версиях Chrome Canary и Dev channel вы можете включить его через --enable-blink-features=SanitizerAPI
и опробовать его прямо сейчас. Ознакомьтесь с инструкциями по запуску Chrome с флагами .
Firefox
Firefox также реализует API Sanitizer в качестве экспериментальной функции. Чтобы включить его, установите флаг dom.security.sanitizer.enabled
в true
в about:config
.
Обнаружение особенностей
if (window.Sanitizer) {
// Sanitizer API is enabled
}
Обратная связь
Если вы попробуете этот API и у вас есть какие-то отзывы, мы будем рады их услышать. Поделитесь своими мыслями о проблемах Sanitizer API GitHub и обсудите их с авторами спецификаций и людьми, заинтересованными в этом API.
Если вы обнаружите какие-либо ошибки или неожиданное поведение в реализации Chrome, отправьте сообщение об ошибке, чтобы сообщить об этом . Выберите компоненты Blink>SecurityFeature>SanitizerAPI
и поделитесь подробностями, чтобы помочь разработчикам отслеживать проблему.
Демо
Чтобы увидеть API Sanitizer в действии, посетите Sanitizer API Playground Майка Уэста :
Ссылки
- Спецификация API HTML Sanitizer
- WICG/sanitizer-api репозиторий
- Часто задаваемые вопросы по API дезинфицирующего средства
- Справочная документация HTML Sanitizer API на MDN
Фото Towfiqu barbhuiya на Unsplash .