T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/render_report.py:311
- Finding
- Untrusted Product Image URLs Trigger Automatic Third-Party Network Requests<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/normalize_results.py:96-106` - `scripts/render_report.py:119-128` - `scripts/render_report.py:311-316` - `scripts/render_report.py:94` **Vulnerability Type**: Unrestricted embedding of untrusted remote resources **Risk Level**: Medium ### Vulnerable Code The normalization layer accepts image URLs directly from untrusted API card data: ```python def normalize_card(kind: str, item: dict, position: int) -> dict: title = first_nonempty(item, ["text", "title", "sku_name", "name", "poi_name", "query"]) card_id = first_nonempty(item, ["pid", "item_id", "sku", "id", "poi_id", "wx_app_id", "source_seq_id"]) url = first_nonempty(item, ["jump_url", "auctionURL", "pc_url", "webURL", "poi_url"]) image = first_nonempty(item, ["image_url", "pic_path", "verticalPic", "photos", "icon"]) shop = first_nonempty(item, ["seller_name", "shop_name", "venueName", "source"]) price = first_nonempty(item, ["price", "priceShowText", "priceLow", "minPrice", "priceStr"]) return { "kind": kind, "position": position, "id": str(card_id) if card_id is not None else None, "title": str(title) if title is not None else None, "shop": str(shop) if shop is not None else None, "price": price, "image_url": str(image) if image is not None else None, "url": str(url) if url is not None else None, "raw": item, } ``` The renderer considers every HTTP or HTTPS host safe: ```python def safe_url(value: Any) -> Optional[str]: if not isinstance(value, str): return None value = value.strip() try: parsed = urlsplit(value) except ValueError: return None return value if parsed.scheme in {"http", "https"} and parsed.netloc else None ``` It then embeds the untrusted URL as an automatically loaded image: ```python def render_product_cards(block: dict) -> str: output = [] for item in block.get("i ...[truncated 3443 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Do not embed untrusted remote images by default.** - Replace remote product images with local placeholders. - Where source access is useful, provide an ordinary user-activated link rather than an automatically loaded resource. 2. **Tighten the report CSP.** - Prefer: ```text img-src 'self' data: ``` - If approved remote providers are indispensable, enumerate their exact HTTPS origins rather than allowing all HTTPS hosts. 3. **Use a controlled image-fetching pipeline if images must be included.** - Maintain an explicit hostname allowlist. - Resolve DNS and reject loopback, private, link-local, multicast, reserved, and metadata-service address ranges. - Repeat destination validation after every redirect. - Limit redirect count, response size, download time, and image dimensions. - Require an approved image media type and verify the file signature rather than trusting `Content-Type`. - Strip metadata where appropriate. - Embed the validated result locally or as a size-limited data URL. 4. **Strengthen URL validation.** - Reject username/password components. - Permit only HTTPS unless a documented requirement justifies HTTP. - Normalize hostnames and ports before allowlist comparison. - Reject malformed or ambiguous host representations. 5. **Extend report validation.** - Fail validation if an image source is not a local/data resource or an explicitly approved origin. - Add tests for attacker-controlled hosts, loopback addresses, private addresses, URL credentials, redirects, IPv6 literals, and DNS-rebinding scenarios. ]]>
