Your S3 objects could be public (even though the AWS Console doesn't say so)

psantus

Paul SANTUS

Posted on May 2, 2024

Your S3 objects could be public (even though the AWS Console doesn't say so)

S3 is an amazing storage service, able to durably store data at exabyte scale and present it with single-digit millisecond latency. Though its name stands for "Simple storage service", its power comes with some risks, one of which is to find your private data has become public.

In this blog post, I'll show a not-so-well known way your objects could mistakenly become public.

How AWS protects your data in S3

I'll start with an obvious statement: that S3, as a web service, is publicly available (i.e. you can use the S3 API without setting up a VPN) doesn't mean that data has to be public.

In fact, S3 buckets have always been private by default. And since 2018, there has been some additional locks at both account and bucket-level that you can set to explicitly prevent objects from being public even if you mistakenly set a policy that could cause public access. And from April 2023, those are enabled by default at bucket-level.

Public Access Block

With great power comes great responsibility! AWS shared security model states that the user, who has the power to set policies to enable public access, is then responsible for its good implementation.

Here are the standard ways that can be used to set permissions in S3:

  1. Through Access Control Lists. ACLs are not recommended anymore but can still be used to grant access to S3 resources (buckets or objects).

  2. Through Resource-based policies. Each bucket has a policy that can allow (or explicitly deny, which always takes precedence) access to objects. That's the recommended way to proceed, as it's easy to set granular permissions and also to grant access to other AWS accounts or AWS services.

  3. Through IAM Identity-based policies. Make sure not to use action = s3:* and resource = * !

Any of those permissions can be neutralized with the aforementioned "Public access block" settings.

So, how could your objects still be public, then?

Apart from the well-known (and voluntary) pattern to use CloudFront CDN's distributions to make S3 data publicly available, there are 2 ways that you could inadvertently make your S3 objects public.

The reason why I wanted to make this blog post is that I recently found both those leaks in a client of mine. He had an S3 bucket which was shown as "public access blocked" in the AWS Console, but data was leaked by those two security holes.

Leaked API Access key / secret key

The first way data could leak was because my client distributed API access key and secret key in a frontend application. In his case the application was a mobile app, but that's still code that runs at the client side, can be decompiled / reverse engineered / memory dumped.

The good thing is that AWS proactively scans the web (which, obviously, seems to include application stores vs. just scanning public repositories) for secrets and warned my client that this particular API Key was available.

Cognito Identity Pool Unauthenticated Guest feature

The second way is more subtle.

Cognito Identity Pools offer the ability to deliver short-term credentials in exchange for an IDP-issued proof of authentication. That's very useful, for instance to let all people from the marketing department access files in the S3 bucket; or to let user JohnDoe access only bucket files that are prefixed by JohnDoe.

And because that's sometimes needed (for instance, you may want customers to display an Amazon Location map even if they don't already have an account on your app), Cognito offers the ability to allow unauthenticated guest access, in which case user's are delivered short-term credentials associated to a role of your own choosing.

Image description

If the role has s3:* access to the bucket, well... users can do pretty much what they want with your bucket and/or objects.

Here is how this can be exploited by an attacker that knows just the identity pool id (which has to be distributed in the front-end application) and the AWS account id (which is quite easy to find if the bucket name is also in the front-end code)

# Creating a guest identity from the pool
% aws cognito-identity get-id \ 
--account-id ACCOUNT-ID_HERE \
--identity-pool-id "REGION:IDENTITY_POOL_ID" \
--region REGION

# AWS API replies with a unique user ID
{
    "IdentityId": "REGION:UNIQUE_USER_ID"
}

# Then we ask for short-term credentials attached to this identity
% aws cognito-identity get-credentials-for-identity \
--identity-id "REGION:UNIQUE_USER_ID" \
--region REGION \
--output json
{
    "IdentityId": "REGION:UNIQUE_USER_ID",
    "Credentials": {
        "AccessKeyId": "ASIAY--EDITED-FOR-SECURITY-REASON--4FJ",
        "SecretKey": "I4D2SZ4--EDITED-FOR-SECURITY-REASON--v1AwAp/",
        "SessionToken": "IQoJb3JpZ2luX2VjEIz//////////wEaCWV1LXdlc3QtMyJHMEUCIQCgXefjo82cstPQSS1WcXALUfmq364unN+Y/v5sb--EDITED-FOR-SECURITY-REASON--mBbD+AzASKDK",
        "Expiration": "2024-04-16T22:17:04+02:00"
    }
}
# In the next step you can actually make any API call that the `my-role-for-cognito-guests` is granted permissions for.
Enter fullscreen mode Exit fullscreen mode

How to safely grant (download/upload) access to specific S3 objects without exposing secrets and managing customer identity in AWS / Cognito?

A simple way to deliver this use case is to use backend-generated S3 pre-signed URLs.

With S3 pre-signed URLs, you can execute your own custom application authorisation logic in your backend code and then use an IAM user credentials known only by the backend app to generate a url that you distribute to the client.

Using this URL, the client can perform only the selected operation on this specific object for a period of time you determine, effectively acting like short-terme credentials specific to this client.

I'm the Security guy for Corporation X. How can I make sure none of my developers use Cognito Unauthenticated Guest feature?

Like most compliance checks, you can either:

  • Scan Cloudtrail logs and look for AllowUnauthenticatedIdentities of the CreateIdentityPool and UpdateIdentityPool API operations
  • Use AWS Config rules. At the time of writing, there is no AWS-managed rule that supports detecting Cognito Identity Pool Unauthenticated Guest Access (hi there, AWS Service team!) but you can always write your own custom Config Rule relying on Lambda!

That's all folks! I hope you learnt something useful today! Don't hesitate to leave a comment below, I'd be happy to keep the discussion going. I'm also available on LinkedIn or via my website.

💖 💪 🙅 🚩
psantus
Paul SANTUS

Posted on May 2, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related