Безопасность приложения

DHTMLX Gantt — это JavaScript-библиотека, разработанная для бесшовной интеграции функционала диаграмм Ганта в веб-приложения. Она предоставляет широкий спектр функций для настройки, позволяя разработчикам адаптировать её к специфическим нуждам проекта. Однако важно отметить, что DHTMLX Gantt не включает встроенную защиту от угроз, таких как SQL-инъекции, XSS или CSRF-атаки. Обеспечение безопасности вашего приложения требует внедрения необходимых конфигураций и защитных мер самостоятельно. Ниже приведены некоторые рекомендации по эффективной обработке HTML-санитизации.

Основные шаги по обеспечению безопасности

Кибербезопасность — это обширная область, и ни одно руководство не может охватить всё. Однако следование этим практическим шагам может помочь устранить распространенные уязвимости и снизить риски.

1. Используйте Политику Безопасности Содержимого (CSP)

Добавление заголовка CSP, как в примере ниже, может заблокировать выполнение встроенных скриптов, значительно снижая риск XSS и CSRF атак:

Content-Security-Policy: script-src 'self'

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

2. Санитизация пользовательского ввода на сервере

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

db.query("INSERT INTO gantt_tasks(text, start_date, duration, progress, parent)"
    + " VALUES (?,?,?,?,?)",
    [task.text, task.start_date, task.duration, task.progress, task.parent])

Используйте библиотеку, такую как DOMPurify с Node.js, для очистки ввода:

const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
 
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
 
...
 
