В приложениях, использующих интерактивные карты, значительная часть данных поступает из внешних источников: API геоданных, пользовательский ввод, GeoJSON-файлы, сторонние сервисы. Именно эта особенность делает подобные приложения чувствительными к XSS-уязвимостям (Cross-Site Scripting), особенно при использовании библиотек визуализации вроде Leaflet.
Leaflet активно работает с HTML-контентом внутри попапов, тултипов и кастомных слоёв. Это создаёт поверхность атаки, если данные вставляются без строгой фильтрации и экранирования.
XSS возникает, когда вредоносный JavaScript попадает в DOM и выполняется в контексте страницы. В Leaflet это чаще всего происходит через:
bindPopup и
bindTooltipinnerHTMLLeaflet не выполняет автоматическую санитизацию контента. Библиотека предполагает, что разработчик контролирует источник данных.
Наиболее частая точка возникновения XSS — метод
bindPopup.
marker.bindPopup(userInput);
Если userInput содержит HTML:
<img src=x oner ror=alert(1)>
код будет вставлен в DOM и выполнен.
Leaflet позволяет передавать HTML-строки напрямую, что делает любые неподготовленные данные потенциально опасными.
Более сложный вариант:
marker.bindPopup(`<b>${title}</b><br>${description}`);
Если title или description приходят извне,
внедрение <script> или обработчиков событий
становится возможным.
GeoJSON — один из наиболее частых источников данных в Leaflet через
L.geoJSON.
L.geoJSON(data, {
onEachFeature: function (feature, layer) {
layer.bindPopup(feature.properties.description);
}
}).addTo(map);
Если feature.properties.description контролируется
пользователем или внешним API, внедрение HTML превращается в прямой
XSS-вектор.
Особенно опасны поля:
properties.nameproperties.descriptionproperties.htmlДаже если GeoJSON приходит с доверенного сервера, компрометация источника данных автоматически переносится в клиент.
При создании собственных маркеров часто используется прямое управление DOM:
const div = L.DomUtil.create('div', 'custom-marker');
div.innerHTML = userContent;
Любая вставка innerHTML без очистки превращает слой в
потенциальный XSS-контейнер.
Даже более «безобидные» конструкции:
marker.bindPopup(`<div class="popup">${content}</div>`);
становятся уязвимыми, если content не экранирован.
Leaflet поддерживает кастомные оверлеи, включая SVG-слои и HTML-слои
через L.divIcon.
L.divIcon({
html: userHtml
});
или
L.svg().addTo(map);
SVG особенно опасен, поскольку поддерживает встроенные события:
<svg onl oad="alert(1)">
Если SVG генерируется из внешних данных без очистки, XSS становится прямым.
Часто Leaflet используется вместе с шаблонизаторами (Handlebars, Mustache, EJS).
Ошибки возникают при отключённом экранировании:
const html = `<div>${template(data)}</div>`;
marker.bindPopup(html);
Если шаблон вставляет значения без HTML-escape, любой пользовательский ввод становится исполняемым кодом.
Особенно опасны конструкции вида:
{{{raw}}} (Handlebars)unescaped output в EJSОсновной защитный механизм — очистка HTML перед вставкой в Leaflet.
Используются специализированные библиотеки:
Пример с DOMPurify:
const clean = DOMPurify.sanitize(userInput);
marker.bindPopup(clean);
DOMPurify удаляет:
jav * ascript:)Критически важный принцип — хранение данных отдельно от HTML.
Плохой подход:
feature.properties.popup = "<b>" + name + "</b>";
Корректный подход:
feature.properties.name = name;
и формирование HTML на клиенте:
layer.bindPopup(`
<b>${escapeHtml(feature.properties.name)}</b>
`);
где escapeHtml экранирует спецсимволы:
function escapeHtml(str) {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
Leaflet часто используется с обработчиками событий:
marker.on('click', function (e) {
popup.setContent(userContent);
});
Если userContent формируется динамически, XSS может
возникать не сразу, а через цепочку событий.
Особенно опасны конструкции:
Content Security Policy ограничивает выполнение скриптов даже при наличии инъекции.
Рекомендуемые ограничения:
unsafe-inlineunsafe-evalscript-src доменыПример политики:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';
CSP не заменяет санитизацию, но снижает ущерб.
Экосистема Leaflet включает множество сторонних плагинов:
Некоторые плагины используют innerHTML без очистки или
предполагают доверенные данные.
Типовая проблема:
L.popupPlugin.setContent(data.html);
если data.html поступает извне — возникает XSS вне
основного кода приложения.
Атаки часто выглядят «невинно» в структуре GeoJSON:
{
"type": "Feature",
"properties": {
"description": "<img src=x oner ror=alert(1)>"
}
}
При визуализации через popup это превращается в выполнение кода.
Даже URL-поля могут быть опасны:
"website": "jav * ascript:alert(1)"
Системный подход включает несколько уровней:
html: в L.divIcon при внешних
данныхСтрогая модель:
layer.bindPopup(document.createTextNode(name));
или:
layer.bindPopup(escapeHtml(name));
или использование безопасного DOM API:
const div = document.createElement('div');
div.textContent = userInput;
marker.bindPopup(div);
textContent полностью исключает интерпретацию HTML.
При использовании React/Vue/Svelte часто возникает конфликт моделей:
Опасный анти-паттерн:
marker.bindPopup(renderToString(component));
если компонент содержит непроверенные props.
Безопаснее:
dangerouslySetInnerHTML внутри map
callbacksРедакторы геометрии (draw tools) позволяют пользователю создавать объекты, которые затем сохраняются и отображаются.
Если такие объекты содержат HTML-поля, XSS становится постоянным (stored XSS):
Особенно критично для:
textContent вместо
innerHTMLonclick,
onload)