T09 · Insecure Skill Coding Practices
- Location
scripts/watch_ticket_pages.py:66- Finding
Unrestricted Target URLs Enable Server-Side Request Forgery
- Content
View full analysis
Vulnerability Details
File Location:
scripts/watch_ticket_pages.py, lines 66-73 and 136-140
Vulnerability Type: Server-Side Request Forgery (SSRF)
Risk Level: MediumVulnerable Code
python def fetch_url(url: str, user_agent: str, timeout_seconds: int) -> dict[str, Any]: req = Request( url, headers={ "User-Agent": user_agent or DEFAULT_USER_AGENT, "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", }, ) started_at = time.time() try: with urlopen(req, timeout=timeout_seconds) as resp: body = resp.read()The URL originates directly from configuration:
python targets.append( Target( name=raw["name"], platform=raw["platform"], url=raw["url"], require_all=normalize_keywords(raw.get("require_all")), match_any=normalize_keywords(raw.get("match_any")), signal_high=normalize_keywords(signal_keywords.get("high")), signal_medium=normalize_keywords(signal_keywords.get("medium")), note=raw.get("note"), ) )Technical Analysis
A configuration-controlled target URL is passed directly to
urllib.request.Requestandurlopen. Validation is limited to checking whether the URL field is non-empty; the code does not restrict URL schemes, destination hosts, ports, resolved IP ranges, or redirect destinations.An attacker who can create or modify the target configuration can cause the process to issue requests from its own network environment to destinations such as:
- Loopback services
- Private-network services
- Link-local or cloud metadata endpoints
- Services reachable only from the host running the skill
- Internal destinations reached through redirects
The application exposes request status and up to the configured
preview_charsof response-derived text in its structured output. This output may subsequently be forwarded to a channel, webhook, file, or queue as recommended by the s ...[truncated 1824 chars]- Remediation
View remediation
Remediation Suggestions
-
Restrict URL schemes
- Parse every target with
urllib.parse.urlsplit. - Permit only explicitly required schemes, preferably
https. - Reject
file,ftp,data, and all other non-HTTP schemes. - Reject URLs containing embedded credentials.
- Parse every target with
-
Apply a destination allowlist
- Prefer an explicit allowlist of supported official ticket-platform domains.
- Compare normalized hostnames exactly or against carefully implemented subdomain rules.
- Do not use unsafe suffix checks such as
hostname.endswith("example.com")without requiring a label boundary.
-
Block non-public network destinations
- Resolve all destination hostnames before connecting.
- Reject every resolved IPv4 and IPv6 address that is loopback, private, link-local, multicast, reserved, unspecified, or otherwise non-global.
- Protect against alternate IP representations and IPv4-mapped IPv6 addresses.
- Consider DNS rebinding defenses by connecting only to validated resolved addresses while preserving correct TLS hostname verification.
-
Control redirects
- Disable automatic redirects or implement a restricted redirect handler.
- Validate the scheme, hostname, port, and resolved addresses of every redirect destination.
- Set a low maximum redirect count.
-
Constrain ports and responses
- Allow only expected ports, normally 443 and optionally 80.
- Limit response size before reading the complete body to reduce denial-of-service exposure.
- Avoid returning response previews by default, especially for failed or non-allowlisted destinations.
-
Harden configuration trust
- Treat target configuration as privileged input.
- Restrict write access to configuration files.
- Validate every target at load time and fail closed with an explicit error rather than silently accepting unsafe destinations.
-
Add security tests
- Test rejection of loopback, RFC 1918, link-local, IPv6 local, m ...[truncated 91 chars]
-
