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
Impact

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule body

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

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actionmatch
  • kms:* transforms: term
  • kms:Encrypt transforms: term
field:"Action" kind:match
Effecteq
  • Allow
field:"Effect" kind:eq value:"Allow"
ErrorCodeis_null
  • (no value, null check)
field:"aws::errorCode" kind:is_null
ErrorMessageis_null
  • (no value, null check)
field:"aws::errorMessage" kind:is_null
EventNamein
  • CreateKey
  • PutKeyPolicy
field:"aws::eventName" kind:in
Principaleq
  • *
field:"Principal" kind:eq value:"*"

Output fields

These fields are emitted when the rule matches.

FieldSource
Statementextend
Actionextend
Effectextend
Principalextend
UserIdentityArnextend
UserNameextend
AccountNameextend
AccountUPNSuffixextend