В приложениях на базе Leaflet данные, поступающие от пользователя, могут попадать в карту через множество каналов: параметры URL, формы фильтрации, результаты API-запросов, GeoJSON-слои, поисковые строки, координаты, а также динамически формируемые описания объектов. Любой из этих источников потенциально становится входной точкой для внедрения вредоносного содержимого.
Особую опасность представляют данные, которые впоследствии интерполируются в HTML-контекст Leaflet-компонентов: попапов, tooltip’ов, кастомных маркеров и control-интерфейсов.
Leaflet позволяет привязывать HTML-контент к объектам карты:
marker.bindPopup("<b>Объект</b><br>" + userInput);
При отсутствии санитизации пользовательский ввод может содержать исполняемый HTML и Jav * aScript:
userInput = `<img src=x oner ror=alert(1)>`;
В результате контент попапа становится вектором XSS-атаки.
Особенность Leaflet заключается в том, что он не выполняет автоматическое экранирование строк при использовании HTML-режима.
GeoJSON-слои часто формируются на основе внешних данных:
L.geoJSON(data, {
onEachFeature: function (feature, layer) {
layer.bindPopup(feature.properties.description);
}
}).addTo(map);
Если feature.properties.description не очищается, любой
HTML или скрипт, встроенный в GeoJSON, становится частью DOM.
Часто координаты, фильтры и идентификаторы объектов берутся из URL:
const type = new URLSearchParams(location.search).get("type");
Дальнейшее использование таких параметров в построении HTML без проверки приводит к внедрению нежелательного содержимого в интерфейс карты.
Leaflet позволяет создавать пользовательские контролы:
L.control.custom = L.Control.extend({
onAdd: function () {
const div = L.DomUtil.create("div", "custom-control");
div.innerHTML = userHtml;
return div;
}
});
Использование innerHTML с внешними данными формирует
прямую поверхность атаки на DOM.
Наиболее надёжный подход — исключение HTML-контента из пользовательских данных:
marker.bindPopup(L.Util.escapeHTML(userInput));
Если используется Leaflet без дополнительных утилит, экранирование должно быть реализовано вручную либо через стандартные механизмы платформы.
При создании кастомных элементов:
const div = L.DomUtil.create("div", "popup");
div.textContent = userInput;
textContent гарантирует интерпретацию данных как текста,
исключая выполнение HTML.
Для случаев, когда допустим ограниченный HTML (например, жирный текст, ссылки), применяется строгая фильтрация:
const clean = DOMPurify.sanitize(userInput, {
ALLOWED_TAGS: ["b", "i", "a"],
ALLOWED_ATTR: ["href"]
});
marker.bindPopup(clean);
Библиотеки санитизации позволяют задавать белые списки тегов и атрибутов, минимизируя риск XSS.
GeoJSON требует особого внимания, так как часто поступает из внешних API.
function sanitizeProperties(properties) {
return {
name: String(properties.name),
description: DOMPurify.sanitize(properties.description, {
ALLOWED_TAGS: [],
ALLOWED_ATTR: []
})
};
}
L.geoJSON(data, {
onEachFeature: function (feature, layer) {
const props = sanitizeProperties(feature.properties);
layer.bindPopup(`
<div>
<strong>${props.name}</strong>
<p>${props.description}</p>
</div>
`);
}
});
Даже при использовании шаблонных строк сохраняется контроль над содержимым.
Любые элементы интерфейса Leaflet-карты, формируемые динамически, должны избегать прямой вставки HTML:
const container = L.DomUtil.create("div", "search-box");
const input = L.DomUtil.create("input");
input.type = "text";
input.addEventListener("input", (e) => {
handleSearch(e.target.value);
});
container.appendChild(input);
Использование DOM API вместо HTML-строк снижает риск внедрения скриптов.
Любые параметры из строки запроса требуют строгой типизации и валидации:
const params = new URLSearchParams(location.search);
const lat = parseFloat(params.get("lat"));
const lng = parseFloat(params.get("lng"));
if (Number.isFinite(lat) && Number.isFinite(lng)) {
map.setView([lat, lng], 13);
}
Отсутствие проверки типов открывает возможность передачи некорректных значений, влияющих на поведение карты.
При работе с динамическими данными предпочтение отдается белым спискам:
const allowedLayers = ["roads", "buildings", "parks"];
if (allowedLayers.includes(layerName)) {
addLayer(layerName);
}
Запрещающий подход (blacklist) менее надёжен, так как не учитывает неизвестные векторы атак.
Если данные приходят из сторонних сервисов и содержат HTML, их следует изолировать в безопасной области DOM:
const wrapper = document.createElement("div");
wrapper.appendChild(
DOMPurify.sanitize(externalHtml, { RETURN_DOM_FRAGMENT: true })
);
popup.setContent(wrapper);
Такой подход предотвращает прямую инъекцию в основной документ.
SVG-маркеры могут содержать встроенные события:
const icon = L.divIcon({
html: `<svg oncl ick="stealData()">...</svg>`
});
Любые inline-обработчики событий рассматриваются как критический риск. Безопасная реализация исключает их использование и формирует SVG через API или безопасные шаблоны.
URL тайловых слоёв также может стать источником подмены:
L.tileLayer(userTileUrl, {
attribution: "Map data"
}).addTo(map);
В случае отсутствия контроля возможна подмена источника тайлов на вредоносный сервер. Использование фиксированного списка провайдеров исключает подобный сценарий.
Любые координаты, строки описаний и идентификаторы проходят этап нормализации:
function normalizeString(value) {
return String(value)
.replace(/[<>]/g, "")
.trim()
.slice(0, 500);
}
Ограничение длины и удаление опасных символов снижает вероятность внедрения payload’ов.
Обработка данных разделяется на три уровня:
Такая архитектура предотвращает попадание необработанных данных в DOM Leaflet-карты.
function createPopupContent(data) {
const name = normalizeString(data.name);
const desc = DOMPurify.sanitize(data.description, {
ALLOWED_TAGS: ["b", "i"],
ALLOWED_ATTR: []
});
const container = document.createElement("div");
const title = document.createElement("strong");
title.textContent = name;
const text = document.createElement("p");
text.innerHTML = desc;
container.appendChild(title);
container.appendChild(text);
return container;
}
Экосистема Leaflet включает большое количество сторонних расширений. Любой плагин, работающий с HTML или внешними данными, рассматривается как потенциальный источник уязвимостей. Особое внимание требуется при использовании плагинов, изменяющих popups, markers или tile rendering.
Наиболее устойчивые решения используют:
innerHTML в пользовательских данныхТакой подход минимизирует поверхность атаки и сохраняет предсказуемость поведения карты даже при работе с недоверенными источниками данных