T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- assets/runtime/bridge.mjs:469
- Finding
- Unauthenticated Bridge Management API Exposes Secrets, Logs, Configuration, and Message-Sending Capabilities<![CDATA[ ## Vulnerability Details **File Location**: `assets/runtime/bridge.mjs:469-513` **Vulnerability Type**: Missing authentication and authorization on a network-accessible management API **Risk Level**: Critical ### Vulnerable Code ```javascript app.get('/logs', (_req, res) => { try { res.json(readdirSync(LOG_DIR).filter(name => name.endsWith('.log')).sort()); } catch { res.json([]); } }); app.get('/logs/:filename', (req, res) => { const path = resolve(LOG_DIR, req.params.filename); if (!existsSync(path)) { res.status(404).send('not found'); return; } res.type('text/plain; charset=utf-8').send(readFileSync(path, 'utf-8')); }); app.get('/config', (_req, res) => { res.json(config); }); app.post('/config', (req, res) => { config = deepMerge(config, req.body || {}); saveConfig(config); res.json({ ok: true, config }); }); app.post('/send_qq', async (req, res) => { const { type, target, message } = req.body || {}; if (!type || !target || !message) { res.status(400).json({ error: 'need type, target, message' }); return; } try { const data = await sendQQMessage(type, target, message, true); res.json({ ok: true, data }); } catch (error) { res.status(502).json({ ok: false, error: error.message }); } }); app.listen(Number(config.bridge.httpPort || 3002), () => { console.log(`[bridge] HTTP listening on ${config.bridge.httpPort || 3002}`); }); ``` ### Technical Analysis The Express application does not authenticate or authorize any management endpoint. Because `app.listen()` does not specify a loopback address, Node.js ordinarily listens on all available interfaces. The exposed endpoints provide security-sensitive capabilities: - `/logs` and `/logs/:filename` disclose stored QQ conversations. - `/config` returns the complete runtime configuration, including the NapCat API bearer token and QQ identifiers. - `POST /config` permits arbitrary configuration changes. - `POST /send_qq` sends ...[truncated 1208 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Bind the API explicitly to loopback: ```javascript app.listen(port, '127.0.0.1', callback); ``` 2. Require a separately generated, high-entropy bearer token on every endpoint. 3. Remove `/config`, `/logs`, and `/send_qq` unless operationally necessary. 4. Return a redacted configuration that never includes API tokens. 5. Apply role-based authorization so read-only health checks cannot send messages or alter settings. 6. Validate log filenames against a strict allowlist such as `^[A-Za-z0-9._-]+\.log$`. 7. Resolve the requested file and verify that it remains beneath `resolve(LOG_DIR)` before reading it. 8. Add host firewall rules restricting the bridge port to the local machine. ]]>
