Securing your S3 Buckets

AWS S3 Bucket security has been in the news a lot recently. Almost 200 million records on US Voters were found on an unsecured S3 bucket owned by a GOP data analytics firm. A third-party vendor to Verizon left 14 million customer records (including SSN partials) publicly readable. Then Dow Jones exposed 2 to 4 million customer records.

AWS has responded to this spate of bad press with a several changes to the AWS Console, and most recently took the extra-ordinary step of emailing account owners informing of any publicly readable buckets.

(Just take me to the tl;dr)

Publicly accessible buckets are a perfectly valid use-case of S3. This blog was moved from WordPress to S3 about two weeks ago. S3 is a way more secure and resilient web-server than WordPress running on a Linux server.

When thinking about bucket security there are a few risks you need to worry about. Readable buckets and data loss of PII have made the news. There is another threat vector that’s not (yet) made the news and that is publicly-writable buckets. Often times these buckets are also opened to the world in order to serve web content. A bucket that is publicly writable and serving web content is an ideal method for distributing malware.

There are two major ways to mis-configure your buckets. You can apply default Access Control Policies to a bucket or you can apply a json-formatted Bucket Policy.

In the case of the first misconfiguration - AWS has not provided clear terminology about what “Everyone”, “All Users” or “Authenticated Users” means. This has led to people granting broader permissions than necessary.

Amazon’s Documentation on this defines three predefined groups. The two that impact this discussion are “Authenticated Users group” and “All Users group” (The third, Log Delivery, grants S3 permissions to write logs to a bucket).

All Users group is defined by them as: “Access permission to this group allows anyone to access the resource. The requests can be signed (authenticated) or unsigned (anonymous). Unsigned requests omit the Authentication header in the request.”

Authenticated Users group is defined as: “… all AWS accounts. Access permission to this group allows any AWS account to access the resource. However, all requests must be signed (authenticated).”

However in the S3 Console (both new and old) “All Users” becomes “Everyone”. In the new Console they try to be a bit clearer with “Any AWS User”, but that still means Any AWS Customer, not just the IAM Users in your account.

This is what the old console would show for permissions: Old Console View

And is what the new console will show you: New Console View

Bucket Policies are much more powerful. You can specify specific AWS accounts who can access your bucket. You can apply specific conditions around Source IP or Encryption settings. You can limit the access by object prefix. The Danger here is that if you specify Principal: * in your policy, you’ve just authorized Any AWS Customer to access your bucket.

Here is a bucket policy you DON’T want:

    {
        "Sid": "AllowAnyone",
        "Effect": "Allow",
        "Principal": "*",
        "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::horrible-insecure-bucket/*"
    }

This Bucket Policy has the same effect as All Users Read.

And this policy will grant anyone full control to your bucket:

    {
        "Sid": "AllowAnyone",
        "Effect": "Allow",
        "Principal": "*",
        "Action": "*",
        "Resource": [
        	"arn:aws:s3:::horrible-insecure-bucket/*"
        	"arn:aws:s3:::horrible-insecure-bucket"
        ]
    }

In both of these cases, you should replace the * in the Principal element, with the ARN for root of your account. That looks like:

"Principal": {
    "AWS": "arn:aws:iam::1234567890:root"
},

Where 1234567890 is your 12 digit account id.

TL;DR

  1. Click on the permissions tab for your buckets to make sure you’re not allowing List, Upload/Delete, Write objects and read or write permissions for “Everyone”, “Any AWS User”, “Any Authenticated User”
  2. Check your bucket policy to make sure you’re not using “Principal”: “*” in your policy’s Allow statements