Экранирование HTML

Экранирование HTML — важнейший механизм защиты и корректного отображения данных в интерфейсах автодополнения. В контексте Awesomplete экранирование предотвращает интерпретацию пользовательских данных как HTML-разметки и защищает приложение от нежелательного внедрения кода.

При работе с подсказками источником данных часто выступают:

  • пользовательский ввод;
  • результаты AJAX-запросов;
  • данные из базы данных;
  • внешние API;
  • содержимое файлов конфигурации.

Если такие данные выводятся без предварительной обработки, браузер может интерпретировать специальные символы как элементы HTML-документа.

Например, строка:

<script>alert('XSS')</script>

может быть воспринята не как обычный текст, а как исполняемый скрипт.

Awesomplete по умолчанию ориентирован на безопасный вывод текстовых значений, однако при глубокой кастомизации компонента ответственность за безопасность часто переходит на разработчика.


Специальные HTML-символы

Наиболее распространённые символы, требующие экранирования:

Символ HTML-представление
< <
> >
& &

Исходная строка:

<div>Текст</div>

После экранирования превращается в:

&lt;div&gt;Текст&lt;/div&gt;

Браузер отображает её как обычный текст:

<div>Текст</div>

а не как HTML-элемент.


Как Awesomplete работает с текстом по умолчанию

Стандартный механизм формирования элементов списка использует безопасное создание DOM-узлов.

Пример обычной инициализации:

new Awesomplete(input, {
    list: [
        "JavaScript",
        "HTML",
        "CSS"
    ]
});

Значения воспринимаются как текстовые данные и отображаются без выполнения HTML-кода.

Даже если список содержит:

[
    "<script>alert(1)</script>"
]

компонент не должен выполнять данный скрипт при использовании штатного рендеринга.

Это связано с тем, что Awesomplete использует текстовые узлы вместо непосредственной вставки HTML-разметки.


Когда возникает необходимость экранирования вручную

Проблемы появляются при переопределении метода генерации элементов списка.

Пример пользовательского рендера:

awesomplete.item = function(text) {
    const li = document.createElement("li");

    li.innerHTML = text;

    return li;
};

Если значение содержит:

<img src=x oner ror="alert('XSS')">

то код будет интерпретирован браузером как настоящий HTML.

В этом случае возникает уязвимость типа XSS (Cross-Site Scripting).


Опасность использования innerHTML

Свойство innerHTML вставляет строку непосредственно в DOM как HTML-разметку.

Пример:

element.innerHTML =
    "<strong>JavaScript</strong>";

Результат:

<strong>JavaScript</strong>

Будет создан настоящий тег <strong>.

Если данные поступают из ненадёжного источника:

element.innerHTML =
    "<script>alert('hack')</script>";

то появляется риск выполнения вредоносного кода.

При работе с Awesomplete использование innerHTML требует особой осторожности.


Безопасная альтернатива — textContent

Для отображения текста рекомендуется использовать свойство textContent.

Пример:

awesomplete.item = function(text) {
    const li = document.createElement("li");

    li.textContent = text;

    return li;
};

Даже если строка содержит:

<script>alert(1)</script>

она будет показана пользователю как обычный текст:

<script>alert(1)</script>

Никакой код не выполнится.


Создание собственной функции экранирования

Иногда требуется самостоятельно преобразовывать специальные символы в HTML-сущности.

Пример простой реализации:

function escapeHtml(text) {
    return text
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#39;");
}

Использование:

const safeText =
    escapeHtml(userInput);

Исходные данные:

<b>Hello</b>

Результат:

&lt;b&gt;Hello&lt;/b&gt;

Экранирование при работе с удалёнными данными

Часто список подсказок формируется через API.

Пример получения данных:

fetch("/api/search")
    .then(response => response.json())
    .then(data => {
        awesomplete.list = data;
    });

Если сервер возвращает значения:

[
    "<b>JavaScript</b>",
    "<img src=x oner ror=alert(1)>"
]

необходимо убедиться, что компонент выводит их как текст, а не как HTML.

Безопасный вариант:

awesomplete.item = function(text) {
    const li = document.createElement("li");

    li.textContent = text;

    return li;
};

Экранирование при кастомном выделении совпадений

Распространённая задача — подсвечивать совпадающую часть текста.

