Keep Your S3 Safe from CloudTrail Auditors

AWSCloudTrailReadOnlyAccess currently allows s3:GetObject for “*” and s3:ListAllMyBuckets – and reading CloudTrail logs may also give access to bucket object keys. BE CAREFUL!

Lior Zatlavi By Lior Zatlavi
Keep Your S3 Safe from CloudTrail Auditors

More than a year ago, we wrote about the dangers of using AWS managed policies, which provide very extensive, often unnecessary access to a wide range of resources. Every now and then, when working with real environments, we uncover vivid examples of why this is true. We recently found one we just had to share.

Most security officers are justifiably concerned about unwarranted access to confidential or sensitive data. For this reason, whenever there’s a good chance of an identity being granted access to permissions such as s3:GetObject, it’s a pretty big deal. That was the case when access to s3:GetObject was temporarily granted to AWS support staff by the update of the AWSSupportServiceRolePolicy attached to a mandatory Service-Linked-Role that exists in all AWS accounts. That was an extreme case because it didn’t even require an AWS account admin to attach the policy to a role (as it exists by default). To AWS’s credit, they reverted that update very quickly. AWS’s Colm MacCarthaigh’s response shows that AWS took this event seriously (we highly recommend this thread by Victor Grenu to learn more).

However, not many people are aware that a similar risk lurks in other innocent-looking AWS managed policies. Here is the sad tale of the AWSCloudTrailReadOnlyAccess managed policy (below is its current version):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:GetBucketLocation"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "cloudtrail:GetTrail",
        "cloudtrail:GetTrailStatus",
        "cloudtrail:DescribeTrails",
        "cloudtrail:ListTrails",
        "cloudtrail:LookupEvents",
        "cloudtrail:ListTags",
        "cloudtrail:ListPublicKeys",
        "cloudtrail:GetEventSelectors",
        "cloudtrail:GetInsightSelectors",
        "s3:ListAllMyBuckets",
        "kms:ListAliases",
        "lambda:ListFunctions"
      ],
      "Resource": "*"
    }
  ]
}

From the policy’s inception, all versions including the current one (version 9) have included the s3:GetObject permission for “*”. It’s understandable why access to reading CloudTrail would require access to s3:GetObject. In addition, since the managed policy can’t be written with a specific bucket or even a bucket name pattern, it’s understandable why it would have to use “*”. Unfortunately, this is the inherent problem with AWS managed policies.

To make matters worse, the policy also grants access to s3:ListAllMyBuckets, which allows the principal who has access to the policy to find all of the bucket names. So - the only missing puzzle piece that’s needed to extract data from the S3 buckets is the object keys.

Do you know where there might be a TON of object keys?  CloudTrail logs! If you enable logging of S3 data events, then the logs may very well contain information about read/write events along with the keys of objects in the bucket. And if the principal doesn’t have access to object keys at all - there’s no reason why it can't just guess until it succeeds.

It should be noted that access can still be denied by IAM, resource-based or service control policy, or if the buckets are encrypted using a customer-managed CMK KMS key. However, it’s still unlikely that anyone who enables a service or a person with read-only access to their account’s CloudTrail logs would also want to grant them access to all the data stored in their S3 buckets where these restrictions don’t apply.

AWS itself recommends the practice of getting started with an AWS managed policy -

"Get started using AWS managed policies – To start using CloudTrail quickly, use AWS managed policies to give your employees the permissions they need."

and later moving to custom policies to “grant least privilege”. This approach must always be used with care since there’s the very probable risk of starting out with an AWS managed policy and either forgetting to replace it with a custom policy or postponing doing so. However in this case, as there are probably only a few buckets that actually store CloudTrail logs, giving access to s3:GetObject to “*” is negligent.

In addition, it’s quite possible that a third-party vendor will request this policy for the IAM role it uses to access your environment. From a very quick search, we found several that do. One actually does so by providing a CloudFormation template that has this snippet in the code setting up its IAM role:

"ManagedPolicyArns": [
   "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess",
   "arn:aws:iam::aws:policy/AWSCloudTrailReadOnlyAccess",
   "arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess",
   "arn:aws:iam::aws:policy/CloudWatchReadOnlyAccess",
   "arn:aws:iam::aws:policy/ComputeOptimizerReadOnlyAccess",
   "arn:aws:iam::aws:policy/AWSSavingsPlansReadOnlyAccess"
]

How many people would pay attention to the content of the managed policies a third party requests or assume that a policy made for read-only access to CloudTrail logs would allow access to data?

So… what can you do?

There are several takeaways that can help you protect your sensitive information:

  • Do your best to avoid using AWS managed policies. And when you absolutely have to use them, do so with caution and vigilance, and be fully aware of the permissions they provide. Specifically, don’t use the AWSCloudTrailReadOnlyAccess managed policy. Rather, create a reduced version of it with the permission to s3:GetObject limited to the buckets where the CloudTrail logs are, such as this:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetBucketLocation"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::${insert-your-cloudtrail-bucket}/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudtrail:DescribeTrails",
                "cloudtrail:GetTrail",
                "cloudtrail:GetTrailStatus",
                "cloudtrail:LookupEvents",
                "cloudtrail:ListPublicKeys",
                "cloudtrail:ListTags",
                "s3:ListAllMyBuckets",
                "kms:ListAliases",
                "lambda:ListFunctions"
            ],
            "Resource": "*"
        }
    ]
}
  • For sensitive data:
    • Use KMS encryption with customer-managed CMK keys on sensitive data. This will prevent any principal who doesn’t have access to the kms:Decrypt permission to the key used to actually access the information.
    • Apply a bucket policy with Deny Effect on s3:GetObject along with conditions that set up a perimeter to protect your data properly. You can find more information about this practice in this great talk about setting up a perimeter for your data with VPC endpoints or in this paper about managing permissions for AWS services for which we also published a review.