T07 · Tool Hijacking and Spoofing
- Location
scripts/relay.mjs:209- Finding
Unauthenticated WebSocket Allows Chrome Extension Impersonation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/relay.mjs, lines 209-235 and 258-273
Vulnerability Type: Unauthenticated local WebSocket and privileged tool spoofing
Risk Level: HighTechnical Analysis
The relay accepts a WebSocket upgrade after checking only the standard WebSocket headers. It does not authenticate the connecting client, validate the request path, verify the
Originheader, or perform an extension-specific challenge-response handshake:js this.httpServer.on('upgrade', (req, socket, head) => this._handleUpgrade(req, socket)); _handleUpgrade(req, socket) { const upgrade = (req.headers.upgrade || '').toLowerCase(); const key = req.headers['sec-websocket-key']; if (upgrade !== 'websocket' || !key) { socket.write('HTTP/1.1 400 Bad Request\r\n\r\n'); socket.destroy(); return; } const accept = crypto.createHash('sha1') .update(key + WS_MAGIC) .digest('base64'); const response = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', `Sec-WebSocket-Accept: ${accept}`, '', '' ].join('\r\n'); socket.write(response); const conn = new WSConnection(socket); this.emit('connection', conn, req); }The first connection is unconditionally treated as the trusted Chrome extension. Subsequent connections, including the legitimate extension, are rejected:
js this.wss.on('connection', (conn) => { if (this.ws) { // First-connection-wins: a connection is already active log('Rejecting new connection (already have one)'); conn.close(1000, 'busy'); return; } log('Extension connected'); this.ws = conn; this.lastPongAt = Date.now(); if (this._extensionReadyResolve) { this._extensionReadyResolve(); this._extensionReadyResolve = null; this._extensionReadyReject = null; } conn.on('message', (text) => this._onMessage(text)); conn.on('close', () => this._onClose()); conn.on('error', (err) => log('Conn ...[truncated 2186 chars]- Remediation
View remediation
Remediation Suggestions
- Generate a cryptographically random, per-installation authentication secret and store it with owner-only permissions.
- Require the secret during the WebSocket upgrade or immediately perform an authenticated challenge-response handshake before assigning the connection to
this.ws. - Validate the
Originheader against the exact expected Chrome extension origin and reject ordinary webpage origins. Do not rely on origin validation as the sole authentication control. - Accept upgrades only on a dedicated, unpredictable or strictly validated WebSocket path.
- Authenticate the localhost HTTP API as well, preventing unrelated local processes or webpages from submitting privileged browser operations.
- Do not let an unauthenticated first connection permanently exclude the legitimate extension.
- Rotate the authentication secret after suspected exposure and avoid placing it in URLs or logs.
- Add negative tests covering ordinary web origins, missing or incorrect tokens, replayed handshakes, and competing connections.
