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.