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

Автодополнение кажется относительно безопасным компонентом интерфейса, однако именно оно часто становится точкой проникновения вредоносных данных в браузер. Библиотека Awesomplete отображает элементы списка подсказок на основе входящих данных, поэтому любые значения, поступающие от пользователя, сервера или внешнего API, должны рассматриваться как потенциально опасные.

Под инъекцией понимается внедрение вредоносного содержимого в данные таким образом, чтобы оно было интерпретировано браузером, сервером или другим компонентом приложения как исполняемый код либо управляющая инструкция.

Наиболее распространённые виды атак:

  • XSS (Cross-Site Scripting);
  • HTML-инъекции;
  • DOM-инъекции;
  • SQL-инъекции на стороне сервера;
  • инъекции в поисковые запросы;
  • инъекции в API-параметры;
  • внедрение вредоносных URL;
  • инъекции в шаблоны рендеринга.

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


Основные источники опасных данных

В приложениях с Awesomplete данные обычно поступают из нескольких источников:

Ввод пользователя

<input id="search">
const input = document.getElementById("search");

Пользователь способен ввести любые символы:

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

или

<img src=x oner ror=alert(1)>

Если такие данные впоследствии попадут в DOM без фильтрации, возникнет риск выполнения произвольного JavaScript-кода.

Ответы сервера

Часто список подсказок загружается через AJAX:

fetch("/api/search?q=" + query)

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

Сторонние API

Источником данных могут выступать:

  • поисковые сервисы;
  • каталоги товаров;
  • географические сервисы;
  • корпоративные API.

Даже доверенный внешний сервис не гарантирует отсутствие вредоносного содержимого.


XSS как главная угроза

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

Рассмотрим опасный пример:

awesomplete.list = [
    "<img src=x oner ror=alert('hack')>"
];

Если содержимое элемента будет вставлено через innerHTML, браузер попытается обработать тег.

Вредоносный код способен:

  • украсть cookies;
  • похитить токены авторизации;
  • выполнить действия от имени пользователя;
  • изменить интерфейс страницы;
  • внедрить фишинговые элементы.

Безопасное отображение текста

Наиболее надёжный подход — отображение данных как обычного текста.

Опасный вариант:

item.innerHTML = suggestion;

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

item.textContent = suggestion;

Свойство textContent не интерпретирует HTML.

Например:

item.textContent =
    "<script>alert(1)</script>";

На странице будет отображён текст:

<script>alert(1)</script>

а не выполняемый код.


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

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

Пример функции:

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

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

const safeValue =
    escapeHtml(userValue);

После обработки строка:

<script>alert(1)</script>

превратится в:

&lt;script&gt;alert(1)&lt;/script&gt;

и станет обычным текстом.


Защита кастомного метода item()

Awesomplete позволяет переопределять функцию генерации элементов списка.

Типичный пример:

new Awesomplete(input, {
    item(text) {
        const li =
            document.createElement("li");

        li.innerHTML = text;

        return li;
    }
});

Такой код опасен.

Безопасная версия:

new Awesomplete(input, {
    item(text) {
        const li =
            document.createElement("li");

        li.textContent = text;

        return li;
    }
});

Даже если злоумышленник передаст HTML-код, он не будет выполнен.


Защита кастомного метода replace()

Функция replace() отвечает за вставку выбранного значения в поле ввода.

Пример:

replace(text) {
    this.input.value = text;
}

Сам по себе такой код безопасен.

Опасность появляется позже, когда значение поля используется для генерации HTML:

result.innerHTML =
    this.input.value;

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

Следовательно, безопасность должна обеспечиваться по всей цепочке обработки данных.


Санитизация данных

Экранирование и санитизация — разные процессы.

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

Преобразует специальные символы:

<

в

&lt;

Санитизация

Удаляет потенциально опасные элементы:

<script>
oner ror=
jav * ascript:

и другие конструкции.

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

const clean =
    DOMPurify.sanitize(data);

После обработки вредоносные конструкции будут удалены.


