T09 · Insecure Skill Coding Practices
- Location
scripts/read_article.py:58- Finding
Authentication Cookies Sent to Arbitrary User-Controlled Destinations
- Content
View full analysis
dict: """Récupère titre + contenu complet d'un article ZoneBourse.""" cookie_header = load_cookies(COOKIES_FILE) cmd = CURL_ARGS + ["-H", f"Cookie: {cookie_header}", url] result = subprocess.run(cmd, capture_output=True, text=True, timeout=20) ``` The destination is taken directly from a command-line argument: ```python if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python3 read_article.py ") sys.exit(1) result = fetch_article(sys.argv[1]) print(json.dumps(result, indent=2, ensure_ascii=False)) ``` ### Technical Analysis `fetch_article()` loads every entry from the local `cookies.txt` file and constructs a `Cookie` request header. According to `SKILL.md`, this file may contain sensitive subscriber credentials such as `zb_auth`, `zb_abonne`, `zb_membre`, and `PHPSESSID`. The destination URL is accepted from `sys.argv[1]` without validating its scheme, hostname, port, or path. The script therefore sends the subscriber cookies to any destination supplied by the caller, rather than restricting their transmission to the intended ZoneBourse HTTPS origin. The command also places the untrusted argument directly into the curl argument list without an explicit `--` option terminator. Although use of an argument list prevents shell metacharacter injection, a value beginning with `-` may still be interpreted by curl as an option. This increases the attack surface and should be addressed alongside strict URL validation. Redirect behavior should also be constrained during remediation. Although the current curl arguments do not explicitly enable redirect following, future changes must not permit credentials to be f ...[truncated 1463 chars]- Remediation
View remediation
str: parsed = urlsplit(url) if parsed.scheme != "https": raise ValueError("Only HTTPS URLs are allowed") if parsed.hostname not in ALLOWED_HOSTS: raise ValueError("Unapproved destination host") if parsed.port not in (None, 443): raise ValueError("Unapproved destination port") if parsed.username is not None or parsed.password is not None: raise ValueError("URL credentials are not allowed") if not parsed.path.startswith("/actualite-bourse/"): raise ValueError("Unexpected article path") return url def fetch_article(url: str) -> dict: validated_url = validate_article_url(url) cookie_header = load_cookies(COOKIES_FILE) cmd = CURL_ARGS + [ "-H", f"Cookie: {cookie_header}", "--", validated_url, ...[truncated 129 chars]
