Many organizations utilizing AWS to host their cloud-native applications. Those applications use several AWS native methods to control the access to the AWS resources - Resource Policy e.g. Key Policy or to restrict the VPC on the VPC Endpoint Policy. Even though some policies are applied on certain resources, there still might be a gap in the policies making the security control not working as expected. That leaves the gap for the attacker for unauthorized access to the cloud resource or to exfiltrate the data from restricted VPC, even though the Policy seems to be restricted at the first glance.

In this post I will present edge cases of using the AWS Account Root principal in various policies on KMS example, I will also try to show few consequences of such misconfiguration and how you can harden your policies to ensure your cloud-native application are properly secure. All of the presented issues require first-level compromise e.g. compromised EC2/ECS instance with IAM role assigned.

Root Principal

Who is the root principal in AWS? The answer is simple, but it is also very often misunderstood. The root principal in AWS equals the AWS account principal, not the cloud admin in that AWS Account. There is an important implication of the root principal, it is mapped to any IAM roles inside that AWS Account. We can find it at the following link: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html

In section “AWS account principals” the AWS informs us that when specifying an AWS account, we can use ARN (arn:aws:iam::AWS-account-ID:root), or a shortened form that consists of the AWS: prefix followed by the account ID:

aws_root

KMS and Key Policy

KMS is a managed service for the creation, storage, and management of cryptographic keys. The KMS provides an easy way to control the key usage across the AWS environment. This is a pretty widely used service, almost all cloud-native applications utilize this service because it solves many problems with key management. This is why I have chosen this service to create this blog post, as the penetration testers would encounter this service almost in all engagements.

Speaking about the key policy, those are the primary way to control access to the keys in AWS KMS. Every key in KMS must have exactly one key policy. The statements in the key policy document determine who has permission to use the CMK and how they can use it. You can also use IAM policies and grants to control access to the CMK, but every CMK must have a key policy.

It is also very important to note, that since every key in KMS must have the Key Policy, very often there will be a default Key policy assigned which basically allows root principal (any IAM role in the AWS Account), to perform any actions on that key - that happens when the key is created programmatically (through SDK, API, CLI) which is also the most common ways of managing bigger AWS environment.

aws_root

Case 1: Improper restrictions on the Key Policy leads to unauthorized access to the Key

Let’s imagine the following scenario: we are targeting the AWS environment and we have already compromised the EC2/ECS instance with the IAM role which has the following IAM policy assigned:

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": "kms:*",
    "Resource": "*"
  }
}

The developers have chosen to give the instance the broad IAM permissions to KMS (no limitations on the resource level, all KMS actions are allowed), because they wanted to manage the Access Control on the resource level, by Key Policy assigned to the Keys in KMS.

There are hundreds of keys created inside the Account’s KMS with different Key policies assigned, let’s consider 3 examples:

  • Key 1: Key policy with strict restrictions on the actions and principal:
      {
      "Sid": "Enable IAM policies",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::0906XXXXXXXX:role/CloudAdmin"
      },
      "Action": "kms:*",
      "Resource": "*"
      },
      {
      "Sid": "Enable IAM policies",
      "Effect": "Allow",
      "Principal": {
       "AWS": "arn:aws:iam::0906XXXXXXXX:role/CMKRole"
      },
      "Action": ["kms:Encrypt", "kms:Decrypt"],
      "Resource": "*"
      }
    
  • Key 2: Key policy with the strict restrictions on the actions and principal, but default policy statement is still left in the policy:
    {
      {
      "Sid": "Enable IAM policies",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::0906XXXXXXXX:root"
      },
      "Action": "kms:*",
      "Resource": "*"
      },
      {
      "Sid": "Enable IAM policies",
      "Effect": "Allow",
      "Principal": {
       "AWS": "arn:aws:iam::0906XXXXXXXX:role/CMKRole"
      },
      "Action": ["kms:Encrypt", "kms:Decrypt"],
      "Resource": "*"
      }
    }
    
  • Key 3: Default key policy:
    {
    "Sid": "Enable IAM policies",
    "Effect": "Allow",
    "Principal": {
      "AWS": "arn:aws:iam::0906XXXXXXXX:root"
     },
    "Action": "kms:*",
    "Resource": "*"
    }
    

