diff --git a/Overlord-Server/src/main-server.ts b/Overlord-Server/src/main-server.ts index 5e10985..6cfbc56 100644 --- a/Overlord-Server/src/main-server.ts +++ b/Overlord-Server/src/main-server.ts @@ -537,7 +537,7 @@ async function startServer() { takePendingNotificationScreenshot: takePendingNotificationScreenshotForClient, storeNotificationScreenshot: storeNotificationScreenshotForPending, handleNotificationScreenshotResult: notificationPluginHandlers.handleNotificationScreenshotResult, - handleConsoleOutput, + handleConsoleOutput: (clientId: string, payload: any) => handleConsoleOutput(clientId, payload), handleFileBrowserMessage: (clientId: string, payload: any) => forwardFileBrowserMessage(clientId, payload, { pendingHttpDownloads, diff --git a/Overlord-Server/src/server/routes/websocket-lifecycle-routes.ts b/Overlord-Server/src/server/routes/websocket-lifecycle-routes.ts index cb78c5c..8fd038a 100644 --- a/Overlord-Server/src/server/routes/websocket-lifecycle-routes.ts +++ b/Overlord-Server/src/server/routes/websocket-lifecycle-routes.ts @@ -65,7 +65,7 @@ type WsLifecycleDeps = { height?: number, ) => void; handleNotificationScreenshotResult: (clientId: string, payload: any) => void; - handleConsoleOutput: (payload: any) => void; + handleConsoleOutput: (clientId: string, payload: any) => void; handleFileBrowserMessage: (clientId: string, payload: any) => void; handleProxyTunnelData: (clientId: string, connectionId: string, data: Uint8Array) => void; handleProxyTunnelClose: (clientId: string, connectionId: string) => void; @@ -290,20 +290,24 @@ export async function handleWebSocketMessage( const countryRaw = geo?.country || (payload as any).country || "ZZ"; const country = /^[A-Z]{2}$/i.test(countryRaw) ? countryRaw.toUpperCase() : "ZZ"; + const _s = (v: unknown, max = 256): string | undefined => { + if (typeof v !== "string") return undefined; + return v.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").slice(0, max); + }; upsertClientRow({ id: resolvedId, - hwid: (payload as any).hwid || resolvedId, + hwid: _s((payload as any).hwid) || resolvedId, role: "client", ip: ip || undefined, - host: (payload as any).host || undefined, - os: (payload as any).os || undefined, - arch: (payload as any).arch || undefined, - version: (payload as any).version || undefined, - user: (payload as any).user || undefined, + host: _s((payload as any).host), + os: _s((payload as any).os), + arch: _s((payload as any).arch, 32), + version: _s((payload as any).version, 64), + user: _s((payload as any).user), monitors: (payload as any).monitors || undefined, - cpu: (payload as any).cpu || undefined, - gpu: (payload as any).gpu || undefined, - ram: (payload as any).ram || undefined, + cpu: _s((payload as any).cpu), + gpu: _s((payload as any).gpu), + ram: _s((payload as any).ram, 64), country, lastSeen: Date.now(), online: 0 as any, @@ -319,17 +323,17 @@ export async function handleWebSocketMessage( deps.notifyDashboard(); deps.notifyDashboardClientEvent("client_purgatory", { id: resolvedId, - host: (payload as any).host || undefined, - user: (payload as any).user || undefined, - os: (payload as any).os || undefined, + host: _s((payload as any).host), + user: _s((payload as any).user), + os: _s((payload as any).os), ip: ip || undefined, country, }); deps.broadcastClientEvent("client_purgatory", { id: resolvedId, - host: (payload as any).host || undefined, - user: (payload as any).user || undefined, - os: (payload as any).os || undefined, + host: _s((payload as any).host), + user: _s((payload as any).user), + os: _s((payload as any).os), ip: ip || undefined, country, }); @@ -468,7 +472,7 @@ export async function handleWebSocketMessage( deps.handleNotificationScreenshotResult(client.id, payload); break; case "console_output": - deps.handleConsoleOutput(payload); + deps.handleConsoleOutput(client.id, payload); break; case "file_list_result": case "file_download": diff --git a/Overlord-Server/src/server/ws-console-rd-hvnc.ts b/Overlord-Server/src/server/ws-console-rd-hvnc.ts index 1a6e462..82dbdea 100644 --- a/Overlord-Server/src/server/ws-console-rd-hvnc.ts +++ b/Overlord-Server/src/server/ws-console-rd-hvnc.ts @@ -911,11 +911,12 @@ export function handleConsoleViewerMessage(ws: ServerWebSocket, raw: } } -export function handleConsoleOutput(payload: any) { +export function handleConsoleOutput(clientId: string, payload: any) { const sessionId = payload.sessionId as string; if (!sessionId) return; const session = sessionManager.getConsoleSession(sessionId); if (!session) return; + if (session.clientId !== clientId) return; const data = payload.data ? textDecoder.decode(payload.data as Uint8Array) : ""; safeSendViewer(session.viewer, { type: "output", diff --git a/Overlord-Server/src/wsHandlers.ts b/Overlord-Server/src/wsHandlers.ts index 8b183ca..928915a 100644 --- a/Overlord-Server/src/wsHandlers.ts +++ b/Overlord-Server/src/wsHandlers.ts @@ -11,6 +11,12 @@ import { import { upsertClientRow } from "./db"; import { metrics } from "./metrics"; +/** Strip control chars and clamp length on client-supplied info strings. */ +function sanitizeInfoString(val: unknown, maxLen = 256): string | undefined { + if (typeof val !== "string") return undefined; + return val.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").slice(0, maxLen); +} + const MAX_PING_RTT_MS = 15_000; const CLIENT_DB_SYNC_INTERVAL_MS = Number(process.env.OVERLORD_CLIENT_DB_SYNC_MS || 5000); const lastClientDbSync = new Map(); @@ -80,23 +86,23 @@ export function handleHello( if (ip) { info.ip = ip; } - info.hwid = (payload as any).hwid as string | undefined; - info.host = payload.host; - info.os = payload.os; - info.arch = payload.arch; - info.version = payload.version; - info.user = payload.user; + info.hwid = sanitizeInfoString((payload as any).hwid); + info.host = sanitizeInfoString(payload.host); + info.os = sanitizeInfoString(payload.os); + info.arch = sanitizeInfoString(payload.arch, 32); + info.version = sanitizeInfoString(payload.version, 64); + info.user = sanitizeInfoString(payload.user); info.monitors = payload.monitors; info.monitorInfo = (payload as any).monitorInfo || info.monitorInfo; info.inMemory = !!(payload as any).inMemory; info.isAdmin = !!(payload as any).isAdmin; - info.elevation = typeof (payload as any).elevation === "string" ? (payload as any).elevation : info.elevation; + info.elevation = sanitizeInfoString((payload as any).elevation, 32) ?? info.elevation; if ((payload as any).permissions && typeof (payload as any).permissions === "object") { info.permissions = (payload as any).permissions; } - info.cpu = (payload as any).cpu || info.cpu; - info.gpu = (payload as any).gpu || info.gpu; - info.ram = (payload as any).ram || info.ram; + info.cpu = sanitizeInfoString((payload as any).cpu) || info.cpu; + info.gpu = sanitizeInfoString((payload as any).gpu) || info.gpu; + info.ram = sanitizeInfoString((payload as any).ram, 64) || info.ram; const geo = ip ? geoip.lookup(ip) : undefined; const countryRaw = geo?.country || (payload as any).country || info.country || "ZZ";