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 или Query Builders
Это помогает предотвратить атаки SQL-инъекций.
Никогда не используйте неэкранированный или непроверенный пользовательский ввод напрямую в ваших SQL-запросах.
Если ваш код сейчас делает это, рекомендуется перейти на параметризованные запросы или использовать функции экранирования, предоставляемые вашей SQL-библиотекой.
5. И наконец: консультируйтесь с экспертами по кибербезопасности и следуйте политикам безопасности, принятым в вашей компании
Безопасность — это непрерывный процесс.
Следуя этим шагам, придерживаясь политик вашей организации и проходя аудит у специалистов по безопасности, вы сможете минимизировать большинство распространённых веб-угроз.
После того как базовые вопросы безопасности решены, рассмотрим особенности Gantt.
Во-первых, вот несколько ключевых моментов при добавлении сложных функций, таких как Gantt, на стороне клиента:
Вот области в DHTMLX Gantt, где могут возникнуть проблемы безопасности:
Рассмотрим эти вопросы подробнее.
Один из первых шагов по защите Gantt — изолировать его от неавторизованного доступа со стороны скомпрометированных компонентов или обманутых пользователей (self-XSS).
Если злоумышленник получит доступ к конфигурационным файлам приложения (включая файл конфигурации Gantt), любые меры защиты от XSS-атак (если они были приняты) могут стать неэффективными, поэтому этот сценарий мы не рассматриваем.
После полной загрузки приложения, если злоумышленники получат доступ к объекту экземпляра Gantt, они смогут изменить всё и переопределить функции.
Поэтому важно изолировать Gantt внутри вашего проекта.
Для этого создайте отдельный экземпляр Gantt внутри функции. Таким образом, код внутри функции будет недоступен извне.
По умолчанию Gantt создаёт новый экземпляр на объекте gantt.
Внутри вашей функции объявите новую переменную с помощью const или let, чтобы скрыть её из внешней области видимости и сохранить там экземпляр Gantt.
function addGantt(){
const gantt = Gantt.getGanttInstance();
}
addGantt()
Вы также можете использовать другое имя переменной, чтобы избежать путаницы с глобальным объектом gantt:
function addGantt(){
const protectedGantt = Gantt.getGanttInstance();
}
addGantt()
После защиты Gantt от нежелательного доступа сосредоточьтесь на том, как данные вводятся и отображаются в диаграмме Gantt.
Это критическая область, которую злоумышленники могут использовать для компрометации безопасности Gantt в вашем приложении.
Точки ввода данных — распространённая цель XSS-атак.
В компоненте Gantt данные могут быть изменены через:
Объект задачи имеет множество свойств, которые используются в зависимости от включённых функций.
Чем больше свойств вы разрешаете редактировать, тем важнее тщательно очищать ввод.
Вот пример, показывающий различные способы повышения защиты от XSS-атак с помощью HTML-санации при работе с DHTMLX Gantt.
Related sample: Example to prevent XSS attacks (security, csp)
В этом примере вы можете изменить имя задачи, скорректировать её дату и длительность, изменить назначения ресурсов и добавить текстовые заметки.
Изменения даты начала и длительности ограничены lightbox и inline-редакторами. Оба inline-редактора явно указывают типы date и number.
В lightbox только длительность можно задать напрямую, а дату — выбрать из выпадающего списка.
Ни один из интерфейсов не позволяет вставлять текст, содержащий вредоносный код.
Если кто-то попытается изменить типы элементов через DOM-инспектор, будут получены недопустимые значения для даты или длительности.
Это вызовет ошибку, которая остановит работу Gantt до перезагрузки страницы. При этом никакие данные не будут отправлены на сервер, так как диаграмма не будет перерисована.
Однако, поскольку имена задач используют тип string, они могут быть уязвимы для XSS-атак.
Поэтому необходима санация ввода. Пример демонстрирует один из видов XSS-атаки и способ её предотвращения.
В реальных проектах важно реализовать комплексную санацию данных.
Здесь мы просто заменяем символы "<" и ">" на их HTML-сущности — <
и >
—
что предотвращает отображение HTML-элементов внутри текста задачи.
Эта замена происходит внутри функции sanitizeText(), как показано ниже:
function sanitizeText(text){
// uncomment to test XSS
// return text
// prevent XSS by disabling HTML elements
return text.split("<").join("<").split(">").join(">");
}
Функция вызывается внутри обработчиков событий: onLightboxSave для lightbox и onBeforeSave для inline-редакторов.
В этом примере вы можете добавить текстовые заметки к задаче с помощью кастомного inline-редактора или кастомного раздела lightbox.
Санацию можно применять внутри функций этих кастомных компонентов — до рендеринга значений и до чтения изменений из DOM-элементов:
// для inline-редактора:
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);
}
})
}
}
Эта функция вызывается внутри обработчика события onLightboxSave:
protectedGantt.attachEvent("onLightboxSave", function(id, task, is_new) {
sanitizeResourceValues(task)
return true;
});
Любые другие строковые параметры в вашей конфигурации Gantt также должны быть очищены.
В этом примере, если вы попытаетесь ввести нежелательное содержимое в назначения ресурсов в таймлайне ресурсов, будут приняты только числовые значения. Нестандартные значения не будут сохранены.
DHTMLX 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, где выводятся данные.
Это предполагает использование шаблонов в настройках каждой колонки грида и применение всех соответствующих шаблонов, чтобы предотвратить отображение опасного содержимого.
Однако более простой способ снизить риски при отображении — контролировать два основных источника данных: пользовательский ввод и данные с сервера.
Очищая входящие данные, вы уменьшаете вероятность появления вредоносного содержимого в диаграмме 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, это предотвратить невозможно.
Однако любые такие изменения будут утрачены при следующем перерисовывании Gantt и не сохранятся на сервере.
Имейте в виду, что валидацию на клиенте легко обойти или отключить, поэтому на неё нельзя полагаться для обеспечения безопасности.
Её задача — давать мгновенную обратную связь о некорректном вводе без ожидания ответа сервера.
Окончательная проверка и меры безопасности должны реализовываться на сервере.
Бэкенд должен корректно валидировать, экранировать и очищать входящие данные, применять правила доступа пользователей и т.д.
Поскольку dhtmlxGantt полностью работает на клиенте, ответственность за предотвращение SQL-инъекций лежит на серверной части.
Обратите внимание на два момента:
Поэтому ваша серверная часть должна реализовывать защиту от SQL-инъекций.
Если вы используете dhtmlxConnector и настраиваете таблицы, как описано в документации, значения экранируются автоматически.
В противном случае следует придерживаться безопасных практик CRUD, рекомендованных для вашей платформы. Реализации, приведённые в руководствах по началу работы, изначально безопасны в отношении SQL-инъекций.
См. эту статью для получения рекомендаций по добавлению пользовательских токенов авторизации или заголовков к запросам, отправляемым Gantt на ваш бэкенд.
Библиотека содержит специальную опцию конфигурации, которая помогает адаптировать ваше приложение dhtmlxGantt для соответствия стандарту Content Security Policy (CSP).
Это повышает безопасность, предотвращая различные атаки через внедрение кода.
Подробнее об использовании CSP в приложении dhtmlxGantt.
К началу