Home/Journal/Teardown
TeardownCriticalssrfcloud-metadataservice-account

The Helper Proxy That Owned the Cloud

An unauthenticated helper proxy could read cloud metadata and expose a production service account. Here is how we found it and how to close it.

Rahul Dharan··9 min read·a contract-AI SaaSServer-side request forgery to cloud takeover (CWE-918)

The setup

The target was a contract-AI SaaS - a contract-lifecycle platform, the kind of product that ingests a company’s agreements, reads them with AI, and lets legal and sales teams search, edit, and approve them. It was a later-stage company, well past its Series D, with real enterprise customers. That means two things worth an attacker’s attention: a large store of confidential legal documents, and a mature cloud footprint with a lot of moving internal parts.

When a platform is built to fetch, parse, and render documents, it tends to grow small internal helper services - a thing that retrieves a URL, a thing that renders a file, a thing that signs an upload. Those helpers are usually written to be called by other services, not by people. That is exactly the layer we wanted to map, because internal helpers get built for convenience and inherit their security posture from an assumption - “only our own services call this” - that stops being true the moment one is reachable.

Recon

Working black-box, from the open internet with no account, we enumerated subdomains and the surface each one exposed. Most of the product behaved as you would expect. But among the hosts we found a small service that looked internal: a request-proxy - a component whose job is to fetch a URL on the server side and return what it gets. It was reachable, and it did not ask for credentials.

We also noted two other things to come back to: an embedded document-editing server, and the platform’s file storage, both of which are natural places for a document company to keep customer data.

The hypothesis

An unauthenticated server-side fetcher is one of the most reliable finds in cloud security, because there is a specific, high-value place to point it. Every major cloud provider runs an internal metadata endpoint at a fixed, link-local address. That endpoint is reachable only from inside the cloud environment, it authenticates nothing, and it hands back the running machine’s identity: which service account it runs as, what scopes that account holds, and the credentials to act as it.

So the hypothesis was direct. If this proxy would fetch arbitrary URLs from the server side, we could point it at the metadata endpoint. The proxy is inside the cloud environment; we are not. It becomes the bridge. And if the production machine’s service account was broadly scoped - which busy platforms often leave it - reading its identity would reveal the makings of a full cloud-project takeover.

What we tried

We pointed the proxy at the metadata endpoint and watched what came back.

The first attempt met a guard. The service had a header filter meant to block requests aimed at metadata. But the filter was checking a request header - and the attacker supplies the header. A control that depends on the caller not sending a particular value is not a control against a caller who controls every value. We shaped the request so the filter’s condition was not met, and the fetch went through.

The metadata endpoint answered. It returned the production service account’s identity and its scope: a broad cloud-platform scope, which is the widest general grant the platform offers. That is the identity the whole production machine runs as, and its permissions were not narrowed to a job.

Here is where restraint mattered most. The same endpoint that names the service account will also, one path deeper, mint an actual access token for it. We did not fetch the token. Proving the reachable identity and its broad scope establishes the vulnerability and its severity completely. Pulling the live token would have crossed from proving exposure into holding working production credentials, and there was no finding on the other side of that line we did not already have. We stopped at identity and scope.

What we found

Three findings that compounded, with the proxy as the centre of gravity.

The proxy to cloud metadata. The unauthenticated request-proxy could be pointed at the cloud metadata endpoint and returned the production service account’s identity and its broad cloud-platform scope. The header filter meant to prevent exactly this was defeated because the block depended on a header the caller controls. The shape:

