Security-хардening: XSS в никах, non-root контейнер, rate-limiting, спавн-фикс
Клиент: ники других игроков рендерились через innerHTML без экранирования — XSS. Добавлен escapeHtml() для nickname и в списке игроков комнаты. Сервер: - Санитайзинг ника и на бэкенде (обрезка длины, вырезание control/HTML символов) — защита не только для этого клиента, но и для любых будущих потребителей (логи, другие рендеры). - onMessage обёрнут в try/catch: payload.value<T>() кидает json::type_error на несовпадении типа поля — одно кривое сообщение могло уронить процесс со всеми активными играми. - Лимит на общее число комнат (200) — иначе клиент, спамящий lobby.create_room, разгонял rooms_ до исчерпания памяти. - Fix: одна из раскладок coop-карты клала укрытие поверх стартовой позиции игрока — танк рождался внутри кирпича и не мог выехать. Тайл под каждым стартовым танком теперь расчищается после построения карты. Docker: gameserver теперь работает от непривилегированного пользователя, а не от root. nginx: rate-limiting на /ws (5 подключений/с на IP, до 20 одновременных) — защита от флуда соединениями/комнатами; CSP и security-заголовки (X-Content-Type-Options, X-Frame-Options, Referrer-Policy).
This commit is contained in:
@@ -13,6 +13,18 @@ let errorMessage = '';
|
||||
let connected = false;
|
||||
let nickname = 'cacto-' + Math.floor(Math.random() * 1000);
|
||||
|
||||
// Ники приходят от других игроков (сервер их только режет по длине, не
|
||||
// экранирует) и попадают сюда через innerHTML — без экранирования это XSS.
|
||||
function escapeHtml(str) {
|
||||
return String(str).replace(/[&<>"']/g, (c) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
}[c]));
|
||||
}
|
||||
|
||||
function roomRow(room) {
|
||||
const full = room.players >= room.max_players;
|
||||
return `
|
||||
@@ -37,7 +49,7 @@ function renderLobbyView() {
|
||||
${connected ? '' : '<p class="lobby-error">подключение...</p>'}
|
||||
<div class="lobby-panel__row">
|
||||
<label class="prompt-inline">nickname</label>
|
||||
<input id="nickname-input" type="text" value="${nickname}" maxlength="20" ${dis} />
|
||||
<input id="nickname-input" type="text" value="${escapeHtml(nickname)}" maxlength="20" ${dis} />
|
||||
</div>
|
||||
<div class="lobby-panel__row">
|
||||
<button data-action="create" data-mode="coop" ${dis}>создать комнату (coop)</button>
|
||||
@@ -66,7 +78,7 @@ function renderRoomView() {
|
||||
<h3 class="prompt">room ${currentRoom.name}</h3>
|
||||
<p>режим: <span class="tag">${currentRoom.mode}</span></p>
|
||||
<ul class="room-players">
|
||||
${currentRoom.players.map((p) => `<li>${p.nickname}${p.ready ? ' — готов' : ''}</li>`).join('')}
|
||||
${currentRoom.players.map((p) => `<li>${escapeHtml(p.nickname)}${p.ready ? ' — готов' : ''}</li>`).join('')}
|
||||
</ul>
|
||||
${statusLine}
|
||||
${readyBtn}
|
||||
|
||||
Reference in New Issue
Block a user