back
vulnerabuild_ / build
attack paths
2 components · 5 vulnerabilities
infrastructure-as-code
format
cloud
region
# ================================================================
# Attack path: SSRF → IMDS → S3 (Capital One pattern)
# Difficulty: hard  ·  Cloud: aws  ·  Est. time: 30 min
#
# Starting position: SSRF discovered in a public web app running on an EC2 instance.
# Objective:         Exfiltrate an internal S3 bucket using credentials lifted from the instance's IAM role.
#
# Playbook:
#   1. Identifies an SSRF primitive in the app (e.g. a URL-fetch parameter that follows redirects).
#   2. Pivots SSRF to `http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>` — IMDSv1 returns STS credentials in plaintext, no token required.
#      exploits: EC2-022
#   3. Exports the credentials and runs `aws s3 ls` against discovered buckets. The role's wildcard `s3:*` grants account-wide access.
#      exploits: IAM-031, S3-001, S3-004
#
# Cleanup: terraform destroy -auto-approve   (or: aws cloudformation delete-stack)
# ================================================================

terraform {
  required_providers {
    aws    = { source = "hashicorp/aws",    version = "~> 5.0" }
    random = { source = "hashicorp/random", version = "~> 3.6" }
  }
}

provider "aws" {
  region = "us-east-1"
}

resource "random_id" "suffix" {
  byte_length = 4
}

data "aws_ami" "al2023" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

# SSRF→IMDS chain
# vulnerabilities: EC2-022, IAM-031
resource "aws_iam_role" "vm_imds_v1" {
  name = "vbuild-vm-imds-v1"
  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Effect    = "Allow",
      Principal = { Service = "ec2.amazonaws.com" },
      Action    = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy" "vm_imds_v1" {
  name = "vbuild-vm-imds-v1-broad"
  role = aws_iam_role.vm_imds_v1.name
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Effect   = "Allow",
      Action   = ["s3:*", "iam:PassRole"],
      Resource = "*"
    }]
  })
}

resource "aws_iam_instance_profile" "vm_imds_v1" {
  name = "vbuild-vm-imds-v1"
  role = aws_iam_role.vm_imds_v1.name
}

resource "aws_instance" "vm_imds_v1" {
  ami                         = data.aws_ami.al2023.id
  instance_type               = "t3.micro"
  iam_instance_profile        = aws_iam_instance_profile.vm_imds_v1.name
  associate_public_ip_address = true
  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "optional" # IMDSv1 allowed — SSRF-prone
    http_put_response_hop_limit = 2
  }
  tags = { Name = "vbuild-vm-imds-v1" }
}

# Public object bucket
# vulnerabilities: S3-001, S3-004, S3-010
resource "aws_s3_bucket" "data_public_bucket" {
  bucket        = "vbuild-public-${random_id.suffix.hex}"
  force_destroy = true
}

resource "aws_s3_bucket_ownership_controls" "data_public_bucket" {
  bucket = aws_s3_bucket.data_public_bucket.id
  rule {
    object_ownership = "BucketOwnerPreferred"
  }
}

resource "aws_s3_bucket_public_access_block" "data_public_bucket" {
  bucket                  = aws_s3_bucket.data_public_bucket.id
  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}

resource "aws_s3_bucket_acl" "data_public_bucket" {
  depends_on = [
    aws_s3_bucket_ownership_controls.data_public_bucket,
    aws_s3_bucket_public_access_block.data_public_bucket,
  ]
  bucket = aws_s3_bucket.data_public_bucket.id
  acl    = "public-read-write"
}

intentionally vulnerable. Apply only in an isolated sub-account / project, time-boxed and tagged. Never deploy on top of production.