XSS защита

XSS-уязвимости в веб-приложениях на CesiumJS возникают в первую очередь там, где 3D-сцена начинает взаимодействовать с HTML-интерфейсом: всплывающими окнами, информационными панелями, пользовательскими данными, внешними источниками (CZML, GeoJSON, REST-API). Несмотря на то что CesiumJS сам по себе не является источником XSS, он активно оперирует строками, которые часто напрямую попадают в DOM без дополнительной обработки, что делает его интеграции чувствительными к внедрению вредоносного скрипта.

Наиболее частые точки возникновения уязвимостей связаны с тем, как приложение отображает данные, связанные с объектами сцены:

1. Entity.description и HTML-рендеринг Описание сущностей (Entity) может содержать HTML-строку. Cesium по умолчанию отображает её внутри InfoBox через innerHTML.

viewer.entities.add({
    name: "Object A",
    description: "<b>Информация</b>"
});

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

description: userInput

Если userInput содержит <script> или вредоносные обработчики событий (onerror, onload), они могут быть выполнены в контексте страницы.


2. InfoBox и автоматическая вставка HTML Компонент InfoBox отображает содержимое description без строгой санитаризации. Это делает его одним из ключевых каналов XSS в Cesium-приложениях.

Особенно опасны конструкции:

  • <img src=x oner ror=alert(1)>
  • <svg onl oad=alert(1)>
  • <iframe src="jav * ascript:...">

3. CallbackProperty и динамические строки Хотя CallbackProperty используется для обновления значений в реальном времени, он часто возвращает строки, которые затем вставляются в UI.

description: new Cesium.CallbackProperty(() => {
    return dynamicString;
}, false);

Если dynamicString формируется на основе пользовательских данных без очистки, XSS может проникнуть через обновляемые поля.


4. GeoJSON и CZML из внешних источников Cesium позволяет загружать данные:

  • GeoJSON
  • CZML
  • KML (через конвертацию)

Эти форматы могут содержать свойства, которые интерпретируются как HTML:

{
  "properties": {
    "description": "<img src=x oner ror=alert(1)>"
  }
}

При отображении таких свойств в Entity без фильтрации возникает риск выполнения скриптов.


Принцип опасности innerHTML в контексте Cesium

Cesium UI-компоненты (InfoBox, Popup, custom panels) часто используют:

  • element.innerHTML = ...
  • шаблонизацию строк
  • вставку HTML из Entity.description

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

Ключевой момент: Cesium не гарантирует санитаризацию входных данных, потому ответственность полностью лежит на приложении.


Санитаризация входных данных

Основная стратегия защиты — очистка HTML перед передачей в Cesium.

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

const clean = DOMPurify.sanitize(userInput);

viewer.entities.add({
    name: "Safe Object",
    description: clean
});

DOMPurify удаляет:

  • <script>
  • inline event handlers (onclick, onerror)
  • опасные URI (jav * ascript:)

Запрет HTML и использование textContent

Если HTML не требуется, безопаснее полностью отказаться от него:

const element = document.createElement("div");
element.textContent = userInput;

В Cesium можно подменять InfoBox:

viewer.infoBox.frame.contentWindow.document.body.textContent = data;

Изоляция данных от DOM

Один из наиболее надёжных подходов — разделение:

  • данные сцены (Cesium Entities)
  • UI слой (HTML/React/Vue)

Нельзя напрямую связывать пользовательский ввод с HTML внутри Cesium.

Пример опасного подхода:

description: `<h3>${userInput}</h3>`

Без экранирования это эквивалентно выполнению кода.

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

description: `<h3>${escapeHtml(userInput)}</h3>`

Экранирование HTML-символов

Базовая функция защиты:

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

Используется, когда требуется сохранить текст, но исключить интерпретацию как HTML.


Безопасная работа с Entity.properties

Cesium позволяет хранить произвольные свойства:

properties: {
    info: userInput
}

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

description: entity.properties.info

Даже если данные были «безопасно» сохранены, они становятся опасными при вставке в DOM.

Правильный подход:

  • хранить «сырые» данные отдельно
  • форматировать только на уровне UI после очистки

Риски пользовательских callback-функций

Cesium поддерживает динамические свойства:

  • CallbackProperty
  • ColorMaterialProperty
  • LabelGraphics.text

Любое значение, возвращающее строку, потенциально может попасть в DOM.

Пример уязвимости:

label: new Cesium.LabelGraphics({
    text: new Cesium.CallbackProperty(() => {
        return userControlledText;
    }, false)
});

Если userControlledText содержит HTML, он может быть интерпретирован в UI слоях, особенно в кастомных оверлеях.


Защита при загрузке внешних данных

При работе с GeoJSON:

Cesium.GeoJsonDataSource.load(url)

Внешние данные следует фильтровать:

  • удалять HTML из properties
  • ограничивать допустимые ключи
  • валидировать структуру

Пример постобработки:

dataSource.entities.values.forEach(entity => {
    if (entity.description) {
        entity.description = DOMPurify.sanitize(entity.description);
    }
});

CSP как слой защиты

Content Security Policy существенно снижает последствия XSS.

Рекомендуемые ограничения:

  • запрет unsafe-inline
  • запрет eval
  • ограничение источников скриптов

Пример:

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

В Cesium-приложениях CSP особенно важен, так как:

  • используется WebGL
  • часто подключаются внешние тайлы и данные
  • есть динамическая HTML-генерация

Опасность внедрения через UI-шаблоны

В кастомных интерфейсах Cesium часто используется генерация HTML:

popup.innerHTML = `
    <div>${entity.description}</div>
`;

Если entity.description не очищен, XSS становится прямым.

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

popup.textContent = entity.description;

или использование безопасного рендера:

popup.innerHTML = DOMPurify.sanitize(entity.description);

Интеграция React/Vue с Cesium

При использовании Cesium вместе с UI-фреймворками:

  • Cesium управляет сценой
  • фреймворк управляет DOM

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

Антипаттерн:

setPopupHtml(entity.description);

Без фильтрации это приводит к XSS в React/Vue через dangerouslySetInnerHTML.


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

Любой источник данных следует считать недоверенным:

  • пользовательский ввод
  • API без контроля
  • файлы GeoJSON/CZML
  • параметры URL
  • WebSocket сообщения

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

  • скрытые события DOM
  • кодированные payload’ы
  • SVG-инъекции

Минимизация поверхности атаки

Снижение риска достигается архитектурно:

  • отключение HTML в описаниях, где не требуется
  • использование текстовых полей вместо description
  • отказ от inline HTML шаблонов
  • централизованная функция рендеринга UI

Пример безопасного слоя:

function renderDescription(text) {
    const div = document.createElement("div");
    div.textContent = text;
    return div;
}

Итоговые принципы защиты в CesiumJS

  • не передавать пользовательские данные напрямую в innerHTML
  • всегда очищать HTML перед вставкой в InfoBox и Entity.description
  • использовать DOMPurify или аналогичные библиотеки
  • предпочитать textContent вместо HTML-рендеринга
  • валидировать GeoJSON/CZML перед отображением
  • применять CSP как обязательный слой защиты
  • разделять данные сцены и UI-представление
  • минимизировать использование HTML в 3D-информационных панелях