Kubernetes Service Account Token Mounts in Bug Bounty Recon
Every pod that mounts a service account token is a credential sitting on disk. The path is boring—/var/run/secrets/kubernetes.io/serviceaccount/token—and hunters skip it because it looks like infrastructure noise. Skip it and you miss the shortest path from container compromise to cluster API access.
I've seen programs dismiss this as "out of scope K8s." Fair when you never get a shell. Unfair when their app already leaks a pod filesystem, a debug endpoint, or a SSRF that can read that path.
Confirm the mount before inventing RBAC lore
If you land a shell or a file-read primitive, check three files:
/var/run/secrets/kubernetes.io/serviceaccount/token
/var/run/secrets/kubernetes.io/serviceaccount/namespace
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
A JWT in token is not automatically cluster-admin. Decode the payload (header.payload only; do not paste live tokens into random websites). Note iss, sub, and expiry. Projected tokens may be short-lived. Bound tokens may refuse to work outside the pod. That detail belongs in the report.
Then ask: is automountServiceAccountToken disabled on the pod or the service account? Many charts still default to true. A token that exists is already a finding if an untrusted tenant can reach the filesystem; severity depends on what the account can do.
Older clusters still ship long-lived secrets as tokens. Newer ones prefer projected, audience-bound tokens. If your proof works hours later from a laptop, say so. If it dies outside the pod, say that too—triage hates mystery JWTs.
Map permissions with the API, not vibes
Point kubectl or curl at the in-cluster API using the mounted CA and token. Prefer read calls:
GET /api/v1/namespacesGET /apis/authorization.k8s.io/v1/selfsubjectrulesreviews(or SelfSubjectAccessReview for one verb)- List secrets only if the program allows secret access testing and you stay in your namespace
Write down each allowed verb and resource. "Can list pods in kube-system" beats "might be privileged." If the account can create pods, exec into nodes, or read secrets across namespaces, say so with the exact response body redacted to a canary name.
SSRF into the metadata path or the API server needs the same discipline. Prove you can obtain a token or call the API. Do not create DaemonSets "to show impact."
When the API server hostname is not obvious, check environment variables such as KUBERNETES_SERVICE_HOST and the resolvable kubernetes.default.svc name from inside the pod network. Outside the cluster you may need an exposed API or a proxied path; do not assume every token works from the public internet.
Where this shows up in web apps
Look for:
- Debug consoles that expose container files
- Path traversal into
/var/run/secrets - Backup downloads that include pod volumes
- CI jobs that print
kubectlconfigs or mount the default SA in build pods - Shared cluster tenancy where your pod's token is broader than your app role
Also watch "helpful" support features: download-the-pod-logs zip files, ephemeral debug containers left running, and admin panels that render raw volume listings. Any of those can turn a boring web bug into a cluster identity issue.
Write the chain triage expects
The report should chain: entry primitive → token read → API identity → concrete capability. Attach a redacted JWT header/payload, the namespace file contents, and two or three authorized API responses. Recommend disabling automount where unused, projecting short-lived tokens, binding least-privilege Roles and RoleBindings (not ClusterRoles by default), and blocking the service account path from app file APIs.
If you found the token via SSRF, include the exact request and show you did not pivot into destructive verbs. Kubernetes docs on service accounts explain projected tokens and automount knobs. Use that language so triage does not argue about whether a JWT on disk "counts."