Suspicious AWS console logins by credential access alerts
| Id | b51fe620-62ad-4ed2-9d40-5c97c0a8231f |
| Rulename | 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 |
| Tactics | InitialAccess CredentialAccess |
| Techniques | T1078 |
| Required data connectors | AWS AzureActiveDirectoryIdentityProtection BehaviorAnalytics MicrosoftDefenderAdvancedThreatProtection MicrosoftThreatProtection OfficeATP |
| Kind | Scheduled |
| Query frequency | 1d |
| Query period | 1d |
| Trigger threshold | 0 |
| Trigger operator | gt |
| Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Multi Cloud Attack Coverage Essentials - Resource Abuse/Analytic Rules/SuspiciousAWSConsolLoginByCredentialAceessAlerts.yaml |
| Version | 1.0.1 |
| Arm template | b51fe620-62ad-4ed2-9d40-5c97c0a8231f.json |
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))
relevantTechniques:
- T1078
entityMappings:
- fieldMappings:
- columnName: AccountUPN
identifier: FullName
- columnName: Name
identifier: Name
- columnName: UPNSuffix
identifier: UPNSuffix
entityType: Account
- fieldMappings:
- columnName: SourceIpAddress
identifier: Address
entityType: IP
triggerThreshold: 0
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.'
customDetails:
UserAgent: UserAgent
AWSUSerUPN: CTUPN
ComonIp: SourceIpAddress
AzureUserUPN: AccountUPN
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
triggerOperator: gt
version: 1.0.1
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Multi Cloud Attack Coverage Essentials - Resource Abuse/Analytic Rules/SuspiciousAWSConsolLoginByCredentialAceessAlerts.yaml
id: b51fe620-62ad-4ed2-9d40-5c97c0a8231f
queryFrequency: 1d
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))
severity: Medium
queryPeriod: 1d
name: Suspicious AWS console logins by credential access alerts
tactics:
- InitialAccess
- CredentialAccess
kind: Scheduled