Перейти к основному содержимому

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

DHTMLX Gantt — это клиентская JavaScript-библиотека, предназначенная для плавной интеграции функциональности Gantt в различные веб-приложения. Поэтому мы не ограничиваем функциональные возможности нашего Gantt, что могло бы повысить безопасность приложения, но одновременно снизить доступные возможности. Таким образом, вы можете настроить большинство функций Gantt в соответствии с требованиями вашего проекта.

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

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

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

1. Используйте Content Security Policy (CSP) в вашем приложении

Добавление заголовка CSP таким образом, как показано ниже, предотвратит выполнение XSS-кода в вашем приложении:

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

Вашему приложению может потребоваться более сложная политика, но отключение выполнения встроенного скрипта предотвратит большое количество XSS и CSRF-атак.

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])

Возможно, стоит привести их к ожидаемому формату и удалить потенциально вредоносный контент. Если вы используете Node.js, это можно сделать с помощью любого из множества доступных библиотек, например DOMPurify:

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-символы, которые могли ввести пользователи, перед подачей данных в Gantt. Приведём пример использования библиотеки 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. Если вы работаете с SQL-базой данных, избегайте создания SQL-запросов путём конкатенации строк. Используйте параметризованные запросы, ORM или конструкторы запросов.

Этот пункт касается типов атак SQL-инъекций. Как правило, вы никогда не должны использовать неэкранированные или невалидированные вводимые пользователем данные в своих SQL-запросах. Если вы обнаружили, что делаете это, подумайте над переписыванием кода с использованием параметризованных запросов или использованием функций экранирования, поддерживаемых вашей СУБД.

5. И последнее: проконсультируйтесь с экспертом по кибербезопасности и следуйте политикам безопасности, принятым вашей компанией

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

Теперь, когда основы охвачены, перейдём к вещам, специфичным для Gantt.

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

Прежде всего, отметим три момента при внедрении сложных функций вроде Gantt на клиентской стороне:

  • DHTMLX Gantt — это клиентская библиотека, поэтому все данные, загружаемые с сервера, попадают в Gantt в исходном виде. Поскольку набор данных сохраняется и хранится на стороне сервера, главная угроза вашему приложению исходит оттуда. Но защита бэкенда выходит за рамки DHTMLX Gantt.
  • Киберпреступники могут обманом заставлять конечных пользователей выполнять вредоносный код с помощью DevTools (само-XSS-атаки), тем самым обходя любые защитные механизмы. Любой код, вставленный в текст задачи, будет работать так же, как если бы DevTools были использованы.
  • Если злоумышленник получит доступ к объекту экземпляра Gantt, любые защитные меры окажутся неэффективными. В таком случае злоумышленники смогут изменить конфигурацию Gantt по своему усмотрению и полностью контролировать её.

Теперь перейдём к списку уязвимых областей DHTMLX Gantt, где можно ожидать потенциальные проблемы с безопасностью:

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

Перейдём к практическим соображениям по этим потенциальным проблемам.

Изоляция доступа к Gantt

Говоря о возможных мерах защиты Gantt, сначала нужно изолировать Gantt от несанкционированного доступа через взломанные компоненты или неверно настроенных пользователей (self-XSS-атаки).

заметка

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

Когда приложение полностью загружено и злоумышленники получают доступ к объекту экземпляра Gantt, они могут изменить буквально всё в Gantt и переопределить все функции. Поэтому следует знать, как изолировать Gantt в вашем проекте.

Для этого необходимо создать отдельный экземпляр Gantt внутри функции. Цель здесь — сделать код, выполняющийся внутри функции, недоступным снаружи.

Кроме того, по умолчанию Gantt создаёт новый экземпляр в объекте gantt. Важно добавить новую переменную внутри функции, используя либо ключевое слово const, либо let, чтобы сделать её недоступной за пределами функции и безопасно поместить экземпляр Gantt внутрь этой переменной.

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

Вы также можете использовать другое имя для экземпляра Gantt, чтобы избежать путаницы с объектом gantt:

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

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

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

Это чувствительная область, которая может быть использована киберпреступниками для компрометации безопасности Gantt в вашем приложении.

Области ввода данных считаются основными целями XSS-атак. В нашем компоненте Gantt возможно изменение данных через:

  • lightbox
  • встроенные редакторы
  • модальные окна с пользовательскими элементами
  • сторонние библиотеки
  • назначения ресурсов в временной шкале загрузки ресурсов
  • дополнительные слои (если у них есть пользовательские элементы, в которые можно ввести данные)
  • любые пользовательские решения, использующие API Gantt и требующие ввода данных (например, панель инструментов или собственная форма редактирования задач)

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

Рассмотрим пример

Мы подготовили пример, демонстрирующий различные шаги, которые можно предпринять для повышения защиты от XSS-атак через очистку HTML при использовании DHTMLX Gantt.

Связанный пример: Пример предотвращения XSS-атак (безопасность, CSP)

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

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

Однако мы используем тип значения string для названий задач, и это может стать узким местом для XSS-атак. Поэтому необходимо очищать вводимое значение. В нашем примере видно лишь один вариант атаки XSS и способ её предотвращения.

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

Вышеописанная замена символов реализована в функции sanitizeText() следующим образом:

function sanitizeText(text){
// uncomment to test XSS
// return text

// prevent XSS by disabling HTML elements
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 не ограничивает ввод значений только типом number, доступно использование строковых значений, что открывает возможность 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);
}
})
}
}

