Securing S3 Buckets: Flaws.cloud level1
gardnerapp
Posted on May 29, 2023
This is a tutorial for those with beginners to moderate computer skills, where we'll be learning security concepts on the AWS cloud platform by playing the flAWS CTF. A pen test or red team exercise is useless unless the security risks and mistakes can be explained to the development team. Unlike other tutorials which only tell you how to capture the flag I'm going to take things a step further by providing remediation and recommendations.
If you'd like to try the CTF on your can check it out at http://flaws.cloud. The first step of every engagement is to conduct reconnaissance, useful information like where our application is running and what it's tech stack is will help us determine at which angle to attack it from. We can figure out the IP address of flaws.cloud by running a pin command like so:
ping flaws.cloud
PING flaws.cloud (52.92.138.91): 56 data bytes
64 bytes from 52.92.138.91: icmp_seq=0 ttl=232 time=116.146 ms
64 bytes from 52.92.138.91: icmp_seq=1 ttl=232 time=116.076 ms
64 bytes from 52.92.138.91: icmp_seq=2 ttl=232 time=114.995 ms
^C
Ping sends a series of packets, specifically ICMP packets to the server that the flaws.cloud domain name points to. Ping is a tool used to see if a server is online, it's like yelling "Hello !"
at the server and waiting for an answer back from the server. Ping automatically converts a domain name to an IP address by contacting what is called a name server, which is basically analogous to a phone book that tells everyone which IP addresses are associated with which server.
Determining the name server can help us understand which services within the cloud the application is running on. We can find the name server by running
nslookup 52.92.138.91
which will produce the following:
Server: 2001:578:3f::30
Address: 2001:578:3f::30#53
Non-authoritative answer:
91.138.92.52.in-addr.arpa name = s3-website-us-west-2.amazonaws.com.
Attacking the name server would be out of scope, but we do find some valuable information in that the website is being hosted in S3 within the us-west-2 region. This information will allow us to interact with the website through the aws command line tool and the other HTTPS endpoints offered by S3.
S3 is an acronym that stands for "simple storage
service", it can be used to store literally anything like CSS, JavaScript, HTML files, log backups, and even memes. To break into a system we must understand what the rules are for using that system like how assets communicate with each other and how their permissions operate. With all that being said here are some basic rules of S3 that you might not know:
1) Objects are stored in collections called buckets, think of a
bucket as a folder or database table.
2) There are a series of permissions that determine which other AWS accounts or services can read, write or edit objects within an S3 bucket.
3) Buckets are stored in regions and in order to access the bucket we must know both its name and the region where it is located. See
https://docs.aws.amazon.com/AmazonS3/latest/userguide/
access-bucket-intro.html for more info.
4) When hosting a static website on S3 the domain name of the bucket must be the same as the buckets name. Don't ask me why this is, it was probably just easier for the AWS developers to set things up this way.
Now that we know the buckets name flaws.cloud
and its region
us-west-2
we can go visit the site via the AWS s3 access
point. HTTP access points all follow the same syntax which is https://<bucket name><bucket region>.amazonaws.com
. Which means the flaws.cloud bucket will be hosted at https://flaws.cloud.s3.us-west-2.amazonaws.com
. If you go on over to this site you'll find this XML tree:
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>flaws.cloud</Name>
<Prefix/>
<Marker/>
<MaxKeys>1000</MaxKeys>
<IsTruncated>false</IsTruncated>
<Contents>
<Key>hint1.html</Key>
<LastModified>2017-03-14T03:00:38.000Z</LastModified>
<ETag>"f32e6fbab70a118cf4e2dc03fd71c59d"</ETag>
<Size>2575</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>hint2.html</Key>
<LastModified>2017-03-03T04:05:17.000Z</LastModified>
<ETag>"565f14ec1dce259789eb919ead471ab9"</ETag>
<Size>1707</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>hint3.html</Key>
<LastModified>2017-03-03T04:05:11.000Z</LastModified>
<ETag>"ffe5dc34663f83aedaffa512bec04989"</ETag>
<Size>1101</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>index.html</Key>
<LastModified>2020-05-22T18:16:45.000Z</LastModified>
<ETag>"f01189cce6aed3d3e7f839da3af7000e"</ETag>
<Size>3162</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>logo.png</Key>
<LastModified>2018-07-10T16:47:16.000Z</LastModified>
<ETag>"0623bdd28190d0583ef58379f94c2217"</ETag>
<Size>15979</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>robots.txt</Key>
<LastModified>2017-02-27T01:59:28.000Z</LastModified>
<ETag>"9e6836f2de6d6e6691c78a1902bf9156"</ETag>
<Size>46</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>secret-dd02c7c.html</Key>
<LastModified>2017-02-27T01:59:30.000Z</LastModified>
<ETag>"c5e83d744b4736664ac8375d4464ed4c"</ETag>
<Size>1051</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
</ListBucketResult>
Alternatively we could see all of the objects in the S3 bucket through the AWS cli by running aws s3 ls s3://flaws.cloud --region us-west-2
.
Why are we seeing this? One of the permissions developers can set on buckets is the ListBucketResult
permission which allows a user to see all of the objects within the bucket. S3 permissions can be configured to allow or prohibit specific AWS users to do or not do this or that but they also apply to non-aws resources like the public.
In this case the ListBucketResult
permission is open to the public meaning that anyone can see everything within the bucket including the secret-dd02c7c.html
file. All static websites hosted on S3 will default to having their homepage set to index.html. If I have a foo.html
file within my bucket this file will be available at mys3website/foo.html
. That being said if we visit flaws.cloud/secret-dd02c7c.html
we'll find the flag and go on to the next level.
So where did the developers go wrong? Their first mistake was allowing for the ListBucketResult
permission open to the public. With this permission anyone and everyone can see that they had a secret file stored in S3. Their second mistake was that they also allowed for the S3GetObject
permission to be open to the public on the entirety of the bucket, including secret file. Which means that anyone can request to see the secret file. This is probably what the permissions for this bucket looked like:
{
"Version": "2012-10-17",
"Id": "FaultyS3Permissions",
"Statement": [
"Sid": "AllowListAndGetToPublic",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::flaws.cloud"
]
}
The meat of these permissions are in the Statement
key where the Effect
grants permissions through Allow
. These permissions are applied to everyone, including the public through the Principal
key which tells AWS which users and services should have access. Its value set to the anonymous permissions as denoted by the splat *
. The Action
list describes what users can actually do, in this case list and get all of the objects in the bucket. Lastly the Resource
key is saying to apply these permissions to the flaws.cloud bucket.
The developer needed to have prohibited the public from being able to list and get every single object in the bucket. By default AWS does not grant users or the public permissions, the developer had to go out of the way to give these permissions. Rather than denying the public the ability to list objects in the bucket and get the secret file we would want permissions that only allow our admin account do so.
Here is what a more secure set of permissions would have looked like:
{
"Version": "2012-10-17",
"Id": "ProhibitPublicListing",
"Statement": [
"Sid": "AllowListBucketForDefau;lt",
"Effect": "Allow",
"Principal": "arn:aws:iam::12345:user/Default",
"Action": [
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::flaws.cloud/"
],
"Statement": [
"Sid": "HideSecretFile",
"Effect": "Allow",
"Principal": "arn:aws:iam::12345:user/Default",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::flaws.cloud/secret-dd02c7c.html"
],
"Statement": [
"Sid": "AllowPublicAccessToWebPages",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::flaws.cloud/index.html",
"arn:aws:s3:::flaws.cloud/hint1.html",
"arn:aws:s3:::flaws.cloud/hint2.html",
]
],
}
The first and second statements allow our iam user
Default with an account id of 122345
to list all objects in the bucket and perform the GetObject
action on the secret file. As mentioned before AWS users and the public are explicetly denied access to resources by default, which was not always the case. Therefore we don't need to set up statements with the Deny
effect all though this is done occasionaly. In the last statement we allow the public to view specific objects in our bucket by specifying those objects as list within the Resource
tag.
Posted on May 29, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.