Creating unintentional ways to bypass AWS IAM policies when using the “ForAllValues” operator

This article explains set operators of the AWS IAM policy language, such as “ForAllValues”. It also shows how incorrect usage can open up ways to bypass the intentions of a policy.

Michael Kirchner
AWS Tip

--

Readers who have worked with AWS IAM before may share the common conclusion that IAM is a powerful and feature-rich service, but is also a complex thing and sometimes hard to get right. Today we are going to look at one of those complex aspects, which are set operators.

IAM policy evaluations

To get started, let’s look at the following short IAM policy and think about what happens in the background when it is evaluated:

The IAM user or role this policy is attached to will be allowed to read all objects stored in a specific bucket. Imagine the following CLI command is run by an IAM role with the above permissions:

aws s3api get-object --bucket sometestbucket --key testfile.txt outfile.txt

The IAM service now needs to make a decision whether this API call shall be allowed or denied. In practice, this is quite a complex procedure (as shown here), but let’s just focus on the short IAM policy shown above. To evaluate a specific API call against a policy, IAM builds up a request context. This request context is compared against the rules set forth in the policy. An exemplary request context for our CLI command above may look like this:

The request context contains some information that was already visible directly in the CLI command, such as the API action called. But there is also additional information (“context keys”) that IAM adds during runtime, such as the source IP address of the API call and additional information about the caller. Which context keys there are is individual per AWS service, but there is a list of global context keys that should be available across all of them (see here). Context keys can, for example, be used in the “Condition” part of an IAM policy, which we will see in a moment.

To make a decision whether to allow or deny the above request, the values found in the request context are matched against the values in the IAM policy. As an example, IAM matches the requested S3 object against the “Resource” specified in the policy:

In our example, the two parts match. If all parts of an “Allow” policy statement are fulfilled (and there is no additional “Deny” statement that is fulfilled as well), the request will be allowed. For our example CLI command, the request will be allowed.

IAM policy evaluations with multi-valued policy parts

Our last policy evaluation example just required matching two strings. Let’s make the policy slightly more complex, by adding a condition:

The condition requires that the IAM role that issues the CLI command is tagged. It needs to have certain department information set as a tag, in order for API calls to be allowed. If there are tags set on our IAM role, this information will be added to the request context and will be available during policy evaluation. Let’s also add the tag information to the role:

During policy evaluation time, the situation will look like this:

If an IAM policy contains multiple values in the form of a JSON array, those will be evaluated using a logical OR. (More information on this can be found here.) In our case, the check will evaluate to true and reading the S3 object will be allowed. If we set the tag on our IAM role to a different value or not set the tag at all, requests would be denied.

IAM policy evaluations with multi-valued request contexts

In our example above, the “department” tag on the IAM role can only be set to one value at a time. It can be set to “blue” or “orange” or any other value, but cannot hold multipe values at the same time. There are, however, request context keys that can be multi-valued, just like our policy statement can be multi-valued (array of “[blue, orange]”).

One such example is the “aws:CalledVia” context key, which is closer described here. This context key is used when an IAM user or role uses an AWS service and the AWS service in turn uses the user’s permissions to send additional API calls. A prominent example is the rollout of a CloudFormation stack: you as the user primarily interact with the CloudFormation service to start a stack deployment. But CloudFormation will send additional API calls on your behalf to roll out the AWS resources you have asked for. So why can “aws:CalledVia” be multi-valued? This is because there might be additional AWS services behind CloudFormation that again will make requests on a user’s behalf. An image in the AWS documentation referenced above nicely shows this example:

Imagine you use the “aws:CalledVia” context key in one of your IAM polices. During policy evaluation time, something like the following check will need to be made:

Now this looks much more complicated than just matching two strings, like we had initially. Which parts should be matched against which ones? And is it enough if only one value matches or do all of them need to match?

