T09 · Insecure Skill Coding Practices
- Location
fruit-pi.py:203- Finding
Unvalidated Price-Source URLs Enable SSRF and Local Resource Access
- Content
View full analysis
Vulnerability Details
File Location:
fruit-pi.py:203-218,fruit-pi.py:320-329,fruit-pi.py:432-450, andfruit-pi.py:554-562
Vulnerability Type: Server-Side Request Forgery (SSRF) and unsafe URL scheme handling
Risk Level: MediumVulnerable Code
python def fetch_url(url, timeout=10): """Fetch URL content with basic headers.""" try: req = urllib.request.Request( url, headers={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8" } ) with urllib.request.urlopen(req, timeout=timeout) as resp: html = resp.read().decode("utf-8", errors="replace") return html except Exception as e: log(f"fetch_url failed for {url}: {e}") return Nonepython for src in sources: url = src.get("url", "") source_name = src.get("name", "") currency = src.get("currency", "CNY") if not url: results["errors"].append(f"{source_name}: 未配置URL") continue html = fetch_url(url)python def add_source(pool, fruit_name, source_name, url, currency="CNY"): """Add a price source to an existing fruit.""" fruits = pool.get("fruits", {}) if fruit_name not in fruits: return {"error": f"{fruit_name} 不在水果池中,请先 --add"} sources = fruits[fruit_name].setdefault("sources", []) for s in sources: if s.get("name") == source_name: s["url"] = url s["currency"] = currency save_fruit_pool(pool) return {"message": f"✅ {fruit_name} 的报价来源已更新: {source_name}"} source ...[truncated 3495 chars]- Remediation
View remediation
Remediation Suggestions
- Parse every source with
urllib.parse.urlsplitand allow only thehttpsscheme. Reject URLs containing credentials, malformed hosts, or unsupported ports. - Prefer a strict allowlist of approved fruit-price domains. If arbitrary public domains are required, require explicit user confirmation before the first request to each new domain.
- Resolve all destination hostnames before connecting. Use the
ipaddressmodule to reject loopback, private, link-local, multicast, reserved, and unspecified IPv4 and IPv6 addresses. - Explicitly block cloud metadata hostnames and addresses, including link-local metadata endpoints.
- Disable automatic redirects or validate the scheme, hostname, port, and resolved IP address of every redirect target before following it.
- Protect against DNS rebinding by ensuring that the validated address is the address used for the connection, or route outbound requests through a controlled proxy with equivalent destination restrictions.
- Apply a response-size limit and reject unexpected content types before reading or parsing the complete response.
- Use short connection and read timeouts and limit the number of redirects.
- Validate URLs both when they are added and immediately before each request, because persisted state may be modified outside the CLI.
- Add automated tests covering loopback, RFC 1918 private ranges, IPv6 local ranges, link-local metadata addresses, alternate numeric IP representations,
file:URLs, embedded credentials, and redirects from an allowed public host to a blocked destination.
- Parse every source with
