При использовании STOMP.js основная угроза инъекций связана не с самим протоколом STOMP, а с обработкой пользовательских данных внутри JavaScript-приложения, WebSocket-соединения и серверной инфраструктуры. STOMP-фреймы передают строки, заголовки и тела сообщений, которые могут содержать вредоносный контент.
Инъекции возникают в следующих случаях:
Наиболее распространённые угрозы:
Наиболее опасная область — body сообщения.
client.publish({
destination: '/app/chat',
body: userInput
});
Если userInput содержит:
<script>alert('XSS')</script>
и сервер или фронтенд позже вставит это содержимое через
innerHTML, произойдёт выполнение вредоносного
JavaScript.
STOMP поддерживает пользовательские заголовки.
client.publish({
destination: '/app/chat',
headers: {
username: userInput
},
body: message
});
Некоторые брокеры или прокси могут некорректно обрабатывать специальные символы:
admin\nreceipt:hacked
Это может привести к подмене STOMP-заголовков.
Опасность представляет динамическое формирование маршрутов.
client.subscribe(`/topic/${roomId}`, callback);
Если roomId не валидируется, злоумышленник может:
Пример опасного значения:
../. ./admin
или:
/topic/system
Часто сообщения сериализуются в JSON.
body: JSON.stringify({
user: username,
text: message
})
Если сервер небезопасно обрабатывает JSON-структуры, возможны:
Клиент получает сообщение:
client.subscribe('/topic/chat', (message) => {
chat.innerHTML += message.body;
});
Злоумышленник отправляет:
<img src=x oner ror="alert(document.cookie)">
В результате браузер выполняет код.
Безопасный вариант:
const div = document.createElement('div');
div.textContent = message.body;
chat.appendChild(div);
textContent не интерпретирует HTML.
Опасный код:
element.innerHTML = data;
Безопасная альтернатива:
element.textContent = data;
или:
element.append(document.createTextNode(data));
Если HTML необходим, используется очистка.
Пример с DOMPurify:
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(message.body);
container.innerHTML = clean;
Дополнительно рекомендуется запрещать:
<script>;jav * ascript: URL;Нельзя доверять данным из WebSocket/STOMP.
Небезопасно:
const data = JSON.parse(message.body);
render(data.username);
Без проверки злоумышленник может отправить:
{
"username": {
"toString": "() => alert(1)"
}
}
Использование строгих схем существенно снижает риск атак.
Пример с Zod:
import { z } from 'zod';
const MessageSchema = z.object({
username: z.string().max(50),
text: z.string().max(1000)
});
const parsed = MessageSchema.parse(
JSON.parse(message.body)
);
Преимущества:
Инъекции часто сопровождаются oversized payload.
Пример атаки:
'A'.repeat(50_000_000)
Это может вызвать:
if (message.length > 5000) {
throw new Error('Message too large');
}
Необходимо ограничивать:
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
Опасный код:
container.innerHTML = `
<div>${message.body}</div>
`;
Если сообщение содержит:
</div><script>alert(1)</script>
структура DOM будет нарушена.
const div = document.createElement('div');
div.textContent = message.body;
container.appendChild(div);
Следует запрещать:
\n\r:Пример фильтра:
function sanitizeHeader(value) {
return value.replace(/[\r\n:]/g, '');
}
Небезопасно:
headers.role = userInput;
Безопасно:
const allowedRoles = ['user', 'moderator'];
if (!allowedRoles.includes(role)) {
throw new Error('Invalid role');
}
Опасный вариант:
client.subscribe(`/topic/${roomId}`);
Безопасный вариант:
if (!/^[a-zA-Z0-9_-]+$/.test(roomId)) {
throw new Error('Invalid room id');
}
Необходимо исключать:
Сам STOMP не взаимодействует с БД, однако сервер часто сохраняет сообщения.
Опасный код:
db.query(`
INS ERT IN TO messages(text)
VALUES('${message.text}')
`);
Вредоносная строка:
'); DR OP TABLE messages; --
Безопасный вариант:
db.query(
'INS ERT IN TO messages(text) VALUES(?)',
[message.text]
);
MongoDB также подвержен инъекциям.
Опасный JSON:
{
"$ne": null
}
Если сервер напрямую использует объект:
users.find(req.body)
злоумышленник сможет обойти проверки.
Следует запрещать:
$.__proto__Пример:
function sanitizeObject(obj) {
for (const key in obj) {
if (key.startsWith('$')) {
delete obj[key];
}
}
return obj;
}
Через JSON возможно изменение прототипов.
Вредоносный payload:
{
"__proto__": {
"isAdmin": true
}
}
Последствия:
const safeObject = Object.create(null);
const forbidden = [
'__proto__',
'constructor',
'prototype'
];
for (const key of Object.keys(data)) {
if (forbidden.includes(key)) {
throw new Error('Forbidden key');
}
}
Логи часто недооцениваются.
Опасный ввод:
[ERROR] Admin logged in
или:
\n[CRITICAL] Database hacked
Злоумышленник способен:
function sanitizeLog(str) {
return str.replace(/[\r\n\t]/g, ' ');
}
Некоторые атаки используют heartbeat-фреймы для:
Следует ограничивать:
Даже валидные сообщения могут использоваться для атак.
Ограничиваются:
Пример:
const limiter = new Map();
Сервер обычно реализует:
Даже при XSS CSP снижает ущерб.
Пример:
Content-Security-Policy:
default-src 'self';
script-src 'self';
object-src 'none';
Современные браузеры поддерживают Trusted Types.
Это предотвращает:
Пример:
window.trustedTypes.createPolicy(
'default',
{
createHTML: (input) => DOMPurify.sanitize(input)
}
);
Следует контролировать тип данных:
headers: {
'content-type': 'application/json'
}
Нельзя автоматически исполнять:
Бинарные payload могут содержать:
Рекомендуется:
Клиентская проверка недостаточна.
Даже если фронтенд запрещает:
/topic/admin
злоумышленник способен отправить frame вручную.
Сервер обязан проверять:
Некоторые атаки вызывают бесконечные reconnect.
Неправильно:
reconnectDelay: 100
Без ограничений это создаёт DDoS на брокер.
Безопаснее:
client.reconnectDelay = 5000;
или:
delay = Math.min(delay * 2, 60000);
Для критичных конфигураций:
Object.freeze(config);
Это снижает риск runtime-подмены.
Все STOMP-сообщения рассматриваются как потенциально вредоносные:
Проверке подлежат:
Наиболее устойчивые системы используют несколько уровней защиты одновременно:
eval(message.body)
Критически опасная практика.
new Function(message.body)
Это эквивалент удалённого выполнения кода.
Object.assign(target, incomingData);
Возможна prototype pollution.
container.innerHTML = body;
Источник большинства DOM XSS.
client.subscribe(userInput);
Позволяет подписываться на любые каналы.
Эффективная защита STOMP.js-приложений строится на комбинации механизмов:
Отсутствие хотя бы одного уровня защиты существенно повышает вероятность успешной инъекции и компрометации приложения.