What is Amazon S3?
Amazon S3, short for Simple Storage Service, is a cloud storage service provided by AWS. It allows you to store and retrieve data of all kinds: files, documents, images, videos, and just about anything else. Because it’s used by individuals and organizations for all sorts of data, it can also be a juicy target for attackers. Just do a quick Google search for “Amazon S3 data leak news” and you’ll see what I mean. It’s been one of the cloud services with the most publicized data leaks, but every single one of those leaks have been a direct result of customer misconfiguration. Learning how to enumerate S3 is a step in the direction of how to learn how to find some of those misconfigurations.
Getting started
First things first, launch the lab (if you haven’t already). This lab should only take a couple of minutes to launch since we’re spinning up new resources just for you. Once it’s launched, you’ll get back an access key ID and a secret access key.
AWS Access Keys
To use these access keys, open up your terminal and type in:
aws configure [--profile <NAME>]
Code language: HTML, XML (xml)
The brackets around [--profile <NAME>]
(and any other command in this course or AWS documentation) means that it’s optional.
…and then input the AWS credentials:
AWS Access Key ID [None]: AKIAT6...
AWS Secret Access Key [None]: sa3CB4...
Default region name [None]: us-east-1
Default output format [None]:
Code language: CSS (css)
(Make sure you use us-east-1
as our labs always use this region unless otherwise specified)
(For default output format, you can just press enter to leave the default value of None. I tend to prefer JSON though so I usually go with that)
Once you have your AWS access keys set up, it’s time to enumerate!
IAM Enumeration
Even thought this is about S3 enumeration and not IAM enumeration, IAM controls a lot in AWS when it comes to access control. For us to know whether we can even enumerate S3 or not, we need to understand what policies we have either for our current user or current role (depending on what you have access to — in the case of this lab, a user).
Let’s start with a very common command used by AWS professionals and attackers:
aws sts get-caller-identity
Code language: JavaScript (javascript)
(Remember to issue the --profile name
if you used a profile, like this:)
aws sts get-caller-identity --profile enumerate
Code language: JavaScript (javascript)
(Otherwise it won’t work, or worse, it will use different credentials and return wrong results!)
I will leave off the profile option going forward for these examples.
Our result should look something like this:
{
"UserId": "AIDAT6ZKEI3EVGXQBH5UJ",
"Account": "272281913033",
"Arn": "arn:aws:iam::272281913033:user/s3-enumerate-Derek"
}
Code language: JSON / JSON with Comments (json)
(Your username and policy names, etc… will be longer than the examples I provide in this walkthrough. I’ve shortened them to reduce noise, but this is totally normal.)
Ok, so we know that we’re authenticated as a user. Let’s start trying to enumerate this user’s permissions.
We’ll start by listing out user policies (only use the username, not the full ARN):
❯ aws iam list-user-policies --user-name s3-enumerate-Derek
{
"PolicyNames": [
"s3-enumerate-AllowS3Operations"
]
}
Code language: PHP (php)
Let’s retrieve this policy:
❯ aws iam get-user-policy --user-name s3-enumerate-Derek --policy-name s3-enumerate-AllowS3Operations
{
"UserName": "s3-enumerate-Derek",
"PolicyName": "s3-enumerate-AllowS3Operations",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"iam:ListPolicies",
"iam:ListPolicyVersions",
"iam:GetPolicy",
"iam:GetUser",
"iam:GetUserPolicy",
"iam:ListUserPolicies"
],
"Resource": "*",
"Effect": "Allow",
"Sid": "AllowIAMActions"
},
{
"Action": [
"s3:ListBucket",
"s3:GetBucketPolicy"
],
"Resource": [
"arn:aws:s3:::cybr-data-bucket1-272281913033",
"arn:aws:s3:::cybr-data-bucket2-272281913033"
],
"Effect": "Allow",
"Sid": "AllowS3Actions"
},
{
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::cybr-data-bucket1-272281913033/*",
"arn:aws:s3:::cybr-data-bucket2-272281913033/*"
],
"Effect": "Allow"
},
{
"Action": [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
}
Code language: JavaScript (javascript)
We can see exactly what this policy permits, which is several actions, but let’s focus on the S3 ones:
s3:ListBucket
s3:GetBucketPolicy
For two resources:
arn:aws:s3:::cybr-data-bucket1-272281913033
arn:aws:s3:::cybr-data-bucket2-272281913033
Then:
s3:GetObject
Again for the same two resources (except now we see a /*
because that action is executed against object resources, and object resource ARNs for S3 require that. Bucket-level actions instead require ARNs without the /*
. It’s a weird S3 quirk to remember.
Finally we see:
s3:ListAllMyBuckets
s3:GetBucketLocation
For all resources (”*”
).
This tells us that we should have access to 2 separate buckets, and the objects within those buckets. We’ll see in a bit if that’s the case or not.
Enumerating S3 – About the CLI and Commands
The AWS S3 CLI is a bit odd, and I think it has to do with S3 being one of AWS’ oldest services (if not the oldest?).
It has multiple different separate service names to access different functionality. There’s:
We’ll ignore the last 2 or this lab.
The first only provides high-level commands we can execute against the S3 service, like:
cp
– copy a local file or S3 object to another location locally or in S3ls
– list S3 objects
…and others.
You can use these if you’d like, and actually I’d recommend practicing using both, but the s3api
is generally much better.
Listing buckets and objects
For example, to list buckets with s3api
, we would do:
❯ aws s3api list-buckets
{
"Buckets": [
{
"Name": "cybr-data-bucket1-272281913033",
"CreationDate": "2024-02-06T01:45:25+00:00"
},
{
"Name": "cybr-data-bucket2-272281913033",
"CreationDate": "2024-02-06T01:45:25+00:00"
}
]
}
Code language: PHP (php)
Or to list objects, we have…wait, what? There are two options?! There’s:
list-objects
list-objects-v2
Yet another S3 quirk :). S3 is a bit…odd. But anyway, it’s recommended that you use list-objects-v2
:
❯ aws s3api list-objects-v2 --bucket cybr-data-bucket1-272281913033
{
"Contents": [
{
"Key": "object.txt",
"LastModified": "2024-02-06T01:46:02+00:00",
"ETag": "\\"cd38bb14cd7a736994f03d28e325b8a5\\"",
"Size": 1349,
"StorageClass": "STANDARD"
}
],
"RequestCharged": null
}
Code language: PHP (php)
As we can see in the documentation, the --bucket
name is required for this command to go through. Once we submit it, we get a result back. For this lab, we have exactly one object and it’s conveniently named object.txt
.
Accessing/Downloading objects
We can even download this object since we have s3:GetObject
permissions (as we learned earlier) by using this command:
❯ aws s3api get-object --bucket cybr-data-bucket1-272281913033 --key object.txt ~/Downloads/object.txt
{
"AcceptRanges": "bytes",
"LastModified": "2024-02-06T01:46:02+00:00",
"ContentLength": 1349,
"ETag": "\\"cd38bb14cd7a736994f03d28e325b8a5\\"",
"ContentType": "text/plain",
"ServerSideEncryption": "AES256",
"Metadata": {}
}
Code language: JavaScript (javascript)
Here we have 3 required options:
--bucket
– the bucket name where the object resides--key
– the specific object to download<outfile>
– the directory and name of the file on our local computer
You can now navigate to where you saved it, and you’ll see the file and can open it like a regular .txt
file!
Inside of this file, you will find the CTF value wrapped in CTF{}. Submit that value to complete the CTF!
But you’ll remember that there is a second bucket. Let’s try to do the same for that bucket.
❯ aws s3api list-objects-v2 --bucket cybr-data-bucket2-272281913033
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
Code language: PHP (php)
Weird…the same command with the 2nd bucket fails? We’re not able to list objects, even though we have access as per our user’s IAM policy.
Resource-based policies vs. Identity-based policies
This is happening because in addition to identity-based policies, we can have resource-based policies in AWS.
Resource-based policies, in S3, can be set per-bucket. If we have access, we can use the command get-bucket-policy
to see it:
❯ aws s3api get-bucket-policy --bucket cybr-data-bucket1-272281913033
{
"Policy": "{\\"Version\\":\\"2012-10-17\\",\\"Id\\":\\"MyDataBucketOnePolicy\\",\\"Statement\\":[{\\"Sid\\":\\"DenyEveryoneElse\\",\\"Effect\\":\\"Deny\\",\\"Principal\\":{\\"AWS\\":\\"arn:aws:iam::272281913033:root\\"},\\"Action\\":\\"s3:*\\",\\"Resource\\":[\\"arn:aws:s3:::cybr-data-bucket1-272281913033/*\\",\\"arn:aws:s3:::cybr-data-bucket1-272281913033\\"],\\"Condition\\":{\\"ArnNotEquals\\":{\\"aws:PrincipalArn\\":[\\"arn:aws:iam::272281913033:user/s3-enumerate-Derek\\",\\"arn:aws:iam::272281913033:role/lab\\"]}}}]}"
}
Code language: JavaScript (javascript)
However in the case of the 2nd bucket, we don’t have access to do that either!
❯ aws s3api get-bucket-policy --bucket cybr-data-bucket2-272281913033
An error occurred (AccessDenied) when calling the GetBucketPolicy operation: Access Denied
Code language: JavaScript (javascript)
Resource-based policies act as a second layer of security. For services that support them, even if you have an IAM user or a role with access to that service or a specific resource, if there’s a resource policy blocking access, you will not have access!
Keep that in mind as you go through security assessments in AWS if you ever get confused as to why you’re getting access denied even though it looks like you should have access.
Conclusion
Et voila! We’ve now listed buckets and objects in an AWS account, and we’ve even downloaded one of the objects. We also learned about resource-based policies and how to enumerate permissions.
Responses