T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/cdp-proxy.mjs:323- Finding
Unauthenticated Local API Enables Cross-User Control of Authenticated Browser Sessions
- Content
View full analysis
Vulnerability Details
File Location:
scripts/cdp-proxy.mjs:323-585
Vulnerability Type: Missing authentication and authorization on a privileged browser-control API
Risk Level: HighTechnical Analysis
The CDP proxy exposes a loopback HTTP service without authenticating callers or authorizing operations. Although it binds only to
127.0.0.1, loopback is generally shared by all users and processes on the host rather than isolated to the operating-system account that launched the proxy.The request handler accepts endpoint paths, query parameters, and bodies from any process able to connect to port 3456:
js const server = http.createServer(async (req, res) => { const parsed = new URL(req.url, `http://localhost:${PORT}`); const pathname = parsed.pathname; const q = Object.fromEntries(parsed.searchParams); if (q.target) touchTab(q.target); res.setHeader('Content-Type', 'application/json; charset=utf-8'); try { // /health 不需要连接浏览器 if (pathname === '/health') { const connected = ws && (ws.readyState === WS.OPEN || ws.readyState === 1); res.end(JSON.stringify({ status: 'ok', connected, browser: connectedBrowser, sessions: sessions.size, managedTabs: managedTabs.size, chromePort, })); return; } await connect(); // GET /targets - 列出所有页面 if (pathname === '/targets') { const resp = await sendCDP('Target.getTargets'); const pages = resp.result.targetInfos.filter(t => t.type === 'page'); res.end(JSON.stringify(pages, null, 2)); }The
/targetsendpoint discloses every browser page and its target identifier. Those identifiers can then be supplied to/eval, which executes arbitrary JavaScript in the selected page:js else if (pathname === '/eval') { const sid = await ensureSession(q.target); const body = await readBody(req); ...[truncated 4925 chars]- Remediation
View remediation
Remediation Suggestions
- Generate a cryptographically random secret for each proxy launch and require it as a bearer token on every endpoint, including read-only endpoints such as
/healthand/targets. - Store the token in an owner-only file with mode
0600, or transfer it through an inherited file descriptor rather than exposing it in process arguments. - Prefer an owner-restricted Unix domain socket on Unix-like systems or an access-controlled named pipe on Windows. This permits enforcement using operating-system peer identity.
- Enforce strict
Originvalidation and reject browser-originated requests unless explicitly authorized. Authentication must remain the primary control because origin checks alone do not protect against local native processes. - Restrict privileged endpoints to tabs created and tracked by the proxy. Do not permit arbitrary target IDs returned by the browser to be attached without explicit authorization.
- Constrain
/setFilesinputs to user-approved files or an allowlisted directory. Resolve paths canonically and reject paths outside the authorized boundary. - Remove caller-selected filesystem destinations from
/screenshot, or constrain output to a dedicated directory using exclusive file creation and canonical path validation. - Use POST or DELETE for state-changing operations and add request size limits, method checks, and structured schema validation.
- Stop the proxy when the authorized task ends, or enforce a short inactivity shutdown period, rather than leaving a high-authority service detached indefinitely.
- Add automated tests proving that unauthenticated requests, requests from unauthorized origins, arbitrary target IDs, and paths outside approved directories are rejected.
- Generate a cryptographically random secret for each proxy launch and require it as a bearer token on every endpoint, including read-only endpoints such as
