T09 · Insecure Skill Coding Practices
- Location
scripts/manus_slides_json_to_pptx.mjs:20- Finding
Unrestricted Slide Asset Downloader Enables Server-Side Request Forgery
- Content
View full analysis
{ const req = client.get(url, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { res.resume(); return resolve(download(res.headers.location, dest)); } if (res.statusCode !== 200) { res.resume(); return reject(new Error(`HTTP ${res.statusCode} for ${url}`)); } const file = fs.createWriteStream(dest); res.pipe(file); file.on('finish', () => file.close(() => resolve(dest))); file.on('error', reject); }); req.on('error', reject); req.setTimeout(120000, () => req.destroy(new Error('timeout'))); }); } ``` The untrusted URL is obtained from the slide JSON and passed directly to the downloader: ```javascript const slides = Array.isArray(obj.slide_ids) ? obj.slide_ids : []; for (let i = 0; i < slides.length; i++) { const slideId = slides[i]; const slide = pptx.addSlide(); const imgUrl = obj.images?.[slideId]; const outline = Array.isArray(obj.outline) ? obj.outline.find((x) => x.id === slideId) : undefined; if (imgUrl) { const imgPath = path.join(outDir, `${String(i + 1).padStart(2, '0')}_${slideId}.png`); if (!fs.existsSync(imgPath)) { await download(imgUrl, imgPath); } ``` ### Technical Analysis The downloader accepts asset URLs directly from an input JSON document. It does not enforce HTTPS, restrict destination hosts to Manus-controlled domains, reject private or link-local IP addresses, restrict destination ports, or limit redirect depth. Any URL not beginning with `https:` is handled by the HTTP client. Redirect destinations ...[truncated 1759 chars]- Remediation
View remediation
