T09 · Insecure Skill Coding Practices
- Location
scripts/youtube_video_to_blog_converter.py:14- Finding
Unvalidated and Unencoded Payment URL Construction
- Content
View full analysis
str: template = os.getenv("SKILLPAY_PAYMENT_URL_TEMPLATE", "").strip() if template: return template.format(user_id=user_id) base = os.getenv("SKILLPAY_TOPUP_BASE_URL", "https://skillpay.me/pay").strip() sep = "&" if "?" in base else "?" return f"{base}{sep}user_id={user_id}" ``` The generated value is exposed in the response: ```python upgrade = { "premium_available": True, "price_usdt": "0.10", "payment_url": _payment_url(validated["user_id"]), "premium_features": ["SEO标题与摘要优化", "自动配图建议", "多平台发布格式(公众号/Medium)"], } ``` ### Technical Analysis The application directly interpolates the user-controlled `user_id` into a URL without percent-encoding it. Reserved URL characters such as `&`, `=`, `#`, and `?` can therefore alter the structure or interpretation of the generated payment URL. For example, a `user_id` containing `victim&amount=100` produces a URL resembling: ```text https://skillpay.me/pay?user_id=victim&amount=100 ``` The payment service may interpret the injected text as a separate query parameter rather than as part of the identifier. Whether a particular parameter changes billing behavior depends on the downstream service, but the generated URL no longer reliably represents the validated input. The configurable base URL and template are also accepted without scheme or hostname validation. If deployment configuration is compromised or incorrectly supplied, the returned link could use plaintext HTTP, an unexpected external host, or a non-web scheme. Template substitution does not encode `user_id`, so the same injection weakness exists in both configuration paths. This issue ...[truncated 2234 chars]- Remediation
View remediation
str: parsed = urlsplit(base) query = dict(parse_qsl(parsed.query, keep_blank_values=True)) query["user_id"] = user_id return urlunsplit( (parsed.scheme, parsed.netloc, parsed.path, urlencode(query), parsed.fragment) ) ``` 2. Parse and validate configured URLs before returning them: - Require the exact `https` scheme. - Require a nonempty hostname. - Use an explicit allowlist of approved payment hostnames. - Reject embedded credentials, control characters, and unexpected ports. - Avoid substring or suffix-only hostname checks that can be bypassed by lookalike domains. 3. Restrict `user_id` to the application's expected identifier format, such as a conservative length-bounded allowlist: ```python if not re.fullmatch(r"[A-Za-z0-9_-]{1,64}", user_id): raise ValidationError("`user_id` has an invalid format") ``` This input restriction should supplement, not replace, proper URL encoding. 4. Avoid arbitrary format templates where possible. Prefer a validated base URL and append parameters programmatically. If template support is required, validate the fully rendered URL after substitution and ensure the identifier is percent-encoded before insertion. 5. Add tests covering: - Identifiers containing `&`, `=`, `?`, `#`, whitespace, and Unicode. - HTTP and non-web schemes. - Unexpected or deceptive hostnames. - Existing query parameters. - Configured templates and base URLs. - Rejection of control characters and malformed URLs. ]]>
