T09 · Insecure Skill Coding Practices
Error
- Location
- seo-analyzer.js:26
- Finding
- Unrestricted Server-Side URL Fetching Enables SSRF<![CDATA[ ## Vulnerability Details **File Location**: `seo-analyzer.js:26-48`, with user-controlled invocation at `seo-analyzer.js:235-248` **Vulnerability Type**: Server-Side Request Forgery (SSRF) **Risk Level**: High ### Vulnerable Code ```js function fetchPage(url) { return new Promise((resolve, reject) => { const parsedUrl = new URL(url); const client = parsedUrl.protocol === 'https:' ? https : http; const options = { hostname: parsedUrl.hostname, path: parsedUrl.pathname + parsedUrl.search, method: 'GET', headers: { 'User-Agent': 'Mozilla/5.0 (compatible; SEO-Analyzer/1.0)' }, timeout: 10000 }; const req = client.request(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => resolve(data)); }); req.on('error', reject); req.on('timeout', () => reject(new Error('Request timeout'))); req.end(); }); } ``` The request target originates directly from the command-line argument: ```js async function main() { const url = process.argv[2]; if (!url) { console.log('Usage: node seo-analyzer.js <url>'); console.log('Example: node seo-analyzer.js https://example.com'); process.exit(1); } // Add protocol if missing const fullUrl = url.match(/^https?:\/\//) ? url : `https://${url}`; try { log(colors.blue, `🔍 Analyzing ${fullUrl}...`); const html = await fetchPage(fullUrl); const results = analyzeHTML(html, fullUrl); printResults(results); } catch (error) { log(colors.red, `Error: ${error.message}`); process.exit(1); } } ``` ### Technical Analysis The command-line argument controls the hostname, port through the parsed URL, request path, query string, and whether the request uses HTTP or HTTPS. The application performs the request from its own network context without applying: - An approved-host allowlist. - Public-address validation. - DNS resolution and resolve ...[truncated 2624 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Restrict destination protocols** - Explicitly permit only `http:` and `https:`. - Reject URLs containing unsupported schemes or malformed authority components. 2. **Resolve and validate every destination** - Resolve the hostname before connecting. - Reject all loopback, private, link-local, multicast, unspecified, reserved, and documentation address ranges. - Apply equivalent validation to IPv4, IPv6, and IPv4-mapped IPv6 addresses. - Explicitly block known cloud metadata addresses, including `169.254.169.254`. 3. **Prevent DNS rebinding** - Validate every resolved address, not only the hostname string. - Connect only to a validated resolved address while preserving the intended hostname for TLS verification. - Re-resolve and revalidate the destination for every new connection. 4. **Control redirects** - If redirect support is added, apply the same protocol, hostname, and resolved-IP validation to every redirect target. - Set a small maximum redirect count. 5. **Prefer an allowlist or controlled egress proxy** - Where operationally possible, limit requests to explicitly approved public domains. - Route requests through an egress proxy that enforces destination and address-range policies. - Apply network firewall rules preventing the process from reaching metadata endpoints and internal management networks. 6. **Limit response resources** - Set a strict maximum response-body size. - Abort and destroy the request when the limit is exceeded. - Validate response content types before processing. - On timeout, call `req.destroy()` rather than only rejecting the promise. 7. **Add regression tests** - Verify rejection of loopback, RFC 1918, link-local, IPv6-local, metadata, alternative numeric IP notation, and DNS-rebinding cases. - Verify that ordinary public HTTP and HTTPS destinations remain functional. ]]>
