В приложениях, использующих Mapbox и библиотеку Mapbox GL JS, уязвимости класса XSS (Cross-Site Scripting) возникают не в самом WebGL-рендерере, а на границе взаимодействия карты с пользовательскими данными и DOM.
Основной риск формируется там, где геоданные или свойства объектов попадают в HTML-контекст без экранирования: всплывающие окна, маркеры, атрибуты слоёв и кастомные UI-компоненты.
Наиболее частый источник XSS — свойства объектов GeoJSON:
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [30.5, 50.5]
},
"properties": {
"title": "<img src=x oner ror=alert(1)>",
"description": "<script>alert('xss')</script>"
}
}
При отображении этих полей в попапах или UI без фильтрации возникает выполнение произвольного JavaScript.
Mapbox GL JS предоставляет API для всплывающих окон:
new mapboxgl.Popup()
.setLngLat([30.5, 50.5])
.setHTML(feature.properties.description)
.addTo(map);
Метод setHTML() является прямой точкой риска, поскольку
вставляет строку как HTML без автоматической санации.
Безопасная альтернатива:
new mapboxgl.Popup()
.setLngLat([30.5, 50.5])
.setText(feature.properties.description)
.addTo(map);
setText() использует textContent, исключая
интерпретацию HTML.
Создание маркеров через DOM:
const el = document.createElement('div');
el.innerHTML = feature.properties.iconHtml;
new mapboxgl.Marker(el).setLngLat([30, 50]).addTo(map);
Опасность возникает при использовании innerHTML с
данными, пришедшими извне.
Безопасный вариант:
const el = document.createElement('div');
el.textContent = feature.properties.label;
new mapboxgl.Marker(el).setLngLat([30, 50]).addTo(map);
Любая функция, связанная с:
setHTMLinnerHTMLявляется потенциальным вектором XSS.
Стили Mapbox используют выражения:
"paint": {
"text-field": ["get", "name"]
}
Сам по себе text-field не выполняет HTML, однако при
использовании format-выражений и кастомных UI-слоёв риск
появляется при переносе значения в DOM.
Типичный анти-паттерн:
const html = `
<h3>${feature.properties.title}</h3>
<p>${feature.properties.description}</p>
`;
popup.setHTML(html);
Если title или description управляются
пользователем, это прямой XSS.
DOM API:
const el = document.createElement('div');
el.textContent = userInput;
Ключевой принцип: никакого HTML из недоверенных источников.
Если HTML необходим, данные должны быть экранированы:
function escapeHtml(str) {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
Использование:
popup.setHTML(`
<h3>${escapeHtml(feature.properties.title)}</h3>
`);
setHTML() допустим только при работе с:
Дополнительный слой защиты:
Content-Security-Policy: default-src 'self'; script-src 'self'
Это ограничивает выполнение inline-scripts даже при ошибках в коде.
Перед добавлением данных в источник карты:
function sanitizeFeature(feature) {
return {
...feature,
properties: {
title: String(feature.properties.title),
description: String(feature.properties.description)
}
};
}
Более строгий вариант:
const blacklist = /<script|onerror|onload|jav * ascript:/i;
function isSafe(value) {
return !blacklist.test(value);
}
Рекомендуемая модель:
map.on('click', 'points', (e) => {
const props = e.features[0].properties;
const el = document.createElement('div');
const title = document.createElement('h3');
title.textContent = props.title;
const desc = document.createElement('p');
desc.textContent = props.description;
el.appendChild(title);
el.appendChild(desc);
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setDOMContent(el)
.addTo(map);
});
Ключевая идея: setDOMContent вместо setHTML.
Маркер с пользовательским HTML:
const el = document.createElement('div');
el.innerHTML = userData.badge;
Безопасная замена:
const el = document.createElement('div');
el.className = 'marker';
el.textContent = userData.label;
Даже изображения могут быть вектором атаки:
const img = document.createElement('img');
img.src = feature.properties.imageUrl;
Риски:
jav * ascript: URIЗащита:
function isSafeUrl(url) {
return /^https:\/\//.test(url);
}
map.on('click', (e) => {
console.log(e.features);
});
Опасность возникает при передаче e.features напрямую в
DOM без обработки.
Даже стили могут содержать пользовательские строки:
map.setLayoutProperty('labels', 'text-field', ['get', 'name']);
Сам рендер безопасен, но риск появляется при выводе значений в HTML-интерфейс.
innerHTML для данных из GeoJSONsetHTML для описаний объектовДанные в Mapbox-приложениях следует разделять:
Каждый слой должен обрабатываться отдельно перед попаданием в DOM.
Архитектурный принцип:
GeoJSON → sanitizer → UI model → DOM render
Безопасная работа с Mapbox GL JS строится вокруг трёх принципов: