Fix 5 critical security vulnerabilities

1. WebSocket exec auth bypass (exec.js): Require valid JWT or API key
   before accepting WebSocket upgrade. Reject unauthenticated requests
   with 401 before the upgrade completes.

2. Shell injection in router auto-login (session-handlers.js): Validate
   baseUrl against safe hostname pattern before embedding in wget shell
   command. Reject with null session if invalid.

3. Path traversal in credentials routes (services.js): Add explicit
   serviceId validation (alphanumeric + dash/underscore/dot, max 100
   chars) to all three credential endpoints. Removed redundant
   try/catch wrapper.

4. execSync injection in CA CSR generation (ca.js): Add sanitize step
   replacing any non-alphanumeric domain chars with underscore before
   interpolation into shell subj argument. Redundant with existing
   validation but provides defense-in-depth.

5. Auth bypass when TOTP disabled (middleware.js): Split the logic
   cleanly — disabled TOTP means no auth (initial setup state), enabled
   TOTP means all auth methods checked (session/JWT/API key). Removed
   the sessionDuration:never conflating shortcut.
This commit is contained in:
Hermes
2026-05-27 18:05:35 -07:00
parent 445da9f5fc
commit 17edb3bc90
6 changed files with 92 additions and 28 deletions
@@ -35,6 +35,12 @@ module.exports = function({ authManager: _authManager, credentialManager: _crede
extraHeaders['Authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
break;
case 'router': {
// Validate baseUrl is a safe hostname before using in shell command
if (!baseUrl || typeof baseUrl !== 'string' || !/^https?:\/\/[a-zA-Z0-9]([a-zA-Z0-9.\-]{0,253}[a-zA-Z0-9])?(:\d{1,5})?(\/|$)/.test(baseUrl)) {
log.warn('auth', 'Router auto-login rejected: invalid baseUrl', { serviceId, baseUrl: String(baseUrl).substring(0, 50) });
appSessionCache.set(serviceId, { failed: true, exp: Date.now() + SESSION_TTL.FAILED_LOGIN });
return null;
}
const routerBody = `username=${formEncode(username)}&password=${formEncode(password)}&Continue=Continue`;
try {
const { spawnSync } = require('child_process');