T09 · Insecure Skill Coding Practices
- Location
scripts/extract.js:4- Finding
Unrestricted Browser Navigation Enables Server-Side Request Forgery
- Content
View full analysis
Vulnerability Details
File Location:
scripts/extract.js, lines 4–15
Vulnerability Type: Server-Side Request Forgery through insufficient URL validation
Risk Level: MediumVulnerable Code
javascript const url = process.argv[2]; if (!url) { console.error('usage: node scripts/extract.js <mp.weixin.qq.com url>'); process.exit(1); } const browser = await chromium.launch({ headless: false }); const page = await browser.newPage({ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36' }); await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });The shell entry point also forwards the argument without validation:
bash URL="${1:-}" if [[ -z "$URL" ]]; then echo "usage: $0 <mp.weixin.qq.com url>" >&2 exit 1 fi SKILL_DIR="$(cd "$(dirname "$0")" && pwd)" cd "$SKILL_DIR" if [[ ! -d node_modules/playwright ]]; then npm install >/dev/null 2>&1 fi PLAYWRIGHT_BROWSERS_PATH="$HOME/.cache/ms-playwright" export PLAYWRIGHT_BROWSERS_PATH if [[ ! -x "$PLAYWRIGHT_BROWSERS_PATH/chromium-1217/chrome-linux64/chrome" ]]; then npx playwright install chromium >/dev/null 2>&1 fi node scripts/extract.js "$URL"Technical Analysis
Although the documentation describes the input as an
mp.weixin.qq.comarticle URL, the implementation only verifies that an argument exists. It does not parse the URL or enforce its scheme, hostname, port, or resolved destination.The untrusted value is supplied directly to Playwright's
page.goto(). Chromium therefore initiates a request using the network privileges and reachability of the machine running the skill. The script subsequently extracts the destination's title and up to 20,000 characters of rendered body text and returns that content as JSON.This creates a browser-based server-side request forgery primitive. A caller may direct the browser to loopback int ...[truncated 1659 chars]
- Remediation
View remediation
Remediation Suggestions
- Parse input with the standard
URLclass and reject malformed values. - Permit only the
https:scheme. - Require the normalized hostname to equal
mp.weixin.qq.com; do not use substring or suffix checks that can accept deceptive domains. - Reject embedded credentials, nonstandard ports, IP-literal hosts, and unsupported URL schemes.
- Intercept browser requests and reject navigation to loopback, private, link-local, multicast, and reserved address ranges.
- Resolve hostnames and verify all returned addresses before navigation, while accounting for DNS rebinding.
- Validate every redirect destination rather than checking only the initial URL.
- Restrict subresources if WeChat pages do not require unrestricted third-party requests.
- Apply outbound firewall or sandbox controls as defense in depth so the browser cannot reach internal or metadata networks.
- Return a clear validation error without navigating when the URL falls outside the approved scope.
A basic initial validation should include logic equivalent to:
javascript function validateArticleUrl(value) { const parsed = new URL(value); if (parsed.protocol !== 'https:') { throw new Error('Only HTTPS URLs are permitted'); } if (parsed.hostname !== 'mp.weixin.qq.com') { throw new Error('Only mp.weixin.qq.com URLs are permitted'); } if (parsed.username || parsed.password || parsed.port) { throw new Error('Credentials and nonstandard ports are not permitted'); } return parsed.href; }This application-level check should be combined with redirect validation and network-layer blocking because hostname validation alone does not fully address DNS rebinding or prohibited subresource requests.
- Parse input with the standard
