T09 · Insecure Skill Coding Practices
- Location
bin/board.js:35- Finding
Unbounded Remote Response Buffering Can Cause Memory Exhaustion
- Content
View full analysis
Vulnerability Details
File Location:
bin/board.js, lines 35-59
Vulnerability Type: Unbounded network response buffering and incomplete timeout handling
Risk Level: Mediumjavascript const timeout = setTimeout(() => reject(new Error('请求超时')), 15000); const req = https.get(url, { headers: { 'Referer': 'https://finance.eastmoney.com', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { clearTimeout(timeout); try { const result = parseData(data, type); resolve(result); } catch (e) { reject(e); } }); }); req.on('error', (e) => { clearTimeout(timeout); reject(e); });Technical Analysis
The application accumulates the entire response from a remote quote service in the
datastring without enforcing a maximum response size. A remote server can therefore return an arbitrarily large or continuously streamed response, causing the Node.js process to consume increasing amounts of memory.The 15-second timer only rejects the surrounding promise. It does not call
req.destroy()or otherwise terminate the active HTTP request. Consequently, response event handlers may continue receiving and buffering data after the promise has timed out.The application also does not validate the HTTP status code or response content type before buffering and parsing the body. This permits error pages and other unexpected content to consume resources before eventually causing JSON parsing to fail.
Attack Path
- A user invokes the
boardcommand. - The command connects to the fixed external East Money API endpoint.
- The upstream service, or infrastructure capable of compromising that service, responds with an oversized or indefinitely streamed body.
- The
dataevent handler repeatedly append ...[truncated 1063 chars]
- A user invokes the
- Remediation
View remediation
Remediation Suggestions
- Enforce a strict maximum response size appropriate for the expected quote payload.
- Track the total bytes received and immediately call
req.destroy()when the configured limit is exceeded. - In the timeout handler, destroy the active request before rejecting the promise.
- Validate that the response has a successful HTTP status before reading its body.
- Validate the expected JSON content type where supported by the upstream service.
- Reject or tightly constrain redirects so the request cannot be redirected to an untrusted destination.
- Ensure cleanup occurs only once and remove or disable event handlers after timeout or rejection.
Example hardening pattern:
javascript const MAX_RESPONSE_SIZE = 2 * 1024 * 1024; let settled = false; const req = https.get(url, options, (res) => { if (res.statusCode !== 200) { res.resume(); req.destroy(new Error(`Unexpected HTTP status: ${res.statusCode}`)); return; } let size = 0; const chunks = []; res.on('data', (chunk) => { size += chunk.length; if (size > MAX_RESPONSE_SIZE) { req.destroy(new Error('Response exceeds maximum allowed size')); return; } chunks.push(chunk); }); res.on('end', () => { if (settled) return; settled = true; clearTimeout(timeout); try { resolve(parseData(Buffer.concat(chunks).toString('utf8'), type)); } catch (error) { reject(error); } }); }); const timeout = setTimeout(() => { if (settled) return; settled = true; req.destroy(new Error('Request timed out')); }, 15000);
