April 29, 2019 | Posted in Red Teams by Evan Perotti
In AWS, authorization is governed by the Identity and Access Management (IAM) service. Unfortunately, as most software configuration goes, there is ample opportunity for misconfigurations that result in security vulnerabilities. As it pertains to AWS IAM, this typically manifests as privilege escalation. However, in some cases, it can result in something as severe as unauthorized account access. In this blog post, I will cover general classes of IAM exploitation and privilege escalation techniques. Keep in mind that IAM exploitation requires some form of credentials in most cases. In other cases, some alternative information may be required, such as an account ID or an ARN.
The most fundamental component of IAM is the policy, a JSON document that determines which action can be performed by which entities and under what conditions. These policies can be directly attached to users and roles, allowing them to perform the actions contained within. They can also be attached to groups, providing those permissions to all users within the group. Certain services, such as S3 and SQS, can have policies directly attached to the service components (e.g. an S3 bucket). For example, a bucket can have a policy attached to it that allows any user to retrieve items from the bucket. Other services can have policies attached to them in the form of roles. This can take the form of either a service-linked roles, where the service is granted access to the role, or having a role attached to a component in the service (e.g. an EC2 instance). These roles provide the service the ability to perform AWS actions similar to a standard user, such as a Lambda function being able to start and stop EC2 instances.
Class 1: IAM policies
Overly-permissive IAM policies
When it comes to IAM policies, traditional privilege escalation is entirely possible when certain permissions are granted. In fact, some permissions on their own are enough to allow escalation. Take the following policy for example:
The iam:AttachUserPolicy permission allows the principal to attach a managed policy to a user. In this case, since the resource is a wildcard, the policy can be attached to any user. If an attacker compromises a user with the above policy attached, they can escalate their privileges by attaching the AdministratorAccess managed policy to themselves, giving them the full access to the account.
For IAM operations specific to user entities, the dangerous permissions tend to fall within one of the following categories:
- Policy attachment: the attacker can directly escalate their privileges by attaching a policy containing permissions higher than their own to their user, role, or a group of which their user is a member. Related permissions:
- Policy versioning: the attacker can create a new version of a policy attached to their user entity that has higher permission or they can revert their existing policy to an older version with more permissive/different grants. Related permissions:
- Group management: the attacker can move their user to a group that grants greater privileges. Related permissions:
- User Management: the attacker can create or change the console access credentials for another user then log into the console as that user or generate a new access key for another user then call the APIs directly. Related permissions:
- Service roles: the attacker can pass a privileged role to an AWS service then access its temporary credentials in order to perform privileged actions. See the “Passing roles to services” section below.
However, in practice, there are a larger number of combinations that could results in a user escalation (or, at least partially) based on the specific configurations present in the account.
IAM Role Combinations
While IAM users are a basic form of user in AWS, another form of user entity is the role. A role is similar to a user in that both can have policies attached directly to them. However, roles have no long-term credentials (console password, standard access keys) associated with them, only short-term credentials granted through the Security Token Service (STS). Rather than log into a role, you assume a role. To assume a role, the assumer must have the proper permissions and the role must be configured to trust the assumer. Roles, as one might imagine, are just as susceptible to misconfiguration as users. In fact, because roles allow the caller to gain different credentials from their own, it opens the door for additional escalation methodologies. For example:
An account is configured with an IAM user and two roles. The first policy below is attached to the user, the second policy is attached to “role1”, and the third policy is attached to “role2”. The attacker has compromised an IAM user with the first policy attached to it and is not a member of any group.
The attacker is allowed to assume both “role1” and “role2”. “role1” grants permissions to add users to the group “Temp”, a group without any privileged permissions. “role2” grants permissions to attach policies to the group “Temp”. For the attacker, either role on its own does not provide much value. However, together they allow the attacker to escalate. The attacker assumes “role1”, moves their compromised user into the “Temp” group, assumes “role2”, then finally attaches the AdministratorAccess policy to the group. The compromised account now inherits the administrative rights via group membership.
Overly permissive resource policies
For some services, the service and/or components of the service can be controlled directly with an IAM policy. These policies are referred to as resource policies. Applicable service include, but are not limited to:
- S3, where policies can be attached to buckets
- SQS, where policies can be attached to queues
- Lambda, where policies can be attached to functions.
Seeing as resource policies are simply IAM policies, they are just as easily mis-configurable as users permissions. Furthermore, since services are typically globally accessible (e.g. not housed within a company’s datacenter) and users can attempt to interact with them without credentials, a misconfigured resource policy can potential expose the resource to everyday external attackers. This is evidenced by the litany of articles detailing open S3 bucket “breaches” of customer data and sensitive configuration information. The following example is an S3 bucket policy that would allow any user to retrieve items in a bucket.
While resource policies are an interesting are to delve into, they are outside the scope of this article.
Class 2: Confused Deputies
Passing roles to services
Roles are a powerful component to IAM. Not only can users assume them, but services can too. For example, if you host an application that integrates with S3 on EC2, that application will need the proper authorization to access the S3 data. Rather than hardcode long-term credentials into the application or its configuration file(s), you can attach an instance role to the EC2 instance. When the application on the EC2 instance makes a call to the S3 service using a supported SDK (e.g. boto3 in Python), the SDK will authorize the call using the instance’s credential. These credentials are retrieved from the AWS metadata service and are short-lived.
To pass a role to a service, you will need to be granted the iam:PassRole permission. Some services require additional permissions, such as with EC2 requiring ec2:AssociateIamInstanceProfile. Unfortunately, this permission is an extremely dangerous one to grant as it can be used to escalate privileges via a confused deputy attack. A confused deputy attack is where an entity with some permission set is induced to act in a manner specified by another entity. The malicious entity is then able to perform some privileged action it otherwise could not by abusing the victim entity. In the case of an EC2 instance, an attacker with the proper permissions and access to the instance can attach a privileged role to the instance, remote into the instance, then perform administrative AWS actions using the instance’s legitimate credentials. This vector can be used against most services that can take on a role, such as Lambda and Glue.
Traditional exploitation vectors, such as SSRF and deserialization, can also be used to exploit AWS accounts. Using a combination of insecure coding practices and permissive service roles, an attacker external to the AWS account can potentially compromise that account without initially having AWS credentials. In the following example scenario, the target account uses both AWS Lambda and API Gateway.
- Lambda is a function-as-a-service platform that allows users to upload code to AWS then run that code directly without the need to provision compute resources such as EC2 instances (also referred to as “serverless”). Lambda functions, the basic Lambda service unit, are given an execution role (traditional IAM role) when launched that determines their permissions.
- API Gateway is AWS’ managed HTTP and WebSocket API gateway service and allows users to create APIs that can integrate directly with other AWS services, including AWS Lambda. A common design pattern with these two services is to create some application functionality in Lambda then expose that functionality as an HTTP API via API Gateway using its Lambda proxy integration (requests to the gateway are passed directly to Lambda).
An organization creates some custom business logic in Python, then creates a Lambda function from it. They want to make this logic available to their users as an API, so they use API Gateway. The new API has one GET resource that accepts one query string parameter, “foo”. At some point in the logic, the data from the query string parameter is passed through Python’s “pickle.load()” function, which will deserialize the data. Since this data is taken directly from the user, the Lambda function is potentially vulnerable to a deserialization vulnerability. For simplicity, just the vulnerable code is shown below:
To simulate a malicious request, the following Lambda test event was used with a base64 encoded serialized Python object:
The code used to generate the payload can be found here:
When executed, the Lambda host will base64 encode the environment variables then POST them to an external server.
The credentials retrieved from the Lambda host can be loaded into a local AWS credential file, then used to interact with AWS APIs. Note: Since these are role credentials, and therefore from the STS, the session token value is required alongside the normal access and secret keys.
Video demonstrating the above attack
Class 3: Trust
Misconfigured service trust
In the previous example, the organization was using API Gateway to interact with Lambda. Behind the scenes API Gateway is calling the lambda:InvokeFunction API to trigger the lambda then passing the HTTP request as the invocation context. Because API Gateway is interacting with another AWS service, proper authorization must be established. Since API Gateway cannot be given a service role, a resource policy must be placed on the Lambda function.
The above policy allows API Gateway to invoke the Lambda function specified in the resource section. However, because the principal is specified to be “apigateway.amazonaws.com”, this introduces a vulnerability.
An attacker that knows the ARN of a Lambda with a permissive resource policy can create an API in their own account using API Gateway then interact with the Lambda in another account. This is because the principal in the policy does not restrict interaction from APIs originating from the source account, only the API Gateway service itself (across all accounts). With access to invoke the Lambda, the attacker can modify the execution context (to the extent allowable through the API Gateway service), potentially gather additional information about the Lambda functionality (e.g. if outputs are mangled by the original API Gateway to hide information, those restrictions will no longer be in place with the attacker controller API Gateway), bypass authorization and authentication if those are implemented on the gateway, and/or potentially discover sensitive functionality.
Permissive Role Trust Policies
When an AWS account administrator needs to allow a user in another account to access their account, they typically either provide that user an IAM user in their account or grant that user access to a role in their account. When roles are used for cross-account access, two conditions must be met. The first is that the role in the target account must have its trust relationship configured to trust the incoming user or the incoming user’s account (specified as an ARN). The second is that the user must have permissions in their own account to assume a role. However, if the administrator has multiple users across multiple accounts that need access to their account, rather than trust each user or account, they can simply trust everyone by settings a wildcard in the trust policy (“*”). Unfortunately, this type of trust policy opens the door to attackers.
To exploit such a policy, an attacker simply needs a valid AWS credential with the ability to assume any role (sts:AssumeRole with a wildcard resource). These credentials can be from any account, even their own.
While knowledge of these vectors is all well and good, what exactly can be done to help mitigate or even prevent them? There is no direct answer, however AWS is nothing if not flexible. Below are a few of the many possible routes a defender can take. Note: Security Risk Advisors is not a reseller or VAR; the tools are only presented as options based on our own research.
- AWS Config comes with prebuilt rules to help identify some of the above issues (as well as several general hardening best practices):
- “iam-policy-blacklisted-check” – detects the use of certain IAM policies; can exclude certain entities if they require that policy
- “iam-policy-no-statements-with-admin-access” – detects the use of admin (“*”:”*”) statements in policies
- “lambda-function-public-access-prohibited” – detects when a Lambda function policy exposes it publicly
- “s3-bucket-blacklisted-actions-prohibited” – similar to the IAM admin check but for actions in bucket policies
- “s3-bucket-public-read-prohibited” and “s3-bucket-public-write-prohibited” – detect public read and write policies on buckets respectively
- AWS Config supports the use of custom rules using Lambda functions
- AWS GuardDuty is a managed detection suite that can be used to identify malicious and suspicious activities
- For IAM permissions, follow the principles of least-privileged access when creating policies and only assign a minimal set of permissions. Use tools like CloudTracker (https://github.com/duo-labs/cloudtracker) to help identify unused permissions
- Some services, such as S3, provide the ability to override configurations to prevent exposure; see https://docs.aws.amazon.com/AmazonS3/latest/user-guide/block-public-access.html
- Third-party tools such as ScoutSuite (https://github.com/nccgroup/ScoutSuite) and Pacu (https://github.com/RhinoSecurityLabs/pacu) can be used to audit accounts for security misconfigurations
- Commercial tools such as Cloudcheckr (https://cloudcheckr.com/) and Cloud Conformity (https://www.cloudconformity.com/) and open-source tools such as Pacbot (https://github.com/tmobile/pacbot) and Cloud Custodian (https://www.capitalone.io/) can be used to monitor for compliance and security issues, among other things
- Limit IAM permissions using service control policies and permissions boundaries
References and Useful Resources
The following list contains resources referenced in this blog as well as ones that are generally useful:
- https://www.cyberark.com/threat-research-blog/cloud-shadow-admin-threat-10-permissions-protect/ + https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/
- https://www.slideshare.net/AmazonWebServices/aws-reinvent-2016-how-to-automate-policy-validation-sec311 + https://www.youtube.com/watch?v=pm-4vSMSDyc
- https://www.slideshare.net/AmazonWebServices/become-an-iam-policy-master-in-60-minutes-or-less-sec316r1-aws-reinvent-2018pdf + https://www.youtube.com/watch?v=YQsK4MtsELU