There’s a reflex every cloud tester has: find an SSRF, point it at
169.254.169.254, collect the managed-identity token, win. During
Operation Vermillion Drift that reflex returned
nothing — and the empty response was more instructive than a token would have been.
Why the metadata call came back empty
The SSRF lived in an Azure Function. Azure Functions and App Service run on a PaaS
fabric that does not expose the VM Instance Metadata Service at 169.254.169.254.
On top of that, this particular function had no managed identity assigned at all, so
there was nothing to steal even if the endpoint had answered. The empty result was a
negative confirmation, not a dead end.
The metadata surface you target has to match the compute you actually landed on:
- Virtual machines expose IMDS at
http://169.254.169.254/metadata/..., and a managed-identity token requires theMetadata: trueheader. - App Service / Functions don’t use IMDS. When they have a managed identity, the
runtime injects
IDENTITY_ENDPOINTandIDENTITY_HEADERenvironment variables, and you request the token from that local endpoint with that header.
So the fix for a “failed” SSRF is often not a different vulnerability — it’s a different target for the same one.
The pivot that still worked
Since the environment held the secrets anyway, I re-aimed the same SSRF at
file:///proc/self/environ and dumped the function’s entire environment: the live
signing key, connection strings, and a bonus storage SAS. When credentials live in the
process environment, a file:// read rivals an IMDS token — and it doesn’t care what
compute type you’re on.
The takeaway
169.254.169.254 coming back empty is a signal, not a verdict. Read it as “wrong
metadata model for this compute,” enumerate what the runtime actually exposes, and
remember that local-file reads through the same SSRF are frequently the shorter path to
the same secrets.