db.query("INSERT INTO gantt_tasks(text, start_date, duration, progress, parent)"
    + " VALUES (?,?,?,?,?)",
    [task.text, task.start_date, task.duration, task.progress, task.parent]
        .map((input) => DOMPurify.sanitize(input))

3. Экранирование HTML-сущностей перед отображением данных

Чтобы предотвратить выполнение HTML-разметки при отображении, экранируйте HTML-символы в данных, предоставленных пользователем. Например, используя библиотеку validator:

const validator = require('validator');
...
 
// GET /data
 
Promise.all([
  db.query("SELECT * FROM gantt_tasks"),
  db.query("SELECT * FROM gantt_links")
]).then(results => {
    let tasks = results[0],
        links = results[1];
 
    tasks.forEach((task) => {
        Object.entries(task).forEach(([key, value]) => {
            if(typeof value === "string") {
                task[key] = validator.escape(value); //#!
            }
        });
        task.open = true;
        task.start_date = task.start_date.format("YYYY-MM-DD hh:mm:ss");
    });
 
    links.forEach((link) => {
        Object.entries(link).forEach(([key, value]) => {
            if(typeof value === "string") {
                link[key] = validator.escape(value); //#!
            }
        });
    });
 
 
    res.send({
        tasks,
        links 
   });

4. Используйте параметризованные запросы или ORM для SQL-баз данных

Чтобы защититься от атак SQL-инъекций, избегайте создания SQL-запросов путём конкатенации строк. Вместо этого используйте параметризованные запросы или конструктор запросов/ORM. Если вы используете необработанные или непроверенные пользовательские вводы в своих запросах, стоит переписать код, чтобы следовать более безопасным практикам.

5. Консультируйтесь с экспертами по безопасности и следуйте корпоративным политикам

Безопасность — это непрерывный процесс. Реализуя эти шаги, соблюдая политики безопасности вашей организации и проверяя свою работу у специалистов, вы можете устранить большинство распространенных угроз.

С этими базовыми мерами давайте рассмотрим вопросы безопасности, специфичные для Gantt.

Уязвимые области в Gantt на клиентской стороне

При использовании сложных инструментов, таких как Gantt, на клиентской стороне необходимо учитывать несколько ключевых моментов:

  • DHTMLX Gantt обрабатывает данные, полученные с сервера, в их необработанном виде. Поскольку данные находятся на сервере, безопасность на серверной стороне чрезвычайно важна, хотя это выходит за рамки Gantt.
  • Злоумышленники могут использовать DevTools для атак самонаправленного XSS, обманом заставляя пользователей выполнять вредоносный код. Любой код, вставленный в текст задачи, ведет себя так, как если бы он был выполнен через DevTools.
  • Если злоумышленник получит доступ к экземпляру Gantt, он может обойти меры безопасности, изменить конфигурацию и получить полный контроль.

Некоторые области, где могут возникнуть уязвимости, включают:

  • Данные, вводимые и сохраняемые пользователями
  • Отображаемые данные Gantt (текст, визуальные элементы)
  • Пользовательские HTML-элементы, взаимодействующие с данными Gantt
  • Доступ к самому объекту Gantt

Изолирование доступа к Gantt

Защита Gantt начинается с его изоляции от несанкционированного доступа, будь то скомпрометированные компоненты или атаки самонаправленного XSS.

Если злоумышленники получат доступ к конфигурационным файлам (включая конфигурации Gantt), защита от XSS может оказаться неэффективной. Этот сценарий выходит за рамки данного документа.

Чтобы предотвратить несанкционированный доступ к экземпляру Gantt, инкапсулируйте его в функции. Это гарантирует, что экземпляр недоступен за пределами области действия функции. Например:

function addGantt(){
  const gantt = Gantt.getGanttInstance();
}
addGantt()

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

function addGantt(){
  const protectedGantt = Gantt.getGanttInstance();
}
addGantt()

После того как доступ к Gantt защищен, сосредоточьтесь на безопасной обработке ввода и отображения данных.

Ввод данных в Gantt

Точки ввода данных являются распространенными целями для XSS-атак. В Gantt данные могут вводиться через:

  • Lightbox
  • Встроенные редакторы
  • Модальные окна с пользовательскими элементами
  • Сторонние библиотеки
  • Назначение ресурсов в временной шкале загрузки ресурсов
  • Дополнительные слои с пользовательскими элементами ввода
  • Пользовательские решения с использованием API Gantt (например, панели инструментов или пользовательские формы редактирования задач)

Объект задачи включает различные параметры в зависимости от включенных функций. Чем больше параметров доступно для редактирования, тем больше данных необходимо санитизировать при вводе.

Пример

Вот пример, иллюстрирующий, как можно усилить защиту от XSS-атак, санитизируя HTML при работе с DHTMLX Gantt.

Related sample:  Example to prevent XSS attacks (security, csp)

В этом примере вы можете изменять имя задачи, даты, продолжительность, назначение ресурсов и добавлять заметки. Изменение даты начала и продолжительности ограничено lightbox и встроенными редакторами. Для встроенных редакторов типы date и number явно определены. В lightbox вы можете установить только продолжительность, а дата выбирается из выпадающего списка.

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

Тем не менее, имена задач, которые используют тип значения string, могут быть слабым местом для XSS-атак. Чтобы решить эту проблему, становится важной санитизация ввода. Пример демонстрирует один из сценариев XSS-атаки и способ его предотвращения.

Для реальных проектов важно реализовать комплексную санитизацию данных. В этом примере символы "<" и ">" заменяются на их HTML-сущности, &lt; и &gt;, чтобы предотвратить отображение HTML-элементов в тексте задачи.

Логика замены реализована в функции sanitizeText():

function sanitizeText(text){
    // раскомментируйте для тестирования XSS
    // return text
 
    // предотвращение XSS путём отключения HTML-элементов
    return text.split("<").join("&lt;").split(">").join("&gt;");
}

Эта функция используется в обработчиках событий, таких как onLightboxSave для lightbox и onBeforeSave для встроенных редакторов.

Пример также включает пользовательские встроенные редакторы и секции lightbox для добавления текстовых заметок. Санитизация применяется в функциях этих пользовательских объектов перед отображением значений или получением изменений из элементов DOM:

// для встроенного редактора:
set_value: function(value, id, column, node){
    node.firstChild.value = sanitizeText(value || "");
},
get_value: function(id, column, node){
    return sanitizeText(node.firstChild.value);
},
 
// для lightbox:
set_value: function(node, value, task){
    node.value = sanitizeText(value || "");
},
get_value: function(node, task){
    return sanitizeText(node.value);
},

Альтернативно, можно обработать санитизацию текстовых заметок, используя обработчики событий onLightboxSave и onBeforeSave:

protectedGantt.attachEvent("onLightboxSave", function(id, task, is_new){
    if (task.notes) {
        task.notes = sanitizeText(task.notes);
    }
    return true;
});
 
protectedGantt.ext.inlineEditors.attachEvent("onBeforeSave", function(state){
    if (state.columnName == "notes") {
        state.newValue = sanitizeText(state.newValue);
    }
    return true;
});

Назначения ресурсов в lightbox также требуют внимания. Поскольку Gantt позволяет строковые значения, санитизация необходима для предотвращения XSS-атак. Функция sanitizeResourceValues() проходит по значениям ресурсов и применяет sanitizeText():

function sanitizeResourceValues(task){
    const resources = task[protectedGantt.config.resource_property];
    if (resources && resources.length) {
        resources.forEach(function (resource) {
            if (typeof resource.value == "string") {
                resource.value = sanitizeText(resource.value);
            }
        })
    }
}

Эта функция привязана к обработчику события onLightboxSave:

protectedGantt.attachEvent("onLightboxSave", function(id, task, is_new) {
    sanitizeResourceValues(task)
    return true;
});

Не забывайте санитизировать любые дополнительные строковые параметры в вашей конфигурации Gantt.

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

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

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

В примере используется пользовательская форма для редактирования имен задач. Функция sanitizeText() включена для обработки экранирования текста:

document.body.querySelector("[name='save']").onclick = function(){
    const newTaskName = document.body.querySelector("[name='text']").value;
    task.text = sanitizeText(newTaskName);
    protectedGantt.updateTask(task.id);
}

Санитизируя данные при их вводе в Gantt, потенциальные XSS-атаки нейтрализуются, гарантируя, что они не достигнут сервера.

Отображение данных в Gantt

Еще одна область, которую следует учитывать, — это отображение данных в диаграмме Gantt. Хотя она не так критична, как ввод данных, санитизация отображаемых данных может прервать цепочки XSS-атак. Например, если сервер скомпрометирован, но Gantt остается недоступным, санитизация может заблокировать атаку.

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

Более простая альтернатива — ограничение потока данных в Gantt. Это гарантирует, что никакой вредоносный код не сможет повлиять на содержимое. Например, свойства задач могут быть санитизированы при загрузке данных с сервера, используя обработчик события onTaskLoading:

protectedGantt.attachEvent("onTaskLoading", function (task) {
    task.text = sanitizeText(task.text);
    if (task.notes) {
        task.notes = sanitizeText(task.notes);
    }
    sanitizeResourceValues(task);
    return true;
});

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

let newTask = await loadFromServer(23);
sanitizeTaskProperties(newTask);
gantt.addTask(newTask);

Хотя пользователи могут манипулировать элементами DOM Gantt с помощью инспекторов браузера, такие изменения не сохранятся и будут утеряны при повторной отрисовке или обновлении сервера.

Серверные аспекты

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

SQL-инъекция

Поскольку dhtmlxGantt полностью на стороне клиента, предотвращение SQL-инъекций является обязанностью серверной стороны.

Основные моменты:

  • Lightbox не имеет валидации по умолчанию, поэтому пользователи могут вводить любые значения, если это не обрабатывается.
  • API на серверной стороне может быть целью атак с использованием PUT/POST запросов, содержащих вредоносные значения.

Если вы используете dhtmlxConnector и конфигурацию таблицы, как описано в документации, значения автоматически экранируются. В противном случае следуйте лучшим практикам для безопасной реализации CRUD на вашей платформе. Руководства по началу работы предоставляют безопасные примеры для предотвращения SQL-инъекций.

CSRF-атаки

Для добавления пользовательских авторизационных токенов или заголовков в запросы на сервер Gantt, обратитесь к этой статье.

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

Чтобы привести ваше приложение dhtmlxGantt в соответствие со стандартом CSP (Политика безопасности содержимого), библиотека предлагает специальную конфигурационную опцию. Это помогает предотвратить атаки с внедрением кода и укрепляет безопасность вашего приложения.

Узнайте больше о применении стандартов CSP в приложениях dhtmlxGantt.

К началу