What would happen if we try to access the key description for those 3 different keys with the compromised IAM role, let’s examine it. Screenshot of the keys: aws_root

  • Key 1 - access denied:

aws_root

  • Key 2 - access granted:

aws_root

  • Key 3 - access granted:

aws_root

As you can see, the simple misunderstanding of the root principal in AWS or the default configuration left on the Key Policy can lead to unauthorized access to the Key inside the AWS KMS.

Recommendations

It is recommended to apply those actions to avoid such mistakes:

  • Implement both strict IAM policies and Resource Policies (Key Policies) to enforce access control on both levels.
  • Regularly review the key policy for the gaps, get rid of the default Policies.
  • Use the Resource Policy (Key Policy) templates to avoid assigning the default Policy.

VPC Endpoint Policy

VPC Endpoint Interfaces or Gateways are used to access the AWS Managed Service like KMS, S3, Secrets Manager securely through the AWS backbone network (not going through the public Internet). This is why many organizations utilize them where the restricted VPC is the compliance/regulatory requirement (no Internet Gateways and NAT Gateways in the environment).

There is a well-known technique of using such unrestricted VPC Endpoint to establish the 2-way communication channel (data exfiltration, data read) with the compute inside that restricted VPC. The first time, I have read this on the Rzepsky Medium blog, link below: https://medium.com/securing/data-leaks-from-aws-ec2-how-can-bob-reveal-alices-secrets-bd6fbe389966

This is only that straightforward when we have unrestricted VPC Endpoint, with the policy like the following:

{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}

However, in recent days there are more and more VPCE with restricted policies, are those restrictions hardened enough to stop the data exfiltration from restricted VPC? Let’s find out.

Case 2: Improper restrictions on the KMS VPC Endpoint Policy leads to the data exfiltration from the restricted VPC

Let’s examine the same case, that we have already compromised the compute instance with the following IAM policy on its IAM role:

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": "kms:*",
    "Resource": "*"
  }
}

Then, let’s say that the developers limited the VPCE Policy to the Principal only, that can be done two ways:

  • First approach - recommended per AWS doc:
    {
    "Sid": "Restrict-acess-to-specific-IAM-role",
    "Effect": "Allow",
    "Principal": "*",
    "Action": "kms:*",
    "Resource": "*",
    "Condition": {
      "ArnEquals": {
        "aws:PrincipalArn": "arn:aws:iam::0906XXXXXXXX:root"
      }
    }
    }
    
  • Second approach - not recommended by AWS doc, but without rationale from AWS:
    {
    "Sid": "Restrict-acess-to-specific-IAM-role",
    "Effect": "Allow",
    "Principal": "*",
    "Action": "kms:*",
    "Resource": "*",
    "Principal": {
       "AWS": "arn:aws:iam::0906XXXXXXXX:root"
      },
    }
    

In such a scenario, we cannot simply login to the attacker’s controlled AWS Account from within the restricted VPC and access our own KMS resource inside the restricted VPC, as the KMS VPCE would not allow us to connect.

What we must do is to utilize the already compromised IAM role belonging to the same AWS Account to access the attacker’s controlled KMS resource - in different AWS Account.

To make it clear, on all of the following screenshots - the compromised AWS Account starts with 0906XXXXXXXX, - the attacker’s controlled AWS Account start with 4647XXXXXXXX.

Now, to successfully exfiltrate the data from the environment we must do the following:

  • Using the attacker’s owned AWS account (4647XXXXXXXX) apply the Key Policy on your own key to allows full access from the Compromised IAM role (0906XXXXXXXX). aws_root

  • Using the compromised IAM role (0906XXXXXXXX), utilize the kms:CreateGrant create the new grant on a cross-account shared key from the attacker’s owned KMS (4647XXXXXXXX) to exfiltrate the data in Grant’s name parameter - it happens inside the restricted VPC and the action is allowed per the VPC Endpoint policy.

