Detection rules › Kusto

AWSCloudTrail - Suspicious overly permissive KMS key policy created

Status
available
Severity
high
Time window
1d
Source
github.com/Azure/Azure-Sentinel

Detects creation or update of KMS key policies that grant broad encryption permissions to all principals. Overly permissive key policies can be abused for malicious encryption operations and indicate potential account compromise or risky misconfiguration.

MITRE ATT&CK coverage

TacticTechniques
ImpactT1486 Data Encrypted for Impact

Rules detecting the same action

Other rules on this platform that filter on the same API call or operation.

Rule body kusto

id: 60dfc193-0f73-4279-b43c-110ade02b201
name: AWSCloudTrail - Suspicious overly permissive KMS key policy created
description: |
  Detects creation or update of KMS key policies that grant broad encryption permissions to all principals.
  Overly permissive key policies can be abused for malicious encryption operations and indicate potential account
  compromise or risky misconfiguration.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1486
query: |
    let kmsActions = dynamic(["kms:Encrypt", "kms:*"]); //Add other overly permissive APIs to this list.
    AWSCloudTrail
    | where EventName in ("CreateKey","PutKeyPolicy") and isempty(ErrorCode) and isempty(ErrorMessage)
    | extend Statement = parse_json(tostring((parse_json(RequestParameters).policy))).Statement
    | mvexpand Statement
    | extend Action = tostring(parse_json(Statement).Action), Effect = tostring(parse_json(Statement).Effect), Principal = iff(isnotempty(tostring(parse_json(Statement).Principal.AWS)),tostring(parse_json(Statement).Principal.AWS), tostring(parse_json(Statement).Principal))
    | where Effect =~ "Allow" and Action has_any (kmsActions) and Principal == "*" 
    | extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
    | extend UserName = tostring(split(UserIdentityArn, '/')[-1])
    | extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
    | extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
      AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  EventName: EventName
  EventSource: EventSource
  AWSRegion: AWSRegion
  UserIdentityArn: UserIdentityArn
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS overly permissive KMS key policy change by {{AccountName}}'
  alertDescriptionFormat: 'Detected {{EventName}} from {{SourceIpAddress}} creating a broadly permissive KMS policy in account {{RecipientAccountId}}.'
version: 1.0.5
kind: Scheduled

Stages and Predicates

Parameters

let kmsActions = dynamic(["kms:Encrypt", "kms:*"]);

Stage 1: source

AWSCloudTrail

Stage 2: where

| where EventName in ("CreateKey","PutKeyPolicy") and isempty(ErrorCode) and isempty(ErrorMessage)

Stage 3: extend

| extend Statement = parse_json(tostring((parse_json(RequestParameters).policy))).Statement

Stage 4: mv-expand

| mvexpand Statement

Stage 5: extend

| extend Action = tostring(parse_json(Statement).Action), Effect = tostring(parse_json(Statement).Effect), Principal = iff(isnotempty(tostring(parse_json(Statement).Principal.AWS)),tostring(parse_json(Statement).Principal.AWS), tostring(parse_json(Statement).Principal))
Principal =
if/* macro: isnotempty(tostring(parse_json(Statement).Principal.AWS)) */tostring(parse_json(Statement).Principal.AWS)
elsetostring(parse_json(Statement).Principal)

Stage 6: where

| where Effect =~ "Allow" and Action has_any (kmsActions) and Principal == "*"

Stage 7: extend (4 consecutive steps)

| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
  AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
Actionmatch
  • kms:*
  • kms:Encrypt
Effecteq
  • Allow
ErrorCodeis_null
  • (no value, null check)
ErrorMessageis_null
  • (no value, null check)
EventNamein
  • CreateKey transforms: cased
  • PutKeyPolicy transforms: cased
Principaleq
  • * transforms: cased

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
Statementextend
Actionextend
Effectextend
Principalextend
UserIdentityArnextend
UserNameextend
AccountNameextend
AccountUPNSuffixextend