Проверка данных перед передачей в список

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

Нежелательный вариант:

awesomplete.list =
    response.items;

Лучше выполнять предварительную обработку:

awesomplete.list =
    response.items.map(item =>
        sanitize(item)
    );

Либо:

awesomplete.list =
    response.items.filter(
        isValidItem
    );

Такой подход уменьшает риск попадания опасных данных в интерфейс.


Белые списки символов

Если формат данных заранее известен, предпочтительно использовать разрешающие правила.

Например, для списка стран:

function isValidCountry(value) {
    return /^[a-zа-яё\s-]+$/i
        .test(value);
}

Для артикулов:

function isValidCode(value) {
    return /^[A-Z0-9_-]+$/
        .test(value);
}

Разрешение только ожидаемых символов значительно повышает уровень безопасности.


Защита серверной части

Awesomplete часто используется совместно с серверным поиском.

Типичный запрос:

fetch(
    "/search?q=" +
    encodeURIComponent(query)
);

На сервере недопустимо выполнять запросы вроде:

const sql =
    "SEL ECT * FR OM users WH ERE name='"
    + query + "'";

Такой код уязвим для SQL-инъекций.

Пример атаки:

' OR 1=1 --

Безопасный подход предполагает параметризованные запросы:

db.query(
    "SELECT * FR OM users WHERE name=?",
    [query]
);

Даже при наличии Awesomplete безопасность сервера остаётся обязательной задачей.


Проверка URL в подсказках

Иногда элементы списка содержат ссылки.

Опасный вариант:

{
    title: "Google",
    url: "jav * ascript:alert(1)"
}

При создании ссылок необходимо проверять протокол:

function isSafeUrl(url) {
    return /^https?:\/\//i
        .test(url);
}

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

if (isSafeUrl(data.url)) {
    link.href = data.url;
}

Это предотвращает выполнение вредоносных URI.


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

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

Опасный код:

document.querySelector(
    userInput
);

Злоумышленник способен передать неожиданные конструкции:

body *

или

#adminPanel

Безопаснее использовать заранее определённые селекторы:

document.getElementById(
    safeId
);

либо выполнять строгую валидацию.


Content Security Policy

Дополнительным уровнем защиты служит CSP.

Пример заголовка:

Content-Security-Policy:
default-src 'self';
script-src 'self';
object-src 'none';

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

Преимущества CSP:

  • снижение риска XSS;
  • блокировка встроенных скриптов;
  • контроль источников ресурсов;
  • дополнительный защитный барьер.

Защита при работе с JSON

Ответ сервера обычно имеет вид:

[
    "JavaScript",
    "Java",
    "Python"
]

Однако злоумышленник может попытаться внедрить:

[
    "<img src=x oner ror=alert(1)>"
]

Поэтому каждый элемент должен проходить проверку:

const safeItems =
    data.filter(
        item =>
            typeof item === "string"
    );

После этого может выполняться дополнительная санитизация.


Ограничение длины входных данных

Очень длинные строки могут использоваться для:

  • обхода фильтров;
  • переполнения памяти;
  • ухудшения производительности;
  • атак на интерфейс.

Пример ограничения:

function normalize(value) {
    return value
        .trim()
        .slice(0, 100);
}

После нормализации чрезмерно длинные данные будут отсечены.


Логирование подозрительных значений

Для выявления атак полезно регистрировать необычные данные.

Пример:

if (
    /<script|onerror|onload/i
    .test(value)
) {
    console.warn(
        "Suspicious input:",
        value
    );
}

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


Многоуровневая модель защиты

Наиболее эффективная стратегия строится из нескольких независимых уровней:

  1. Валидация пользовательского ввода.
  2. Проверка данных на сервере.
  3. Использование параметризованных SQL-запросов.
  4. Санитизация ответов API.
  5. Экранирование HTML.
  6. Использование textContent вместо innerHTML.
  7. Контроль URL и атрибутов.
  8. Ограничение длины данных.
  9. Настройка Content Security Policy.
  10. Логирование подозрительной активности.

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