T09 · Insecure Skill Coding Practices
- Location
- scripts/verify.mjs:5
- Finding
- Server-Side Request Forgery Through Untrusted On-Chain Token URI<![CDATA[ ## Vulnerability Details **File Location**: `scripts/verify.mjs:5-8, 40-49` **Vulnerability Type**: Server-Side Request Forgery (SSRF) and unrestricted network access **Risk Level**: High ### Vulnerable Code ```javascript function ipfsToHttp(uri, gatewayHost) { if (!uri.startsWith("ipfs://")) return uri; const cidPath = uri.replace("ipfs://", ""); return `https://${gatewayHost}/ipfs/${cidPath}`; } ``` ```javascript const gatewayHost = args.pinataGateway ?? process.env.PINATA_GATEWAY; let card = null; let registrationMatches = null; if (tokenUri && gatewayHost) { const url = ipfsToHttp(tokenUri, gatewayHost); const res = await fetch(url); if (res.ok) { card = await res.json(); const registrations = Array.isArray(card.registrations) ? card.registrations : []; const expectedRegistry = `eip155:${network.chainId}:${getAddress(network.registry)}`; registrationMatches = registrations.some( (entry) => Number(entry?.agentId) === Number(agentIdRaw) && entry?.agentRegistry === expectedRegistry ); } } ``` ### Technical Analysis The `tokenUri` value is obtained from the ERC-8004 registry for an identity selected through the user-supplied `--agentId`. An NFT owner can control that token URI. The `ipfsToHttp()` function only transforms values beginning with `ipfs://`. Every other URI is returned unchanged and subsequently passed to `fetch()`. No validation restricts the protocol, hostname, destination port, resolved IP address, or redirect destination. Consequently, an attacker-controlled identity can cause the verification process to issue HTTP requests to destinations accessible from the execution environment, including: - Loopback services such as `127.0.0.1` or `[::1]` - Private network services - Link-local addresses - Cloud instance metadata endpoints - Administrative APIs exposed only inside the host or container network The request also lacks a timeout and response-size limit. A malicious endpoint could keep th ...[truncated 1756 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Reject all non-IPFS token URIs if verification only needs Pinata/IPFS content: ```javascript if (!tokenUri.startsWith("ipfs://")) { throw new Error("Only ipfs:// token URIs are supported"); } ``` 2. Construct the gateway URL from a trusted, preconfigured HTTPS gateway rather than accepting an arbitrary destination derived from the token URI. 3. Validate `gatewayHost` against an explicit allowlist. Do not permit credentials, paths, ports, or schemes inside the host argument. 4. If arbitrary HTTPS metadata must be supported: - Allow only `https:`. - Resolve the hostname before connecting. - Reject loopback, private, link-local, multicast, reserved, and cloud metadata address ranges for both IPv4 and IPv6. - Disable redirects or repeat destination validation after every redirect. - Protect against DNS rebinding by ensuring the validated address is the address used for the connection. 5. Add an `AbortController` timeout and enforce a strict maximum response size before parsing JSON. 6. Validate the response content type and registration-card schema before processing it. ]]>
