T09 · Insecure Skill Coding Practices
- Location
ebusy_api.py:39- Finding
Unvalidated Base URL Allows Credential Exfiltration and Server-Side Request Forgery
- Content
View full analysis
Vulnerability Details
File Location:
ebusy_api.py, lines 39-75
Vulnerability Type: Unrestricted credential destination and server-side request forgery
Risk Level: HighVulnerable 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"}, )Technical Analysis
EBUSY_BASE_URLis accepted directly from the environment and used to construct both the initial login-page request and the request containing the configured username and password. The code does not validate the URL scheme, hostname, port, embedded user information, resolved IP address, or redirect destination.This contradicts th ...[truncated 2220 chars]
- Remediation
View remediation
Remediation Suggestions
- Parse the configured URL with
urllib.parse.urlsplitbefore creating any request. - Require the scheme to be exactly
https. - Require a nonempty hostname equal to
ebusy.deor ending exactly in.ebusy.de. A plain substring orendswith("ebusy.de")check is insufficient because it may accept deceptive domains. - Reject embedded usernames or passwords, fragments, unexpected paths, and nonstandard ports unless a specific port is demonstrably required.
- Resolve the hostname and reject loopback, private, link-local, multicast, reserved, and unspecified IP addresses. Consider DNS rebinding when designing this control.
- Disable automatic redirects with
allow_redirects=False, or manually validate the scheme, host, port, and resolved address of every redirect before following it. - Enforce the same allowlist through runtime egress controls so application validation is not the only security boundary.
- Remove
"remember-me": "on"unless persistent server-side login is required for the availability query. - Add tests covering HTTP URLs, unrelated domains, deceptive suffixes, embedded credentials, localhost, private IP addresses, alternate ports, and cross-origin redirects.
- Parse the configured URL with
