T09 · Insecure Skill Coding Practices
Warning
- Location
- skill.py:39
- Finding
- Spoofable Domain Reputation Classification Mislabels Attacker-Controlled Sites as Trusted<![CDATA[ ## Vulnerability Details **File Location**: `skill.py`, lines 39–45 **Vulnerability Type**: Improper domain validation through unrestricted substring matching **Risk Level**: Medium ### Vulnerable Code ```python def calc_domain_score(url): domain = urlparse(url).netloc.lower() for good, score in HIGH_QUALITY.items(): if good in domain: return score for bad in LOW_QUALITY: if bad in domain and 'baike' not in domain: return 0.3 return 1.0 ``` ### Technical Analysis The search-result reputation mechanism tests whether a trusted string occurs anywhere in the URL authority: ```python if good in domain ``` This does not establish that the hostname is the trusted domain or one of its legitimate subdomains. An attacker can register a hostname containing a trusted substring, such as: - `github.com.attacker.example` - `notgithub.com` - `official-malware.example` - `docs-phishing.example` Such a hostname can match entries in `HIGH_QUALITY` and receive an elevated score. Generic entries such as `docs` and `official` make this particularly easy to exploit. Because results are sorted by score and presented with labels such as “official” or “high quality,” the flaw can cause an attacker-controlled result to appear more trustworthy than it is. Using `urlparse(url).netloc` instead of the normalized `hostname` property also retains port and user-information syntax, making validation less precise. ### Attack Path 1. An attacker registers or controls a domain whose hostname contains a trusted substring from `HIGH_QUALITY`. 2. The attacker publishes a phishing, malware-delivery, or deceptive page relevant to a likely search query. 3. The page is indexed by Bing and returned in the HTML search results processed by the Skill. 4. `calc_domain_score()` performs substring matching and assigns the attacker-controlled URL an elevated reputation score. 5. The Skill sorts the malicious result ahead of lower-scored results and displays an a ...[truncated 688 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse and validate the normalized hostname rather than the complete network-location field: ```python parsed = urlparse(url) domain = (parsed.hostname or "").lower().rstrip(".") ``` 2. Require exact hostname or subdomain-boundary matches: ```python def matches_domain(hostname, trusted_domain): trusted_domain = trusted_domain.lower().rstrip(".") return hostname == trusted_domain or hostname.endswith("." + trusted_domain) ``` 3. Replace substring-based scoring with explicit verified domains: ```python def calc_domain_score(url): domain = (urlparse(url).hostname or "").lower().rstrip(".") for trusted_domain, score in HIGH_QUALITY.items(): if domain == trusted_domain or domain.endswith("." + trusted_domain): return score for blocked_domain in LOW_QUALITY: if domain == blocked_domain or domain.endswith("." + blocked_domain): return 0.3 return 1.0 ``` 4. Remove generic reputation entries such as `docs` and `official`; they are not registrable-domain identities and cannot safely establish ownership or authority. 5. Maintain a narrowly scoped allowlist of independently verified registrable domains. If subdomains should not inherit trust automatically, require exact hostname matches instead. 6. Add tests covering deceptive hostnames, including `notgithub.com`, `github.com.attacker.example`, `official-malware.example`, trailing-dot hostnames, mixed-case hostnames, ports, and URL user-information syntax. 7. Consider presenting reputation labels as heuristic rankings rather than definitive claims of official ownership unless domain ownership has been explicitly verified. ]]>
