T09 · Insecure Skill Coding Practices
- Location
scripts/meta_ads.py:322- Finding
Meta Access Token Can Be Disclosed to Arbitrary Network Destinations
- Content
View full analysis
Vulnerability Details
File Location:
scripts/meta_ads.py:322-365, with exploitable call paths atscripts/meta_ads.py:500-519,scripts/meta_ads.py:693-705, and configurable endpoint input atscripts/meta_ads.py:902-905
Vulnerability Type: Unrestricted authenticated request destination and credential disclosure
Risk Level: HighVulnerable Code
The URL builder accepts absolute HTTP and HTTPS URLs without validating their destination:
python def build_url(self, path: str) -> str: if path.startswith("http://") or path.startswith("https://"): return path clean = path.lstrip("/") base = self.graph_base.rstrip("/") if not clean: return f"{base}/{self.api_version}" if clean.startswith(f"{self.api_version}/"): return f"{base}/{clean}" return f"{base}/{self.api_version}/{clean}"The Meta bearer token is then attached unconditionally to the resulting URL:
python def request( self, method: str, path: str, params: Optional[Dict[str, Any]] = None, files: Optional[Dict[str, Tuple[str, bytes, str]]] = None, ) -> Dict[str, Any]: method = method.upper() url = self.build_url(path) encoded_params = normalise_params(params) data_bytes: Optional[bytes] = None headers: Dict[str, str] = { "Authorization": f"Bearer {self.access_token}", "User-Agent": USER_AGENT, } if method in {"GET", "HEAD"} and not files: url = append_query(url, encoded_params) elif files: body, content_type = build_multipart(encoded_params, files) headers["Content-Type"] = content_type data_bytes = body else: headers["Content-Type"] = "application/x-www-form-urlencoded" data_bytes = urllib.parse.urlencode(encoded_params).encode("utf-8") if encoded_params else b"" last_payload: Any = None last_headers: Dict[str, str] = {} for attempt in range(self.max_retries + 1): req = urllib.request.Req ...[truncated 5647 chars]- Remediation
View remediation
Remediation Suggestions
-
Restrict authenticated requests to trusted Meta origins
- Parse every final URL with
urllib.parse.urlsplit(). - Require the
httpsscheme. - Allow only an explicit hostname allowlist, normally
graph.facebook.com. - Reject embedded credentials, unexpected ports, malformed hostnames, and non-HTTPS URLs.
- Parse every final URL with
-
Reject absolute user-supplied request paths
- Make the low-level
requestcommand accept only relative Graph API paths. - Resolve those paths against a validated, fixed Meta Graph API base.
- Do not treat
http://orhttps://values as valid API paths.
- Make the low-level
-
Constrain or remove custom Graph bases
- Remove
--graph-baseandMETA_GRAPH_BASEif they are not operationally required. - If custom endpoints are required for testing, require an explicit unsafe-development mode and never reuse production
META_ACCESS_TOKENcredentials with them. - Prefer a separate test credential for custom Graph-compatible endpoints.
- Remove
-
Validate pagination URLs
- Parse each
paging.nextvalue before following it. - Require it to use HTTPS and match the original validated Meta origin.
- Prefer extracting the relative path and approved query parameters rather than directly requesting the supplied absolute URL.
- Parse each
-
Harden redirect handling
- Disable automatic redirects or implement a redirect handler that revalidates every destination.
- Never forward the
Authorizationheader when the scheme, hostname, or port changes. - Reject HTTPS-to-HTTP redirects unconditionally.
-
Add security regression tests
- Verify rejection of arbitrary absolute URLs.
- Verify rejection of plain HTTP, foreign hosts, foreign ports, hostname confusion, and cross-origin redirects.
- Verify that malicious
paging.nextvalues are not followed. - Verify that credentials are only attached after destination validation.
-
Operational response
- Rotate any token that may have been used ...[truncated 214 chars]
-