To configure the desired evaluation logic, AWS provides two set operators, which are “ForAnyValue” and “ForAllValues”. These only make sense for multi-valued request context keys, such as “aws:CalledVia”. The AWS documentation defines them as follows:

ForAnyValue: Tests whether at least one member of the set of request values matches at least one member of the set of condition key values. The condition returns true if any one of the key values in the request matches any one of the condition values in the policy.

ForAllValues: Tests whether the value of every member of the request set is a subset of the condition key set. The condition returns true if every key value in the request matches at least one value in the policy. It also returns true if there are no keys in the request, or if the key values resolve to a null data set, such as an empty string.

The following exemplary IAM policy condition makes use of “ForAnyValue”. The condition will match if the “aws:CalledVia” request context key contains at least “cloudformation.amazon.com”. It will also match if the context key contains additional values, such as “dynamodb.amazonaws.com”.

Creating unintentional ways to bypass IAM policies

In practice, the set operators “ForAnyValue” and “ForAllValues” are only needed in rare cases. Most of the request context keys available on AWS are single-valued, such as the “aws:PrincipalTag/tag-key” we had above. When doing security reviews of AWS accounts, I’ve seen cases though where the use of set operators has lead to situations where the intentions of a policy could be bypassed.

The situation unfolds when the “ForAllValues” set operator is (mis)used for single-valued request context keys. This likely happens because the policy author wanted to test against multiple values in their policy, such as the tag values “blue” and “orange”. They thus thought of set operators, although they are not needed for these cases. Let’s look at an example.

If we come back to our previous situation where we wanted to test the “department” tag against the values “blue” and “orange”, all we had to write was the following condition statement:

In practice, however, it unfortunately happens that policy authors write the following:

This changes the meaning of the policy. Can you spot how?

The first policy requires the presence of the “department” tag. If the “department” tag is not present on the IAM role that makes the API call, the request will be denied. The second policy does not require the presence of the “department” tag. Requests will be allowed if the “department” tag is set to “blue” or “orange” or if the “department” tag is not present at all. This is because the definition of the “ForAllValues” operator contains the following statement (repeated from above):

It also returns true if there are no keys in the request, or if the key values resolve to a null data set, such as an empty string.

When you think of “ForAllValues” as a set operator, this behavior makes sense: we are asking whether all values of a set meet a certain condition. If the set is empty, the answer is yes.

As another example, the following IAM policy condition was built with the intention to reduce the impact of successful credential exfiltration attacks. If attackers indeed managed to get access to valid IAM access keys for this principal, they should not be able to make use of the credentials because access is only granted when the API calls orginate from certain VPCs:

While this is, in principle, a nice way to shape permissions, the “aws:SourceVpc” context key is only populated when requests are made via a VPC endpoint (see here for the details). If requests are made from the outside, like attackers would likely do with exfiltrated credentials, this context key is not set. The “ForAllValues” operator then causes the requests to be allowed, essentially nullifying the desired protection mechanism.

An easy way to fix both situations shown above is to just remove the “ForAllValues” operator, as it’s use is not required with single-valued context keys.

Conclusion

It of course always depends on the individual workload you run on AWS whether the issue discussed in this article is relevant and has the potential to lead to a practical security impact. Putting a “ForAllValues” operator at a place where it does not belong unfortunately happens though in practice.

A nice feature is that the visual policy editor of the AWS Console already shows warnings when you use “ForAllValues” on context keys that are single-valued:

If you use infrastructure-as-code mechanisms without additional security linting tools, it is likely though that you will not see such a warning when writing your policies.

As a closing recommendation, always have a quick look into the AWS documentation when using context keys as part of your IAM polices: Only use the “ForAnyValue” and “ForAllValues” operators on multi-valued request context keys, but not on single-valued ones. Except for when you really and intentionally want missing request context keys to lead to a positive evaluation of a policy statement. But I consider this to be a rare case. And that’s all for today :)

--

--