Cheeky Bucket Squatting Defeated by Terraform
A failed bounty experiment of mine. Having previously pwned a company's AWS, I had a list of their S3 buckets and more importantly their bucket naming scheme e.g. TARGETNAME-SERVICENAME-staging
and TARGETNAME-SERVICENAME-prod
.
If you can generate a list of possible bucket names, you can easily check if they exist. The simplest is to make an HTTP request to BUCKETNAME.s3.amazonaws.com and see if the response is NoSuchBucket
, but when automated it is much faster to use DNS lookups rather than HTTP.
I can maintain a list of words that the target could plausibly use in their bucket names. By keeping an eye on the target's blog, announcements and changes to bundled JavaScript source code, we can add some names of new features and services to our list and regularly enumerate looking for new buckets.
Like all research, it was initially exciting and then felt like a waste of time until I spotted a new bucket for a newly announced feature: TARGETNAME-NEWSERVICENAME-results-staging
. Most excitingly, TARGETNAME-NEWSERVICENAME-results-prod
had not yet been claimed. That meant that I could use my own AWS account to claim the bucket. I made the permissions as open as possible in the hope that the target shrugs at the bucket already existing and just starts using it, storing their data in my AWS account. This involved using the ACL to give http://acs.amazonaws.com/groups/global/AllUsers
full control and then adding a bucket policy which enables most of s3:*
to principal AWS:*
.
In order to monitor the experiment, I enabled S3 bucket logging on my squatted bucket and waited.
Results #
The S3 bucket logs:
01:20:45 *IP_1* arn:aws:sts::*AWS_ACCOUNT_ID*:assumed-role/TerraformAccountAccessRole/aws-go-sdk-*TIMESTAMP1* REST.HEAD.BUCKET - "HEAD / HTTP/1.1" 200 - "-" "APN/1.0 HashiCorp/1.0 Terraform/1.3.10 (+https://www.terraform.io) terraform-provider-aws/4.65.0 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go/1.44.251 (go1.19.8; linux; amd64)" *BUCKETNAME*.s3.amazonaws.com
04:00:30 *IP_2* arn:aws:sts::*AWS_ACCOUNT_ID*:assumed-role/TerraformAccountAccessRole/aws-go-sdk-*TIMESTAMP2* REST.HEAD.BUCKET - "HEAD / HTTP/1.1" 200 - "-" "APN/1.0 HashiCorp/1.0 Terraform/1.3.10 (+https://www.terraform.io) terraform-provider-aws/4.65.0 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go/1.44.251 (go1.19.8; linux; amd64)" *BUCKETNAME*.s3.amazonaws.com
04:17:49 *IP_3* REST.GET.BUCKET - "GET /*BUCKETNAME* HTTP/1.1" 200 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
04:18:04 *10. IP_4* REST.GET.BUCKET - "GET /*BUCKETNAME* HTTP/1.1" 200 - "-" "Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)" s3.amazonaws.com
04:18:08 *IP_5* REST.GET.BUCKET - "GET /*BUCKETNAME* HTTP/1.1" 200 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:120.0) Gecko/20100101 Firefox/120.0" s3.amazonaws.com
04:24:40 *IP_3* REST.GET.BUCKET - "GET /*BUCKETNAME* HTTP/1.1" 200 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" s3.amazonaws.com
04:52:00 *IP_3* REST.GET.BUCKET - "GET / HTTP/1.1" 200 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" *BUCKETNAME*.s3.amazonaws.com
04:52:00 *IP_3* REST.GET.OBJECT favicon.ico "GET /favicon.ico HTTP/1.1" 404 NoSuchKey "http://*BUCKETNAME*.s3.amazonaws.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" *BUCKETNAME*.s3.amazonaws.com
08:54:02 *IP_6* REST.GET.BUCKET - "GET / HTTP/1.1" 200 - "-" "Python/3.10 aiohttp/3.9.0" *BUCKETNAME*.s3.amazonaws.com
Redacted information is surround by *
.
So the target finally attempted to terraform the prod bucket. The terraform role HEAD
ed the bucket but it must have failed. Nearly 4 hours later they retried and 17 minutes after that failed an engineer with IP_3 went to load the bucket listing in Safari. In utter surprise and confusion, the engineer posted the bucket link to Slack (as seen by the Slackbot user agent), followed by team member with IP_5 loading the bucket listing in Firefox. Do they suspect malevolent forces are involved? Are they scared?
Outcome #
So looks like this was pointless. It's plausible that the engineers seeing a Terraform error and a bucket with their super secret naming scheme could force Terraform to use my evil bucket, but still fairly unlikely. In this case, they just made new buckets with another name.
If my aim was just to harrass their dev team, I totally would have gone full speed on the enumeration and registered a prod bucket every time I noticed a new staging bucket, but I let it slide. I did find the modified staging bucket name but was boring and decided not to preregister the prod bucket.
This wasted way too much time and all the information you can get is:
- AWS account ID
- AWS Terraform role names
- Engineer IP addresses pointing to different cities
- Engineer favourite browsers and versions
- Timestamps of when engineers are awake (or awake due to a failed Terraform alert)
If they didn't use terraform but use some sort of create-if-not-exists script for S3 buckets then I might have been owning all their data. Instead I just annoyed them.