T09 · Insecure Skill Coding Practices
Error
- Location
- ebusy_api.py:40
- Finding
- Unrestricted Login Destination Can Expose eBusy Credentials<![CDATA[ ## Vulnerability Details **File Location**: `ebusy_api.py`, lines 40-78 **Vulnerability Type**: Unvalidated credential transmission destination **Risk Level**: High ### Vulnerable Code ```python BASE_URL = os.getenv("EBUSY_BASE_URL", "https://medenhalle.ebusy.de") USERNAME = os.getenv("EBUSY_USERNAME") PASSWORD = os.getenv("EBUSY_PASSWORD") COURT_ID = int(os.getenv("EBUSY_COURT_ID", "1")) FIRST_COURT_NO = int(os.getenv("EBUSY_FIRST_COURT_NO", "1")) class EbusyAPI: def __init__(self, base_url: str | None = None): self.base_url = base_url or BASE_URL self.session = requests.Session() def login(self, username: str, password: str) -> bool: """Log into the eBusy instance using CSRF token + session. Returns True on success, False otherwise. """ login_page = self.session.get(f"{self.base_url}/login") login_page.raise_for_status() soup = BeautifulSoup(login_page.text, "html.parser") csrf_el = soup.find("input", {"name": "_csrf"}) if not csrf_el or not csrf_el.get("value"): raise RuntimeError("Could not find CSRF token on login page") csrf_token = csrf_el["value"] login_data = { "username": username, "password": password, "_csrf": csrf_token, "remember-me": "on", } response = self.session.post( f"{self.base_url}/login", data=login_data, headers={"Accept": "application/xml, text/xml"}, ) return response.ok ``` ### Technical Analysis The client reads `EBUSY_BASE_URL` from the environment and uses it directly as the destination for authenticated network requests. It does not parse or validate the URL's scheme, hostname, port, embedded credentials, or network address. The ability to select a hall endpoint is necessary for the declared multi-hall functionality. However, allowing an unrestricted destination is broader than ne ...[truncated 3068 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse the configured URL with `urllib.parse.urlparse` and require an absolute `https://` URL. 2. Reject URLs containing embedded credentials, fragments, unexpected ports, malformed hostnames, or unsupported components. 3. Maintain an explicit allowlist of approved eBusy hostnames. Configuration should select a known profile rather than allowing an arbitrary credential destination. 4. Resolve the hostname and reject loopback, private, link-local, multicast, reserved, and other non-public address ranges unless a specifically approved private deployment requires them. 5. Bind each credential set to its approved hall hostname so credentials cannot be combined with an unrelated URL. 6. Disable automatic redirects on login requests with `allow_redirects=False`, or validate the scheme and hostname of every redirect before following it. Do not forward authentication data across origins. 7. Apply explicit connection and read timeouts to all requests. 8. Update `SKILL.md` to document the HTTPS-only requirement, trusted-host policy, redirect behavior, and credential-to-host binding. 9. Keep credentials in a protected secret store with the narrowest possible runtime exposure, and rotate affected credentials if the Skill may have run with an untrusted base URL. A hardened validation flow should occur before the first network request and should fail closed if the destination cannot be conclusively identified as an approved eBusy server. ]]>
