T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/upload.js:433
- Finding
- Unrestricted URL Fetching Enables SSRF and Public Data Exfiltration## Vulnerability Details **File Location**: `scripts/upload.js:433-457`, `scripts/upload.js:480-485`, `scripts/upload.js:517-524`, and `scripts/upload.js:572-578` **Vulnerability Type**: Server-Side Request Forgery (SSRF) with public disclosure **Risk Level**: High **Vulnerable code:** ```js function getBufferFromUrl(url) { return new Promise((resolve, reject) => { const parsed = new URL(url); const client = parsed.protocol === 'https:' ? https : http; client.get(url, { timeout: 60000 }, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { return getBufferFromUrl(res.headers.location).then(resolve).catch(reject); } const chunks = []; let size = 0; res.on('data', (chunk) => { size += chunk.length; if (size > MAX_SIZE) { res.destroy(); reject(new Error('File exceeds the 100 MB limit')); return; } chunks.push(chunk); }); res.on('end', () => resolve(Buffer.concat(chunks))); res.on('error', reject); }).on('error', reject); }); } ``` ```js if (/^https?:\/\//i.test(input)) { try { buffer = await getBufferFromUrl(input); } catch (e) { console.error('Download failed:', e.message || e); process.exit(1); } ``` The downloaded bytes are subsequently made publicly readable: ```js await client.put(key, buffer, { timeout: 600000, headers: { 'content-type': getContentType(filename), 'x-oss-object-acl': 'public-read', }, }); ``` ```js const putParams = { Bucket: bucket, Region: region, Key: key, Body: buffer, ContentType: getContentType(filename), ACL: 'public-read', }; ``` ### Technical Analysis The script accepts any user-supplied HTTP or HTTPS URL and requests it from the host running the skill. It does not resolve ...[truncated 2297 chars]
- Remediation
- ## Remediation Suggestions 1. Disable remote URL ingestion by default unless it is essential to the skill. 2. Prefer an explicit allowlist of trusted HTTPS hostnames. 3. Resolve the hostname before connecting and reject every address in loopback, private, link-local, multicast, unspecified, documentation, carrier-grade NAT, and other reserved IPv4 and IPv6 ranges. 4. Perform destination validation for every resolved address, not only the first result. 5. Re-resolve and revalidate the destination after every redirect. 6. Set a small redirect limit and resolve relative `Location` values with `new URL(location, currentUrl)`. 7. Reject protocol changes and allow only HTTPS where possible. 8. Protect against DNS rebinding by connecting to a previously validated address while preserving the expected TLS hostname, or use a hardened outbound proxy. 9. Accept only successful response statuses and enforce response-size limits using both `Content-Length` and streaming byte counts. 10. Require explicit user confirmation before publishing remotely fetched content and consider private objects or short-lived signed URLs instead of `public-read`. 11. Block cloud metadata destinations at both application and network/firewall layers.
