AWSCompromisedKeyQuarantineV2
(v3 was released during the creation of this article) is an AWS policy that attaches to identities whose credentials are leaked. It denies access to certain actions, applied by the AWS team in the event that an IAM user's credentials have been compromised or exposed publicly. AWS recently modified their public documentation to include the following:
“The policy aims to limit the potential damage that may be caused by fraud-related activity leading to unauthorized charges, while not impacting
the existing resources.”
While it is not the intended use of the policy, many see it as the first line of defense for an exposed access key. In fact, we have observed several organizations preemptively assign this policy to sensitive identities to limit actions that can occur.
The blog article will dive into the many ways we have observed threat actors continue their path through the Attack lifecycle, skirting around the limitations of the quarantine policy that was applied.
As a policy, AWSCompromisedKeyQuarantineV2 denies access to many dangerous privileges that can lead to Privilege Escalation like Creating and Attaching Policies, Creating Access Keys or Login Profiles, Password Change, Putting Roles on Resources or affecting data inside S3 Buckets.
The way the policy works is by denying several privileges that an identity may have. In principle, AWS Policies will always prioritize Deny over Allow. In the example below, since the second policy prevents 3/4 privileges the first policy allows, the identity is only allowed to execute iam:ChangePassword
. The privileges allowed by other policies, but not prevented by AWSCompromisedKeyQuarantineV2
are the ones we are interested in
AWSCompromisedKeyQuarantineV2
Policy contains 60 Actions affecting 8 services and preventing most common privileges used by attackers. It tries to prevent:
iam:AttachRolePolicy
, iam:AttachUserPolicy
, iam:AttachGroupPolicy
, iam:PutRolePolicy
, iam:PutUserPolicy
, iam:PutGroupPolicy
), Passing Role to a Service (iam:PassRole
), Updating Policies (iam:CreatePolicyVersion
, iam:SetDefaultPolicyVersion
) and Credential Creation and Update (iam:CreateAccessKey
, iam:ChangePassword
, iam:CreateLoginProfile
, iam:UpdateLoginProfile
)iam:CreateInstanceProfile
)cloudtrail:LookupEvents
)lambda:GetFunction
), Attaching Permissions to Function (lambda:AddPermissions
), Updating Function Code (lambda:UpdateFunctionCode
), or Tagging/Untagging (lambda:TagResource
, lambda:UntagResource
)organization:CreateOrganization
) and Account Creation (organization:CreateAccount
) or Invitation (organization:InviteAccountToOrganization
)lightsail:Create
, lightsail:Update
, lightsail:Delete
, lightsail:Start
) and Instance Access Preventing (lightsail:DownloadDefaultKeyPair
, lightsail:GetInstanceAccessDetails
)s3:DeleteBucket
, s3:DeleteObject
, s3:DeleteObjectVersion
), Deleting or Putting Bucket ACL and Policy (s3:PutAccountPublicAccessBlock
, s3:PutBucketPolicy
, s3:PutBucketAcl
), Putting lifecycle Configuration (s3:PutLifecycleConfiguration
), Putting Bucket Ownership (s3:PutBucketOwnershipControls
, s3:ObjectOwnerOverrideToBucketOwner
), Listing Buckets (s3:ListAllMyBuckets
)The policy does touch some impactful and used services and permissions commonly used by the attackers, but leaves a lot of them unchecked. Some of them allow Potential Privilege Escalation, and others affect the Infrastructure by modifying or deleting Resources. Overall, what we found were:
s3:ListAllMyBuckets
, or cloudtrail:LookupEvents
are not allowed, but IAM Enumeration
is allowed, SecretsManager secret grabbing
is too and Bucket Download
The only Read Privileges that AWSCompromisedKeyQuarantineV2 limits are:
cloudtrail:LookupEvents
lambda:ListTags
lambda:GetPolicy
lightsail:GetInstanceAccessDetails
s3:ListAllMyBuckets
Other than those, no other Read access is limited, so, provided the identity with the leaked credentials contains any other Read Privileges, an attacker can misuse those privileges to gather important information like:
iam:List*
, iam:Get*
privileges),iam:SimulateCustomPolicy
, iam:SimulatePrincipalPolicy
)ec2:List*
, ec2:Describe*
, ec2:Get*
)s3:ListObjectsV2
, s3:GetObject
, s3:HeadObject
, s3:GetBucketEncryptionCOnfiguration
)sm:ListSecrets
, sm:GetSecretValue
)kms:GetKeyPolicy
, kms:Decrypt
, and other KMS Read)lambda:GetFunction
or lambda:ListFunctions
)*:List
, *:Get
, *:Describe
, etc)In 2019, RhinoSec released a blog where they presented 28 Methods for AWS Privilege Escalation, which to this day has become the de-facto AWS Privilege Escalation Methodology for Pentesters and Security Engineers in the field. A lot of these techniques rely on either a Policy Modification, Attachment or a Role Assigned to a Resource. Permiso found several techniques an attacker could abuse to reach potential Privilege Escalation. It should be noted, since iam:Put*Policy
, iam:Attach*Policy
or iam:PassRole
, are not allowed, we cannot modify the role’s policies or attach new policies to the identities attached to the services. This means, out of 28, 20 of them are fully nullified, 4 of them are partially allowed to be executed and 4 of them are fully allowed to be executed:
Not Allowed | Partially Allowed | Fully Allowed |
---|---|---|
Creating a new policy version | Assume Role (Updating the Assume Role Policy Document of a role not allowed) | Updating an existing Glue Dev Endpoint |
Setting the default policy version to an existing version | Invoke Lambda Function (Passing a role to a new Lambda function not allowed) | Associating a Codestar team member |
Creating an EC2 instance with an existing instance profile | Invoke Lambda using DynamoDB (Passing a role to a new Lambda function not allowed) | Adding a malicious Lambda layer to an existing Lambda function |
Creating a new user access key | Update DataPipeline Definition | Gaining access to an existing SageMaker Jupyter notebook |
Creating a new login profile | ||
Updating an existing login profile | ||
Attaching a policy to a user | ||
Attaching a policy to a group | ||
Attaching a policy to a role | ||
Creating/updating an inline policy for a user | ||
Creating/updating an inline policy for a group | ||
Creating/updating an inline policy for a role | ||
Adding a user to a group | ||
Passing a role to a new Lambda function, then invoking it cross-account | ||
Updating the code of an existing Lambda function | ||
Passing a role to CloudFormation | ||
Passing a role to a new CodeStar project | ||
Passing a role to a new SageMaker Jupyter notebook | ||
Passing a role to a Glue Development Endpoint | ||
Creating a CodeStar project from a template (no longer possible) |
Since no privilege addition is allowed (Adding role to a service (iam:PassRole
), Policy Addition (iam:AttachUserPolicy
, iam:PutGroupPolicy
, iam:AttachGroupPolicy
, iam:PutGroupPolicy
, iam:AttachRolePolicy
, iam:PutRolePolicy
), Group Membership Addition (iam:AddUserToGroup
), or Policy Modification (iam:CreatePolicyVersion
and iam:SetDefaultPolicyVersion
)), these Privilege “Escalations” do only give access to the current privileges of the Role Attached to the resource attacked.
Among the Privilege Escalation Methods, the ones partially allowed are:
Privilege Escalation by Assuming Roles
In the occasion a high privileged role (including on another account) is allowed to be assumed by the leaked identity, the policy does not prevent it, which depending on the access of the target role, can lead to Privilege Escalation. For this attack, sts:AssumeRole
privilege is needed, which is allowed by the user. To Assume a Role, the source identity, which in this case is the compromised identity, will need to be allowed by the Role’s Trust Policy. To modify the Trust Policy, iam:UpdateAssumeRolePolicy
is needed and it is prevented by the Quarantine Policy.
Privilege Escalation by Invoking a Vulnerable Function
To invoke a Lambda Function, lambda:InvokeFunction
privilege is needed, which is not prevented by the Quarantine Policy. If an attacker invokes a vulnerable function, they can get the credentials of the Role Attached to it, which are found on the Environment Variables and use those credentials and the privileges they already have. Since the attacker cannot add a new role to the function, or give the current role more privileges, the attack will only grant them access to the current privileges of the function.
Privilege Escalation by connecting a service to Invoke a Lambda Function
This technique works by connecting a service to a function using lambda:CreateEventSourceMapping
and invoking the function using lambda:InvokeFunction
, to again get access to the credentials of the role attached to the function. Again, since the attacker cannot add a new role to the function, or give the current role more privileges, the attack will only grant them access to the current privileges of the function.
Privilege Escalation by Updating DataPipeline Definition
If a pipeline with a role is created, an attacker can use datapipeline:PutPipelineDefinition
on the pipeline to execute a code as the role attached to it and get the privileges of the role. Alternatively, the attacker can create a pipeline using datapipeline:CreatePipeline
and pass a role using iam:PassRole
, but since iam:PassRole
is not allowed, the attacker can only use current pipelines.
The methods below are not limited by the Quarantine Policy:
Privilege Escalation by Associating a Codestar team member
Due to Codestar deprecating as a service, this attack only works for projects created before August 31st 2024 and no new projects can be created after it. The attack involves adding a new team member on the project using codestar:AssociateTeamMember
and then creating a profile for that member using codestar:CreateUserProfile
, none of which are prevented by the Quarantine Policy, to allow the user to access the project and its role attached.
Privilege Escalation by Gaining access to an existing SageMaker Jupyter notebook
Sagemaker allows a user to create an authorized URL, using sagemaker:CreatePresignedNotebookInstanceURL
, which gives them access to the notebook, which contains a terminal to the notebook instance and the Instance Profile attached to it. That means, an attacker can get access to those credentials, thus escalating their privileges.
Privilege Escalation Using Glue Environment
Glue Environments have Endpoints created with an Instance Profile attached to them. That instance profile, by default has 3 Policies attached:
AmazonS3ReadOnlyAccess
AWSGlueConsoleFullAccess
AWSGlueServiceRole
Glue also offers the chance to attach AmazonS3FullAccess
to the role, if asked by the administrator. Other policies can also be attached to the Role, depending on the necessities of the project, which can lead to a high privilege escalation, but will most likely not happen.
AWSGlueConsoleFullAccess
is an interesting policy, because it allows iam:PassRole
, to only roles that contain AWSGlueServiceRole
only passable to Glue Resources and AWSGlueServiceNotebookRole
only passable to EC2 Instances. The Roles though do not contain much more privileges that cannot be found on the current credentials. Overall. what AWSGlueConsoleFullAccess
offers is:
s3:ListAllMyBuckets
IAM Enumeration
(list users, roles, groups, role policies)EC2 Enumeration
S3 full access
(if AmazonS3FullAccess
is attached)ec2:RunInstances
, which is specifically prevented by the Quarantine Policy, thus giving the attacker Instance Creation PrivilegesPrivilege Escalation by modifying Lambda Layer
Lambda Layers are basically libraries that a function uses. This attack allows code execution on a Lambda function and access to its role’s privileges. The attack is essentially updating the Lambda Layers, using lambda:UpdateFunctionConfiguration
.
AWS allows for a layer to be a cross account resource, meaning an attacker can just ask the function to use a layer on its own AWS Account, thus removing the need to create the layer on the target’s account. When the lambda is invoked, the credentials are sent to the attacker, which aside from being a privilege escalation method, makes for a great persistence.
Aside from escalating privileges, an attacker can impact an infrastructure even without getting higher privileges, provided the compromised identity has access to. They either give the attacker access to a service or resource, or
SSM Command Execution on Instances (Using ssm:SendCommand
or ssm:StartSession
, an attacker can get access to an EC2 Instance they have access to.)
S3 Bucket pillage (An attacker can use s3:GetObject
and s3:PutObject
to get and modify objects inside a bucket. They can also use s3:PutBucketEncryptionConfiguration
to enable S3 Encryption on a bucket using KMS)
S3 Versioning Bypass (When it comes to sensitive data, the S3 Bucket is one of the resources that needs to be monitored. To prevent accidental deletion of Bucket Objects, AWS offers Bucket Versioning, which keeps all the modified versions of a file. To Add or Modify the Versioning on a Bucket s3:PutBucketVersioning
is used, which is not prevented by the Quarantine Policy)
S3 Ransomware (Using s3:PutBucketEncryptionConfiguration
to put an attacker controlled KMS key and s3:GetObject
and s3:PutObject
to download the unencrypted object and upload the encrypted version of the object, the attacker can deny access to the data to the target, thus using it for extortion)
Update KMS Key Policy (an attacker can update the KMS key policy using kms:PutKeyPolicy
to update the KMS Key policy access, thus using the key to decrypt sensitive information)
Lambda Function Invoking and Deleting (Though lambda:CreateFunction
is prevented by the Quarantine Policy, lambda:InvokeFunction
and lambda:DeleteFunction
is not. That means, an attacker can just choose to impact the target by simply invoking the function on a loop or straight up deleting it, resulting in a denial of service)
Shutting down or Terminating instances in production (Same as the Lambda Scenario, an attacker can simply shut down using ec2:StopInstances
or Terminate an instance using ec2:TerminateInstances
, thus resulting in a denial of service)
Modifying User Data (This attack requires some target interactions, but if we get it, it is possible to achieve Command Execution on an instance. Since Stopping Instances using ec2:StopInstances
and modifying Instance User Data using ec2:ModifyInstanceAttribute
are not prevented by the Quarantine Policy, an attacker can stop the instance, modify the startup scripts, otherwise called User-Data on the Instance and achieve code execution when the instance is started again by the target)
Getting Management Console Access (To get access to the Management Console, an attacker will need to either create a Login Profile using iam:CreateLoginProfile, or update one using iam:UpdateLoginProfile. There is a third mostly unknown way to get access to it, by abusing sts:GetFederationToken. GetFederationToken will provide a set of temporary credentials for a user, which can be forwarded to signin.aws.amazon.com/federation
and provide a signed URL, which gives access to the Management Console without a password or even a Login Profile. This technique is implemented on looks like aws_consoler, Pacu and Nebula)
Stopping and Deleting CloudTrail (Usually, when an attacker wants to stop logging, they either stop trail logging with cloudtrail:StopLogging
, delete the trail using cloudtrail:DeleteTrail
, or just delete the trail’s bucket’s content. Deleting the bucket is not allowed (though theoretically updating the content using s3:PutObject
is), but the policy does not prevent neither cloudtrail:StopLogging
, nor cloudtrail:DeleteTrail
)
Tampering with GuardDuty (No GuardDuty events are limited to the identity, which can lead to an attacker finding this policy to be able to tamper with the protections. Since no GD event is limited, an attacker can use any of the following events to tamper with GuardDuty:
guardduty:DeleteDetector
guardduty:DeleteIPSet
guardduty:DeleteInvitations
guardduty:DeleteMembers
guardduty:UpdateIPSet
guardduty:DisassociateFromMasterAccount
guardduty:DisassociateMembers
guardduty:DisassociateFromAdministratorAccount
guardduty:UpdateDetector
)
bedrock:InvokeModelWithResponseStream
bedrock:InvokeModel
bedrock:ListFoundationModelAgreementOffers
bedrock:CreateFoundationModelAgreement
bedrock:GetFoundationModelAvailability
bedrock:ListInferenceProfiles
bedrock:ListFoundationModels
bedrock:GetModelInvocationLoggingConfiguration
bedrock:PutFoundationModelEntitlement
bedrock:PutUseCaseForModelAccess
bedrock:GetUseCaseForModelAccess
)
Another issue we noticed with the policy is the fact that the policy uses the current identity’s credentials to attach the policy to itself, thus requiring iam:AttachUserPolicy
, or iam:AttachRolePolicy
from it. When the identity does not have access to attach policy, AWS will only try once, result in a failure and stop trying. This behavior happens once per key. Also, when the event fails, requestParameters
and responseElements
will be null, making it harder for a defender to know what this event is supposed to be.
{
"eventVersion": "1.09",
"userIdentity": {
"type": "IAMUser",
"principalId": "AIDAZBGIGYA4KWUEUMRVS",
"arn": "arn:aws:iam::************:user/s3admin",
"accountId": "************",
"accessKeyId": "ASIAZBGIGYA4HVP2SMRW",
"userName": "s3admin",
"sessionContext": {
"attributes": {
"creationDate": "2024-06-25T16:16:26Z",
"mfaAuthenticated": "false"
}
},
"invokedBy": "AWS Internal"
},
"eventTime": "2024-06-25T16:16:26Z",
"eventSource": "iam.amazonaws.com",
"eventName": "AttachUserPolicy",
"awsRegion": "us-east-1",
"sourceIPAddress": "AWS Internal",
"userAgent": "AWS Internal",
"errorCode": "AccessDenied",
"errorMessage": "User: arn:aws:iam::************:user/s3admin is not authorized to perform: iam:AttachUserPolicy on resource: user s3admin because no identity-based policy allows the iam:AttachUserPolicy action",
**"requestParameters": null,
"responseElements": null,**
"requestID": "dc63a1dd-ac15-4ce3-bb64-0d2a7d4389f7",
"eventID": "826cf05c-bf2f-4239-ba81-af57a2ad5da8",
"readOnly": false,
"eventType": "AwsApiCall",
"managementEvent": true,
"recipientAccountId": "************",
"eventCategory": "Management"
}
Permiso wrote and released an open source tool called DetentionDodger
, which finds identities with leaked credentials and the impact they have on the target:
The tool will list all the identities with a Quarantine Policy (version 1-3) and look for failed policy attachments of a Quarantine Policy in CloudTrail Logs to generate a list of users with leaked credentials. After that, it will list all the inline and attached policies of the user and each group it is part of and check the impact the identity have based on a predefined list of privileges required for an attack:
Each scenario is editable, as well as new scenarios can be added by the user to test:
On 1st of July 2024, we sent the disclosure to AWS with a list of attacks that needed mitigation
On August 2024, AWS created Version 3 of the Policy which was updated with the new restrictions on October 2024. They also updated Version 2 of the policy with the same document as the Version 3. The new Policy limits the following privileges on the environment:
amplify:CreateBackendEnvironment
amplify:CreateDeployment
bedrock:CreateFoundationModelAgreement
bedrock:CreateModelInvocationJob
bedrock:InvokeModel
bedrock:InvokeModelWithResponseStream
bedrock:PutFoundationModelEntitlement
codebuild:CreateProject
ecr:GetAuthorizationToken
ecs:CreateCluster
ecs:CreateService
ecs:RegisterTaskDefinition
glue:CreateJob
iam:DeleteAccessKey
iam:DeleteRole
iam:ListUsers
lambda:GetEventSourceMapping
mediapackagev2:CreateChannel
s3:CreateBucket
s3:GetObject
s3:ListBucket
s3:PutBucketCors
sagemaker:CreateEndpointConfig
sagemaker:CreateProcessingJob
ses:GetSendQuota
ses:ListIdentities
sns:GetSMSAttributes
sts:GetFederationToken
sts:GetSessionToken
As it seems, not all of the privileges listed above were added, so we are adding a list of privileges to be aware of on the compromised identities:
sts:AssumeRole
dynamodb:PutItem
dynamodb:CreateTable
lambda:ListFunctions
lambda:GetFunction
lambda:UpdateFunctionConfiguration
lambda:InvokeFunction
lambda:CreateEventSourceMapping
lambda:DeleteFunction
glue:CreateDevEndpoint
glue:UpdateDevEndpoint
cloudformation:CreateStack
cloudformation:*
datapipeline:CreatePipeline
datapipeline:PutPipelineDefinition
codestar:CreateProjectFromTemplate
codestar:CreateProject
codestar:AssociateTeamMember
iam:List*
iam:Get*
iam:Simulate*
sagemaker:CreateNotebookInstance
sagemaker:CreatePresignedNotebookInstanceUrl
s3:PutBucketVersioning
s3:PutBucketEncryptionConfiguration
s3:PutObject
kms:*
ssm:StartSession
ssm:SendCommand
secretsmanager:GetSecretValue
secretsmanager:ListSecrets
secretsmanager:UpdateSecret
secretsmanager:Delete
ec2:StopInstances
ec2:ModifyInstanceAttribute
ec2:TerminateInstance
guardduty:DeleteDetector
guardduty:DeleteIPSet
guardduty:DeleteInvitations
guardduty:DeleteMembers
guardduty:UpdateIPSet
guardduty:DisassociateFromMasterAccount
guardduty:DisassociateMembers
guardduty:DisassociateFromAdministratorAccount
guardduty:UpdateDetector
cloudtrail:StopLogging
cloudtrail:DeleteTrail
iam:GetRole
iam:RemoveRoleFromInstanceProfile
ec2:StopInstance
ec2:TerminateInstances
bedrock:InvokeModelWithResponseStream
bedrock:InvokeModel
bedrock:ListFoundationModelAgreementOffers
bedrock:CreateFoundationModelAgreement
bedrock:GetFoundationModelAvailability
bedrock:ListInferenceProfiles
bedrock:ListFoundationModels
bedrock:GetModelInvocationLoggingConfiguration
bedrock:PutFoundationModelEntitlement
bedrock:PutUseCaseForModelAccess
bedrock:GetUseCaseForModelAccess
It is recommended to limit some if not all of the above privileges to prevent attacks on cloud infrastructures by attackers who find leaked credentials.
In addition to the open source tool, Permiso has included new detections available to all customers immediately. These detections enable customers to detect identities with leaked credentials by both listing the policies attached and finding the failed CloudTrail Logs for Quarantine Policy Attachment.
Detection | Description |
---|---|
P0_AWS_ACCESS_KEY_EXPOSED_1 | Your AWS Access Key is exposed to the public. |
P0_AWS_SELF_APPLIED_QUARANTINE_POLICY_1 | A user has applied the AWSCompromisedKeyQuarantineV2 policy to another user (AttachUserPolicy ). This policy is typically used and automatically applied by AWS through AWS Health if an access key is discovered in the public, such as a GitHub repository. |
P0_AWS_SECRETKEY_EXPOSED_1 | A user had the AWSCompromisedKeyQuarantineV2 policy applied using the AttachUserPolicy privilege, indicating an exposed static AWS API key. |
P0_AWS_INTERNAL_ATTACH_POLICY_FAILURE_1 | A policy failed to get attached to an identity |
As a conclusion, Permiso managed to find several bypasses, which go from Enumeration to Privilege Escalation and Persistence. This research determined that the following AWSCompromisedKeyQuarantineV2
Policy security limitation bypasses were found:
s3:ListAllMyBuckets
, or cloudtrail:LookupEvents
are not allowed, but IAM Enumeration
is allowed, SecretsManager secret grabbing
is too and Bucket Download
iam:AttachPolicy
, which if not allowed, puts AWS Accounts at risk of further attack.As always, it is important to understand that even if a security feature exists, does not mean it is perfect and non bypass-able. That is why it is important to always test the effectiveness of a restriction or recommendation, to always be certain of its usability.
You can access DetentionDodger on GitHub: https://github.com/Permiso-io-tools/DetentionDodger