T09 · Insecure Skill Coding Practices
- Location
scripts/ptt_scraper.py:293- Finding
Arbitrary URL Fetching Enables Server-Side Request Forgery
- Content
View full analysis
Vulnerability Details
File Location:
scripts/ptt_scraper.py:69-72,scripts/ptt_scraper.py:293-296, andscripts/ptt_scraper.py:367-368
Vulnerability Type: Server-Side Request Forgery (SSRF) through an unrestricted user-controlled URL
Risk Level: HighVulnerable code:
python def fetch_soup(session: requests.Session, url: str, params: dict | None = None) -> BeautifulSoup: resp = session.get(url, params=params, timeout=15) resp.raise_for_status() return BeautifulSoup(resp.text, "html.parser")python def get_article(session: requests.Session, url_or_path: str) -> dict: url = url_or_path if url_or_path.startswith("http") else BASE_URL + url_or_path soup = fetch_soup(session, url) return parse_article(soup, url)python p_article = sub.add_parser("article", help="fetch one article with push/boo counts") p_article.add_argument("url", help="full URL or /bbs/... path")python elif args.command == "article": result = get_article(session, args.url)Technical Analysis
The
articlecommand accepts an arbitrary URL from the command line. Inget_article(), any input beginning withhttpis passed tofetch_soup()without validating its scheme, hostname, port, resolved IP address, or path.fetch_soup()then performs the request withrequests.Session.get().Consequently, the request is not restricted to the Skill's declared destination,
https://www.ptt.cc. An attacker who can control or influence the article argument can direct the process toward:- Loopback services such as
127.0.0.1or[::1] - Private network services
- Link-local addresses and cloud instance metadata services
- Services on unusual ports
- Attacker-controlled hosts that redirect to internal destinations
The
requestslibrary follows redirects by default. Therefore, checking only the initial URL would also be insufficient unless every redi ...[truncated 1825 chars]- Loopback services such as
- Remediation
View remediation
Remediation Suggestions
- Prefer accepting only relative PTT article paths matching a strict pattern such as
/bbs/<board>/M.<identifier>.html. - If full URLs must be supported, parse them with
urllib.parse.urlsplit()and require:- Scheme exactly equal to
https - Hostname exactly equal to
www.ptt.cc - No embedded username or password
- No non-default port
- A path matching an expected PTT article path
- Scheme exactly equal to
- Disable automatic redirects with
allow_redirects=False, or validate the destination of every redirect before following it. - Resolve the hostname and reject loopback, private, link-local, multicast, reserved, and unspecified IP addresses for both IPv4 and IPv6.
- Do not rely solely on textual hostname checks, because DNS rebinding and alternate IP representations can bypass weak validation.
- Keep network egress restricted at the container or host level so the process cannot access metadata endpoints or internal administrative networks.
- Add tests covering localhost, IPv6 loopback, private addresses, link-local addresses, unusual ports, embedded credentials, malformed schemes, and redirect chains.
- Prefer accepting only relative PTT article paths matching a strict pattern such as
