T09 · Insecure Skill Coding Practices
Error
- Location
- processors/image_processor.py:68
- Finding
- Unvalidated Image URLs Enable Server-Side Request Forgery and Response Exfiltration to OSS<![CDATA[ ## Vulnerability Details **File Location**: `fetchers/offline_parser.py:105-121`, `main.py:37-45`, `processors/image_processor.py:42-44,68-80` **Vulnerability Type**: Server-Side Request Forgery with response exfiltration **Risk Level**: High ### Vulnerable Code ```python # fetchers/offline_parser.py:105-121 def _fix_and_collect_images(container: BeautifulSoup) -> List[str]: images: List[str] = [] seen = set() for img in container.find_all('img'): ds = (img.get('data-src') or '').strip() src = (img.get('src') or '').strip() real = ds or src if not real or real.startswith('data:image'): continue real = real.split('?')[0] img['src'] = real if 'data-src' in img.attrs: del img['data-src'] if real in seen: continue seen.add(real) images.append(real) logger.debug(f"Extracted {len(images)} images") return images ``` ```python # main.py:37-45 image_urls = article_data.get('images', []) if image_urls: logger.info(f"Found {len(image_urls)} images; starting upload") url_mapping = image_processor.upload_images( image_urls, platform, article_id, article_url=url ) ``` ```python # processors/image_processor.py:42-44 response = self._download_image(img_url, platform_referer) result = self.bucket.put_object(oss_path, response.content) ``` ```python # processors/image_processor.py:68-80 @staticmethod def _download_image(img_url: str, fallback_referer: str): try: resp = requests.get(img_url, timeout=30) if resp.status_code != 403: resp.raise_for_status() return resp except requests.HTTPError: pass if fallback_referer: resp = requests.get( img_url, headers={'Referer': fallback_referer}, timeout=30 ) resp.raise_for_status() return resp raise RuntimeError(f"I ...[truncated 2539 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse every image URL and allow only `https` URLs. Reject `http`, `file`, `ftp`, `gopher`, protocol-relative URLs, malformed URLs, and URLs containing user information. 2. Resolve the destination hostname before connecting and reject every address in loopback, private, link-local, multicast, reserved, and unspecified ranges for both IPv4 and IPv6. 3. Explicitly block common metadata destinations, including `169.254.169.254` and platform-specific metadata hostnames. 4. Disable automatic redirects. If redirects are needed, process them manually and repeat scheme, hostname, DNS, and IP validation for every hop. 5. Protect against DNS rebinding by ensuring the validated address is the address used for the connection. 6. Require an image MIME type from an explicit allowlist, such as `image/jpeg`, `image/png`, `image/gif`, and `image/webp`. 7. Stream downloads with a strict maximum byte count instead of loading an unlimited response into memory. 8. Consider allowing only known image CDN domains associated with each supported platform. 9. Do not upload a response to OSS unless all URL, status, MIME-type, and size checks pass. 10. Add regression tests covering loopback, RFC1918 addresses, IPv6 local addresses, metadata endpoints, encoded IP representations, DNS rebinding, and redirects to internal destinations. ]]>