Небезопасная реализация:

li.innerHTML =
    text.replace(
        query,
        `<mark>${query}</mark>`
    );

Если пользователь введёт специальные символы, структура HTML может нарушиться.

Более безопасный подход включает предварительное экранирование:

const safeText =
    escapeHtml(text);

const safeQuery =
    escapeHtml(query);

li.innerHTML =
    safeText.replace(
        safeQuery,
        `<mark>${safeQuery}</mark>`
    );

Такой вариант существенно снижает вероятность внедрения вредоносного кода.


Использование DOM API вместо строковых шаблонов

Современный безопасный подход основан на создании элементов через DOM.

Пример:

const li = document.createElement("li");

const mark = document.createElement("mark");

mark.textContent = query;

li.append(mark);

В данном случае браузер самостоятельно обрабатывает содержимое как текст.

Не требуется ручное экранирование каждой строки.


XSS-атаки через подсказки Awesomplete

Автодополнение может стать точкой проникновения вредоносного кода.

Пример атакующей строки:

<img src=x oner ror="fetch('/cookies')">

Если такая строка окажется в подсказке и будет вставлена через innerHTML, произойдёт выполнение обработчика события.

Последствия:

  • кража пользовательских данных;
  • перехват токенов авторизации;
  • выполнение произвольного JavaScript-кода;
  • изменение содержимого страницы;
  • перенаправление на вредоносные сайты.

Поэтому любые данные, отображаемые в Awesomplete, должны рассматриваться как потенциально опасные.


Санитизация и экранирование

Экранирование и санитизация решают разные задачи.

Экранирование:

<b>Test</b>

&lt;b&gt;Test&lt;/b&gt;

HTML становится текстом.

Санитизация:

<b>Test</b>
<script>alert(1)</script>

<b>Test</b>

Разрешённые теги сохраняются, опасные удаляются.

Если требуется отображать часть HTML-разметки внутри подсказок, одной экранизации недостаточно. В этом случае применяется санитизация.


Использование специализированных библиотек

Для сложных случаев рекомендуется применять готовые решения.

Популярный пример:

const clean =
    DOMPurify.sanitize(html);

После очистки данные можно безопаснее вставлять в интерфейс.

Пример:

li.innerHTML =
    DOMPurify.sanitize(text);

Такой подход особенно полезен, если подсказки содержат форматирование, ссылки или другие HTML-элементы.


Проверка безопасности кастомного рендера

При разработке собственных шаблонов для Awesomplete полезно тестировать следующие строки:

<script>alert(1)</script>
<img src=x oner ror=alert(1)>
<iframe src="evil.html"></iframe>
<a href="jav * ascript:alert(1)">link</a>

Если хотя бы один из вариантов выполняется браузером, механизм отображения нуждается в дополнительной защите.


Типичные ошибки при экранировании

Двойное экранирование

Исходная строка:

&lt;div&gt;

Повторное экранирование:

&amp;lt;div&amp;gt;

На экране появится:

&lt;div&gt;

вместо ожидаемого:

<div>

Частичное экранирование

Неполная обработка:

text.replace(/</g, "&lt;");

оставляет незащищёнными символы:

>
&
"
'

Это может приводить к обходу защитных механизмов.


Слепое доверие данным сервера

Нередко предполагается, что сервер уже выполнил очистку.

Пример:

li.innerHTML = serverData;

Если серверная фильтрация будет отключена или окажется неполной, клиентская часть станет уязвимой.

Безопаснее считать любые внешние данные потенциально опасными.


Практические рекомендации

Для текстовых подсказок

Использовать:

textContent

вместо:

innerHTML

Для пользовательских данных

Всегда выполнять экранирование или санитизацию перед отображением.

Для сложного форматирования

Использовать специализированные библиотеки очистки HTML.

Для кастомных шаблонов Awesomplete

Предпочитать создание DOM-узлов через:

document.createElement()

вместо конкатенации HTML-строк.

Для проверки безопасности

Тестировать компонент на XSS-полезных нагрузках и нестандартных символах.

Грамотное экранирование HTML в Awesomplete обеспечивает корректное отображение подсказок, предотвращает выполнение внедрённого кода и является одним из ключевых элементов защиты пользовательского интерфейса от XSS-уязвимостей.