T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/wecom-drive-browser.mjs:288
- Finding
- Unrestricted URL Navigation from a Persistent Authenticated Browser Context## Vulnerability Details **File Location**: `scripts/wecom-drive-browser.mjs:288-311, 347-350` **Vulnerability Type**: Server-Side Request Forgery–like browser navigation and insufficient destination validation **Risk Level**: Medium ### Vulnerable Code ```js const targetUrl = values.url || DEFAULT_LOGIN_URL; const timeoutMs = Number.parseInt(values["timeout-ms"] || "30000", 10); const qrPath = values["qr-path"] || defaultQrPath(); const jsonPath = values["json-path"]; const profileDir = values["profile-dir"] || DEFAULT_PROFILE_DIR; const headed = Boolean(values.headed); const keepOpen = Boolean(values["keep-open"]); let context; try { const executablePath = await resolveBrowserExecutable(); await mkdir(profileDir, { recursive: true }); await mkdir(DEFAULT_OUTPUT_DIR, { recursive: true }); context = await chromium.launchPersistentContext(profileDir, { executablePath, headless: !headed, viewport: { width: 1440, height: 960 }, locale: "zh-CN", args: ["--disable-dev-shm-usage"], }); const page = context.pages()[0] || (await context.newPage()); await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: timeoutMs, }); ``` When authentication is not detected, page information is collected and included in the output: ```js if (!result.loginRequired) { const summary = await collectPageSummary(page); result.page.links = summary.links; result.page.editableElements = summary.editableElements; } ``` The result also contains text extracted during state detection: ```js page: { textHints: state.textHints, links: [], editableElements: [], }, ``` ### Technical Analysis The `--url` argument is accepted without validating its scheme, hostname, resolved IP address, or redirect destination. The supplied value is passed directly to `page.goto()` inside a persistent Chromium context. Although the Skill is documented as an interface to official WeCom and Tencent document services, the implementation does no ...[truncated 2492 chars]
- Remediation
- ## Remediation Suggestions 1. **Enforce an explicit destination allowlist** - Accept only `https:` URLs. - Restrict hostnames to the official domains required by the Skill, such as precisely enumerated WeCom and Tencent document hosts. - Compare normalized hostnames exactly or by a safe subdomain rule; do not use substring matching. 2. **Block non-public network destinations** - Resolve the hostname before navigation. - Reject loopback, private, link-local, multicast, unspecified, and reserved IPv4 and IPv6 ranges. - Recheck DNS resolution immediately before connecting to reduce DNS rebinding risk. 3. **Validate redirects** - Inspect every navigation request and redirect destination. - Abort navigation if any destination falls outside the hostname allowlist or resolves to a prohibited address. - Validate `page.url()` again before extracting or returning page content. 4. **Isolate persistent authentication state** - Use the persistent profile only for validated official WeCom destinations. - Use a fresh, non-persistent browser context with no cookies or stored credentials for any explicitly supported external destination. - Consider disabling service workers and clearing unrelated site data from the persistent profile. 5. **Minimize returned page data** - Do not return body-text hints, links, or editable-element metadata until the final origin has passed validation. - Apply conservative length limits and redact potentially sensitive values. - Return a structured error when the destination is not approved. 6. **Add security tests** - Verify rejection of `http:`, `file:`, `data:`, loopback addresses, private IPv4 ranges, IPv6 loopback/link-local addresses, and cloud metadata destinations. - Test redirects from an approved-looking entry point to a prohibited destination. - Test deceptive hostnames such as `doc.weixin.qq.com.attacker.example`.
