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

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

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.