Функция sanitizeResourceValues() вызывается в обработчике события 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

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

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

Однако существует более простое решение для потенциальных проблем с отображением данных в диаграмме Gantt. Поскольку данные могут загружаться в диаграмму 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. Например, объект задачи может приходить отдельно с сервера и обрабатываться какой‑то функцией. После этого новая задача добавляется в диаграмму Gantt или существующая задача обновляется. В таком случае нужно очистить данные задачи внутри этой функции перед загрузкой данных в Gantt.

Это может выглядеть так:

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

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

Проблемы на стороне сервера

Обратите внимание, что валидация на стороне клиента легко может быть скомпрометирована или полностью обойденa, поэтому нельзя полагаться на неё как на средство безопасности. Её задача — предоставить пользователю мгновенную обратную связь при неверном вводе, без ожидания ответа сервера, в то время как итоговую проверку следует выполнять на сервере.

Бэкенд должен корректно валидировать/экранировать/очищать входящие данные, правила доступа пользователей и т. д.

SQL Инъекции

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

Есть две точки, которые следует учитывать:

  • у lightbox нет какой-либо стандартной валидации, которая, если её не обработать, позволяет пользователю вводить любые значения в редактируемые поля
  • ваш backend API может быть вызван методом PUT/POST с опасными значениями вручную, обходя клиентский UI

Поэтому вам потребуется какая‑то защита от SQL-инъекций на вашем бэкенде. Если вы используете dhtmlxConnector и указываете конфигурацию таблицы, как показано в соответствующей документации, все значения будут экранированы автоматически. В противном случае необходимо использовать безопасную реализацию CRUD в соответствии с практиками вашей платформы. Реализации, приведённые в [Решениях запуска] (integrations/howtostart-guides.md), должны быть безопасны с точки зрения SQL-инъекций.

CSRF-атаки

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

Content Security Policy

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

Подробнее об применении стандарта CSP к приложению на базе dhtmlxGantt.

Защита XSS во фреймворк-обёртках

Начиная с версии v10.0, обёртки [React], [Vue] и [Angular] обрабатывают строковые значения, возвращаемые пользовательскими функциями-шаблонами, чтобы HTML, поступающий из шаблонов, по умолчанию был безопасным — даже если он внедряет неочищенные данные задач/событий. Это охватывает:

  • функции, переданные через свойство templates
  • функции config.columns[].template
  • функции config.scales[].format

Поведение контролируется свойством компонента htmlTemplatePolicy:

ПолитикаПоведение
"basic-sanitize" (значение по умолчанию)Фильтрует возвращаемый HTML по белому списку: безопасное форматирование (b, i, span, div, mark, ...), class, ограниченный набор встроенных стилей, data-* атрибутов, contenteditable и img с безопасным src сохраняются. <script>, встроенные обработчики событий (on*) и опасные URL (javascript:, vbscript:, не‑изображение data:) удаляются.
"escape"Рендерит строку как текст — HTML‑теги становятся видимыми символами. (Встроенные шаблоны, такие как иконки дерева грида, по-прежнему санитизуются, чтобы грид рендерился корректно.)
"unsafe-html"Рендерит сырую строку без обработки — поведение до версии 10, эквивалентно dangerouslySetInnerHTML. Используйте только с полностью доверенным выводом.
{ mode: "sanitize", sanitize }Делегирует в пользовательский санитайзер, например [DOMPurify], позволяя санитизировать богатый HTML без добавления зависимости‑обёртки.

"basic-sanitize" — небольшой санитайзер без зависимостей, предназначенный для простой разметки, меток, цветов и изображений — не является полноценным общепригодным санитайзером. Для произвольного богатого HTML предпочтительно возвращать узлы фреймворка из шаблонов (см. ниже) или подключить специализированный санитайзер.

Возврат узлов фреймворка (рекомендуется для богатой разметки)

Самый безопасный способ отобразить пользовательскую разметку — вернуть элемент фреймворка из шаблона вместо HTML-строки. React/Vue/Angular по умолчанию экранируют вставляемые значения, поэтому HTML‑санитизация не требуется:

<ReactGantt
templates={{
task_text: (start, end, task) => <span className="task-label"><b>{task.text}</b></span>
}}
/>

По-шаблонному сырый HTML (raw HTML)

Чтобы отобразить сырую строку для одного конкретного шаблона независимо от активной политики, оберните её помощником allowRawHTML, экспортируемым из обёртки. Затем вы несёте ответственность за чистку пользовательских данных — используйте экспортируемый утилитный escapeHTML:

import { allowRawHTML, escapeHTML } from "@dhx/react-gantt";

const templates = {
task_text: allowRawHTML((start, end, task) => `<b>${escapeHTML(task.text)}</b>`)
};

Выбор глобальной политики

// оставлять сырый HTML повсюду (поведение до v10)
<ReactGantt htmlTemplatePolicy="unsafe-html" />

// подключить DOMPurify для богатого HTML
import DOMPurify from "dompurify";
<ReactGantt htmlTemplatePolicy={{ mode: "sanitize", sanitize: (html) => DOMPurify.sanitize(html) }} />

Более подробную информацию см. в Примечаниях по миграции.

Need help?
Got a question about the documentation? Reach out to our technical support team for help and guidance. For custom component solutions, visit the Services page.