Detection rules › Kusto
AWSCloudTrail - Login to AWS Management Console without MFA
Identifies successful AWS Management Console sign-ins where CloudTrail records a ConsoleLogin event without multi-factor authentication. The rule looks for logins where MFAUsed is not Yes and the console response is not Failure, which can indicate credential misuse or weak account protection.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Privilege Escalation | |
| Stealth |
Telemetry coverage
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: d25b1998-a592-4bc5-8a3a-92b39eedb1bc
name: AWSCloudTrail - Login to AWS Management Console without MFA
description: |
Identifies successful AWS Management Console sign-ins where CloudTrail records a ConsoleLogin event without
multi-factor authentication. The rule looks for logins where MFAUsed is not Yes and the console response is not
Failure, which can indicate credential misuse or weak account protection.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
- connectorId: AWSS3
dataTypes:
- AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- PrivilegeEscalation
- Persistence
- InitialAccess
relevantTechniques:
- T1078
query: |
AWSCloudTrail
| where EventName =~ "ConsoleLogin"
| extend MFAUsed = tostring(parse_json(AdditionalEventData).MFAUsed), LoginResult = tostring(parse_json(ResponseElements).ConsoleLogin), indexId = indexof(tostring(UserIdentityPrincipalid),":")
| where MFAUsed !~ "Yes" and LoginResult !~ "Failure"
| where SessionIssuerUserName !contains "AWSReservedSSO"
| 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]), "")
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by EventName, EventTypeName, LoginResult, MFAUsed, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, UserIdentityPrincipalid, UserAgent,
UserIdentityUserName, SessionMfaAuthenticated, SourceIpAddress, AWSRegion, indexId
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
LoginResult: LoginResult
MFAUsed: MFAUsed
AWSRegion: AWSRegion
UserAgent: UserAgent
alertDetailsOverride:
alertDisplayNameFormat: 'AWS Management Console sign-in without MFA: {{AccountName}} from {{SourceIpAddress}}'
alertDescriptionFormat: 'Successful ConsoleLogin without MFA for {{AccountName}} from {{SourceIpAddress}} in account {{RecipientAccountId}}.'
version: 1.0.6
kind: Scheduled
Stages and Predicates
Stage 1: source
AWSCloudTrail
Stage 2: where
| where EventName =~ "ConsoleLogin"
Stage 3: extend
| extend MFAUsed = tostring(parse_json(AdditionalEventData).MFAUsed), LoginResult = tostring(parse_json(ResponseElements).ConsoleLogin), indexId = indexof(tostring(UserIdentityPrincipalid),":")
Stage 4: where
| where MFAUsed !~ "Yes" and LoginResult !~ "Failure"
Stage 5: where
| where SessionIssuerUserName !contains "AWSReservedSSO"
Stage 6: 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]), "")
Stage 7: summarize
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by EventName, EventTypeName, LoginResult, MFAUsed, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, UserIdentityPrincipalid, UserAgent,
UserIdentityUserName, SessionMfaAuthenticated, SourceIpAddress, AWSRegion, indexId
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
SessionIssuerUserName | contains | AWSReservedSSO | excludes:SessionIssuerUserName field:"SessionIssuerUserName" value:"AWSReservedSSO" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventName | eq |
| field:"aws::eventName" kind:eq value:"ConsoleLogin" |
LoginResult | ne |
| field:"LoginResult" kind:ne value:"Failure" |
MFAUsed | ne |
| field:"MFAUsed" kind:ne value:"Yes" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AWSRegion | summarize |
AccountName | summarize |
AccountUPNSuffix | summarize |
EndTimeUtc | summarize |
EventName | summarize |
EventTypeName | summarize |
LoginResult | summarize |
MFAUsed | summarize |
RecipientAccountId | summarize |
SessionMfaAuthenticated | summarize |
SourceIpAddress | summarize |
StartTimeUtc | summarize |
UserAgent | summarize |
UserIdentityAccountId | summarize |
UserIdentityPrincipalid | summarize |
UserIdentityUserName | summarize |
indexId | summarize |