Detection rules › Kusto
Suspicious AWS console logins by credential access alerts
This query aims to detect instances of successful AWS console logins that align with high-severity credential access or Initial Access alerts generated by Defender Products. Specifically, it focuses on scenarios where the successful login takes place within a 60-minute timeframe of the high-severity alert. The login is considered relevant if it originates from an IP address associated with potential attackers.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | IdentityInfo action any: Identity information |
Rules detecting the same action
These rules filter on the same operation.
- AWS Console Login (Panther)
- AWS Console Login Failed During MFA Challenge (Splunk)
- AWS ConsoleLogin Failed Authentication (Sigma)
- AWS CreateLoginProfile (Splunk)
- AWS Credential Access Failed Login (Splunk)
- AWS High Number Of Failed Authentications For User (Splunk)
- AWS High Number Of Failed Authentications From Ip (Splunk)
- AWS Multiple Failed MFA Requests For User (Splunk)
Rule body
id: b51fe620-62ad-4ed2-9d40-5c97c0a8231f
name: Suspicious AWS console logins by credential access alerts
description: |
'This query aims to detect instances of successful AWS console logins that align with high-severity credential access or Initial Access alerts generated by Defender Products.
Specifically, it focuses on scenarios where the successful login takes place within a 60-minute timeframe of the high-severity alert. The login is considered relevant if it originates from an IP address associated with potential attackers.'
severity: Medium
requiredDataConnectors:
- connectorId: OfficeATP
dataTypes:
- SecurityAlert
- connectorId: AWS
dataTypes:
- AWSCloudTrail
- connectorId: MicrosoftDefenderAdvancedThreatProtection
dataTypes:
- SecurityAlert
- connectorId: AzureActiveDirectoryIdentityProtection
dataTypes:
- SecurityAlert (IPC)
- connectorId: BehaviorAnalytics
dataTypes:
- IdentityInfo
- connectorId: MicrosoftThreatProtection
dataTypes:
- SecurityAlert
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CredentialAccess
relevantTechniques:
- T1078
query: |
SecurityAlert
// Filtering alerts based on Microsoft product names
| where ProductName in ("Microsoft 365 Defender", "Azure Active Directory", "Microsoft Defender Advanced Threat Protection", "Microsoft Cloud App Security","Azure Active Directory Identity Protection", "Microsoft Defender ATP")
// Narrowing down alerts to specific tactics
| where Tactics in("CredentialAccess", "InitialAccess")
// Focusing on high-severity alerts
| where AlertSeverity == "High"
// Parsing and extending the 'Entities' column as JSON objects
| extend Entities = parse_json(Entities)
// Exploring IP entities within the alert entities
| mv-apply Entity = Entities on
(
where Entity.Type == 'ip'
| extend EntityIp = tostring(Entity.Address)
)
// Exploring account entities within the alert entities
| mv-apply Entity = Entities on
(
where Entity.Type == 'account'
| extend AccountObjectId = tostring(Entity.AadUserId)
)
// Filtering out alerts with missing IP or account information
| where isnotempty(EntityIp) and isnotempty(AccountObjectId)
// Summarizing relevant fields for further analysis
| summarize
by
AlertName,
ProductName,
ProviderName,
AlertSeverity,
EntityIp,
Tactics,
Techniques,
AlertTime= bin(TimeGenerated, 1min),
AccountObjectId,
AlertTimeGenerated=TimeGenerated
// Joining with IdentityInfo to obtain additional account details
| join kind=inner (
IdentityInfo
| where TimeGenerated >= ago(1d)
| distinct AccountObjectId, AccountUPN=tolower(AccountUPN)
)
on AccountObjectId
|extend Name = tostring(split(AccountUPN,'@')[0]), UPNSuffix =tostring(split(AccountUPN,'@')[1])
// Joining with AWSCloudTrail data to correlate AWS console logins
| join kind=inner (
AWSCloudTrail
| where EventName == "ConsoleLogin"
| extend CTUPN= tolower(tostring(tolower(split(UserIdentityArn, "/", 2)[0])))
| extend ActionType= tostring(parse_json(ResponseElements).ConsoleLogin)
| where ActionType == "Success"
| extend AWSTime= bin(TimeGenerated, 1min)
| project
EventName,
EventSource,
EventTypeName,
RecipientAccountId,
ResponseElements,
SessionMfaAuthenticated,
SourceIpAddress,
TimeGenerated,
UserAgent,
UserIdentityArn,
UserIdentityType,
CTUPN,
AWSTime,
UserIdentityUserName
)
on $left.EntityIp == $right.SourceIpAddress
// Filtering login event after the Alert generation time
| where AlertTimeGenerated >= AWSTime
// Calculating the time difference between alert generation and AWS login
| extend timediff = datetime_diff('minute', AlertTimeGenerated, TimeGenerated)
// Filtering alerts with a time difference of up to 60 minutes
| where timediff between ((-60)..(60))
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountUPN
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
customDetails:
AWSUSerUPN: CTUPN
AzureUserUPN: AccountUPN
ComonIp: SourceIpAddress
UserAgent: UserAgent
kind: Scheduled
version: 1.0.1
Stages and Predicates
Stage 1: source
SecurityAlert
Stage 2: where
| where ProductName in ("Microsoft 365 Defender", "Azure Active Directory", "Microsoft Defender Advanced Threat Protection", "Microsoft Cloud App Security","Azure Active Directory Identity Protection", "Microsoft Defender ATP")
Stage 3: where
| where Tactics in("CredentialAccess", "InitialAccess")
Stage 4: where
| where AlertSeverity == "High"
Stage 5: extend
| extend Entities = parse_json(Entities)
Stage 6: kusto:mv-apply
| mv-apply Entity = Entities on
(
where Entity.Type == 'ip'
| extend EntityIp = tostring(Entity.Address)
)
Stage 7: kusto:mv-apply
| mv-apply Entity = Entities on
(
where Entity.Type == 'account'
| extend AccountObjectId = tostring(Entity.AadUserId)
)
Stage 8: where
| where isnotempty(EntityIp) and isnotempty(AccountObjectId)
Stage 9: summarize
| summarize
by
AlertName,
ProductName,
ProviderName,
AlertSeverity,
EntityIp,
Tactics,
Techniques,
AlertTime= bin(TimeGenerated, 1min),
AccountObjectId,
AlertTimeGenerated=TimeGenerated
Stage 10: join
| join kind=inner (
IdentityInfo
| where TimeGenerated >= ago(1d)
| distinct AccountObjectId, AccountUPN=tolower(AccountUPN)
)
on AccountObjectId
Stage 11: extend
| extend Name = tostring(split(AccountUPN,'@')[0]), UPNSuffix =tostring(split(AccountUPN,'@')[1])
Stage 12: join
| join kind=inner (
AWSCloudTrail
| where EventName == "ConsoleLogin"
| extend CTUPN= tolower(tostring(tolower(split(UserIdentityArn, "/", 2)[0])))
| extend ActionType= tostring(parse_json(ResponseElements).ConsoleLogin)
| where ActionType == "Success"
| extend AWSTime= bin(TimeGenerated, 1min)
| project
EventName,
EventSource,
EventTypeName,
RecipientAccountId,
ResponseElements,
SessionMfaAuthenticated,
SourceIpAddress,
TimeGenerated,
UserAgent,
UserIdentityArn,
UserIdentityType,
CTUPN,
AWSTime,
UserIdentityUserName
)
on $left.EntityIp == $right.SourceIpAddress
Stage 13: where
| where AlertTimeGenerated >= AWSTime
Stage 14: extend
| extend timediff = datetime_diff('minute', AlertTimeGenerated, TimeGenerated)
Stage 15: where
| where timediff between ((-60)..(60))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccountObjectId | is_not_null | field:"AccountObjectId" kind:is_not_null | |
ActionType | eq |
| field:"ActionType" kind:eq value:"Success" |
AlertSeverity | eq |
| field:"AlertSeverity" kind:eq value:"High" |
AlertTimeGenerated | cross_field_compare |
| field:"AlertTimeGenerated" kind:cross_field_compare value:"AWSTime" |
EntityIp | is_not_null | field:"EntityIp" kind:is_not_null | |
EventName | eq |
| field:"aws::eventName" kind:eq value:"ConsoleLogin" |
ProductName | in |
| field:"ProductName" kind:in |
Tactics | in |
| field:"Tactics" kind:in |
Type | eq |
| field:"Type" kind:eq |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountObjectId | summarize |
AlertName | summarize |
AlertSeverity | summarize |
AlertTime | summarize |
AlertTimeGenerated | summarize |
EntityIp | summarize |
ProductName | summarize |
ProviderName | summarize |
Tactics | summarize |
Techniques | summarize |
Name | extend |
UPNSuffix | extend |
timediff | extend |