Detection rules › Kusto
AWSCloudTrail - Suspicious overly permissive KMS key policy created
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
| Tactic | Techniques |
|---|---|
| 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)else
tostring(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.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | match |
| field:"Action" kind:match |
Effect | eq |
| field:"Effect" kind:eq value:"Allow" |
ErrorCode | is_null | field:"aws::errorCode" kind:is_null | |
ErrorMessage | is_null | field:"aws::errorMessage" kind:is_null | |
EventName | in |
| field:"aws::eventName" kind:in |
Principal | eq |
| field:"Principal" kind:eq value:"*" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Statement | extend |
Action | extend |
Effect | extend |
Principal | extend |
UserIdentityArn | extend |
UserName | extend |
AccountName | extend |
AccountUPNSuffix | extend |