T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/fetch-news.js:21
- Finding
- Unrestricted Redirect Following Enables SSRF and Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch-news.js:21-37` **Vulnerability Type**: Unrestricted redirects, unbounded response buffering, and missing redirect limits **Risk Level**: Medium ### Vulnerable Code ```javascript function fetch(url) { return new Promise((resolve, reject) => { const mod = url.startsWith('https') ? https : http; mod.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' }, timeout: 10000 }, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { return fetch(res.headers.location).then(resolve).catch(reject); } let data = ''; res.on('data', c => data += c); res.on('end', () => resolve(data)); res.on('error', reject); }).on('error', reject); }); } ``` ### Technical Analysis The `fetch` function recursively follows every HTTP redirect without validating the destination protocol, hostname, resolved IP address, or redirect count. Although the initial feed URLs are hardcoded, any configured feed server can return an attacker-controlled `Location` header. Consequently, a compromised or malicious feed server can redirect the process to loopback, link-local, or private-network services. It can also redirect an HTTPS request to plaintext HTTP. Recursive redirects have no maximum depth, allowing redirect loops to consume resources. The response is accumulated into the `data` string without a maximum body size. A server can therefore return a very large or indefinitely streamed body and cause excessive memory consumption. The request timeout does not replace explicit body-size, redirect-count, and total-operation limits. ### Attack Path 1. An attacker compromises one of the configured RSS servers or otherwise gains control over its HTTP response. 2. The server returns a `3xx` response with a `Location` header pointing to an internal service, such as a loopback or private-network HTTP endpoint. 3. The script recursively calls `fet ...[truncated 1237 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Permit redirects only to an explicit allowlist of expected RSS hostnames. 2. Require HTTPS for the initial request and every redirect destination; reject HTTP downgrades. 3. Resolve redirects safely with `new URL(location, currentUrl)` so relative redirects are handled predictably. 4. Resolve destination hostnames and reject loopback, link-local, private, multicast, and otherwise non-public IP address ranges for both IPv4 and IPv6. 5. Repeat destination validation after every redirect and DNS resolution to reduce DNS rebinding exposure. 6. Add a strict redirect limit, such as three redirects. 7. Reject unsupported URL schemes and URLs containing unexpected credentials or malformed hostnames. 8. Enforce a maximum response size and destroy the request when the limit is exceeded. 9. Add explicit request, idle, and total-operation time limits and abort the request when they expire. 10. Validate acceptable status codes and content types before parsing the body. A hardened implementation should carry redirect state explicitly, for example: ```javascript const MAX_REDIRECTS = 3; const MAX_RESPONSE_BYTES = 2 * 1024 * 1024; async function fetchFeed(url, redirects = 0) { if (redirects > MAX_REDIRECTS) { throw new Error('Redirect limit exceeded'); } const parsed = new URL(url); if (parsed.protocol !== 'https:') { throw new Error('Only HTTPS feed URLs are allowed'); } if (!ALLOWED_FEED_HOSTS.has(parsed.hostname)) { throw new Error('Feed hostname is not allowed'); } // Resolve and reject private, loopback, and link-local addresses here. // Abort if the body exceeds MAX_RESPONSE_BYTES. // Revalidate every redirect before following it. } ``` ]]>
