В веб-картографии основная поверхность атаки формируется за счёт динамического отображения внешних данных: GeoJSON, WMS/GetFeatureInfo, атрибутов объектов, пользовательских слоёв и всплывающих окон. Любая точка, полигон или подпись часто содержит произвольные поля, которые интерпретируются интерфейсом как текст или HTML. Именно здесь возникает ключевая проблема XSS: данные становятся частью DOM без достаточной очистки.
В контексте OpenLayers опасность усиливается тем, что библиотека предоставляет гибкие механизмы рендеринга через стили, попапы и пользовательские функции отрисовки. Эта гибкость требует строгого разделения между данными и представлением.
Каждый объект векторного слоя может содержать произвольные свойства:
Опасность возникает при использовании таких значений в интерфейсе:
Классическая ошибка — прямое внедрение:
popup.innerHTML = feature.get('description');
Если description приходит из внешнего GeoJSON или API,
он становится полноценным XSS-вектором.
Overlays часто строятся через innerHTML, так как
разработчики хотят поддерживать форматирование. Это создаёт риск
внедрения:
<script>onerrorjav * ascript: URIОсобенно опасны сценарии, где popup формируется через шаблон:
content.innerHTML = `
<h3>${name}</h3>
<p>${description}</p>
`;
Даже если имя кажется безопасным, оно может содержать закрывающие теги и внедрение новых DOM-узлов.
GeoJSON часто загружается с API или пользовательских сервисов. Основные проблемы:
Пример опасного поля:
{
"type": "Feature",
"properties": {
"name": "<img src=x oner ror=alert(1)>"
}
}
Если это значение напрямую отображается, возникает XSS.
Запросы GetFeatureInfo могут возвращать HTML-ответы от
серверов ГИС. Если сервер не очищает данные, клиентское приложение
автоматически становится уязвимым.
Типичные проблемы:
OpenLayers позволяет использовать SVG-иконки и динамические стили. Опасные ситуации:
icon.srcSVG может содержать:
onloadonclick<script>Базовое правило: данные никогда не должны интерпретироваться как HTML.
Безопасный подход:
textContent вместо
innerHTMLconst el = document.createElement('div');
el.textContent = feature.get('description');
Если HTML всё же необходим (например, для форматирования карточек объектов), требуется строгая санация:
Пример подхода:
import DOMPurify from 'dompurify';
popup.innerHTML = DOMPurify.sanitize(rawHtml);
Важно: даже очищенный HTML должен быть минимизирован по возможностям.
Шаблоны интерфейса не должны включать прямую интерполяцию внешних данных:
// небезопасно
`<div>${feature.get('name')}</div>`
Безопаснее:
const div = document.createElement('div');
div.appendChild(document.createTextNode(feature.get('name')));
При обработке кликов по карте часто извлекаются объекты:
map.on('click', (event) => {
const feature = map.forEachFeatureAtPixel(event.pixel, f => f);
});
Опасность возникает при немедленном отображении свойств без проверки.
Безопасный подход:
Хотя координаты кажутся безопасными, они могут использоваться в:
Опасность возникает при вставке без проверки:
innerHTML = `Lat: ${lat}, Lon: ${lon}`;
Даже числовые данные должны выводиться как текстовые узлы.
Подключение тайловых серверов:
Важно:
Любые ссылки из данных должны проходить фильтрацию:
jav * ascript:Опасный пример:
<a href="jav * ascript:alert(1)">link</a>
CSP снижает ущерб даже при ошибках:
Рекомендуемая стратегия:
script-src 'self''unsafe-inline'img-srcCSP не заменяет санацию, но блокирует большинство XSS-пейлоадов.
OpenLayers позволяет использовать функции:
style: function(feature) {
return new Style({
text: new Text({
text: feature.get('name')
})
});
}
Опасность:
Правило:
Перед использованием внешних источников:
Пример подхода:
name, id,
type)Иконки часто загружаются по URL из данных:
Риски:
Меры:
Архитектурный принцип:
Типичная схема:
Каждая из этих ошибок приводит к тому, что внешние данные становятся исполняемым контентом внутри страницы.
const name = feature.get('name');
const el = document.createElement('div');
const title = document.createElement('strong');
title.textContent = name;
el.appendChild(title);
popup.setElement(el);
Ключевая идея: никакого HTML-строкового конструирования.