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:
@@ -10,25 +10,61 @@ const docker = new Docker();
|
||||
* @param {http.Server} server - The HTTP server instance
|
||||
* @param {Object} log - Logger
|
||||
*/
|
||||
module.exports = function attachExecWS(server, log) {
|
||||
module.exports = function attachExecWS(server, log, authManager) {
|
||||
const wss = new WebSocketServer({ noServer: true });
|
||||
|
||||
server.on('upgrade', (req, socket, head) => {
|
||||
// Authenticate WebSocket upgrade request before accepting it
|
||||
server.on('upgrade', async (req, socket, head) => {
|
||||
const parsed = url.parse(req.url, true);
|
||||
const match = parsed.pathname.match(/^\/ws\/exec\/([a-zA-Z0-9_.-]+)$/);
|
||||
if (!match) return; // Not our route — let other handlers deal with it
|
||||
|
||||
const containerId = decodeURIComponent(match[1]);
|
||||
|
||||
// Validate container ID format to prevent injection
|
||||
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.test(containerId)) {
|
||||
log.warn('exec', 'Invalid container ID in WebSocket path', { containerId });
|
||||
socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check auth — require valid JWT or API key from the upgrade request
|
||||
let auth = null;
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader && authHeader.startsWith('Bearer ')) {
|
||||
const token = authHeader.substring(7);
|
||||
const payload = await authManager.verifyJWT(token);
|
||||
if (payload) {
|
||||
auth = { type: 'jwt', userId: payload.userId, scope: payload.scope || [] };
|
||||
}
|
||||
} else {
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
if (apiKey) {
|
||||
const keyData = await authManager.verifyAPIKey(apiKey);
|
||||
if (keyData) {
|
||||
auth = { type: 'apikey', keyId: keyData.keyId, scope: keyData.scopes || [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!auth) {
|
||||
log.warn('exec', 'Unauthenticated WebSocket exec attempt', { containerId, ip: req.socket.remoteAddress });
|
||||
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Auth passed — proceed with WebSocket upgrade
|
||||
wss.handleUpgrade(req, socket, head, (ws) => {
|
||||
handleExec(ws, containerId, log);
|
||||
handleExec(ws, containerId, log, auth);
|
||||
});
|
||||
});
|
||||
|
||||
return wss;
|
||||
};
|
||||
|
||||
async function handleExec(ws, containerId, log) {
|
||||
async function handleExec(ws, containerId, log, auth) {
|
||||
let execStream = null;
|
||||
let execInstance = null;
|
||||
|
||||
@@ -42,6 +78,12 @@ async function handleExec(ws, containerId, log) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info('exec', 'Authenticated exec session started', {
|
||||
containerId,
|
||||
authType: auth.type,
|
||||
authId: auth.type === 'jwt' ? auth.userId : auth.keyId
|
||||
});
|
||||
|
||||
// Detect available shell
|
||||
let shell = '/bin/sh';
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user