Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update SigninBruteForce-AzurePortal.yaml #9176

Merged
merged 3 commits into from
Oct 10, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
id: 28b42356-45af-40a6-a0b4-a554cdfd5d8a
name: Brute force attack against Azure Portal
description: |
'Identifies evidence of brute force activity against Azure Portal by highlighting multiple authentication failures and by a successful authentication within a given time window.
Default Failure count is 10 and default Time Window is 20 minutes.
References: https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes.'
description: >
Detects Azure Portal brute force attacks by monitoring for multiple authentication failures and a successful login within a 20-minute window. Default settings: 10 failures, 25 deviations.
praveenthepro marked this conversation as resolved.
Show resolved Hide resolved
Ref: https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes.
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
Expand All @@ -13,7 +12,7 @@ requiredDataConnectors:
dataTypes:
- AADNonInteractiveUserSignInLogs
queryFrequency: 1d
queryPeriod: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
status: Available
Expand All @@ -22,48 +21,64 @@ tactics:
relevantTechniques:
- T1110
query: |
let timeRange = 24h;
let failureCountThreshold = 10;
let authenticationWindow = 20m;
let aadFunc = (tableName:string){
table(tableName)
| where AppDisplayName has "Azure Portal"
| extend
DeviceDetail = todynamic(DeviceDetail),
//Status = todynamic(Status),
LocationDetails = todynamic(LocationDetails)
| extend
OS = tostring(DeviceDetail.operatingSystem),
Browser = tostring(DeviceDetail.browser),
//StatusCode = tostring(Status.errorCode),
//StatusDetails = tostring(Status.additionalDetails),
State = tostring(LocationDetails.state),
City = tostring(LocationDetails.city),
Region = tostring(LocationDetails.countryOrRegion)
// Split out failure versus non-failure types
| extend FailureOrSuccess = iff(ResultType in ("0", "50125", "50140", "70043", "70044"), "Success", "Failure")
// sort for sessionizing - by UserPrincipalName and time of the authentication outcome
| sort by UserPrincipalName asc, TimeGenerated asc
// sessionize into failure groupings until either the account changes or there is a success
| extend SessionStartedUtc = row_window_session(TimeGenerated, timeRange, authenticationWindow, UserPrincipalName != prev(UserPrincipalName) or prev(FailureOrSuccess) == "Success")
// bin outcomes based on authenticationWindow
| summarize FailureOrSuccessCount = count() by FailureOrSuccess, UserId, UserDisplayName, AppDisplayName, IPAddress, Browser, OS, State, City, Region, Type, CorrelationId, bin(TimeGenerated, authenticationWindow), ResultType, UserPrincipalName,SessionStartedUtc
// count the failures in each session
| summarize FailureCountBeforeSuccess=sumif(FailureOrSuccessCount, FailureOrSuccess == "Failure"), StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), makelist(FailureOrSuccess), IPAddress = make_set(IPAddress,15), make_set(Browser,15), make_set(City,15), make_set(State,15), make_set(Region,15), make_set(ResultType,15) by SessionStartedUtc, UserPrincipalName, CorrelationId, AppDisplayName, UserId, Type
// the session must not start with a success, and must end with one
| where array_index_of(list_FailureOrSuccess, "Success") != 0
| where array_index_of(list_FailureOrSuccess, "Success") == array_length(list_FailureOrSuccess) - 1
| project-away SessionStartedUtc, list_FailureOrSuccess
// where the number of failures before the success is above the threshold
| where FailureCountBeforeSuccess >= failureCountThreshold
// expand out ip for entity assignment
| mv-expand IPAddress
| extend IPAddress = tostring(IPAddress)
| extend timestamp = StartTime
};
// Set threshold value for deviation
let threshold = 25;
// Set the time range for the query
let timeRange = 24h;
// Set the authentication window duration
let authenticationWindow = 20m;
// Define a reusable function 'aadFunc' that takes a table name as input
let aadFunc = (tableName: string) {
// Query the specified table
table(tableName)
// Filter data within the last 24 hours
| where TimeGenerated > ago(1d)
// Filter records related to "Azure Portal" applications
| where AppDisplayName has "Azure Portal"
// Extract and transform some fields
| extend
DeviceDetail = todynamic(DeviceDetail),
LocationDetails = todynamic(LocationDetails)
| extend
OS = tostring(DeviceDetail.operatingSystem),
Browser = tostring(DeviceDetail.browser),
State = tostring(LocationDetails.state),
City = tostring(LocationDetails.city),
Region = tostring(LocationDetails.countryOrRegion)
// Categorize records as Success or Failure based on ResultType
| extend FailureOrSuccess = iff(ResultType in ("0", "50125", "50140", "70043", "70044"), "Success", "Failure")
// Sort and identify sessions
| sort by UserPrincipalName asc, TimeGenerated asc
| extend SessionStartedUtc = row_window_session(TimeGenerated, timeRange, authenticationWindow, UserPrincipalName != prev(UserPrincipalName) or prev(FailureOrSuccess) == "Success")
// Summarize data
| summarize FailureOrSuccessCount = count() by FailureOrSuccess, UserId, UserDisplayName, AppDisplayName, IPAddress, Browser, OS, State, City, Region, Type, CorrelationId, bin(TimeGenerated, authenticationWindow), ResultType, UserPrincipalName, SessionStartedUtc
| summarize FailureCountBeforeSuccess = sumif(FailureOrSuccessCount, FailureOrSuccess == "Failure"), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), makelist(FailureOrSuccess), IPAddress = make_set(IPAddress, 15), make_set(Browser, 15), make_set(City, 15), make_set(State, 15), make_set(Region, 15), make_set(ResultType, 15) by SessionStartedUtc, UserPrincipalName, CorrelationId, AppDisplayName, UserId, Type
// Filter records where "Success" occurs in the middle of a session
| where array_index_of(list_FailureOrSuccess, "Success") != 0
| where array_index_of(list_FailureOrSuccess, "Success") == array_length(list_FailureOrSuccess) - 1
// Remove unnecessary columns from the output
| project-away SessionStartedUtc, list_FailureOrSuccess
// Join with another table and calculate deviation
| join kind=inner (
table(tableName)
| where TimeGenerated > ago(7d)
| where AppDisplayName has "Azure Portal"
| extend FailureOrSuccess = iff(ResultType in ("0", "50125", "50140", "70043", "70044"), "Success", "Failure")
| summarize avgFailures = avg(todouble(FailureOrSuccess == "Failure")) by UserPrincipalName
) on UserPrincipalName
| extend Deviation = abs(FailureCountBeforeSuccess - avgFailures) / avgFailures
// Filter records based on deviation and failure count criteria
| where Deviation > threshold and FailureCountBeforeSuccess >= 10
// Expand the IPAddress array
| mv-expand IPAddress
| extend IPAddress = tostring(IPAddress)
| extend timestamp = StartTime
};
// Call 'aadFunc' with different table names and union the results
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
union isfuzzy=true aadSignin, aadNonInt
// Additional transformation: Split UserPrincipalName
| extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
Expand All @@ -72,9 +87,11 @@ entityMappings:
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- identifier: AadUserId
columnName: UserId
- entityType: IP
fieldMappings:
praveenthepro marked this conversation as resolved.
Show resolved Hide resolved
- identifier: Address
columnName: IPAddress
version: 2.1.1
version: 2.1.2
kind: Scheduled