T09 · Insecure Skill Coding Practices
- Location
scripts/csv-import/import-shopify-csv.js:282- Finding
Server-Side Request Forgery Through CSV-Controlled Image URLs
- Content
View full analysis
{ const transport = url.startsWith('https:') ? https : http; transport.get(url, { timeout: 30000 }, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { return downloadImage(res.headers.location).then(resolve).catch(reject); } if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`)); const bufs = []; res.on('data', d => bufs.push(d)); res.on('end', () => resolve(Buffer.concat(bufs))); }).on('error', reject).on('timeout', () => reject(new Error('Timeout'))); }); } async function uploadImages(images, productTitle, imgConcurrency = 5, imgRetries = 3) { const slugify = s => s.replace(/[^a-zA-Z0-9\u4e00-\u9fff-]/g, '_').substring(0, 30); const results = await concurrentMap(images, imgConcurrency, async (img) => { let lastErr; for (let attempt = 0; attempt <= imgRetries; attempt++) { try { const buf = await downloadImage(img.src); const ext = getExt(img.src); const r = await api.post('/api/skill/base-image/upload', { image_base64encode: buf.toString('base64'), image_name: `${slugify(productTitle)}_${img.position}.${ext}`, group_type: 'product' }); ``` ### Technical Analysis The importer obtains `img.src` from the CSV `Image Src` column and passes it directly to `http.get` or `https.get`. It performs no validation of: - The URL scheme - The destination hostname - Resolved IP addresses - Loopback, link-local, private, or reserved address ranges - Redirect destinations - ...[truncated 1915 chars]- Remediation
View remediation
