T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/uptime_check.py:34
- Finding
- Sensitive Custom Headers May Be Disclosed Through Cross-Origin Redirects<![CDATA[ ## Vulnerability Details **File Location**: `scripts/uptime_check.py`, lines 34–54 **Vulnerability Type**: Sensitive credential disclosure through unrestricted redirect handling **Risk Level**: Medium ### Vulnerable Code ```python req_headers = {"User-Agent": "UptimeChecker/1.0"} if headers: req_headers.update(headers) request = urllib.request.Request(url, method=method, headers=req_headers) # SSL context ctx = None if url.startswith("https://"): ctx = ssl.create_default_context() if not verify_ssl: ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE start = time.monotonic() try: if not follow_redirects: # Build opener without redirect handler class NoRedirect(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, hdrs, newurl): result["redirect_url"] = newurl return None opener = urllib.request.build_opener(NoRedirect, urllib.request.HTTPSHandler(context=ctx) if ctx else urllib.request.HTTPHandler()) response = opener.open(request, timeout=timeout) else: response = urllib.request.urlopen(request, timeout=timeout, context=ctx) ``` ### Technical Analysis The checker accepts arbitrary custom request headers and attaches them directly to the `urllib.request.Request`. The documented usage explicitly supports authentication headers such as: ```text --header "Authorization:Bearer token123" ``` Redirects are followed by default through `urllib.request.urlopen`, but the code does not check whether a redirect remains on the same origin. It also does not remove sensitive headers when the destination scheme, hostname, or port changes. Because user-provided headers are regular request headers, redirect processing may propagate them to the redirected request. Consequently, an endpoint that is compromised, malicious, or capable of controlling its redirect response can direct the checker to another orig ...[truncated 1505 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Implement a custom redirect handler that compares the original and redirected URL origins. 2. Strip sensitive headers whenever the scheme, hostname, or effective port changes. At minimum, remove: - `Authorization` - `Proxy-Authorization` - `Cookie` - API-key headers such as `X-API-Key` - Any caller-designated sensitive headers 3. Prefer disabling redirect following by default when authentication headers are present, unless the user explicitly authorizes cross-origin redirects. 4. Reject redirects to a less secure scheme, particularly HTTPS-to-HTTP redirects. 5. Consider maintaining sensitive headers separately and adding them only to requests whose origin exactly matches the originally requested origin. 6. Add tests covering same-origin redirects, cross-origin redirects, port changes, scheme changes, and sensitive custom headers. A secure policy should follow this pattern: ```python from urllib.parse import urlsplit SENSITIVE_HEADERS = { "authorization", "proxy-authorization", "cookie", "x-api-key", } def same_origin(first_url, second_url): first = urlsplit(first_url) second = urlsplit(second_url) def effective_port(parts): if parts.port is not None: return parts.port return 443 if parts.scheme.lower() == "https" else 80 return ( first.scheme.lower() == second.scheme.lower() and first.hostname == second.hostname and effective_port(first) == effective_port(second) ) class SafeRedirectHandler(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): redirected = super().redirect_request( req, fp, code, msg, headers, newurl ) if redirected is not None and not same_origin(req.full_url, newurl): for name in list(redirected.headers): if name.lower() in SENSITIVE_HEADERS: redirected.remove_heade ...[truncated 191 chars]
