Защита от HTML-инъекций#
HTML-инъекция возникает, когда данные вставляются в HTML без обработки и браузер интерпретирует их как часть разметки. Это может изменить структуру страницы, нарушить работу интерфейса или привести к выполнению JavaScript-кода в браузере пользователя.
Перед вставкой в HTML необходимо обрабатывать данные, которые могли быть введены пользователем или получены из внешнего источника: значения полей, комментарии, описания, заголовки, подписи и другие текстовые данные. Способ обработки зависит от того, должны ли эти данные отображаться как обычный текст или как HTML-разметка.
Зачем нужна защита#
Защита от HTML-инъекций нужна, чтобы пользовательский ввод не мог изменить страницу или выполнить JavaScript-код в браузере пользователя системы. Такая проблема может возникнуть, если данные из поля, комментария, описания или внешнего источника вставляются в HTML без обработки.
Особенно опасны данные, которые сохраняются в БД и затем отображаются другим пользователям. В этом случае вредоносный HTML-фрагмент или JavaScript-код может срабатывать при открытии карточки, списка, описания или другого интерфейса, где используется сохраненное значение.
Варианты защиты#
В системе используются два основных способа защиты от HTML-инъекций.
Экранирование текстовых данных применяется, когда данные нужно вставить в HTML как обычный текст.
HTML-теги и JavaScript в таких данных не выполняются, а отображаются как текст.
Для экранирования используется
ru.bitec.app.btk.utils.HTMLUtils.escape.При работе с XML используйте
ru.bitec.app.gtk.xml.SafeXMLиru.bitec.app.gtk.xml.XMLFactoryвместоscala.xml.XML.
Санитизация пользовательской HTML-разметки применяется, когда пользователю разрешено вводить HTML-разметку и ее нужно сохранить.
Допустимая разметка остается, а JavaScript, небезопасные элементы и ссылки на ресурсы, доступные не по HTTPS, удаляются.
Для санитизации используется
ru.bitec.app.btk.utils.HTMLUtils.sanitize.
Если поле предназначено для обычного текста, пользовательский ввод не должен интерпретироваться как HTML. Если поле предназначено для HTML-разметки, его нужно очищать от вредоносного содержимого через HTMLUtils.sanitize.
Экранирование текстовых данных#
Экранирование используется, когда прикладной код формирует HTML и вставляет в него обычные текстовые данные: имя пользователя, заголовок, комментарий, описание, подпись или другое значение из поля.
Экранирование нужно, чтобы текстовые данные не изменяли HTML-разметку, в которую они вставляются. После экранирования символы HTML отображаются как часть текста и не воспринимаются браузером как теги или скрипты.
Например, система может формировать HTML-блок с именем пользователя. Имя должно отображаться как текст. Оно не должно изменять HTML-разметку блока, даже если в поле сохранены символы <, >, закрывающие теги или JavaScript-код.
Для экранирования можно использовать HTMLUtils.escape.
При работе с XML используйте ru.bitec.app.gtk.xml.SafeXML и ru.bitec.app.gtk.xml.XMLFactory вместо scala.xml.XML.
Исходные данные
В поле имени сохранена строка с HTML-фрагментом:
val sName = ropUser.get(_.sName) // интересное имя: "Ваня</h1><script>alert('XSS')</script><h1>"
По смыслу это значение должно быть обычным текстом имени. Но если вставить его в HTML без обработки, часть строки станет разметкой.
Небезопасный вариант
val sHTML = s"""
<h1>$sName</h1>
"""
После подстановки получится HTML, в котором значение из поля имени закрывает исходный тег <h1> и добавляет тег <script>:
<h1>Ваня</h1><script>alert('XSS')</script><h1></h1>
В этом примере произошла HTML-инъекция: строка sName, которая должна была отображаться как обычный текст имени, стала частью HTML-разметки.
Безопасный вариант через HTMLUtils.escape
Перед вставкой текстовых данных в HTML-строку передайте их в HTMLUtils.escape.
val sHTML_correct = s"""
<h1>${HTMLUtils.escape(sName)}</h1>
"""
После экранирования специальные HTML-символы преобразуются в текстовое представление:
<h1>Ваня</h1><script>alert('XSS')</script><h1></h1>
В результате пользователь видит введенное имя как текст, а браузер не выполняет его как HTML или JavaScript.
Формирование HTML с динамическими данными#
При формировании HTML в WebHtml можно использовать интерполятор html"...". Обычные подставляемые значения автоматически экранируются.
val sName = ...
val content = html"""
<h1>${sName}</h1>
"""
Если динамическое значение должно содержать HTML-разметку, используйте ToSanitize:
import ru.bitec.app.btk.utils.HTMLUtils.ToSanitize
val content = html"""
<section>${ToSanitize(sDescription)}</section>
"""
Если HTML получен из доверенного источника или его безопасность уже обеспечена вызывающим кодом, значение можно передать как Html:
import ru.bitec.app.btk.utils.HTMLUtils.Html
val content = html"""
<section>${Html(sTrustedHtml)}</section>
"""
Html вставляет значение без дополнительного экранирования или санитизации.
Санитизация пользовательской HTML-разметки#
Санитизация используется, когда пользователю разрешено вводить HTML-разметку и эту разметку нужно сохранить. В этом случае обычное экранирование не подходит: оно превратит HTML-разметку в текст.
Для пользовательской HTML-разметки используйте HTMLUtils.sanitize. Метод очищает HTML от опасного содержимого и оставляет допустимую разметку.
HTMLUtils.sanitize:
удаляет JavaScript;
удаляет небезопасные HTML-элементы;
удаляет или очищает ссылки на ресурсы, доступные не по HTTPS.
Пример очистки HTML
/* HTML, введенный пользователем:
<img src="https://hips.hearstapps.com/clv.h-cdn.co/assets/16/18/gettyimages-586890581.jpg?crop=0.668xw:1.00xh;0.219xw,0&resize=980:*">
<img src="x" onError=alert('XSS')/>
*/
val sHTML = ropDoc.get(_.sDescription)
/* Безопасный для использования HTML:
<img src="https://hips.hearstapps.com/clv.h-cdn.co/assets/16/18/gettyimages-586890581.jpg?crop=0.668xw:1.00xh;0.219xw,0&resize=980:*">
<img src="x">
*/
val sHTML_correct = HTMLUtils.sanitize(sHTML)
В результате из HTML удаляется небезопасный обработчик события onError, а допустимые элементы остаются доступными для отображения.
Очистка HTML перед сохранением
Если HTML-значение хранится в БД и затем используется в интерфейсе, санитизацию можно выполнить при установке значения. В этом случае в БД сохраняется уже очищенный HTML.
override def setsDescription(rop: ApiRop, value: NString): Unit = {
super.setsDescription(rop, HTMLUtils.sanitize(value))
}
Такой вариант подходит для атрибутов, в которых по смыслу должна храниться пользовательская HTML-разметка. Если атрибут предназначен для обычного текста, не используйте санитизацию как замену экранированию: текстовые значения нужно безопасно выводить как текст.
Защита при использовании HtmlFrame#
HtmlFrame не выполняет автоматическую санитизацию результата getHtmlText. Если HTML формируется из данных базы данных, пользовательского ввода или других динамических значений, эти значения необходимо экранировать или санитизировать до включения в итоговый HTML.
Для WebHtml также предусмотрены дополнительные ограничения: sandbox для содержимого iframe и проверка схем URL. Эти механизмы не заменяют экранирование или санитизацию динамических данных.
Sandbox#
Содержимое HtmlFrame отображается в iframe с атрибутом sandbox. Набор разрешений определяется методом getSandBoxFlags отображения WebHtml.
При построении HTML через buildContentAsHtml по умолчанию используются разрешения:
allow-same-origin— позволяет содержимомуiframeработать в исходном origin, в том числе использовать cookies иlocalStorage;allow-scripts— разрешает выполнение JavaScript внутриiframe.
Наличие sandbox не заменяет обработку динамических данных: при такой конфигурации выполнение JavaScript внутри фрейма разрешено.
Если JavaScript не требуется конкретному отображению, разрешение AllowScripts можно исключить из набора sandbox-флагов.
Пустой набор флагов оставляет iframe в sandbox без дополнительных разрешений:
override def getSandBoxFlags = Seq.empty
Проверка URL#
При загрузке страницы в HtmlFrame через buildContentAsLink можно ограничить схемы URL, которые разрешено передавать во фрейм. Это позволяет отклонять ссылки с непредусмотренными схемами до их загрузки.
Для включения проверки переопределите:
override def checkLinkScheme: Boolean = true
По умолчанию checkLinkScheme имеет значение false.
При включенной проверке:
относительные ссылки без схемы разрешаются;
для абсолютного URL проверяется, входит ли его схема в список разрешенных;
по умолчанию разрешены
httpиhttps;некорректный URL или запрещенная схема приводят к ошибке.
Если отображению требуется дополнительная схема, ее можно указать в allowedSchemasOnLink:
override def allowedSchemasOnLink: Seq[String] =
Seq("http", "https", "jexl")
Дополнительные схемы следует разрешать только для сценариев, в которых они необходимы.
Проверка checkLinkScheme применяется только к URL, передаваемым через buildContentAsLink. Она не проверяет автоматически ссылки, содержащиеся внутри HTML-разметки.
Рекомендации для проектирования#
При проектировании полей и интерфейсов заранее определите, что должно храниться и отображаться:
обычный текст без HTML-разметки;
пользовательская HTML-разметка, для которой разрешено ограниченное форматирование.
В требованиях к новым полям, которые отображаются в HTML, необходимо явно указывать, допускается ли пользовательская HTML-разметка. От этого зависит способ обработки данных: текстовые данные нужно экранировать, а пользовательскую HTML-разметку — очищать через HTMLUtils.sanitize.
Если поле предназначено для обычного текста, пользовательский ввод не должен интерпретироваться как HTML. Если поле предназначено для HTML-разметки, необходимо предусмотреть очистку через HTMLUtils.sanitize до сохранения или перед отображением.
Правила безопасного использования#
Не вставляйте пользовательские данные в HTML без обработки. Даже если поле обычно содержит простой текст, оно может быть заполнено строкой с HTML-разметкой или JavaScript-кодом.
Не добавляйте необработанные пользовательские данные в HTML после вызова HTMLUtils.sanitize. Если после очистки строка снова изменяется и в нее добавляется пользовательский ввод, этот ввод также должен быть обработан.
Не используйте HTMLUtils.sanitize как универсальную замену безопасному выводу. Для текстовых значений используйте экранирование, а для HTML-разметки — санитизацию.
Примеры на этой странице относятся к вставке данных в HTML и обработке пользовательской HTML-разметки. Если данные используются внутри JavaScript, CSS, URL или другого контекста, способ безопасной обработки нужно определять отдельно для этого контекста.