In the re:Ivent 2018 talk The Theory and Math Behind Data Privacy and Security Assurance, starting at 41:15, Dan Peebles discussed a confused deputy problem that can occur in AWS resource policies. He shows the following example resource policy associated with a Lambda function:
// BAD - DO NOT USE
{
"Action": "lambda:InvokeFunction",
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Resource": "arn:aws:lambda:...:function:foo"
}
The problem he points out is that this isn’t just allowing the API Gateway within this account to call this Lambda, but instead is allowing API Gateway, from any account, to call this Lambda! This means if you had some sort of restrictions on your API Gateway, they could be bypassed by an attacker setting up their own API Gateway to call this function.
Such an attack would require knowing about this Lambda ARN. If an attacker obtained access to terraform/CloudFormation scripts or other knowledge of an environment’s internals, they could abuse this issue. This discussion follows on the post How to audit AWS IAM and resource policies.
Fixing these policies
The above policy can be fixed by adding a condition for a SourceARN
.
"Condition": {
"ArnLike": {
"AWS:SourceArn":
"arn:aws:execute-api:us-east-1:123456789012:abcd123456/*/*/myapi"
}
}
This ensures that only a specific API Gateway can invoke the Lambda.
S3 buckets can also sometimes be the SourceArn
involved. When this happens, AWS provides an additional precaution to use a condition to confirm the account owner of the S3 bucket. The reason for this is because S3 bucket ARNs don’t contain an account ID, so this can result in the scenario of you deleting your S3 bucket, someone else creating an S3 bucket with the same name, and then abusing this trust relationship. To prevent that scenario, you can use the following condition:
"Condition": {
"ArnLike": {
"aws:SourceArn": "arn:aws:s3:::source-bucket"
},
"StringEquals": {
"aws:SourceAccount": "123456789012",
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
When is this a problem?
Unfortunately, all of the situations in which a SourceArn should be specified are not known. It should be specified in most cases for resource policies for Lambdas, S3, SQS, or SNS when a service is specified as the principal.
However, in some cases, it is not possible. For example, the policy you should use for allowing CloudTrail to write to an S3 bucket is:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailAclCheck20150319",
"Effect": "Allow",
"Principal": {"Service": "cloudtrail.amazonaws.com"},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::myBucketName"
},
{
"Sid": "AWSCloudTrailWrite20150319",
"Effect": "Allow",
"Principal": {"Service": "cloudtrail.amazonaws.com"},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::myBucketName/optional_prefix/AWSLogs/myAccountID/*",
"Condition": {"StringEquals": {"s3:x-amz-acl": "bucket-owner-full-control"}}
}
]
}
In this case, CloudTrail is being restricted for s3:PutObject
by the Resource
field instead of a SourceArn
condition. The reason for this is that there is no ARN for CloudTrail, but CloudTrail does have a specific file path format that can be used to control access.
Often people will accept the risk of other accounts being able to write CloudTrail logs to their account, as the attacker won’t be able to delete or modify the real logs, so they may just use a Resource
of arn:aws:s3:::myBucketName/*
. This is especially common when an organization creates many accounts and writes all their logs to the same bucket. The only known risk is you might end up with some extra logs from an attacker account, which doesn’t concern most people.
When reviewing resource policies, if you see a Service
being used as a Principal
, consider whether that can be associated with a single resource, and if so, it may be possible to restrict the policy using a SourceArn
condition.