Attempt to bypass conditional access rule in Azure AD
Id | 3af9285d-bb98-4a35-ad29-5ea39ba0c628 |
Rulename | Attempt to bypass conditional access rule in Azure AD |
Description | Identifies an attempt to Bypass conditional access rule(s) in Azure Active Directory. The ConditionalAccessStatus column value details if there was an attempt to bypass Conditional Access or if the Conditional access rule was not satisfied (ConditionalAccessStatus == 1). References: https://docs.microsoft.com/azure/active-directory/conditional-access/overview https://docs.microsoft.com/azure/active-directory/reports-monitoring/concept-sign-ins https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes ConditionalAccessStatus == 0 // Success ConditionalAccessStatus == 1 // Failure ConditionalAccessStatus == 2 // Not Applied ConditionalAccessStatus == 3 // unknown |
Severity | Low |
Tactics | InitialAccess Persistence |
Techniques | T1078 T1098 |
Required data connectors | AzureActiveDirectory |
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/Azure Active Directory/Analytic Rules/BypassCondAccessRule.yaml |
Version | 1.0.3 |
Arm template | 3af9285d-bb98-4a35-ad29-5ea39ba0c628.json |
let threshold = 1;
let aadFunc = (tableName:string){
table(tableName)
| where ConditionalAccessStatus == 1 or ConditionalAccessStatus =~ "failure"
| extend CAP = parse_json(ConditionalAccessPolicies)
| mv-apply CAP on (
project ConditionalAccessPoliciesName = CAP.displayName, result = CAP.result
| where result =~ "failure"
)
| extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)
| extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
| extend Status = strcat(StatusCode, ": ", ResultDescription)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Status = make_list(Status), StatusDetails = make_list(StatusDetails), IPAddresses = make_list(IPAddress), IPAddressCount = dcount(IPAddress), CorrelationIds = make_list(CorrelationId), ConditionalAccessPoliciesName = make_list(ConditionalAccessPoliciesName)
by UserPrincipalName, AppDisplayName, tostring(Browser), tostring(OS), City, State, Region, Type
| where IPAddressCount > threshold and StatusDetails !has "MFA successfully completed"
| mvexpand IPAddresses, Status, StatusDetails, CorrelationIds
| extend Status = strcat(Status, " ", StatusDetails)
| summarize IPAddresses = make_set(IPAddresses), Status = make_set(Status), CorrelationIds = make_set(CorrelationIds), ConditionalAccessPoliciesName = make_set(ConditionalAccessPoliciesName)
by StartTime, EndTime, UserPrincipalName, AppDisplayName, tostring(Browser), tostring(OS), City, State, Region, IPAddressCount, Type
| extend timestamp = StartTime, AccountCustomEntity = UserPrincipalName, IPCustomEntity = tostring(IPAddresses)
};
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
union isfuzzy=true aadSignin, aadNonInt
queryPeriod: 1d
version: 1.0.3
relevantTechniques:
- T1078
- T1098
queryFrequency: 1d
kind: Scheduled
name: Attempt to bypass conditional access rule in Azure AD
id: 3af9285d-bb98-4a35-ad29-5ea39ba0c628
entityMappings:
- fieldMappings:
- columnName: AccountCustomEntity
identifier: FullName
entityType: Account
- fieldMappings:
- columnName: IPCustomEntity
identifier: Address
entityType: IP
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Azure Active Directory/Analytic Rules/BypassCondAccessRule.yaml
severity: Low
query: |
let threshold = 1;
let aadFunc = (tableName:string){
table(tableName)
| where ConditionalAccessStatus == 1 or ConditionalAccessStatus =~ "failure"
| extend CAP = parse_json(ConditionalAccessPolicies)
| mv-apply CAP on (
project ConditionalAccessPoliciesName = CAP.displayName, result = CAP.result
| where result =~ "failure"
)
| extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)
| extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
| extend Status = strcat(StatusCode, ": ", ResultDescription)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Status = make_list(Status), StatusDetails = make_list(StatusDetails), IPAddresses = make_list(IPAddress), IPAddressCount = dcount(IPAddress), CorrelationIds = make_list(CorrelationId), ConditionalAccessPoliciesName = make_list(ConditionalAccessPoliciesName)
by UserPrincipalName, AppDisplayName, tostring(Browser), tostring(OS), City, State, Region, Type
| where IPAddressCount > threshold and StatusDetails !has "MFA successfully completed"
| mvexpand IPAddresses, Status, StatusDetails, CorrelationIds
| extend Status = strcat(Status, " ", StatusDetails)
| summarize IPAddresses = make_set(IPAddresses), Status = make_set(Status), CorrelationIds = make_set(CorrelationIds), ConditionalAccessPoliciesName = make_set(ConditionalAccessPoliciesName)
by StartTime, EndTime, UserPrincipalName, AppDisplayName, tostring(Browser), tostring(OS), City, State, Region, IPAddressCount, Type
| extend timestamp = StartTime, AccountCustomEntity = UserPrincipalName, IPCustomEntity = tostring(IPAddresses)
};
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
union isfuzzy=true aadSignin, aadNonInt
tactics:
- InitialAccess
- Persistence
description: |
'Identifies an attempt to Bypass conditional access rule(s) in Azure Active Directory.
The ConditionalAccessStatus column value details if there was an attempt to bypass Conditional Access
or if the Conditional access rule was not satisfied (ConditionalAccessStatus == 1).
References:
https://docs.microsoft.com/azure/active-directory/conditional-access/overview
https://docs.microsoft.com/azure/active-directory/reports-monitoring/concept-sign-ins
https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes
ConditionalAccessStatus == 0 // Success
ConditionalAccessStatus == 1 // Failure
ConditionalAccessStatus == 2 // Not Applied
ConditionalAccessStatus == 3 // unknown'
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
status: Available
triggerThreshold: 0
triggerOperator: gt
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"workspace": {
"type": "String"
}
},
"resources": [
{
"id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/3af9285d-bb98-4a35-ad29-5ea39ba0c628')]",
"name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/3af9285d-bb98-4a35-ad29-5ea39ba0c628')]",
"type": "Microsoft.OperationalInsights/workspaces/providers/alertRules",
"kind": "Scheduled",
"apiVersion": "2022-11-01",
"properties": {
"displayName": "Attempt to bypass conditional access rule in Azure AD",
"description": "'Identifies an attempt to Bypass conditional access rule(s) in Azure Active Directory.\nThe ConditionalAccessStatus column value details if there was an attempt to bypass Conditional Access\nor if the Conditional access rule was not satisfied (ConditionalAccessStatus == 1).\nReferences:\nhttps://docs.microsoft.com/azure/active-directory/conditional-access/overview\nhttps://docs.microsoft.com/azure/active-directory/reports-monitoring/concept-sign-ins\nhttps://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes\nConditionalAccessStatus == 0 // Success\nConditionalAccessStatus == 1 // Failure\nConditionalAccessStatus == 2 // Not Applied\nConditionalAccessStatus == 3 // unknown'\n",
"severity": "Low",
"enabled": true,
"query": "let threshold = 1;\nlet aadFunc = (tableName:string){\ntable(tableName)\n| where ConditionalAccessStatus == 1 or ConditionalAccessStatus =~ \"failure\"\n| extend CAP = parse_json(ConditionalAccessPolicies)\n| mv-apply CAP on (\n project ConditionalAccessPoliciesName = CAP.displayName, result = CAP.result\n | where result =~ \"failure\"\n)\n| extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)\n| extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser\n| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)\n| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)\n| extend Status = strcat(StatusCode, \": \", ResultDescription)\n| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Status = make_list(Status), StatusDetails = make_list(StatusDetails), IPAddresses = make_list(IPAddress), IPAddressCount = dcount(IPAddress), CorrelationIds = make_list(CorrelationId), ConditionalAccessPoliciesName = make_list(ConditionalAccessPoliciesName)\nby UserPrincipalName, AppDisplayName, tostring(Browser), tostring(OS), City, State, Region, Type\n| where IPAddressCount > threshold and StatusDetails !has \"MFA successfully completed\"\n| mvexpand IPAddresses, Status, StatusDetails, CorrelationIds\n| extend Status = strcat(Status, \" \", StatusDetails)\n| summarize IPAddresses = make_set(IPAddresses), Status = make_set(Status), CorrelationIds = make_set(CorrelationIds), ConditionalAccessPoliciesName = make_set(ConditionalAccessPoliciesName)\nby StartTime, EndTime, UserPrincipalName, AppDisplayName, tostring(Browser), tostring(OS), City, State, Region, IPAddressCount, Type\n| extend timestamp = StartTime, AccountCustomEntity = UserPrincipalName, IPCustomEntity = tostring(IPAddresses)\n};\nlet aadSignin = aadFunc(\"SigninLogs\");\nlet aadNonInt = aadFunc(\"AADNonInteractiveUserSignInLogs\");\nunion isfuzzy=true aadSignin, aadNonInt\n",
"queryFrequency": "P1D",
"queryPeriod": "P1D",
"triggerOperator": "GreaterThan",
"triggerThreshold": 0,
"suppressionDuration": "PT1H",
"suppressionEnabled": false,
"tactics": [
"InitialAccess",
"Persistence"
],
"techniques": [
"T1078",
"T1098"
],
"alertRuleTemplateName": "3af9285d-bb98-4a35-ad29-5ea39ba0c628",
"customDetails": null,
"entityMappings": [
{
"fieldMappings": [
{
"identifier": "FullName",
"columnName": "AccountCustomEntity"
}
],
"entityType": "Account"
},
{
"fieldMappings": [
{
"identifier": "Address",
"columnName": "IPCustomEntity"
}
],
"entityType": "IP"
}
],
"OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Azure Active Directory/Analytic Rules/BypassCondAccessRule.yaml",
"status": "Available",
"templateVersion": "1.0.3"
}
}
]
}