aws_root

  • Now, the attacker can read the data in its own AWS Account (4647XXXXXXXX) outside of the restricted VPC by using the kms:ListGrants action

aws_root

It is important to note that we can also send the data to restricted VPC going another way around, that is even simpler as we can use the key description for that. The attacker modified the key description on its side (4647XXXXXXXX), then from the restricted VPC calls the kms:DescribeKey on the cross-account shared KMS key using compromised AWS principal (0906XXXXXXXX). See below the attacker calling the kms:DescribeKey shared cross-account using the compromised AWS principal inside the restricted VPC:

aws_root

Value marked on red is controlled by the attacker from different AWS account and any env e.g. attacker’s workstation.

As with the other exfiltration techniques, there is no trace in the compromised AWS Account’s (0906XXXXXXXX) Cloudwatch logs, as everything happens in the attacker’s controlled KMS resources (4647XXXXXXXX).

Case 3: More restricted KMS VPCE still can be used for data exfiltration

It is interesting that with the KMS we can bypass even more restricted VPCE Policies, let’s consider the case where we have the restrictions on the resource level as well on the VPCE KMS Policy:

{
  "Sid": "Restrict-acess-to-specific-IAM-role",
  "Effect": "Allow",
  "Principal": "*",
  "Action": "kms:*",
  "Resource": "arn:aws:kms:us-east-1:0906XXXXXXXX:*",
  "Condition": {
    "ArnEquals": {
      "aws:PrincipalArn": "arn:aws:iam::0906XXXXXXXX:root"
    }
  }
}

Those restrictions mean that through this KMS VPCE can only be used to access the KMS resource inside the 0906XXXXXXXX AWS Account using the AWS principal from the Account 0906XXXXXXXX. That is pretty restricted, but considering the case where we already have the compromised IAM role with the full access to KMS and Key in KMS with default Key Policy, we would be still able to exfiltrate.

The procedure is as follows:

  1. Using the KMS Key in the 0906XXXXXXXX and AWS principal in the 0906XXXXXXXX AWS Account, we create the grant using the CreateGrant API and provide the attacker’s controlled AWS Principal - 0503XXXXXXXX. We grant at least DescribeKey (reading) and CreateGrant (writing) permissions on that key.

aws_root

As you can see, it was successful and we have got the grant-token. Now there is a gap in this technique, as the grant-token is required on the other side of communication - so the blind RCE would not work. However, in some scenarios, it still might be useful and it still leaves a gap in the VPC Restrictions.

  1. Then, the attacker in its own AWS Account (0503XXXXXXXX), outside of the restricted VPC uses this grant to perform either the read operation on the key (DescribeKey) or write operation on the key (CreateGrant). The Grant-token is required.

aws_root

It is important to note that the copied access token is not required to be provided in each call. The Grants in AWS to take place needs around 5 minutes, that is why it is better to provide it when you want the changes to take place imidiatelly. However, if you as the attacker do not have options to send this GrantToken - because you acting blind. It is better to wait, util the grant takes place. More on the Grant token: https://docs.aws.amazon.com/kms/latest/developerguide/grants.html

This technique has disadvantage: the KMS key used for the exfiltration is a part of the target AWS Account (0906XXXXXXXX) hence it can generate the CloudWatch alerts in targeted AWS Account (0906XXXXXXXX).

Recommendations

When implementing the VPCE policy take the following into consideration:

  • Implement the restrictions on the resource level,
  • If not possible, use the extra Conditional statement to control to what resource (in which AWS accounts) can the VPC Endpoint be used to connect to.
  • Harden your IAM and Resouce Policy as the Resource inside your AWS Account can be used to exfiltrate the data outside of your AWS Account - resource sharing.

Conclusions

Access Control and VPC restrictions in AWS are difficult as there are many things to consider - IAM policy, Resource policy, VPCE policy, and others. The examples were presented on the AWS KMS, however other AWS-managed services are prone to a misconfiguration in a similar or the same way as KMS. Do your cloud pentests regularly and stay safe.