T09 · Insecure Skill Coding Practices
Error
- Location
- bb-sites/gumtree/listing.js:64
- Finding
- Server-Side Request Forgery Through Unrestricted Listing URL## Vulnerability Details **File Location**: `bb-sites/gumtree/listing.js`, lines 64–75 **Vulnerability Type**: Server-Side Request Forgery (SSRF) **Risk Level**: High ### Vulnerable Code ```js if (!args.url) return { error: 'Missing argument: url', hint: 'e.g. bb-browser site gumtree/listing "https://www.gumtree.com/p/.../ID"' }; let path = String(args.url).trim(); if (!path.startsWith('http')) { if (!path.startsWith('/')) path = '/' + path; path = 'https://www.gumtree.com' + path; } const resp = await fetch(path, { headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', Accept: 'text/html,application/xhtml+xml', 'Accept-Language': 'en-GB,en;q=0.9', }, }); ``` ### Technical Analysis The handler is documented as accepting a Gumtree listing URL, but any input beginning with `http` is passed directly to `fetch()`. It does not parse and validate the URL, restrict the protocol to HTTPS, enforce the expected `www.gumtree.com` hostname, reject embedded credentials or nonstandard ports, block private and loopback addresses, or validate redirect destinations. A simple prefix check is not an adequate trust boundary. Values such as `http://127.0.0.1:PORT/`, cloud metadata addresses, or internal service URLs satisfy the check and can therefore become outbound requests from the runtime hosting `bb-browser`. Automatic redirects present an additional route: even if an initial URL were considered trusted, the code does not verify `resp.url` before consuming the response. A server-controlled redirect could consequently send the request to a prohibited destination. The response body is parsed for JSON-LD and Open Graph fields, and selected values are returned to the caller. Internal services that return compatible HTML metadata may therefore expose data through this interface. Even where response contents cannot be extracted, observable status, timing, redirects ...[truncated 1852 chars]
- Remediation
- ## Remediation Suggestions 1. Parse the input with `new URL()` and reject malformed URLs. 2. Permit only the `https:` protocol. 3. Require the normalized hostname to equal `www.gumtree.com`; do not use substring or suffix-only checks. 4. Reject URLs containing embedded username or password fields and reject unexpected ports. 5. Configure requests not to follow redirects automatically. If redirects are required, parse and validate every destination using the same policy before following it. 6. Verify that the final `resp.url` remains an approved Gumtree HTTPS URL before reading or returning response data. 7. Where broader host support is ever required, resolve DNS and reject loopback, private, link-local, multicast, reserved, and cloud-metadata address ranges for both IPv4 and IPv6. Repeat this validation for every redirect and mitigate DNS rebinding. 8. Apply request timeouts, response-size limits, and conservative rate limits to reduce scanning and denial-of-service potential. 9. Prefer accepting a validated Gumtree listing path or listing identifier instead of an arbitrary URL, then construct the complete URL internally. A minimal hostname restriction should follow this pattern: ```js let target; try { target = new URL(String(args.url).trim(), 'https://www.gumtree.com'); } catch { return { error: 'Invalid listing URL' }; } if ( target.protocol !== 'https:' || target.hostname !== 'www.gumtree.com' || target.username || target.password || (target.port && target.port !== '443') ) { return { error: 'Only HTTPS Gumtree listing URLs are allowed' }; } const resp = await fetch(target.href, { redirect: 'manual', headers: { 'User-Agent': 'Mozilla/5.0', Accept: 'text/html,application/xhtml+xml', 'Accept-Language': 'en-GB,en;q=0.9', }, }); if (resp.status >= 300 && resp.status < 400) { return { error: 'Redirects are not permitted without destination validation' }; } ```
