Защита от 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>Ваня</h1><script>alert('XSS')</script><h1></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&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 или другого контекста, способ безопасной обработки нужно определять отдельно для этого контекста.