T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/monitor.py:130
- Finding
- Unrestricted Channel URL Fetch Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/monitor.py:130-131`, `scripts/monitor.py:233-245`, and `scripts/monitor.py:281-282` **Vulnerability Type**: Server-Side Request Forgery (SSRF) caused by missing URL validation **Risk Level**: Medium ### Vulnerable Code ```python def get_channel_videos(channel_url, limit=5): """Get recent video IDs from a YouTube channel page.""" resp = requests.get(channel_url, headers={"User-Agent": UA}, timeout=20) if resp.status_code != 200: print(f" ERROR: Channel HTTP {resp.status_code}") return [] ``` ```python def add_channel(url, alias=None): channels = load_json(CHANNELS_FILE, []) for ch in channels: if ch.get("url") == url: print(f"Already exists: {ch.get('alias', 'unnamed')}") return if not alias: alias = get_channel_info(url) channels.append({"url": url, "alias": alias or url, "added": time.strftime("%Y-%m-%d")}) save_json(CHANNELS_FILE, channels) print(f"Added: {alias} ({url})") ``` ```python for ch in channels: alias = ch.get("alias", "?") url = ch.get("url") print(f"\n📺 {alias}") videos = get_channel_videos(url, limit) ``` ### Technical Analysis The `add_channel()` function accepts and persists an arbitrary URL without validating its scheme, hostname, port, resolved address, or intended destination. During a subsequent `check` operation, `check_channels()` passes that stored URL to `get_channel_videos()`, which performs a server-side `requests.get()` call. The Skill's declared functionality only requires access to YouTube channel pages. Allowing requests to arbitrary destinations therefore exceeds the minimum network privileges necessary for its intended operation. An attacker who can provide command-line arguments or modify `data/channels.json` can direct the process toward loopback addresses, private network services, link-local endpoints, or cloud instance metadata services. Redirect ...[truncated 1971 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Restrict network access to the exact destinations required by the Skill: 1. Parse every channel URL with `urllib.parse.urlsplit()`. 2. Require the `https` scheme. 3. Permit only exact approved YouTube hostnames, such as `www.youtube.com` and `youtube.com`; do not use suffix checks that could accept domains such as `youtube.com.attacker.example`. 4. Reject URLs containing embedded credentials, fragments, or nonstandard ports. 5. Resolve the hostname and reject loopback, private, link-local, reserved, multicast, and unspecified IP addresses for both IPv4 and IPv6. 6. Disable automatic redirects or validate every redirect destination against the same allowlist. 7. Validate URLs both when they are added and immediately before each request because `data/channels.json` can be modified independently. 8. Consider constructing canonical YouTube URLs from validated channel identifiers rather than storing arbitrary URLs. 9. Apply equivalent validation to any future feature that accepts remote media URLs. Example defensive direction: ```python from urllib.parse import urlsplit ALLOWED_YOUTUBE_HOSTS = {"youtube.com", "www.youtube.com"} def validate_channel_url(value): parsed = urlsplit(value) if parsed.scheme != "https": raise ValueError("Only HTTPS YouTube URLs are allowed") if parsed.hostname not in ALLOWED_YOUTUBE_HOSTS: raise ValueError("Only approved YouTube hosts are allowed") if parsed.username or parsed.password or parsed.port not in (None, 443): raise ValueError("Credentials and nonstandard ports are not allowed") return value ``` The implementation should additionally validate DNS resolution and redirect targets to address DNS rebinding and redirect-based bypasses. ]]>
