GCP Metadata SSRF: From a Fetch Bug to Cloud Credentials
SSRF is worth ten times more on a cloud host than on a bare-metal one, because the cloud gives every instance a metadata server sitting at a fixed internal address. On Google Cloud that's 169.254.169.254 (also metadata.google.internal). If your SSRF can reach it, you're one request away from the instance's service account token.
Confirm you're on GCP first
Before chasing credentials, make the SSRF hit the metadata root and read the platform's fingerprint. GCP's metadata server has a distinctive quirk that also happens to be its main defense: it requires a header.
GET http://169.254.169.254/computeMetadata/v1/
Metadata-Flavor: Google
Without Metadata-Flavor: Google, GCP returns a 403. That single requirement stops a lot of naive SSRF, and it's why GCP metadata attacks depend on whether your SSRF primitive lets you set headers.
The header problem is the whole fight
Plenty of SSRF bugs only control a URL, not headers. If that's your case, you can't hit the v1 metadata endpoints directly. Look for angles:
- An SSRF that forwards your request headers verbatim (some proxy and webhook features do)
- Gopher or a raw request primitive that lets you craft the full HTTP request including
Metadata-Flavor - A URL parser confusion (
@, fragments, DNS rebinding) that reaches the metadata IP while a header you influence rides along
If you truly cannot set the header, be honest in the report: you have SSRF to an internal address, but not confirmed credential theft. Those are different severities.
Grabbing the token
With the header controllable, the prize is the default service account token:
GET http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token
Metadata-Flavor: Google
That returns a short-lived OAuth access token. Also readable: the account email, the granted scopes, the project ID, and any custom metadata (which teams sometimes stuff with startup secrets they shouldn't).
Prove it without abusing it
Do not go spelunking through the victim's cloud with a stolen token. The clean proof is bounded: exfiltrate the token to your own listener, then call one read-only, identity-only endpoint that reveals scope, such as the token info endpoint, to confirm it's live. Showing the service account email and its scopes proves the credential is real and dangerous without touching a single customer resource.
Redact most of the token in your report and note its expiry — these are short-lived, so a screenshot from hours ago proves nothing. Timestamps matter.
Impact framing and fixes
Severity tracks the service account's IAM roles. A token scoped only to logging.write is minor; one with editor on the project is close to full compromise. State the scopes you observed and stop there — you don't need to demonstrate project takeover to earn the payout.
Recommend the standard defenses: require metadata v1 with Metadata-Flavor (already default), but more importantly fix the SSRF — allowlist outbound destinations, block link-local ranges (169.254.0.0/16), resolve and pin DNS, and reject redirects to internal IPs. On the cloud side, run instances with least-privilege service accounts and consider GKE Workload Identity so pods never see the node's metadata token.
Google's metadata server documentation details the endpoint layout and the Metadata-Flavor requirement. Read it so your report describes exactly which endpoint you reached and why the header mattered.