Защита от 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-символы преобразуются в безопасное текстовое представление, поэтому браузер отображает введенную строку как текст, а не выполняет ее как разметку или JavaScript.

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

Пример экранирования текстовых данных

val sName = ropUser.get(_.sName) // интересное имя: "Ваня</h1><script>alert('XSS')</script><h1>"

/* Произошла HTML-инъекция:
  <h1>Ваня</h1><script>alert('XSS')</script><h1></h1>
*/
val sHTML = s"""
  <h1>$sName</h1>
  """

/* Значение будет выведено как обычный текст:
  <h1>Ваня&lt;/h1&gt;&lt;script&gt;alert('XSS')&lt;/script&gt;&lt;h1&gt;</h1>
*/
val sHTML_correct = s"""
  <h1>${HTMLUtils.escape(sName)}</h1>
  """

/* scala.XML сама экранирует значения. Значение будет выведено как обычный текст:
  <h1>Ваня&lt;/h1&gt;&lt;script&gt;alert('XSS')&lt;/script&gt;&lt;h1&gt;</h1>
*/
val HTML = <h1>{sName}</h1>.toString

В первом варианте значение sName вставляется без обработки, поэтому браузер может интерпретировать часть строки как HTML. Во втором варианте значение экранируется через HTMLUtils.escape. В третьем варианте HTML формируется через scala.XML, который автоматически экранирует значения внутри 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 или другого контекста, способ безопасной обработки нужно определять отдельно для этого контекста.