Detection rules › Kusto
AWSGuardDuty - GuardDuty Alert
Identifies Amazon GuardDuty findings and creates a Microsoft Sentinel alert for each finding. Use the GuardDuty finding details in the alert to determine the specific malicious or suspicious activity that was detected.
Rule body kusto
id: bf0cde21-0c41-48f6-a40c-6b5bd71fa106
name: AWSGuardDuty - GuardDuty Alert
description: |
Identifies Amazon GuardDuty findings and creates a Microsoft Sentinel alert for each finding. Use the GuardDuty finding details in the alert to determine the specific malicious or suspicious activity that was detected.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWSS3
dataTypes:
- AWSGuardDuty
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
// https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings.html
AWSGuardDuty
// Parse the finding
// https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_finding-format.html
// Example: "ThreatPurpose:ResourceTypeAffected/ThreatFamilyName.DetectionMechanism!Artifact"
| extend findingTokens = split(ActivityType, ":")
| extend ThreatPurpose=findingTokens[0], findingTokens=split(findingTokens[1], "/")
| extend ResourceTypeAffected=findingTokens[0], findingTokens= split(findingTokens[1], ".")
| extend ThreatFamilyName=findingTokens[0], findingTokens=split(findingTokens[1], "!")
| extend DetectionMechanism=findingTokens[0], Artifact=findingTokens[1]
// Assign severity level
// https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings.html#guardduty_findings-severity
| extend Severity =
case (
Severity >= 7.0, "High",
Severity between (4.0 .. 6.9), "Medium",
Severity between (1.0 .. 3.9), "Low",
"Unknown"
)
// Pull out any available resource details we can extract entities from. These may not exist in the alert.
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_Resource.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_AccessKeyDetails.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_RdsDbUserDetails.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_KubernetesDetails.html
| extend AccessKeyDetails=ResourceDetails.accessKeyDetails
| extend RdsDbUserDetails=ResourceDetails.rdsDbUserDetails
| extend KubernetesDetails=ResourceDetails.kubernetesDetails
// Pull out any available action details we can extract entities from. These may not exist in the alert.
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_Action.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_AwsApiCallAction.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_KubernetesApiCallAction.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_NetworkConnectionAction.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_RdsLoginAttemptAction.html
| extend ServiceAction =
case(
isnotempty(ServiceDetails.action.awsApiCallAction), ServiceDetails.action.awsApiCallAction,
isnotempty(ServiceDetails.action.kubernetesApiCallAction), ServiceDetails.action.kubernetesApiCallAction,
isnotempty(ServiceDetails.action.networkConnectionAction), ServiceDetails.action.networkConnectionAction,
isnotempty(ServiceDetails.action.rdsLoginAttemptAction), ServiceDetails.action.rdsLoginAttemptAction,
dynamic(null)
)
// The IPv4 remote address of the connection
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_RemoteIpDetails.html
// or
// The IP of the Kubernetes API caller and the IPs of any proxies or load balancers between the caller and the API endpoint
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_KubernetesApiCallAction.html
| extend RemoteIpAddress =
coalesce(
tostring(ServiceAction.remoteIpDetails.ipAddressV4),
tostring(parse_json(ServiceAction.sourceIPs)[0])
)
// The IPv4 local address of the connection
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_LocalIpDetails.html
| extend LocalIpAddress = ServiceAction.localIpDetails.ipAddressV4
// The AWS account ID of the remote API caller.
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_AwsApiCallAction.html
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_RemoteAccountDetails.html
| extend RemoteAWSAccountId = ServiceAction.remoteAccountDetails.accountId
// The IAM access key details (user information) of a user that engaged in the activity that prompted GuardDuty to generate a finding
// https://docs.aws.amazon.com/guardduty/latest/APIReference/API_AccessKeyDetails.html
| extend AccountUpn =
case(
AccessKeyDetails.userType == "IAMUser", AccessKeyDetails.userName,
AccessKeyDetails.userType == "AssumedRole", split(AccessKeyDetails.principalId, ":", 1)[0],
isnotempty(RdsDbUserDetails.user), RdsDbUserDetails.user,
isnotempty(KubernetesDetails.kubernetesUserDetails.username), KubernetesDetails.kubernetesUserDetails.username,
""
)
| extend AccountName = split(AccountUpn, "@", 0)[0]
| extend UPNSuffix = split(AccountUpn, "@", 1)[0]
// Clean up the output
| extend GuardDutyDetails =
bag_pack(
"DetectorId", ServiceDetails.detectorId,
"Partition", Partition,
"Region", Region
)
| extend FindingLink =
iff(
isnotempty(Region) and isnotempty(Id),
strcat("https://", Region, ".console.aws.amazon.com/guardduty/home?region=", Region, "#/findings?fId=", Id),
""
)
| extend FindingLinkDescription =
iff(
isnotempty(FindingLink),
strcat("Link to GuardDuty finding (AWS): ", FindingLink),
""
)
| project-rename
FindingArn=Arn,
FindingId=Id,
AWSAccountId=AccountId
| project-away
ActivityType,
findingTokens,
Partition,
Region,
SchemaVersion,
TimeGenerated,
Type
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- identifier: ObjectGuid
columnName: RemoteAWSAccountId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: RemoteIpAddress
- entityType: IP
fieldMappings:
- identifier: Address
columnName: LocalIpAddress
- entityType: URL
fieldMappings:
- identifier: Url
columnName: FindingLink
customDetails:
ThreatPurpose: ThreatPurpose
ResourceTypeAffected: ResourceTypeAffected
ThreatFamilyName: ThreatFamilyName
DetectionMechanism: DetectionMechanism
Artifact: Artifact
alertDetailsOverride:
alertDisplayNameFormat: '{{Title}}'
alertDescriptionFormat: '{{Description}}'
alertTacticsColumnName: ThreatPurpose
alertSeverityColumnName: Severity
kind: Scheduled
version: 1.0.7
Stages and Predicates
Stage 1: source
AWSGuardDuty
Stage 2: extend (19 consecutive steps)
| extend findingTokens = split(ActivityType, ":")
| extend ThreatPurpose=findingTokens[0], findingTokens=split(findingTokens[1], "/")
| extend ResourceTypeAffected=findingTokens[0], findingTokens= split(findingTokens[1], ".")
| extend ThreatFamilyName=findingTokens[0], findingTokens=split(findingTokens[1], "!")
| extend DetectionMechanism=findingTokens[0], Artifact=findingTokens[1]
| extend Severity =
case (
Severity >= 7.0, "High",
Severity between (4.0 .. 6.9), "Medium",
Severity between (1.0 .. 3.9), "Low",
"Unknown"
)
| extend AccessKeyDetails=ResourceDetails.accessKeyDetails
| extend RdsDbUserDetails=ResourceDetails.rdsDbUserDetails
| extend KubernetesDetails=ResourceDetails.kubernetesDetails
| extend ServiceAction =
case(
isnotempty(ServiceDetails.action.awsApiCallAction), ServiceDetails.action.awsApiCallAction,
isnotempty(ServiceDetails.action.kubernetesApiCallAction), ServiceDetails.action.kubernetesApiCallAction,
isnotempty(ServiceDetails.action.networkConnectionAction), ServiceDetails.action.networkConnectionAction,
isnotempty(ServiceDetails.action.rdsLoginAttemptAction), ServiceDetails.action.rdsLoginAttemptAction,
dynamic(null)
)
| extend RemoteIpAddress =
coalesce(
tostring(ServiceAction.remoteIpDetails.ipAddressV4),
tostring(parse_json(ServiceAction.sourceIPs)[0])
)
| extend LocalIpAddress = ServiceAction.localIpDetails.ipAddressV4
| extend RemoteAWSAccountId = ServiceAction.remoteAccountDetails.accountId
| extend AccountUpn =
case(
AccessKeyDetails.userType == "IAMUser", AccessKeyDetails.userName,
AccessKeyDetails.userType == "AssumedRole", split(AccessKeyDetails.principalId, ":", 1)[0],
isnotempty(RdsDbUserDetails.user), RdsDbUserDetails.user,
isnotempty(KubernetesDetails.kubernetesUserDetails.username), KubernetesDetails.kubernetesUserDetails.username,
""
)
| extend AccountName = split(AccountUpn, "@", 0)[0]
| extend UPNSuffix = split(AccountUpn, "@", 1)[0]
| extend GuardDutyDetails =
bag_pack(
"DetectorId", ServiceDetails.detectorId,
"Partition", Partition,
"Region", Region
)
| extend FindingLink =
iff(
isnotempty(Region) and isnotempty(Id),
strcat("https://", Region, ".console.aws.amazon.com/guardduty/home?region=", Region, "#/findings?fId=", Id),
""
)
| extend FindingLinkDescription =
iff(
isnotempty(FindingLink),
strcat("Link to GuardDuty finding (AWS): ", FindingLink),
""
)
Stage 3: project-rename
| project-rename
FindingArn=Arn,
FindingId=Id,
AWSAccountId=AccountId
Stage 4: project-away
| project-away
ActivityType,
findingTokens,
Partition,
Region,
SchemaVersion,
TimeGenerated,
Type
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.
| Field | Source |
|---|---|
ThreatPurpose | extend |
ResourceTypeAffected | extend |
ThreatFamilyName | extend |
Artifact | extend |
DetectionMechanism | extend |
Severity | extend |
AccessKeyDetails | extend |
RdsDbUserDetails | extend |
KubernetesDetails | extend |
ServiceAction | extend |
RemoteIpAddress | extend |
LocalIpAddress | extend |
RemoteAWSAccountId | extend |
AccountUpn | extend |
AccountName | extend |
UPNSuffix | extend |
GuardDutyDetails | extend |
FindingLink | extend |
FindingLinkDescription | extend |
AWSAccountId | project-rename |
FindingArn | project-rename |
FindingId | project-rename |