Защита от HTML-инъекций#

HTML-инъекция возникает, когда данные вставляются в HTML без обработки и браузер интерпретирует их как часть разметки. Это может изменить структуру страницы, нарушить работу интерфейса или привести к выполнению JavaScript-кода в браузере пользователя.

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

Зачем нужна защита#

Защита от HTML-инъекций нужна, чтобы пользовательский ввод не мог изменить страницу или выполнить JavaScript-код в браузере пользователя системы. Такая проблема может возникнуть, если данные из поля, комментария, описания или внешнего источника вставляются в HTML без обработки.

Особенно опасны данные, которые сохраняются в БД и затем отображаются другим пользователям. В этом случае вредоносный HTML-фрагмент или JavaScript-код может срабатывать при открытии карточки, списка, описания или другого интерфейса, где используется сохраненное значение.

Варианты защиты#

В системе используются два основных способа защиты от HTML-инъекций.

  • Экранирование текстовых данных применяется, когда данные нужно вставить в HTML как обычный текст.

    • HTML-теги и JavaScript в таких данных не выполняются, а отображаются как текст.

    • Для экранирования используется ru.bitec.app.btk.utils.HTMLUtils.escape.

    • При формировании HTML через XML-литералы Scala можно использовать 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. Если HTML формируется через XML-литералы Scala, можно использовать 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>Ваня&lt;/h1&gt;&lt;script&gt;alert('XSS')&lt;/script&gt;&lt;h1&gt;</h1>

В результате пользователь видит введенное имя как текст, а браузер не выполняет его как HTML или JavaScript.

Формирование HTML через scala.XML

Если HTML формируется через XML-литералы Scala, значения, переданные как содержимое XML-узлов, экранируются автоматически.

val HTML = <h1>{sName}</h1>.toString

В этом варианте scala.XML сам преобразует специальные символы в безопасное текстовое представление.

Санитизация пользовательской 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&amp;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-разметка. Если атрибут предназначен для обычного текста, не используйте санитизацию как замену экранированию: текстовые значения нужно безопасно выводить как текст.

Рекомендации для проектирования#

При проектировании полей и интерфейсов заранее определите, что должно храниться и отображаться:

  • обычный текст без 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 или другого контекста, способ безопасной обработки нужно определять отдельно для этого контекста.