Implemented function to look at AADNonInteractiveSigninLogs in current SigninLogs detectections
This commit is contained in:
Родитель
d13cef0be3
Коммит
ce0e6212b5
|
@ -8,6 +8,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
- connectorId: AWS
|
||||
dataTypes:
|
||||
- AWSCloudTrail
|
||||
|
@ -26,13 +29,21 @@ query: |
|
|||
//Adjust this threshold to fit your environment
|
||||
let signin_threshold = 5;
|
||||
//Make a list of IPs with AAD signin failures above our threshold
|
||||
let aadFunc = (tableName:string){
|
||||
let Suspicious_signins =
|
||||
SigninLogs
|
||||
table(tableName)
|
||||
| where ResultType !in ("0", "50125", "50140")
|
||||
| where IPAddress != "127.0.0.1"
|
||||
| summarize count() by IPAddress
|
||||
| where count_ > signin_threshold
|
||||
| summarize make_list(IPAddress);
|
||||
Suspicious_signins
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
let Suspicious_signins =
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
| summarize make_set(list_IPAddress);
|
||||
//See if any of those IPs have sucessfully logged into the AWS console
|
||||
AWSCloudTrail
|
||||
| where EventName =~ "ConsoleLogin"
|
||||
|
|
|
@ -8,6 +8,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
- connectorId: SecurityEvents
|
||||
dataTypes:
|
||||
- SecurityEvent
|
||||
|
@ -29,10 +32,11 @@ query: |
|
|||
//Adjust this threshold to fit the environment
|
||||
let signin_threshold = 5;
|
||||
//Make a list of all IPs with failed signins to AAD above our threshold
|
||||
let aadFunc = (tableName:string){
|
||||
let suspicious_signins =
|
||||
SigninLogs
|
||||
table(tableName)
|
||||
| where ResultType !in ("0", "50125", "50140")
|
||||
| where IPAddress != "127.0.0.1"
|
||||
| where IPAddress !in ('127.0.0.1', '::1')
|
||||
| summarize count() by IPAddress
|
||||
| where count_ > signin_threshold
|
||||
| summarize make_list(IPAddress);
|
||||
|
@ -56,6 +60,10 @@ query: |
|
|||
| project TimeGenerated, Account, AccountType, Computer, Activity, EventID, LogonProcessName, IpAddress, LogonTypeName, TargetUserSid, Reason;
|
||||
union isfuzzy=true linux_logons,win_logons
|
||||
| extend timestamp = TimeGenerated, AccountCustomEntity = Account, IPCustomEntity = IpAddress, HostCustomEntity = Computer
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -9,6 +9,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
- connectorId: PaloAltoNetworks
|
||||
dataTypes:
|
||||
- CommonSecurityLog
|
||||
|
@ -27,8 +30,9 @@ query: |
|
|||
//Set a threshold of failed AAD signins from an IP address within 1 day above which we want to deem those logins suspicious.
|
||||
let signin_threshold = 5;
|
||||
//Make a list of IPs with AAD signin failures above our threshold.
|
||||
let aadFunc = (tableName:string){
|
||||
let suspicious_signins =
|
||||
SigninLogs
|
||||
table(tableName)
|
||||
//Looking for logon failure results
|
||||
| where ResultType !in ("0", "50125", "50140")
|
||||
//Exclude localhost addresses to reduce the chance of FPs
|
||||
|
@ -36,6 +40,13 @@ query: |
|
|||
| summarize count() by IPAddress
|
||||
| where count_ > signin_threshold
|
||||
| summarize make_list(IPAddress);
|
||||
suspicious_signins
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
let suspicious_signins =
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
| summarize make_list(list_IPAddress);
|
||||
//See if any of those IPs have sucessfully logged into PA VPNs during the same timeperiod
|
||||
CommonSecurityLog
|
||||
//Select only PA VPN sucessful logons
|
||||
|
@ -46,9 +57,9 @@ query: |
|
|||
| where SourceIP in (suspicious_signins)
|
||||
| extend Reason = "Multiple failed AAD logins from SourceIP"
|
||||
//Parse out other useful information from Message field
|
||||
| extend User = extract("User name: ([^,]+)", 1, Message)
|
||||
| extend ClientOS = extract("Client OS version: ([^,\"]+)", 1, Message)
|
||||
| extend Location = extract("Source region: ([^,]{2})",1, Message)
|
||||
| extend User = extract('User name: ([^,]+)', 1, Message)
|
||||
| extend ClientOS = extract('Client OS version: ([^,\"]+)', 1, Message)
|
||||
| extend Location = extract('Source region: ([^,]{2})',1, Message)
|
||||
| project TimeGenerated, Reason, SourceIP, User, ClientOS, Location, Message, DeviceName, ReceiptTime, DeviceVendor, DeviceEventClassID, Computer, FileName
|
||||
| extend AccountCustomEntity = User, IPCustomEntity = SourceIP, timestamp = TimeGenerated, HostCustomEntity = DeviceName
|
||||
entityMappings:
|
||||
|
|
|
@ -13,6 +13,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 14d
|
||||
triggerOperator: gt
|
||||
|
@ -37,11 +40,12 @@ query: |
|
|||
//The time to project forward after the last login activity [default: 60min]
|
||||
let projectedEndTime = 60min;
|
||||
//Get Teams successful signins globally
|
||||
let aadFunc = (tableName:string){
|
||||
let signinData =
|
||||
SigninLogs
|
||||
table(tableName)
|
||||
| where AppDisplayName has "Teams"
|
||||
| where ConditionalAccessStatus =~ "success"
|
||||
| extend country = tostring(LocationDetails['countryOrRegion'])
|
||||
| extend country = tostring(todynamic(LocationDetails)['countryOrRegion'])
|
||||
| where isnotempty(country) and isnotempty(IPAddress);
|
||||
// Collect successful signins to teams
|
||||
let loginEvents =
|
||||
|
@ -108,6 +112,10 @@ query: |
|
|||
| extend activitySummary = pack(tostring(StartTime), pack("Operation",tostring(Operation), "OperationTime", OperationTime))
|
||||
| summarize make_bag(activitySummary) by UserPrincipalName, SuspiciousIP, SuspiciousLoginCountry, SuspiciousCountryPrevalence
|
||||
| extend IPCustomEntity = SuspiciousIP, AccountCustomEntity = UserPrincipalName
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -25,7 +25,10 @@ requiredDataConnectors:
|
|||
- SecurityEvent
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
- connectorId: AzureMonitor(WireData)
|
||||
dataTypes:
|
||||
- WireData
|
||||
|
@ -103,6 +106,11 @@ query: |
|
|||
| where isnotempty(IPAddress)
|
||||
| where IPAddress in (IPList)
|
||||
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
|
||||
),
|
||||
(AADNonInteractiveUserSignInLogs
|
||||
| where isnotempty(IPAddress)
|
||||
| where IPAddress in (IPList)
|
||||
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
|
||||
),
|
||||
(W3CIISLog
|
||||
| where isnotempty(cIP)
|
||||
|
|
|
@ -8,6 +8,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
- connectorId: SecurityEvents
|
||||
dataTypes:
|
||||
- SecurityEvent
|
||||
|
@ -48,11 +51,16 @@ query: |
|
|||
| where count_ > signin_threshold
|
||||
| summarize make_list(SourceIP);
|
||||
//See if any of the IPs with failed host logins hve had a sucessful Azure AD login
|
||||
SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
table(tableName)
|
||||
| where ResultType !in ("0", "50125", "50140")
|
||||
| where IPAddress in (win_fails) or IPAddress in (nix_fails)
|
||||
| extend Reason= "Multiple failed host logins from IP address with successful Azure AD login"
|
||||
| extend timstamp = TimeGenerated, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
|
||||
| extend timstamp = TimeGenerated, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress, Type = Type
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -25,6 +25,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
- connectorId: AzureMonitor(IIS)
|
||||
dataTypes:
|
||||
- W3CIISLog
|
||||
|
@ -85,6 +88,11 @@ query: |
|
|||
| where IPAddress in (IPList)
|
||||
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
|
||||
),
|
||||
(AADNonInteractiveUserSignInLogs
|
||||
| where isnotempty(IPAddress)
|
||||
| where IPAddress in (IPList)
|
||||
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
|
||||
),
|
||||
(W3CIISLog
|
||||
| where isnotempty(cIP)
|
||||
| where cIP in (IPList)
|
||||
|
|
|
@ -14,6 +14,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
- connectorId: AWS
|
||||
dataTypes:
|
||||
- AWSCloudTrail
|
||||
|
|
|
@ -12,6 +12,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 1d
|
||||
triggerOperator: gt
|
||||
|
@ -23,6 +26,7 @@ relevantTechniques:
|
|||
query: |
|
||||
|
||||
let PrivateIPregex = @'^127\.|^10\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168\.';
|
||||
let aadFunc = (tableName:string){
|
||||
CommonSecurityLog
|
||||
| where DeviceVendor =~ "Cisco"
|
||||
| where DeviceAction =~ "denied"
|
||||
|
@ -33,10 +37,14 @@ query: |
|
|||
// Successful signins from IPs blocked by the firewall solution are suspect
|
||||
// Include fully successful sign-ins, but also ones that failed only at MFA stage
|
||||
// as that supposes the password was sucessfully guessed.
|
||||
SigninLogs
|
||||
table(tableName)
|
||||
| where ResultType in ("0", "50074", "50076")
|
||||
) on $left.SourceIP == $right.IPAddress
|
||||
| extend timestamp = TimeGenerated, IPCustomEntity = SourceIP, AccountCustomEntity = UserPrincipalName
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -10,6 +10,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 14d
|
||||
triggerOperator: gt
|
||||
|
@ -23,9 +26,11 @@ query: |
|
|||
let lookBack_long = 14d;
|
||||
let lookBack_med = 7d;
|
||||
let lookBack = 1d;
|
||||
SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
table(tableName)
|
||||
| where TimeGenerated >= startofday(ago(lookBack_long))
|
||||
| extend locationString = strcat(tostring(LocationDetails["countryOrRegion"]), "/", tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]), ";")
|
||||
| extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
|
||||
| extend locationString = strcat(tostring(LocationDetails.countryOrRegion), "/", tostring(LocationDetails.state), "/", tostring(LocationDetails.city), ";")
|
||||
| project TimeGenerated, AppDisplayName , UserPrincipalName, locationString
|
||||
// Create time series
|
||||
| make-series dLocationCount = dcount(locationString) on TimeGenerated in range(startofday(ago(lookBack_long)),now(), 1d)
|
||||
|
@ -37,30 +42,35 @@ query: |
|
|||
| where Slope > 0.3
|
||||
| top 50 by Slope desc
|
||||
| join kind = leftsemi (
|
||||
SigninLogs
|
||||
table(tableName)
|
||||
| where TimeGenerated >= startofday(ago(lookBack_med))
|
||||
| extend locationString = strcat(tostring(LocationDetails["countryOrRegion"]), "/", tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]), ";")
|
||||
| extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
|
||||
| extend locationString = strcat(tostring(LocationDetails.countryOrRegion), "/", tostring(LocationDetails.state), "/", tostring(LocationDetails.city), ";")
|
||||
| project TimeGenerated, AppDisplayName , UserPrincipalName, locationString
|
||||
| make-series dLocationCount = dcount(locationString) on TimeGenerated in range(startofday(ago(lookBack_med)) ,now(), 1d)
|
||||
by UserPrincipalName, AppDisplayName
|
||||
| extend (RSquare,Slope,Variance,RVariance,Interception,LineFit)=series_fit_line(dLocationCount)
|
||||
| top 50 by Slope desc
|
||||
| extend (RSquare,Slope,Variance,RVariance,Interception,LineFit)=series_fit_line(dLocationCount)
|
||||
| where Slope > 0.3
|
||||
| top 50 by Slope desc
|
||||
) on UserPrincipalName, AppDisplayName
|
||||
| join kind = leftsemi (
|
||||
SigninLogs
|
||||
table(tableName)
|
||||
| where TimeGenerated >= startofday(ago(lookBack))
|
||||
| extend locationString = strcat(tostring(LocationDetails["countryOrRegion"]), "/", tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]), ";")
|
||||
| extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
|
||||
| extend locationString = strcat(tostring(LocationDetails.countryOrRegion), "/", tostring(LocationDetails.state), "/", tostring(LocationDetails.city), ";")
|
||||
| project TimeGenerated, AppDisplayName , UserPrincipalName, locationString
|
||||
| make-series dLocationCount = dcount(locationString) on TimeGenerated in range(startofday(ago(lookBack)) ,now(), 1d)
|
||||
by UserPrincipalName, AppDisplayName
|
||||
| extend (RSquare,Slope,Variance,RVariance,Interception,LineFit)=series_fit_line(dLocationCount)
|
||||
| extend (RSquare,Slope,Variance,RVariance,Interception,LineFit)=series_fit_line(dLocationCount)
|
||||
| where Slope > 5
|
||||
| top 50 by Slope desc
|
||||
// Higher threshold requirement on last day anomaly
|
||||
| where Slope > 5
|
||||
) on UserPrincipalName, AppDisplayName
|
||||
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName
|
||||
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -9,6 +9,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1h
|
||||
queryPeriod: 1h
|
||||
triggerOperator: gt
|
||||
|
@ -21,15 +24,21 @@ tags:
|
|||
- Solorigate
|
||||
- NOBELIUM
|
||||
query: |
|
||||
SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
table(tableName)
|
||||
| where AppId =~ "1b730954-1685-4b74-9bfd-dac224a7b894" // AppDisplayName IS Azure Active Directory PowerShell
|
||||
| where TokenIssuerType =~ "AzureAD"
|
||||
| where ResourceIdentity !in ("00000002-0000-0000-c000-000000000000", "00000003-0000-0000-c000-000000000000") // ResourceDisplayName IS NOT Windows Azure Active Directory OR Microsoft Graph
|
||||
| extend Status = todynamic(Status)
|
||||
| where Status.errorCode == 0 // Success
|
||||
| project-reorder IPAddress, UserAgent, ResourceDisplayName, UserDisplayName, UserId, UserPrincipalName
|
||||
| project-reorder IPAddress, UserAgent, ResourceDisplayName, UserDisplayName, UserId, UserPrincipalName, Type
|
||||
| order by TimeGenerated desc
|
||||
// New entity mapping
|
||||
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -7,6 +7,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 1d
|
||||
triggerOperator: gt
|
||||
|
@ -23,22 +26,27 @@ query: |
|
|||
let NumberOfStds = 3;
|
||||
let MinThreshold = 10.0;
|
||||
let EndRunTime = StartTime - RunTime;
|
||||
let EndLearningTime = StartTime + LearningPeriod;
|
||||
let GitHubFailedSSOLogins = (SigninLogs
|
||||
let EndLearningTime = StartTime + LearningPeriod;
|
||||
let aadFunc = (tableName:string){
|
||||
let GitHubFailedSSOLogins = (table(tableName)
|
||||
| where AppDisplayName == "GitHub.com"
|
||||
| where ResultType != 0);
|
||||
GitHubFailedSSOLogins
|
||||
| where TimeGenerated between (ago(EndLearningTime) .. ago(StartTime))
|
||||
| summarize FailedLoginsCountInBinTime = count() by UserPrincipalName, bin(TimeGenerated, BinTime)
|
||||
| summarize AvgOfFailedLoginsInLearning = avg(FailedLoginsCountInBinTime), StdOfFailedLoginsInLearning = stdev(FailedLoginsCountInBinTime) by UserPrincipalName
|
||||
| summarize FailedLoginsCountInBinTime = count() by UserPrincipalName, bin(TimeGenerated, BinTime), Type
|
||||
| summarize AvgOfFailedLoginsInLearning = avg(FailedLoginsCountInBinTime), StdOfFailedLoginsInLearning = stdev(FailedLoginsCountInBinTime) by UserPrincipalName, Type
|
||||
| extend LearningThreshold = max_of(AvgOfFailedLoginsInLearning + StdOfFailedLoginsInLearning * NumberOfStds, MinThreshold)
|
||||
| join kind=innerunique (
|
||||
GitHubFailedSSOLogins
|
||||
| where TimeGenerated between (ago(StartTime) .. ago(EndRunTime))
|
||||
| summarize FailedLoginsCountInRunTime = count() by User = Identity, UserPrincipalName, bin(TimeGenerated, BinTime)
|
||||
| summarize FailedLoginsCountInRunTime = count() by User = Identity, UserPrincipalName, bin(TimeGenerated, BinTime), Type
|
||||
) on UserPrincipalName
|
||||
| where FailedLoginsCountInRunTime > LearningThreshold
|
||||
| extend AccountCustomEntity = UserPrincipalName , timestamp = TimeGenerated
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -17,6 +17,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 1d
|
||||
triggerOperator: gt
|
||||
|
@ -30,20 +33,30 @@ relevantTechniques:
|
|||
query: |
|
||||
|
||||
let threshold = 1;
|
||||
SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
table(tableName)
|
||||
| where ConditionalAccessStatus == 1 or ConditionalAccessStatus =~ "failure"
|
||||
| extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser
|
||||
| extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(DeviceDetail), 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 ConditionalAccessPolicies = todynamic(ConditionalAccessPolicies)
|
||||
| extend ConditionalAccessPol0Name = tostring(ConditionalAccessPolicies[0].displayName)
|
||||
| extend ConditionalAccessPol1Name = tostring(ConditionalAccessPolicies[1].displayName)
|
||||
| extend ConditionalAccessPol2Name = tostring(ConditionalAccessPolicies[2].displayName)
|
||||
| extend Status = strcat(StatusCode, ": ", ResultDescription)
|
||||
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), Status = makelist(Status), StatusDetails = makelist(StatusDetails), IPAddresses = makelist(IPAddress), IPAddressCount = dcount(IPAddress) , CorrelationIds = makelist(CorrelationId) by UserPrincipalName, AppDisplayName, tostring(Browser), tostring(OS), Location, ConditionalAccessPol0Name, ConditionalAccessPol1Name, ConditionalAccessPol2Name
|
||||
| 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)
|
||||
by UserPrincipalName, AppDisplayName, tostring(Browser), tostring(OS), City, State, Region, ConditionalAccessPol0Name, ConditionalAccessPol1Name, ConditionalAccessPol2Name, Type
|
||||
| where IPAddressCount > threshold and StatusDetails !has "MFA successfully completed"
|
||||
| mvexpand IPAddresses, Status, StatusDetails, CorrelationIds
|
||||
| extend Status = strcat(Status, " ", StatusDetails)
|
||||
| summarize IPAddresses = makeset(IPAddresses), Status = makeset(Status), CorrelationIds = makeset(CorrelationIds) by StartTimeUtc, EndTimeUtc, UserPrincipalName, AppDisplayName, tostring(Browser), tostring(OS), Location, ConditionalAccessPol0Name, ConditionalAccessPol1Name, ConditionalAccessPol2Name, IPAddressCount
|
||||
| extend timestamp = StartTimeUtc, AccountCustomEntity = UserPrincipalName, IPCustomEntity = tostring(IPAddresses)
|
||||
| summarize IPAddresses = make_set(IPAddresses), Status = make_set(Status), CorrelationIds = make_set(CorrelationIds)
|
||||
by StartTime, EndTime, UserPrincipalName, AppDisplayName, tostring(Browser), tostring(OS), City, State, Region, ConditionalAccessPol0Name, ConditionalAccessPol1Name, ConditionalAccessPol2Name, IPAddressCount, Type
|
||||
| extend timestamp = StartTime, AccountCustomEntity = UserPrincipalName, IPCustomEntity = tostring(IPAddresses)
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -10,6 +10,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 1d
|
||||
triggerOperator: gt
|
||||
|
@ -21,13 +24,18 @@ relevantTechniques:
|
|||
query: |
|
||||
|
||||
let threshold = 3;
|
||||
SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
table(tableName)
|
||||
| where ResultType == "50057"
|
||||
| where ResultDescription =~ "User account is disabled. The account has been disabled by an administrator."
|
||||
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), count(), applicationCount = dcount(AppDisplayName),
|
||||
applicationSet = makeset(AppDisplayName) by UserPrincipalName, IPAddress
|
||||
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), applicationCount = dcount(AppDisplayName),
|
||||
applicationSet = make_set(AppDisplayName), count() by UserPrincipalName, IPAddress, Type
|
||||
| where applicationCount >= threshold
|
||||
| extend timestamp = StartTimeUtc, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
|
||||
| extend timestamp = StartTime, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -13,6 +13,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 1d
|
||||
triggerOperator: gt
|
||||
|
@ -25,20 +28,27 @@ query: |
|
|||
|
||||
let s_threshold = 30;
|
||||
let l_threshold = 3;
|
||||
SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
table(tableName)
|
||||
| where OperationName =~ "Sign-in activity"
|
||||
// Error codes that we want to look at as they are related to the use of incorrect password.
|
||||
| where ResultType in ("50126", "50053" , "50055", "50056")
|
||||
| extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser
|
||||
| extend LocationString= strcat(tostring(LocationDetails["countryOrRegion"]), "/", tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]))
|
||||
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated),LocationCount=dcount(LocationString), Location = make_set(LocationString),
|
||||
| extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
|
||||
| extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser
|
||||
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
|
||||
| extend LocationString = strcat(tostring(LocationDetails.countryOrRegion), "/", tostring(LocationDetails.state), "/", tostring(LocationDetails.city))
|
||||
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), LocationCount=dcount(LocationString), Location = make_set(LocationString),
|
||||
IPAddress = make_set(IPAddress), IPAddressCount = dcount(IPAddress), AppDisplayName = make_set(AppDisplayName), ResultDescription = make_set(ResultDescription),
|
||||
Browser = make_set(Browser), OS = make_set(OS), SigninCount = count() by UserPrincipalName
|
||||
Browser = make_set(Browser), OS = make_set(OS), SigninCount = count() by UserPrincipalName, Type
|
||||
// Setting a generic threshold - Can be different for different environment
|
||||
| where SigninCount > s_threshold and LocationCount >= l_threshold
|
||||
| extend tostring(Location), tostring(IPAddress), tostring(AppDisplayName), tostring(ResultDescription), tostring(Browser), tostring(OS)
|
||||
| distinct *
|
||||
| extend timestamp = StartTime, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -7,6 +7,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 1d
|
||||
triggerOperator: gt
|
||||
|
@ -16,12 +19,16 @@ tactics:
|
|||
relevantTechniques:
|
||||
- T1110
|
||||
query: |
|
||||
SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
table(tableName)
|
||||
| where ResultType == 500121
|
||||
| where Status has "MFA Denied; user declined the authentication"
|
||||
| extend AccountCustomEntity = UserPrincipalName
|
||||
| extend IPCustomEntity = IPAddress
|
||||
| extend URLCustomEntity = ClientAppUsed
|
||||
| extend Type = Type
|
||||
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress, URLCustomEntity = ClientAppUsed
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -13,6 +13,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 7d
|
||||
triggerOperator: gt
|
||||
|
@ -29,7 +32,8 @@ query: |
|
|||
let threshold_FailedwithSingleIP = 20;
|
||||
let threshold_IPAddressCount = 2;
|
||||
let isGUID = "[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}";
|
||||
let azPortalSignins = materialize(SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
let azPortalSignins = materialize(table(tableName)
|
||||
| where TimeGenerated >= ago(lookBack)
|
||||
// Azure Portal only
|
||||
| where AppDisplayName =~ "Azure Portal")
|
||||
|
@ -70,16 +74,22 @@ query: |
|
|||
// Join Signins that had resolved names with list of unresolved that now have a resolved name
|
||||
let u_azPortalSignins = failnoSuccess | where Unresolved == false | union unresolvedNames;
|
||||
u_azPortalSignins
|
||||
| extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
|
||||
| extend Status = strcat(ResultType, ": ", ResultDescription), OS = tostring(DeviceDetail.operatingSystem), Browser = tostring(DeviceDetail.browser)
|
||||
| extend FullLocation = strcat(Location,'|', LocationDetails.state, '|', LocationDetails.city)
|
||||
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
|
||||
| extend FullLocation = strcat(Region,'|', State, '|', City)
|
||||
| summarize TimeGenerated = makelist(TimeGenerated), Status = makelist(Status), IPAddresses = makelist(IPAddress), IPAddressCount = dcount(IPAddress), FailedLogonCount = count()
|
||||
by UserPrincipalName, UserId, UserDisplayName, AppDisplayName, Browser, OS, FullLocation
|
||||
by UserPrincipalName, UserId, UserDisplayName, AppDisplayName, Browser, OS, FullLocation, Type
|
||||
| mvexpand TimeGenerated, IPAddresses, Status
|
||||
| extend TimeGenerated = todatetime(tostring(TimeGenerated)), IPAddress = tostring(IPAddresses), Status = tostring(Status)
|
||||
| project-away IPAddresses
|
||||
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserPrincipalName, UserId, UserDisplayName, Status, FailedLogonCount, IPAddress, IPAddressCount, AppDisplayName, Browser, OS, FullLocation
|
||||
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserPrincipalName, UserId, UserDisplayName, Status, FailedLogonCount, IPAddress, IPAddressCount, AppDisplayName, Browser, OS, FullLocation, Type
|
||||
| where (IPAddressCount >= threshold_IPAddressCount and FailedLogonCount >= threshold_Failed) or FailedLogonCount >= threshold_FailedwithSingleIP
|
||||
| extend timestamp = StartTime, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -7,6 +7,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1h
|
||||
queryPeriod: 1h
|
||||
triggerOperator: gt
|
||||
|
@ -16,12 +19,17 @@ tactics:
|
|||
relevantTechniques:
|
||||
- T1110
|
||||
query: |
|
||||
SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
table(tableName)
|
||||
| where AppDisplayName == "GitHub.com"
|
||||
| where ResultType == 0
|
||||
| summarize CountOfLocations = dcount(Location), Locations = make_set(Location), BurstStartTime = min(TimeGenerated), BurstEndTime = max(TimeGenerated) by UserPrincipalName
|
||||
| summarize CountOfLocations = dcount(Location), Locations = make_set(Location), BurstStartTime = min(TimeGenerated), BurstEndTime = max(TimeGenerated) by UserPrincipalName, Type
|
||||
| where CountOfLocations > 1
|
||||
| extend timestamp = BurstStartTime, AccountCustomEntity = UserPrincipalName
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -9,6 +9,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 1d
|
||||
triggerOperator: gt
|
||||
|
@ -21,27 +24,32 @@ relevantTechniques:
|
|||
- T1098
|
||||
query: |
|
||||
|
||||
SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
table(tableName)
|
||||
| where ResultType == "50057"
|
||||
| where ResultDescription == "User account is disabled. The account has been disabled by an administrator."
|
||||
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), disabledAccountLoginAttempts = count(),
|
||||
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), disabledAccountLoginAttempts = count(),
|
||||
disabledAccountsTargeted = dcount(UserPrincipalName), applicationsTargeted = dcount(AppDisplayName), disabledAccountSet = makeset(UserPrincipalName),
|
||||
applicationSet = makeset(AppDisplayName) by IPAddress
|
||||
applicationSet = makeset(AppDisplayName) by IPAddress, Type
|
||||
| order by disabledAccountLoginAttempts desc
|
||||
| join kind= leftouter (
|
||||
// Consider these IPs suspicious - and alert any related successful sign-ins
|
||||
SigninLogs
|
||||
| where ResultType == 0
|
||||
| summarize successfulAccountSigninCount = dcount(UserPrincipalName), successfulAccountSigninSet = makeset(UserPrincipalName, 15) by IPAddress
|
||||
| summarize successfulAccountSigninCount = dcount(UserPrincipalName), successfulAccountSigninSet = makeset(UserPrincipalName, 15) by IPAddress, Type
|
||||
// Assume IPs associated with sign-ins from 100+ distinct user accounts are safe
|
||||
| where successfulAccountSigninCount < 100
|
||||
) on IPAddress
|
||||
// IPs from which attempts to authenticate as disabled user accounts originated, and had a non-zero success rate for some other account
|
||||
| where successfulAccountSigninCount != 0
|
||||
| project StartTimeUtc, EndTimeUtc, IPAddress, disabledAccountLoginAttempts, disabledAccountsTargeted, disabledAccountSet, applicationSet,
|
||||
successfulAccountSigninCount, successfulAccountSigninSet
|
||||
| project StartTime, EndTime, IPAddress, disabledAccountLoginAttempts, disabledAccountsTargeted, disabledAccountSet, applicationSet,
|
||||
successfulAccountSigninCount, successfulAccountSigninSet, Type
|
||||
| order by disabledAccountLoginAttempts
|
||||
| extend timestamp = StartTimeUtc, IPCustomEntity = IPAddress
|
||||
| extend timestamp = StartTime, IPCustomEntity = IPAddress
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: IP
|
||||
fieldMappings:
|
||||
|
|
|
@ -11,6 +11,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 1d
|
||||
triggerOperator: gt
|
||||
|
@ -24,20 +27,26 @@ query: |
|
|||
let failureCountThreshold = 5;
|
||||
let successCountThreshold = 1;
|
||||
let authenticationWindow = 20m;
|
||||
SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
table(tableName)
|
||||
| extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
|
||||
| extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser
|
||||
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
|
||||
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city)
|
||||
| where AppDisplayName contains "Azure Portal"
|
||||
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
|
||||
| where AppDisplayName has "Azure Portal"
|
||||
// Split out failure versus non-failure types
|
||||
| extend FailureOrSuccess = iff(ResultType in ("0", "50125", "50140", "70043", "70044"), "Success", "Failure")
|
||||
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), IPAddress = makeset(IPAddress), makeset(OS), makeset(Browser), makeset(City),
|
||||
makeset(ResultType), FailureCount = countif(FailureOrSuccess=="Failure"), SuccessCount = countif(FailureOrSuccess=="Success")
|
||||
by bin(TimeGenerated, authenticationWindow), UserDisplayName, UserPrincipalName, AppDisplayName
|
||||
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), IPAddress = make_set(IPAddress), make_set(OS), make_set(Browser), make_set(City),
|
||||
make_set(State), make_set(Region),make_set(ResultType), FailureCount = countif(FailureOrSuccess=="Failure"), SuccessCount = countif(FailureOrSuccess=="Success")
|
||||
by bin(TimeGenerated, authenticationWindow), UserDisplayName, UserPrincipalName, AppDisplayName, Type
|
||||
| where FailureCount >= failureCountThreshold and SuccessCount >= successCountThreshold
|
||||
| mvexpand IPAddress
|
||||
| extend IPAddress = tostring(IPAddress)
|
||||
| extend timestamp = StartTimeUtc, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
|
||||
| extend timestamp = StartTime, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -13,6 +13,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 7d
|
||||
triggerOperator: gt
|
||||
|
@ -31,26 +34,28 @@ query: |
|
|||
let failureCodes = dynamic([50053, 50126, 50055]); // invalid password, account is locked - too many sign ins, expired password
|
||||
let successCodes = dynamic([0, 50055, 50057, 50155, 50105, 50133, 50005, 50076, 50079, 50173, 50158, 50072, 50074, 53003, 53000, 53001, 50129]);
|
||||
// Lookup up resolved identities from last 7 days
|
||||
let identityLookup = SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
let identityLookup = table(tableName)
|
||||
| where TimeGenerated >= ago(lookBack)
|
||||
| where not(Identity matches regex isGUID)
|
||||
| where isnotempty(UserId)
|
||||
| summarize by UserId, lu_UserDisplayName = UserDisplayName, lu_UserPrincipalName = UserPrincipalName;
|
||||
| summarize by UserId, lu_UserDisplayName = UserDisplayName, lu_UserPrincipalName = UserPrincipalName, Type;
|
||||
// collect window threshold breaches
|
||||
SigninLogs
|
||||
table(tableName)
|
||||
| where TimeGenerated > ago(timeRange)
|
||||
| where ResultType in(failureCodes)
|
||||
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), make_set(ClientAppUsed), count() by bin(TimeGenerated, authenticationWindow), IPAddress, AppDisplayName, UserPrincipalName
|
||||
| summarize FailedPrincipalCount = dcount(UserPrincipalName) by bin(TimeGenerated, authenticationWindow), IPAddress, AppDisplayName
|
||||
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), make_set(ClientAppUsed), count() by bin(TimeGenerated, authenticationWindow), IPAddress, AppDisplayName, UserPrincipalName, Type
|
||||
| summarize FailedPrincipalCount = dcount(UserPrincipalName) by bin(TimeGenerated, authenticationWindow), IPAddress, AppDisplayName, Type
|
||||
| where FailedPrincipalCount >= authenticationThreshold
|
||||
| summarize WindowThresholdBreaches = count() by IPAddress
|
||||
| summarize WindowThresholdBreaches = count() by IPAddress, Type
|
||||
| join kind= inner (
|
||||
// where we breached a threshold, join the details back on all failure data
|
||||
SigninLogs
|
||||
table(tableName)
|
||||
| where TimeGenerated > ago(timeRange)
|
||||
| where ResultType in(failureCodes)
|
||||
| extend FullLocation = strcat(Location,'|', LocationDetails.state, '|', LocationDetails.city)
|
||||
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), make_set(ClientAppUsed), make_set(FullLocation), FailureCount = count() by IPAddress, AppDisplayName, UserPrincipalName, UserDisplayName, Identity, UserId
|
||||
| extend LocationDetails = todynamic(LocationDetails)
|
||||
| extend FullLocation = strcat(LocationDetails.countryOrRegion,'|', LocationDetails.state, '|', LocationDetails.city)
|
||||
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), make_set(ClientAppUsed), make_set(FullLocation), FailureCount = count() by IPAddress, AppDisplayName, UserPrincipalName, UserDisplayName, Identity, UserId, Type
|
||||
// lookup any unresolved identities
|
||||
| extend UnresolvedUserId = iff(Identity matches regex isGUID, UserId, "")
|
||||
| join kind= leftouter (
|
||||
|
@ -58,19 +63,23 @@ query: |
|
|||
) on $left.UnresolvedUserId==$right.UserId
|
||||
| extend UserDisplayName=iff(isempty(lu_UserDisplayName), UserDisplayName, lu_UserDisplayName)
|
||||
| extend UserPrincipalName=iff(isempty(lu_UserPrincipalName), UserPrincipalName, lu_UserPrincipalName)
|
||||
| summarize StartTime = min(StartTime), EndTime = max(EndTime), make_set(UserPrincipalName), make_set(UserDisplayName), make_set(set_ClientAppUsed), make_set(set_FullLocation), make_list(FailureCount) by IPAddress, AppDisplayName
|
||||
| summarize StartTime = min(StartTime), EndTime = max(EndTime), make_set(UserPrincipalName), make_set(UserDisplayName), make_set(set_ClientAppUsed), make_set(set_FullLocation), make_list(FailureCount) by IPAddress, AppDisplayName, Type
|
||||
| extend FailedPrincipalCount = arraylength(set_UserPrincipalName)
|
||||
) on IPAddress
|
||||
| project IPAddress, StartTime, EndTime, TargetedApplication=AppDisplayName, FailedPrincipalCount, UserPrincipalNames=set_UserPrincipalName, UserDisplayNames=set_UserDisplayName, ClientAppsUsed=set_set_ClientAppUsed, Locations=set_set_FullLocation, FailureCountByPrincipal=list_FailureCount, WindowThresholdBreaches
|
||||
| project IPAddress, StartTime, EndTime, TargetedApplication=AppDisplayName, FailedPrincipalCount, UserPrincipalNames=set_UserPrincipalName, UserDisplayNames=set_UserDisplayName, ClientAppsUsed=set_set_ClientAppUsed, Locations=set_set_FullLocation, FailureCountByPrincipal=list_FailureCount, WindowThresholdBreaches, Type
|
||||
| join kind= inner (
|
||||
SigninLogs // get data on success vs. failure history for each IP
|
||||
table(tableName) // get data on success vs. failure history for each IP
|
||||
| where TimeGenerated > ago(timeRange)
|
||||
| where ResultType in(successCodes) or ResultType in(failureCodes) // success or failure types
|
||||
| summarize GlobalSuccessPrincipalCount = dcountif(UserPrincipalName, (ResultType in(successCodes))), ResultTypeSuccesses = make_set_if(ResultType, (ResultType in(successCodes))), GlobalFailPrincipalCount = dcountif(UserPrincipalName, (ResultType in(failureCodes))), ResultTypeFailures = make_set_if(ResultType, (ResultType in(failureCodes))) by IPAddress
|
||||
| summarize GlobalSuccessPrincipalCount = dcountif(UserPrincipalName, (ResultType in(successCodes))), ResultTypeSuccesses = make_set_if(ResultType, (ResultType in(successCodes))), GlobalFailPrincipalCount = dcountif(UserPrincipalName, (ResultType in(failureCodes))), ResultTypeFailures = make_set_if(ResultType, (ResultType in(failureCodes))) by IPAddress, Type
|
||||
| where GlobalFailPrincipalCount > GlobalSuccessPrincipalCount // where the number of failed principals is greater than success - eliminates FPs from IPs who authenticate successfully alot and as a side effect have alot of failures
|
||||
) on IPAddress
|
||||
| project-away IPAddress1
|
||||
| extend timestamp=StartTime, IPCustomEntity = IPAddress
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: IP
|
||||
fieldMappings:
|
||||
|
|
|
@ -8,6 +8,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 1d
|
||||
triggerOperator: gt
|
||||
|
@ -21,20 +24,25 @@ relevantTechniques:
|
|||
query: |
|
||||
|
||||
let logonDiff = 10m;
|
||||
SigninLogs
|
||||
let aadFunc = (tableName:string){
|
||||
table(tableName)
|
||||
| where ResultType == "0"
|
||||
| where AppDisplayName !in ("Office 365 Exchange Online", "Skype for Business Online")
|
||||
| project SuccessLogonTime = TimeGenerated, UserPrincipalName, SuccessIPAddress = IPAddress, AppDisplayName, SuccessIPBlock = strcat(split(IPAddress, ".")[0], ".", split(IPAddress, ".")[1])
|
||||
| project SuccessLogonTime = TimeGenerated, UserPrincipalName, SuccessIPAddress = IPAddress, AppDisplayName, SuccessIPBlock = strcat(split(IPAddress, ".")[0], ".", split(IPAddress, ".")[1]), Type
|
||||
| join kind= inner (
|
||||
SigninLogs
|
||||
table(tableName)
|
||||
| where ResultType !in ("0", "50140")
|
||||
| where ResultDescription !~ "Other"
|
||||
| where AppDisplayName !in ("Office 365 Exchange Online", "Skype for Business Online")
|
||||
| project FailedLogonTime = TimeGenerated, UserPrincipalName, FailedIPAddress = IPAddress, AppDisplayName, ResultType, ResultDescription
|
||||
| project FailedLogonTime = TimeGenerated, UserPrincipalName, FailedIPAddress = IPAddress, AppDisplayName, ResultType, ResultDescription, Type
|
||||
) on UserPrincipalName, AppDisplayName
|
||||
| where SuccessLogonTime < FailedLogonTime and FailedLogonTime - SuccessLogonTime <= logonDiff and FailedIPAddress !startswith SuccessIPBlock
|
||||
| summarize FailedLogonTime = max(FailedLogonTime), SuccessLogonTime = max(SuccessLogonTime) by UserPrincipalName, SuccessIPAddress, AppDisplayName, FailedIPAddress, ResultType, ResultDescription
|
||||
| summarize FailedLogonTime = max(FailedLogonTime), SuccessLogonTime = max(SuccessLogonTime) by UserPrincipalName, SuccessIPAddress, AppDisplayName, FailedIPAddress, ResultType, ResultDescription, Type
|
||||
| extend timestamp = SuccessLogonTime, AccountCustomEntity = UserPrincipalName, IPCustomEntity = SuccessIPAddress
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -13,6 +13,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1h
|
||||
queryPeriod: 14d
|
||||
triggerOperator: gt
|
||||
|
@ -24,27 +27,33 @@ query: |
|
|||
let dt_lookBack = 1h;
|
||||
let ioc_lookBack = 14d;
|
||||
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
|
||||
let aadFunc = (tableName:string){
|
||||
ThreatIntelligenceIndicator
|
||||
| where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now()
|
||||
| where Active == true
|
||||
//Filtering the table for Email related IOCs
|
||||
| where isnotempty(EmailSenderAddress)
|
||||
| join (
|
||||
SigninLogs | where TimeGenerated >= ago(dt_lookBack) and isnotempty(UserPrincipalName)
|
||||
table(tableName) | where TimeGenerated >= ago(dt_lookBack) and isnotempty(UserPrincipalName)
|
||||
//Normalizing the column to lower case for exact match with EmailSenderAddress column
|
||||
| extend UserPrincipalName = tolower(UserPrincipalName)
|
||||
| where UserPrincipalName matches regex emailregex
|
||||
| extend Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
|
||||
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
|
||||
| extend Region = tostring(LocationDetails["countryOrRegion"]), State = tostring(LocationDetails["state"]), City = tostring(LocationDetails["city"])
|
||||
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
|
||||
// renaming timestamp column so it is clear the log this came from SigninLogs table
|
||||
| extend SigninLogs_TimeGenerated = TimeGenerated
|
||||
| extend SigninLogs_TimeGenerated = TimeGenerated, Type = Type
|
||||
)
|
||||
on $left.EmailSenderAddress == $right.UserPrincipalName
|
||||
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
|
||||
| project LatestIndicatorTime, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore, SigninLogs_TimeGenerated,
|
||||
EmailSenderName, EmailRecipient, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, FileHashValue, FileHashType, IPAddress, UserPrincipalName, AppDisplayName,
|
||||
StatusCode, StatusDetails, NetworkIP, NetworkDestinationIP, NetworkSourceIP
|
||||
StatusCode, StatusDetails, NetworkIP, NetworkDestinationIP, NetworkSourceIP, Type
|
||||
| extend timestamp = SigninLogs_TimeGenerated, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress, URLCustomEntity = Url
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
|
@ -13,6 +13,9 @@ requiredDataConnectors:
|
|||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AADNonInteractiveUserSignInLogs
|
||||
queryFrequency: 1h
|
||||
queryPeriod: 14d
|
||||
triggerOperator: gt
|
||||
|
@ -23,6 +26,7 @@ query: |
|
|||
|
||||
let dt_lookBack = 1h;
|
||||
let ioc_lookBack = 14d;
|
||||
let aadFunc = (tableName:string){
|
||||
ThreatIntelligenceIndicator
|
||||
| where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now()
|
||||
| where Active == true
|
||||
|
@ -34,17 +38,22 @@ query: |
|
|||
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
|
||||
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
|
||||
| join (
|
||||
SigninLogs | where TimeGenerated >= ago(dt_lookBack)
|
||||
table(tableName) | where TimeGenerated >= ago(dt_lookBack)
|
||||
| extend Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
|
||||
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
|
||||
| extend Region = tostring(LocationDetails["countryOrRegion"]), State = tostring(LocationDetails["state"]), City = tostring(LocationDetails["city"])
|
||||
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
|
||||
// renaming time column so it is clear the log this came from
|
||||
| extend SigninLogs_TimeGenerated = TimeGenerated
|
||||
| extend SigninLogs_TimeGenerated = TimeGenerated, Type = Type
|
||||
)
|
||||
on $left.TI_ipEntity == $right.IPAddress
|
||||
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
|
||||
| project LatestIndicatorTime, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore, SigninLogs_TimeGenerated,
|
||||
TI_ipEntity, IPAddress, UserPrincipalName, AppDisplayName, StatusCode, StatusDetails, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress
|
||||
TI_ipEntity, IPAddress, UserPrincipalName, AppDisplayName, StatusCode, StatusDetails, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, Type
|
||||
| extend timestamp = SigninLogs_TimeGenerated, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress, URLCustomEntity = Url
|
||||
};
|
||||
let aadSignin = aadFunc("SigninLogs");
|
||||
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
|
||||
union isfuzzy=true aadSignin, aadNonInt
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
|
|
Загрузка…
Ссылка в новой задаче