T09 · Insecure Skill Coding Practices
- Location
scripts/webhook_audit.py:27- Finding
Server-Side Request Forgery Through Unrestricted Webhook Probes
- Content
View full analysis
dict: if not url or not url.startswith(("http://", "https://")): return {"reachable": False, "error": "invalid url"} try: req = urllib.request.Request(url, method="HEAD") with urllib.request.urlopen(req, timeout=timeout) as resp: return {"reachable": True, "status": resp.status} ``` ### Technical Analysis The probe accepts any URL beginning with `http://` or `https://` and sends a request using `urllib.request.urlopen()`. It does not validate: - The destination hostname or resolved IP address - Loopback addresses such as `127.0.0.1` or `::1` - Private network ranges - Link-local and cloud metadata addresses - Reserved or unspecified IP ranges - Destination ports - Redirect destinations - DNS rebinding between validation and connection The URLs come from webhook records retrieved from the ActiveCampaign account. Consequently, a user who can create or modify a webhook can influence requests originating from the environment running the Skill. The implementation also conflicts with the claim in `SECURITY.md` that the webhook probe cannot be redirected to arbitrary targets. `urlopen()` can follow HTTP redirects, and no destination validation is performed before or after redirection. ### Attack Path 1. An attacker obtains permission to create or modify webhook records in the connected ActiveCampaign account. 2. The attacker configures a webhook URL targeting an internal address, such as a loopback service, private-network endpoint, or cloud metadata service. 3. Alternatively, the attacker configures a public URL that redirects to an internal destination. 4. The operator or Agent runs `scripts/webhook_audit.py` without the optional `--skip-probe` flag. 5. `probe_url()` issues ...[truncated 946 chars]- Remediation
View remediation