-> proxy fetch, destination = the cloud metadata endpoint
   (request shaped so the header filter's condition was not met)

<- { service account: <production account>,
     scopes: [ "cloud-platform" ] }        # the widest general scope

An unauthenticated, internet-reachable service had just read the identity that the entire production environment runs as. With a broad scope on that account, that is the doorway to controlling the cloud project.

The document-editing server with signing disabled. Separately, an embedded document-editing server had its token signing turned off. Signing is what makes its access tokens unforgeable; with it disabled, the tokens meant to gate access to customer documents could be produced rather than earned, exposing those documents.

The storage bucket with two locks, one of them open. The platform’s file storage had two independent access layers: the cloud provider’s own IAM layer, and the storage service’s own rules layer. The IAM layer was locked down correctly. The rules layer was wide open. Because either layer can grant access, the open one governed, and thousands of customer legal documents were reachable through it.

Why it happens

None of these was a careless decision in isolation. Each is a normal consequence of building a document platform quickly on a rich cloud.

The proxy is trusted because it is “internal.” But “internal” is a statement about intent, not about reachability. The service was written to be called by other services, so nobody attached authentication to it - and then it ended up on a reachable host, still fetching whatever URL it was handed. The header filter was a real attempt to close the metadata path, but it was built on the wrong assumption: that the attacker would send a particular header, when the attacker sets every header.

The broad service-account scope is what you get when a machine needs to touch several cloud services and the fastest way to unblock it is the widest grant. It works immediately, and the narrowing pass that should follow rarely gets scheduled.

The storage bucket is the classic two-locks trap: a team hardens the IAM layer, reasonably believes storage is now locked, and never checks that the storage service’s own separate rules layer was left at a permissive default. Two independent systems, and access is granted if either one says yes.

For developers

If you run server-side fetchers on a cloud platform, these controls close this class:

  1. Never let a server-side fetcher reach the metadata endpoint. Block the metadata IP address at the network or egress layer for any service that does not strictly need it. This is the single control that neutralizes the whole chain regardless of what else is misconfigured.
  2. Require authentication on the proxy. An internal helper that fetches URLs is a powerful primitive. It should authenticate its callers, not assume that only trusted services will find it.
  3. Allow-list destinations; do not blocklist headers. A filter that blocks a known-bad request shape fails when the attacker controls the request. Constrain a fetcher to an explicit list of destinations it is allowed to reach, rather than trying to enumerate the ones it must refuse.
  4. Scope service accounts minimally. A broad cloud-platform scope turns any identity leak into a project takeover. Grant each machine only the specific permissions its job needs, so that reading its identity reveals a narrow capability rather than the keys to everything.
  5. Check both layers on cloud storage. Where a bucket has both an IAM layer and a storage-rules layer, verify both. Access is granted if either says yes, so the permissive one wins.
  6. Keep token signing on. For any embedded service that issues access tokens, signing is what makes those tokens unforgeable. Disabling it, even in a hurry, removes the thing standing between a request and the data.

One detection step: from a machine in your environment, try to reach the metadata endpoint through each of your server-side fetchers. If any of them returns it, that fetcher is a bridge from wherever it is reachable to your cloud identity.

The takeaway

An internal helper proxy is trusted because it is called “internal,” but internal describes who was meant to call it, not who can. Once a server-side fetcher is reachable and will retrieve attacker-chosen URLs, it becomes a bridge from the internet to the cloud metadata endpoint - and from there to the identity your whole production environment runs as. A header filter does not stop a caller who sets the header, and a broad service-account scope turns one leak into a takeover. Block the metadata path, authenticate the fetcher, and keep the identity behind it small.

This teardown is one instance of a pattern about how real severity is built: not from one flaw, but from several that compound. We wrote up the general case, and how to defend against it, in Chaining: Where Real Severity Comes From.

Frequently asked

What is server-side request forgery to cloud takeover?
It is an attack where a server can be tricked into making requests to destinations the attacker chooses, including the cloud provider's internal metadata endpoint. That endpoint hands back the machine's service-account identity and permissions, which, if broadly scoped, can lead to control of the whole cloud project.
Why is a cloud metadata endpoint a target for SSRF?
The metadata endpoint is reachable only from inside the cloud environment and returns the running machine's identity, scopes, and credentials with no authentication of its own. An unauthenticated server-side fetcher that will retrieve any URL becomes a bridge from the internet straight to that endpoint.
How do you prevent SSRF against cloud metadata?
Block the metadata IP address at the network layer, require authentication on any server-side fetcher, allow-list the destinations it may request rather than blocking known-bad ones, and scope every service account to the minimum permissions it needs so a leak is not a takeover.

This is one finding from a harness that runs continuously. See how Greywatch finds, proves, and fixes them.

How it works →