T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/utils/validator.js:3
- Finding
- Unrestricted URL Fetching Enables Server-Side Request Forgery and Data Exfiltration<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/utils/validator.js:3-8` - `scripts/video2text/index.js:96-118` - `scripts/video2text/index.js:140-154` - `scripts/utils/download.js:478-493` - `scripts/utils/download.js:909-926` **Vulnerability Type**: Server-Side Request Forgery (SSRF) with subsequent response upload **Risk Level**: High ### Vulnerable Code ```js // scripts/utils/validator.js:3-8 function isUrl(url) { try { const parsedUrl = new URL(url); return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:"; } catch (_) { return false; } } ``` ```js // scripts/video2text/index.js:96-118 if (validator.isUrl(file)) { const filepath = utils.downloadPath(); try { await fs.promises.mkdir(filepath, { recursive: true }); } catch (error) { utils.printError("临时下载目录创建失败: " + (error.message || String(error))); process.exit(1); } try { const downloadResult = await helper.download(file, filepath); let tempFilePath = downloadResult?.filePath || ""; if (tempFilePath === "") { utils.printError("下载失败: 未返回文件路径"); process.exit(1); } ``` ```js // scripts/video2text/index.js:140-154 const presignedUrl = await video.getPresignedUrl(tokenValue, file); if (!presignedUrl || !presignedUrl?.url || presignedUrl.url === "") { throw new Error("获取预签名URL失败,请反馈给开发者"); } utils.printInfo("上传文件到安全空间..."); await upload.uploadFileToOSS(file, presignedUrl.url, presignedUrl.headers); utils.printInfo("文件上传到安全空间成功,获取视频分析任务ID"); if (presignedUrl.url.indexOf("?") === -1) { throw new Error("预签名URL格式错误,请反馈给开发者"); } const url = presignedUrl.url.substring(0, presignedUrl.url.indexOf("?")); const task = await video.getVideoId(tokenValue, url); ``` ```js // scripts/utils/download.js:478-493 if (this.__isRequireRedirect(response)) { this.__redirectCount++; if (this.__redirectCount > this.__opts.maxRedirects) { const err = new Error("Too many redirects"); this.__setState(this.__states.F ...[truncated 3637 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Resolve each destination hostname before making a request and reject every resolved address in prohibited IPv4 and IPv6 ranges, including: - Loopback - Private-use - Link-local - Multicast - Unspecified addresses - Reserved and documentation ranges - Known cloud metadata addresses 2. Apply the same validation to every redirect destination. Do not rely solely on validating the initial URL. 3. Prevent DNS rebinding by connecting only to an IP address that was resolved and validated by the application while retaining the original hostname for TLS Server Name Indication and certificate verification. 4. Where operationally possible, use an allowlist of approved public video hosts rather than accepting arbitrary HTTP endpoints. 5. Consider requiring HTTPS for remote media. If HTTP must remain supported, explicitly document and constrain its use. 6. Reject URLs containing embedded credentials unless this capability is explicitly required. 7. Disable redirects by default or limit them to a small number and require each redirect to retain an approved public destination. 8. Separate downloading from uploading and verify that the result is an expected media type before sending it to an external service. 9. Add tests covering direct and redirected requests to loopback, private IPv4, IPv4-mapped IPv6, link-local, metadata, and DNS-rebinding destinations. ]]>
