T09 · Insecure Skill Coding Practices
- Location
scripts/pdf_parser.py:65- Finding
Unrestricted URL Retrieval Enables Server-Side Request Forgery and Resource Exhaustion
- Content
View full analysis
Vulnerability Details
File Location:
scripts/pdf_parser.py:65-94, with attacker-controlled input declared atscripts/pdf_parser.py:374
Vulnerability Type: Unrestricted outbound request / SSRF
Risk Level: MediumVulnerable Code
python def download_pdf(url: str, output_path: Optional[Path] = None, timeout: int = 30) -> Optional[Path]: """ Download PDF file from URL Args: url: PDF file URL output_path: Output path (optional, defaults to temp file) timeout: Request timeout (seconds) Returns: Downloaded PDF file path, None on failure """ if not url: print("[ERROR] URL is empty") return None try: print(f"[INFO] 正在下载PDF: {url[:80]}...") response = requests.get(url, timeout=timeout, stream=True) response.raise_for_status() content_type = response.headers.get('content-type', '') if 'pdf' not in content_type.lower(): print(f"[WARNING] Response may not be PDF: {content_type}") if output_path is None: output_path = Path(tempfile.mktemp(suffix='.pdf')) output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): if chunk: f.write(chunk)The URL is accepted directly from a command-line argument:
python parser.add_argument('--url', '-u', required=True, help='PDF文件URL')Technical Analysis
The script passes a user-controlled URL directly to
requests.get()without validating:- The URL scheme
- The destination hostname
- Resolved IP addresses
- Loopback, private, link-local, or reserved address ranges
- Redirect destinations
- Whether the destination belongs to an approved Minist ...[truncated 2020 chars]
- Remediation
View remediation
Remediation Suggestions
- Restrict retrieval to approved HTTPS domains, such as an explicit allowlist for official Ministry of Education hosts.
- Reject all schemes except
https. - Resolve the hostname before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IP ranges for both IPv4 and IPv6.
- Disable redirects or validate every redirect destination using the same hostname and IP checks.
- Fail closed when the response content type is not an accepted PDF media type.
- Validate the downloaded file signature, such as the
%PDF-header, before parsing it. - Enforce a strict response-size limit using both
Content-Lengthand a running byte counter while streaming. - Apply separate connection and read timeouts.
- Use an outbound proxy or network policy that prevents access to internal and metadata networks.
- Replace
tempfile.mktemp()withNamedTemporaryFile(delete=False)to avoid a separate temporary-file race condition.
