Cloud Security Wire
AWS Azure GCP RSS
AWS Misconfiguration high

AWS KMS Key Policy and Grant Misconfigurations: Privilege Escalation and Data Exposure

Overly broad KMS key policies, unchecked CreateGrant permissions and unmonitored key deletion let attackers decrypt data or destroy it. Audit commands, policy fixes and detection for AWS KMS.

By Cloud Security Wire · ·
#AWS#KMS#key policy#grants#encryption#privilege escalation#IAM#CloudTrail#hardening
High Severity

This issue has been assessed as high severity. Review affected configurations immediately.

Why KMS is a quiet escalation path

AWS KMS protects S3, EBS, RDS, Secrets Manager and many other services. Its access model is unusual: a key policy is the root of authority, and IAM policies only apply if the key policy allows it. Teams that treat KMS like any other IAM-governed resource often leave gaps. Three recurring ones are an over-delegating key policy, unrestricted grants, and unmonitored key lifecycle actions.

Misconfiguration 1: the “enable IAM” statement taken too far

The default key policy contains this statement:

{
  "Sid": "Enable IAM User Permissions",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
  "Action": "kms:*",
  "Resource": "*"
}

This is not inherently wrong. It delegates control to IAM in the account. The consequence is that any principal with kms:* or kms:Decrypt in an IAM policy can use every key with this statement. A compromised developer role carrying AdministratorAccess can decrypt every secret and snapshot in the account, whatever the data owner intended. Sensitive keys should instead name specific principals in the key policy.

Worse is a wildcard principal:

{
  "Effect": "Allow",
  "Principal": { "AWS": "*" },
  "Action": ["kms:Decrypt", "kms:GenerateDataKey*"],
  "Resource": "*"
}

Without a restricting condition, this exposes the key to every AWS principal, in any account. It often appears when someone is trying to make cross-account sharing work.

Audit for it

for k in $(aws kms list-keys --query 'Keys[].KeyId' --output text); do
  aws kms get-key-policy --key-id "$k" --policy-name default \
    --query Policy --output text \
    | jq -r --arg k "$k" 'select(.Statement[] | select(.Effect=="Allow" and (.Principal=="*" or .Principal.AWS=="*") and (has("Condition")|not))) | "OPEN POLICY: \($k)"'
done

IAM Access Analyzer also reports external access to KMS keys as findings:

aws accessanalyzer list-findings-v2 --analyzer-arn <analyzer-arn> \
  --filter '{"resourceType":{"eq":["AWS::KMS::Key"]}}'

Misconfiguration 2: unrestricted CreateGrant

Grants are an alternate, policy-free way to give access to a key. A principal holding kms:CreateGrant can hand a grantee permission to decrypt, and the grant does not appear in the key policy or in IAM. An attacker with a foothold can create a grant to an identity they control and keep access after the original IAM permission is removed.

# Attacker-style persistence (for detection testing in your own account)
aws kms create-grant --key-id <key-id> \
  --grantee-principal arn:aws:iam::999988887777:role/external \
  --operations Decrypt GenerateDataKey

Review grants regularly:

aws kms list-grants --key-id <key-id> \
  --query 'Grants[].{Grantee:GranteePrincipal,Ops:Operations,Creator:IssuingAccount}'
aws kms retire-grant --key-id <key-id> --grant-id <grant-id>

Restrict creation to AWS service use only:

{
  "Sid": "AllowGrantsForAWSServicesOnly",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::111122223333:role/app-role" },
  "Action": "kms:CreateGrant",
  "Resource": "*",
  "Condition": { "Bool": { "kms:GrantIsForAWSResource": "true" } }
}

Misconfiguration 3: key administration mixed with key use

Administrators who can call kms:PutKeyPolicy can rewrite the policy and grant themselves usage. Separate the two roles, and never allow a data-plane role to hold PutKeyPolicy, ScheduleKeyDeletion or DisableKey. Scheduling deletion is a destructive option: the waiting period is 7 to 30 days, and once the key is deleted, data encrypted under it is unrecoverable.

Hardening: constrain use to the service and the organisation

{
  "Sid": "UseOnlyViaS3WithinOrg",
  "Effect": "Allow",
  "Principal": { "AWS": "*" },
  "Action": ["kms:Decrypt", "kms:GenerateDataKey*"],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "aws:PrincipalOrgID": "o-exampleorgid",
      "kms:ViaService": "s3.us-east-1.amazonaws.com"
    }
  }
}

Add a Service Control Policy that blocks key deletion outside a break-glass role:

{
  "Effect": "Deny",
  "Action": ["kms:ScheduleKeyDeletion", "kms:DisableKey", "kms:PutKeyPolicy"],
  "Resource": "*",
  "Condition": {
    "ArnNotLike": { "aws:PrincipalArn": "arn:aws:iam::*:role/KmsBreakGlass" }
  }
}

Detection

Alert on these CloudTrail events: CreateGrant (especially where the grantee is outside your account), PutKeyPolicy, ScheduleKeyDeletion, DisableKey and ImportKeyMaterial.

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=CreateGrant \
  --max-results 20

If a deletion is scheduled in error or by an intruder, cancel it during the waiting period:

aws kms cancel-key-deletion --key-id <key-id>
aws kms enable-key --key-id <key-id>

Remediation checklist

  1. Run the policy audit above and Access Analyzer findings across all regions.
  2. Replace wildcard principals with named principals or add aws:PrincipalOrgID conditions.
  3. Review every grant with list-grants and retire unknown ones.
  4. Require kms:GrantIsForAWSResource on CreateGrant.
  5. Split key administrators from key users.
  6. Deploy the SCP that guards deletion, disabling and policy changes.
  7. Send KMS management events to an alert pipeline.
  8. Enable automatic key rotation with aws kms enable-key-rotation --key-id <key-id>.

KMS failures rarely show up in posture scans that only look at IAM. Auditing key policies and grants directly closes a path attackers use to read, persist in, or destroy encrypted data.

← All Analysis Subscribe via RSS