Why choose Panoptica?
Four reasons you need the industry’s leading cloud-native security solution.
Our researchers discovered that AWS IAM policy evaluation logic does not work the same way as security engineers may be used with other authorization mechanisms. We outline many risky and confusing examples when using AWS groups and policies. We then look at a specific IAM deny policy example where a group has an explicit denial. This will only impact Group actions, not User actions, opening organizations up to misconfiguration and vulnerabilities if they assume the process to be the same as the ‘Allow’ path for evaluation logic.
AWS Identity and Access Management (IAM) is a service that enables the account owner or administrators to manage access to any Amazon Web Services and resources. IAM service provides identities such as users, groups and roles to control who can do what on which resource. You can manage access by creating policies, for example a bucket policy, and attaching them to IAM identities. For example, you can attach a policy to a user that allows the user to list all objects in a specific S3 bucket. If the IAM identity does not have a policy that explicitly allows some action, the default for that action will be an implicit deny. Therefore, a user without any policy attached will have no permissions at all (assuming there are no resource based policies that allow the user some actions explicitly).
Each request to AWS is performed by a principal. A principal is a person or application that uses the AWS account root user, an IAM user, or an IAM role to sign in and make requests to AWS. AWS evaluates the requesting principal’s policies to determine whether the request is allowed or denied.
Let’s say that a hypothetical company has an account with AWS. The account administrator created an IAM user for Alice, the developer, and attached her user the AWSLambdaFullAccess managed policy. Additionally, the administrator created a managed policy that allows users to change their own password and named it “user-change-self-password”. The administrator attached Alice’s user the “user-change-self-password” policy as well.
A few weeks later a new developer, Bob, joins the company. The AWS account administrator creates a new IAM user for Bob, but what are the permissions Bob should have? The administrator already configured all necessary permissions to a different developer, Alice, and attached to her all the requested policies. Doing that over and over again for each developer sounds like an inconvenient solution that is very hard to maintain. This is exactly what groups are meant for!
Using groups, the account administrator can group users into logical groups, each serving the same role and needs. Using the same company as our example, the administrator can create a single group named “developers” and then add both Alice and Bob (and all other current and future developers) to this group. Then, the administrator can attach all required policies to the developers’ group (AWSLambdaFullAccess, user-change-self-password, etc.) and they will apply across the board.
Grouping users into groups in authorization mechanisms assists in providing or denying permissions from multiple users at once. Groups are almost always identical to the logical groups we have in the company or organization: developers, QA, administrators, billing and more.
AWS resources support both attribute-based access control (ABAC) and role-based access control (RBAC) authorization models. ABAC is implemented by AWS tags while RBAC is implemented by creating different policies for different job functions (roles). These policies are attached to the relevant IAM identities or to specific resources such as S3 buckets or Lambda functions.
When a principal sends a request, AWS completes several steps to determine whether to allow or deny the request. One of the steps is processing the request and gathering the following information into a request context:
Once the request context is created, AWS gathers all of the policies that apply to the request context in order to decide if the request should be allowed or denied.
The following flow chart provides details about how the decision is made.
By default, all requests are implicitly denied and will be allowed only if there is an identity based policy or a resource-based policy that allows it.
As we have seen before, an explicit IAM DENY policy in ANY policy overrides any allows!
Or, Does It?
As this post focuses on AWS IAM users and groups, let’s examine their respective actions.
As you can see, there are some actions that are similar between User and Group objects, but there are many actions related to the User object that are completely irrelevant for the Group object such as iam:UpdateLoginProfile.
ARN’s, Groups, Paths and the Big Confusion Between Them in IAM Policy Evaluation
With AWS IAM policies, some actions support multiple resource types, and some support only specific Resources as the ARN sets the resource type.
The ARN name convention for user and group are as follow:
arn:${Partition}:iam::${Account}:user/${UserNameWithPath}
arn:${Partition}:iam::${Account}:group/${GroupNameWithPath}
A new term defined here is the path. A path is an optional name that an AWS user can give to an AWS IAM user or group. It can be a single path or multiple paths, same as a directory structure.
For example: /division_a/department_x/sales/
Paths can only be created using the AWS API and not through the console, and that’s the first pitfall you might meet when trying to use paths with AWS IAM.
Some examples of paths that we can use:
arn:aws:iam::123456789012:user/division_a/department_x/sales/* - that means all the users created in a specific nested path in our organization.
arn:aws:iam::123456789012:group/division_a/department_x/marketing/* - that means all the groups created in a specific nested path in our organization.
One would think that the wildcard (*) used in that case is for users in a specific group, as the ARN in the example does not provide us with any information regarding the Resource. And since we can only create such IAM objects (with path) from the API, we don’t have warnings or notice regarding the IAM actions used in conjunction with that ARN.
Another pitfall here is that even if there is no path defined, we can still define a wildcard after the group as the following example:
arn:aws:iam::123456789012:group/marketingAdmins/*
that can be defined both from console and API as a resource in AWS Policy.
Confused? This is just the beginning.
As for IAM Groups, AWS states that “A group cannot be identified as a Principal in resource based policies.” (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_groups.html) so in our following examples, we are not going to use resource based policies, and we will stick with Identity based policies in both directions, Allow and Deny.
When going back to the evaluation logic and focusing on a case where we only have identity based policies involved in the process, here we know that once we have an explicit deny for a user (since groups aren’t evaluated, but the policies used in groups where the user is a member of) the following example, when created by the AWS API, is syntax valid and makes sense:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowGroup",
"Effect": "Deny",
"Action": "iam:*",
"Resource": "arn:aws:iam:: 123456789012:group/managers2/*"
}
]
}
Is it a path called “managers2” or a group named “managers2”?
The answer is that it can either refer to a path with groups inside or to a group named “managers2” (where the wildcard is ineffective).
One is a valid case, and the second one is not valid if we don’t have such a path, both will be created in AWS with two different effects.
Let’s take another example where we want to ensure we have an explicit deny for access to nested objects inside a resource. In S3, for example, the following ARN can be used as a resource in an explicit deny policy (still with Identity based policies):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Deny",
"Action": "s3:DeleteObject",
"Resource": "arn:aws:s3:::customersdata/*"
}
]
}
This big gap between the User and the Group objects actions can cause critical security misconfiguration while defining policies.
Let’s go back to our hypothetical company. It employs 3 global managers who should have full access to the company’s account on AWS. So, the account administrator creates a “managers” group and attaches it to the “AdministratorAccess” policy.
aws iam create-group --group-name managers
aws iam attach-group-policy --group-name managers --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
The account administrator wants to protect their users from any possible malicious compromise and decides to create a special guard policy named “ProtectManagers” with the following content (file://policy):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ProtectManagersByDeny",
"Effect": "Deny",
"Action": "*",
"Resource": "arn:aws:iam::123456789999:group/managers"
}
]
}
aws iam create-policy --policy-name ProtectManagers --policy-document file://policy
The account administrator attaches this guard policy to any user, group (besides managers) and role in the account.
aws iam attach-group-policy --group-name NOTmanagers --policy-arn arn:aws:iam::ACCOUNT:policy/ProtectManagers
At first glance it looks like it will provide very good protection since an explicit deny overrides any allows and if access is denied for a group then it is also denied for the group members. But in fact, this is not the answer, and in AWS - it doesn’t. The deny implies ONLY for Group object actions and all other User object actions are still allowed.
Now assume there is an attacker with initial access to the account and its role allows iam:UpdateLoginProfile to any user. The attacker is also limited by the “ProtectManagers'' policy. Additionally, the attacker knows that John is one of the company’s managers. We would expect that if the attacker cannot do any action against the “managers' ' group then the attacker also won’t be able to do any action against the group users, including John. But since the deny is not on John user explicitly, the attacker can change John’s user password and login on his behalf with the following command:
aws --region us-east-2 iam update-login-profile --user-name john --password Aa12Bb3456@ --profile attacker
On discovering this issue, we approached AWS for a response, and they commented that this approach is by design, and not an error. AWS treats groups as a separate object, and they don’t treat a user as part of a group when it comes to deny rules. As this can open up serious vulnerabilities for any organization who would assume that IAM works the same way as Active Directory on Azure for example, or Windows as seen above, we believe it’s important to draw attention to this issue.
That’s why here at Panoptica, we’ve created an open-source scanner that security teams can leverage to uncover user permissions that are loosely defined and could open an organization up to undue risk as a result of this vulnerability.
After using the scanner, organizations have a few options in order to secure groups from this risk. Here are two:
Whatever you choose, there is no silver bullet for this issue. The main thing is, as AWS do not highlight this discrepancy - to be aware that AWS resources work differently from what you may be used to, and to retain visibility to plug these vulnerabilities in advance, or adapt your infrastructure to suit.