T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/zhilian_parser.py:156
- Finding
- Unrestricted URL Fetching Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/boss_parser.py:127-130` - `scripts/zhilian_parser.py:156-159` - `scripts/qiancheng_parser.py:170-173` **Vulnerability Type**: Server-Side Request Forgery (SSRF) **Risk Level**: Medium ### Vulnerable Code ```python # scripts/boss_parser.py:127-130 def get_job_detail(self, job_url: str) -> Optional[Dict]: try: response = self.session.get(job_url, timeout=10) ``` ```python # scripts/zhilian_parser.py:156-159 def get_job_detail(self, job_url: str) -> Optional[Dict]: try: response = self.session.get(job_url, timeout=10) ``` ```python # scripts/qiancheng_parser.py:170-173 def get_job_detail(self, job_url: str) -> Optional[Dict]: try: response = self.session.get(job_url, timeout=10) ``` ### Technical Analysis All three parser classes expose a public `get_job_detail()` method that accepts an arbitrary URL and passes it directly to `requests.Session.get()`. The code does not validate: - The URL scheme. - The destination hostname. - Whether the resolved address is loopback, private, link-local, reserved, or multicast. - Whether the destination belongs to the recruitment platform handled by the parser. - Redirect destinations. The required functionality only needs access to known recruitment domains. Allowing arbitrary destinations creates an SSRF primitive that can access any HTTP service reachable from the process. The normal command-line search workflow does not currently invoke these methods, which reduces immediate exposure. However, they are public APIs and may be called directly by an integration or future workflow. In addition, job URLs extracted from HTML are not consistently restricted to expected platform domains. ### Attack Path 1. An attacker supplies a crafted URL through an integration that calls `get_job_detail()`. 2. The URL targets a service reachable from the Skill host, such as a loopback service, private-network application, or cloud metadata ...[truncated 1341 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Create a centralized URL-validation function and require every detail request to pass validation. 2. Permit only `https` URLs. 3. Use a strict hostname allowlist for each parser, for example: - BOSS Zhipin: `www.zhipin.com` and explicitly reviewed subdomains. - Zhaopin: `www.zhaopin.com`, `jobs.zhaopin.com`, and explicitly reviewed subdomains. - 51job: `www.51job.com`, `jobs.51job.com`, and explicitly reviewed subdomains. 4. Perform exact hostname or safe subdomain matching. Do not use substring checks such as `"zhaopin.com" in hostname`. 5. Resolve the hostname and reject loopback, private, link-local, reserved, multicast, and unspecified IP addresses for both IPv4 and IPv6. 6. Disable automatic redirects or validate the destination before following every redirect. 7. Reject URLs containing embedded credentials or ambiguous hostname encodings. 8. Consider removing `job_url` from the public API and accepting a platform-specific job identifier instead. 9. Add tests covering: - Loopback and private IPv4 addresses. - IPv6 loopback and private addresses. - Decimal, hexadecimal, and encoded IP representations. - DNS rebinding scenarios. - Open redirects from permitted domains. - User-information hostname confusion such as `allowed.example@127.0.0.1`. A hardened request flow should resemble: ```python validated_url = validate_platform_url(job_url, allowed_hosts) response = self.session.get( validated_url, timeout=10, allow_redirects=False, ) ``` ]]>
