Reducing charcount by improving syntax
This commit is contained in:
Родитель
6fff1aaf6d
Коммит
d7b1be5ded
|
@ -18,10 +18,10 @@ relevantTechniques:
|
|||
- T1110
|
||||
query: |
|
||||
|
||||
let lookback = 1d;
|
||||
let FailedLogonMin = 3;
|
||||
let UserNameMatchMin = 2;
|
||||
let upnFunc = (startTimeSpan:timespan, tableName:string){
|
||||
let lookback = 1d;
|
||||
let fl_Min = 3;
|
||||
let un_MatchMin = 2;
|
||||
let upnFunc = (startTimeSpan:timespan, tableName:string){
|
||||
table(tableName) | where TimeGenerated >= ago(lookback)
|
||||
| extend Operation = columnifexists("Operation", "Sign-in activity")
|
||||
| where Operation == "UserLoginFailed" or Operation == "Sign-in activity"
|
||||
|
@ -30,86 +30,85 @@ query: |
|
|||
| extend ResultValue = case(Result == "0", "Success", Result == "Success" or Result == "Succeeded", "Success", Result)
|
||||
| where ResultValue != "Success"
|
||||
| extend UserPrincipalName = columnifexists("UserPrincipalName", "tempValue")
|
||||
| extend UserPrincipalName = iff(tableName == "OfficeActivity", tolower(UserId), tolower(UserPrincipalName))
|
||||
| extend UPN = split(UserPrincipalName, "@")
|
||||
| extend UserNameOnly = tostring(UPN[0])
|
||||
| extend DomainOnly = tostring(UPN[1])
|
||||
| where UserNameOnly contains "." or UserPrincipalName contains "-" or UserPrincipalName contains "_"
|
||||
// Verify we only get accounts without other separators, it would be difficult to identify multi-level separators
|
||||
// Count of any that are not alphanumeric
|
||||
| extend charcount = countof(UserNameOnly, '[^0-9A-Za-z]', "regex")
|
||||
// Drop any that have non-alphanumeric characters still included
|
||||
| where charcount < 2
|
||||
// Creating array of name pairs that include the separators we are interested in, this can be added to if needed.
|
||||
| extend unoArray = case(
|
||||
UserNameOnly contains ".", split(UserNameOnly, "."),
|
||||
UserNameOnly contains "-", split(UserNameOnly, "-"),
|
||||
UserNameOnly contains "_", split(UserNameOnly, "_"),
|
||||
UserNameOnly
|
||||
)
|
||||
| extend First = iff(isnotempty(tostring(parsejson(unoArray)[0])), tostring(parsejson(unoArray)[0]),tostring(unoArray))
|
||||
| extend Last = tostring(parsejson(unoArray)[1])
|
||||
| extend First4char = iff(countof(substring(First, 0,4), '[0-9A-Za-z]', "regex") >= 4, substring(First, 0,4), "LessThan4")
|
||||
| extend First6char = iff(countof(substring(First, 0,6), '[0-9A-Za-z]', "regex") >= 6, substring(First, 0,6), "LessThan6")
|
||||
| extend First8char = iff(countof(substring(First, 0,8), '[0-9A-Za-z]', "regex") >= 8, substring(First, 0,8), "LessThan8")
|
||||
| extend Last4char = iff(countof(substring(Last, 0,4), '[0-9A-Za-z]', "regex") >= 4, substring(Last, 0,4), "LessThan4")
|
||||
| extend Last6char = iff(countof(substring(Last, 0,6), '[0-9A-Za-z]', "regex") >= 6, substring(Last, 0,6), "LessThan6")
|
||||
| extend Last8char = iff(countof(substring(Last, 0,8), '[0-9A-Za-z]', "regex") >= 8, substring(Last, 0,8), "LessThan8")
|
||||
| where First != Last
|
||||
| summarize UserNames = makeset(UserNameOnly), FailedLogonCount = count() by bin(TimeGenerated, 10m), First4char, First6char, First8char, Last4char, Last6char, Last8char, Type
|
||||
};
|
||||
let SigninList = upnFunc(lookback,"SigninLogs");
|
||||
let OffActList = upnFunc(lookback,"OfficeActivity");
|
||||
| extend UserPrincipalName = iff(tableName == "OfficeActivity", tolower(UserId), tolower(UserPrincipalName))
|
||||
| extend UPN = split(UserPrincipalName, "@")
|
||||
| extend UserNameOnly = tostring(UPN[0]), DomainOnly = tostring(UPN[1])
|
||||
| where UserNameOnly contains "." or UserPrincipalName contains "-" or UserPrincipalName contains "_"
|
||||
// Verify we only get accounts without other separators, it would be difficult to identify multi-level separators
|
||||
// Count of any that are not alphanumeric
|
||||
| extend charcount = countof(UserNameOnly, '[^0-9A-Za-z]', "regex")
|
||||
// Drop any that have non-alphanumeric characters still included
|
||||
| where charcount < 2
|
||||
// Creating array of name pairs that include the separators we are interested in, this can be added to if needed.
|
||||
| extend unoArray = case(
|
||||
UserNameOnly contains ".", split(UserNameOnly, "."),
|
||||
UserNameOnly contains "-", split(UserNameOnly, "-"),
|
||||
UserNameOnly contains "_", split(UserNameOnly, "_"),
|
||||
UserNameOnly)
|
||||
| extend First = iff(isnotempty(tostring(parsejson(unoArray)[0])), tostring(parsejson(unoArray)[0]),tostring(unoArray))
|
||||
| extend Last = tostring(parsejson(unoArray)[1])
|
||||
| extend First4char = iff(countof(substring(First, 0,4), '[0-9A-Za-z]', "regex") >= 4, substring(First, 0,4), "LessThan4"),
|
||||
First6char = iff(countof(substring(First, 0,6), '[0-9A-Za-z]', "regex") >= 6, substring(First, 0,6), "LessThan6"),
|
||||
First8char = iff(countof(substring(First, 0,8), '[0-9A-Za-z]', "regex") >= 8, substring(First, 0,8), "LessThan8"),
|
||||
Last4char = iff(countof(substring(Last, 0,4), '[0-9A-Za-z]', "regex") >= 4, substring(Last, 0,4), "LessThan4"),
|
||||
Last6char = iff(countof(substring(Last, 0,6), '[0-9A-Za-z]', "regex") >= 6, substring(Last, 0,6), "LessThan6"),
|
||||
Last8char = iff(countof(substring(Last, 0,8), '[0-9A-Za-z]', "regex") >= 8, substring(Last, 0,8), "LessThan8")
|
||||
| where First != Last
|
||||
| summarize UserNames = makeset(UserNameOnly),
|
||||
fl_Count = count() by bin(TimeGenerated, 10m), First4char, First6char, First8char, Last4char, Last6char, Last8char, Type
|
||||
};
|
||||
let SigninList = upnFunc(lookback,"SigninLogs");
|
||||
let OffActList = upnFunc(lookback,"OfficeActivity");
|
||||
let UserNameList = (union isfuzzy=true SigninList, OffActList);
|
||||
let Char4List = UserNameList
|
||||
| project TimeGenerated, First4char, Last4char, UserNames, FailedLogonCount, Type
|
||||
| where First4char != "LessThan4" and Last4char != "LessThan4";
|
||||
// Break out first and last so we can then join and see where a first and last match.
|
||||
let First4charList = Char4List | where isnotempty(First4char) | summarize UserNameMatchOnFirst = makeset(UserNames), FailedLogonCountForFirst = sum(FailedLogonCount) by TimeGenerated, CharSet = First4char, Type
|
||||
| project TimeGenerated, CharSet, UserNameMatchOnFirst, UserNameMatchOnFirstCount = array_length(UserNameMatchOnFirst), FailedLogonCountForFirst, Type;
|
||||
let Last4charList = Char4List | where isnotempty(Last4char) | summarize UserNameMatchOnLast = makeset(UserNames), FailedLogonCountForLast = sum(FailedLogonCount) by TimeGenerated, CharSet = Last4char, Type
|
||||
| project TimeGenerated, CharSet, UserNameMatchOnLast, UserNameMatchOnLastCount = array_length(UserNameMatchOnLast), FailedLogonCountForLast, Type;
|
||||
let char4 = First4charList | join (
|
||||
Last4charList
|
||||
) on $left.CharSet == $right.CharSet and $left.TimeGenerated == $right.TimeGenerated
|
||||
| project-away TimeGenerated1, CharSet1
|
||||
// Make sure that we get more than a single match for First or Last
|
||||
| where UserNameMatchOnFirstCount >= UserNameMatchMin or UserNameMatchOnLastCount >= UserNameMatchMin
|
||||
| where FailedLogonCountForFirst >= FailedLogonMin or FailedLogonCountForLast >= FailedLogonMin
|
||||
;
|
||||
let Char6List = UserNameList
|
||||
| project TimeGenerated, First6char, Last6char, UserNames, FailedLogonCount, Type
|
||||
| where First6char != "LessThan6" and Last6char != "LessThan6";
|
||||
// Break out first and last so we can then join and see where a first and last match.
|
||||
let First6charList = Char6List | where isnotempty(First6char) | summarize UserNameMatchOnFirst = makeset(UserNames), FailedLogonCountForFirst = sum(FailedLogonCount) by TimeGenerated, CharSet = First6char, Type
|
||||
| project TimeGenerated, CharSet, UserNameMatchOnFirst, UserNameMatchOnFirstCount = array_length(UserNameMatchOnFirst), FailedLogonCountForFirst, Type;
|
||||
let Last6charList = Char6List | where isnotempty(Last6char) | summarize UserNameMatchOnLast = makeset(UserNames), FailedLogonCountForLast = sum(FailedLogonCount) by TimeGenerated, CharSet = Last6char, Type
|
||||
| project TimeGenerated, CharSet, UserNameMatchOnLast, UserNameMatchOnLastCount = array_length(UserNameMatchOnLast), FailedLogonCountForLast, Type;
|
||||
let char6 = First6charList | join (
|
||||
Last6charList
|
||||
) on $left.CharSet == $right.CharSet and $left.TimeGenerated == $right.TimeGenerated
|
||||
let Char4List = UserNameList
|
||||
| project TimeGenerated, First4char, Last4char, UserNames, fl_Count, Type
|
||||
| where First4char != "LessThan4" and Last4char != "LessThan4";
|
||||
// Break out first and last so we can then join and see where a first and last match.
|
||||
let First4charList = Char4List | where isnotempty(First4char)
|
||||
| summarize un_MatchOnFirst = makeset(UserNames),
|
||||
fl_CountForFirst = sum(fl_Count) by TimeGenerated, CharSet = First4char, Type
|
||||
| project TimeGenerated, CharSet, un_MatchOnFirst, un_MatchOnFirstCount = array_length(un_MatchOnFirst), fl_CountForFirst, Type;
|
||||
let Last4charList = Char4List | where isnotempty(Last4char)
|
||||
| summarize un_MatchOnLast = makeset(UserNames), fl_CountForLast = sum(fl_Count) by TimeGenerated, CharSet = Last4char, Type
|
||||
| project TimeGenerated, CharSet, un_MatchOnLast, un_MatchOnLastCount = array_length(un_MatchOnLast), fl_CountForLast, Type;
|
||||
let char4 = First4charList | join Last4charList on CharSet and TimeGenerated
|
||||
| project-away TimeGenerated1, CharSet1
|
||||
// Make sure that we get more than a single match for First or Last
|
||||
| where UserNameMatchOnFirstCount >= UserNameMatchMin or UserNameMatchOnLastCount >= UserNameMatchMin
|
||||
| where FailedLogonCountForFirst >= FailedLogonMin or FailedLogonCountForLast >= FailedLogonMin
|
||||
;
|
||||
let Char8List = UserNameList
|
||||
| project TimeGenerated, First8char, Last8char, UserNames, FailedLogonCount, Type
|
||||
| where First8char != "LessThan8" and Last8char != "LessThan8";
|
||||
// Break out first and last so we can then join and see where a first and last match.
|
||||
let First8charList = Char8List | where isnotempty(First8char) | summarize UserNameMatchOnFirst = makeset(UserNames), FailedLogonCountForFirst = sum(FailedLogonCount) by TimeGenerated, CharSet = First8char, Type
|
||||
| project TimeGenerated, CharSet, UserNameMatchOnFirst, UserNameMatchOnFirstCount = array_length(UserNameMatchOnFirst), FailedLogonCountForFirst, Type;
|
||||
let Last8charList = Char8List | where isnotempty(Last8char) | summarize UserNameMatchOnLast = makeset(UserNames), FailedLogonCountForLast = sum(FailedLogonCount) by TimeGenerated, CharSet = Last8char, Type
|
||||
| project TimeGenerated, CharSet, UserNameMatchOnLast, UserNameMatchOnLastCount = array_length(UserNameMatchOnLast), FailedLogonCountForLast, Type;
|
||||
let char8 = First8charList | join (
|
||||
Last8charList
|
||||
) on $left.CharSet == $right.CharSet and $left.TimeGenerated == $right.TimeGenerated
|
||||
| project-away TimeGenerated1, CharSet1
|
||||
| where un_MatchOnFirstCount >= un_MatchMin or un_MatchOnLastCount >= un_MatchMin
|
||||
| where fl_CountForFirst >= fl_Min or fl_CountForLast >= fl_Min;
|
||||
let Char6List = UserNameList
|
||||
| project TimeGenerated, First6char, Last6char, UserNames, fl_Count, Type
|
||||
| where First6char != "LessThan6" and Last6char != "LessThan6";
|
||||
// Break out first and last so we can then join and see where a first and last match.
|
||||
let First6charList = Char6List | where isnotempty(First6char)
|
||||
| summarize un_MatchOnFirst = makeset(UserNames), fl_CountForFirst = sum(fl_Count) by TimeGenerated, CharSet = First6char, Type
|
||||
| project TimeGenerated, CharSet, un_MatchOnFirst, un_MatchOnFirstCount = array_length(un_MatchOnFirst), fl_CountForFirst, Type;
|
||||
let Last6charList = Char6List | where isnotempty(Last6char)
|
||||
| summarize un_MatchOnLast = makeset(UserNames), fl_CountForLast = sum(fl_Count) by TimeGenerated, CharSet = Last6char, Type
|
||||
| project TimeGenerated, CharSet, un_MatchOnLast, un_MatchOnLastCount = array_length(un_MatchOnLast), fl_CountForLast, Type;
|
||||
let char6 = First6charList | join Last6charList on CharSet and TimeGenerated
|
||||
| project-away TimeGenerated1, CharSet1
|
||||
// Make sure that we get more than a single match for First or Last
|
||||
| where UserNameMatchOnFirstCount >= UserNameMatchMin or UserNameMatchOnLastCount >= UserNameMatchMin
|
||||
| where FailedLogonCountForFirst >= FailedLogonMin or FailedLogonCountForLast >= FailedLogonMin
|
||||
;
|
||||
(union isfuzzy=true char4, char6, char8)
|
||||
| project Type, TimeGenerated, CharSet, UserNameMatchOnFirst, UserNameMatchOnFirstCount, FailedLogonCountForFirst, UserNameMatchOnLast, UserNameMatchOnLastCount, FailedLogonCountForLast
|
||||
| where un_MatchOnFirstCount >= un_MatchMin or un_MatchOnLastCount >= un_MatchMin
|
||||
| where fl_CountForFirst >= fl_Min or fl_CountForLast >= fl_Min;
|
||||
let Char8List = UserNameList
|
||||
| project TimeGenerated, First8char, Last8char, UserNames, fl_Count, Type
|
||||
| where First8char != "LessThan8" and Last8char != "LessThan8";
|
||||
// Break out first and last so we can then join and see where a first and last match.
|
||||
let First8charList = Char8List | where isnotempty(First8char)
|
||||
| summarize un_MatchOnFirst = makeset(UserNames), fl_CountForFirst = sum(fl_Count) by TimeGenerated, CharSet = First8char, Type
|
||||
| project TimeGenerated, CharSet, un_MatchOnFirst, un_MatchOnFirstCount = array_length(un_MatchOnFirst), fl_CountForFirst, Type;
|
||||
let Last8charList = Char8List | where isnotempty(Last8char)
|
||||
| summarize un_MatchOnLast = makeset(UserNames), fl_CountForLast = sum(fl_Count) by TimeGenerated, CharSet = Last8char, Type
|
||||
| project TimeGenerated, CharSet, un_MatchOnLast, un_MatchOnLastCount = array_length(un_MatchOnLast), fl_CountForLast, Type;
|
||||
let char8 = First8charList | join Last8charList on CharSet and TimeGenerated
|
||||
| project-away TimeGenerated1, CharSet1
|
||||
// Make sure that we get more than a single match for First or Last
|
||||
| where un_MatchOnFirstCount >= un_MatchMin or un_MatchOnLastCount >= un_MatchMin
|
||||
| where fl_CountForFirst >= fl_Min or fl_CountForLast >= fl_Min;
|
||||
(union isfuzzy=true char4, char6, char8)
|
||||
| project Type, TimeGenerated, CharSet, UserNameMatchOnFirst = un_MatchOnFirst, UserNameMatchOnFirstCount = un_MatchOnFirstCount,
|
||||
FailedLogonCountForFirst = fl_CountForFirst, UserNameMatchOnLast = un_MatchOnLast, UserNameMatchOnLastCount = un_MatchOnLastCount,
|
||||
FailedLogonCountForLast = fl_CountForLast
|
||||
| sort by UserNameMatchOnFirstCount desc, UserNameMatchOnLastCount desc
|
||||
| extend timestamp = TimeGenerated
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
id: 431cccd3-2dff-46ee-b34b-61933e45f556
|
||||
name: Tracking Privileged Account Rare Activity
|
||||
description: |
|
||||
'This query will determine rare activity by a high-value account carried out on a system or service.
|
||||
'This query will determine rare activity by a high-value account carried out on a system or service.
|
||||
High Value accounts are determined by Group Membership to High Value groups via events listed below.
|
||||
Rare here means an activity type seen in the last day which has not been seen in the previous 7 days.
|
||||
If any account with such rare activity is found, the query will attempt to retrieve related activity
|
||||
from that account on that same day and summarize the information.'
|
||||
from that account on that same day and summarize the information.
|
||||
4728 - A member was added to a security-enabled global group
|
||||
4732 - A member was added to a security-enabled local group
|
||||
4756 - A member was added to a security-enabled universal group'
|
||||
requiredDataConnectors:
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
|
@ -28,153 +32,144 @@ relevantTechniques:
|
|||
- T1087
|
||||
query: |
|
||||
|
||||
let WellKnownLocalSID = "S-1-5-32-5[0-9][0-9]";
|
||||
let WellKnownGroupSID = "S-1-5-21-[0-9]*-[0-9]*-[0-9]*-5[0-9][0-9]|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1102|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1103";
|
||||
let LocalSID = "S-1-5-32-5[0-9][0-9]";
|
||||
let GroupSID = "S-1-5-21-[0-9]*-[0-9]*-[0-9]*-5[0-9][0-9]|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1102|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1103";
|
||||
let timeframe = 8d;
|
||||
let InferredPrivilegedAccounts = SecurityEvent
|
||||
| where TimeGenerated > ago(timeframe)
|
||||
| where EventID in ("4728", "4732", "4756")
|
||||
| where AccountType == "User" and MemberName == "-"
|
||||
let p_Accounts = SecurityEvent
|
||||
| where TimeGenerated > ago(timeframe)
|
||||
| where EventID in ("4728", "4732", "4756") and AccountType == "User" and MemberName == "-"
|
||||
// Exclude Remote Desktop Users group: S-1-5-32-555 and IIS Users group S-1-5-32-568
|
||||
| where TargetSid !in ("S-1-5-32-555", "S-1-5-32-568")
|
||||
// 4728 - A member was added to a security-enabled global group
|
||||
// 4732 - A member was added to a security-enabled local group
|
||||
// 4756 - A member was added to a security-enabled universal group
|
||||
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
|
||||
| where TargetSid matches regex LocalSID or TargetSid matches regex GroupSID
|
||||
| summarize by DomainSlashAccount = tolower(SubjectAccount), NtDomain = SubjectDomainName,
|
||||
AccountAtDomain = tolower(strcat(SubjectUserName,"@",SubjectDomainName)), AccountNameOnly = tolower(SubjectUserName);
|
||||
AccountAtDomain = tolower(strcat(SubjectUserName,"@",SubjectDomainName)), AccountName = tolower(SubjectUserName);
|
||||
// Build custom high value account list
|
||||
let CustomAccountList = datatable(Account:string, NtDomain:string, Domain:string)[
|
||||
"john", "Contoso", "contoso.com", "greg", "Contoso", "contoso.com", "larry", "Domain", "contoso.com"];
|
||||
let AccountFormatCustomList = CustomAccountList
|
||||
| extend AccountAtDomain = tolower(strcat(Account,"@",Domain)), AccountNameOnly = tolower(Account),
|
||||
let cust_Accounts = datatable(Account:string, NtDomain:string, Domain:string)[
|
||||
"john", "Contoso", "contoso.com", "greg", "Contoso", "contoso.com", "larry", "Domain", "contoso.com"];
|
||||
let c_Accounts = cust_Accounts
|
||||
| extend AccountAtDomain = tolower(strcat(Account,"@",Domain)), AccountName = tolower(Account),
|
||||
DomainSlashAccount = tolower(strcat(NtDomain,"\\",Account));
|
||||
let AccountFormat = InferredPrivilegedAccounts | union
|
||||
AccountFormatCustomList;
|
||||
// Normalize activity from diverse sources into common schema
|
||||
// Specify this in a function to support retrieving activity from different timeframes
|
||||
let activity = view (ActivityStartTime:datetime, ActivityEndTime:datetime) {
|
||||
let AccountFormat = p_Accounts | union c_Accounts | project AccountName, AccountAtDomain, DomainSlashAccount;
|
||||
// Normalize activity from diverse sources into common schema using a function
|
||||
let activity = view (a_StartTime:datetime, a_EndTime:datetime) {
|
||||
(union isfuzzy=true
|
||||
(AccountFormat | project AccountName=AccountNameOnly, AccountAtDomain, DomainSlashAccount
|
||||
| join kind=inner
|
||||
(AccountFormat | join kind=inner
|
||||
(AWSCloudTrail
|
||||
| where TimeGenerated <= ActivityEndTime and TimeGenerated >= ActivityStartTime
|
||||
| extend DataType = "AWSCloudTrail", ClientIP = "-", AccountName = tolower(UserIdentityUserName), WinSecEventDomain = "-"
|
||||
| where TimeGenerated >= a_StartTime and TimeGenerated <= a_EndTime
|
||||
| extend ClientIP = "-", AccountName = tolower(UserIdentityUserName), WinSecEventDomain = "-"
|
||||
| project-rename EventType = EventName, ServiceOrSystem = EventSource)
|
||||
on AccountName),
|
||||
(AccountFormat | project AccountNameOnly, AccountName = AccountAtDomain, DomainSlashAccount
|
||||
| join kind=inner
|
||||
on AccountName),
|
||||
(AccountFormat | join kind=inner
|
||||
(SigninLogs
|
||||
| where TimeGenerated <= ActivityEndTime and TimeGenerated >= ActivityStartTime
|
||||
| extend DataType = "SigninLogs", AccountName = tolower(UserPrincipalName), WinSecEventDomain = "-"
|
||||
| project-rename EventType = ResultType, ServiceOrSystem = AppDisplayName, ClientIP = IPAddress)
|
||||
on AccountName),
|
||||
(AccountFormat | project AccountNameOnly, AccountName = AccountAtDomain, DomainSlashAccount
|
||||
| join kind=inner
|
||||
(OfficeActivity
|
||||
| where TimeGenerated <= ActivityEndTime and TimeGenerated >= ActivityStartTime
|
||||
| extend DataType = "OfficeActivity", AccountName = tolower(UserId), WinSecEventDomain = "-"
|
||||
| project-rename EventType = Operation, ServiceOrSystem = OfficeWorkload)
|
||||
on AccountName),
|
||||
(AccountFormat | project AccountNameOnly, AccountAtDomain, AccountName=DomainSlashAccount
|
||||
| join kind=inner
|
||||
| where TimeGenerated >= a_StartTime and TimeGenerated <= a_EndTime
|
||||
| extend AccountName = tolower(split(UserPrincipalName, "@")[0]), WinSecEventDomain = "-"
|
||||
| project-rename EventType = strcat(OperationName, "-", ResultType, "-", ResultDescription), ServiceOrSystem = AppDisplayName, ClientIP = IPAddress)
|
||||
on AccountName),
|
||||
(AccountFormat | join kind=inner
|
||||
(OfficeActivity
|
||||
| where TimeGenerated >= a_StartTime and TimeGenerated <= a_EndTime
|
||||
| extend AccountName = tolower(split(UserId, "@")[0]), WinSecEventDomain = "-"
|
||||
| project-rename EventType = strcat(Operation, "-", ResultStatus), ServiceOrSystem = OfficeWorkload)
|
||||
on AccountName),
|
||||
(AccountFormat | join kind=inner
|
||||
(SecurityEvent
|
||||
| where TimeGenerated <= ActivityEndTime and TimeGenerated >= ActivityStartTime
|
||||
| where TimeGenerated >= a_StartTime and TimeGenerated <= a_EndTime
|
||||
| where EventID in (4624, 4625)
|
||||
| extend DataType = "SecurityEvent", ClientIP = "-"
|
||||
| extend AccountName = tolower(tostring(split(Account,"\\")[1])), Domain = tolower(tostring(split(Account,"\\")[0]))
|
||||
| extend Account = tolower(Account)
|
||||
| extend ClientIP = "-"
|
||||
| extend AccountName = tolower(split(Account,"\\")[1]), Domain = tolower(split(Account,"\\")[0])
|
||||
| project-rename EventType = Activity, ServiceOrSystem = Computer, WinSecEventDomain = Domain)
|
||||
on AccountName),
|
||||
(AccountFormat | project AccountNameOnly, AccountName = AccountAtDomain, DomainSlashAccount
|
||||
| join kind=inner
|
||||
on AccountName),
|
||||
(AccountFormat | join kind=inner
|
||||
(W3CIISLog
|
||||
| where TimeGenerated <= ActivityEndTime and TimeGenerated >= ActivityStartTime
|
||||
| extend DataType = "W3CIISLog", AccountName = tolower(csUserName), WinSecEventDomain = "-"
|
||||
| where TimeGenerated >= a_StartTime and TimeGenerated <= a_EndTime
|
||||
| where csUserName != "-" and isnotempty(csUserName)
|
||||
| extend AccountName = tolower(csUserName), WinSecEventDomain = "-"
|
||||
| project-rename EventType = csMethod, ServiceOrSystem = sSiteName, ClientIP = cIP)
|
||||
on AccountName),
|
||||
(AccountFormat | project AccountName=AccountNameOnly, AccountAtDomain, DomainSlashAccount
|
||||
| join kind=inner
|
||||
on AccountName),
|
||||
(AccountFormat | join kind=inner
|
||||
(W3CIISLog
|
||||
| where TimeGenerated <= ActivityEndTime and TimeGenerated >= ActivityStartTime
|
||||
| extend DataType = "W3CIISLog", AccountName = tolower(csUserName), WinSecEventDomain = "-"
|
||||
| where TimeGenerated >= a_StartTime and TimeGenerated <= a_EndTime
|
||||
| where csUserName != "-" and isnotempty(csUserName)
|
||||
| extend AccountAtDomain = tolower(csUserName), WinSecEventDomain = "-"
|
||||
| project-rename EventType = csMethod, ServiceOrSystem = sSiteName, ClientIP = cIP)
|
||||
on AccountName)
|
||||
);
|
||||
on AccountAtDomain));
|
||||
};
|
||||
// Find new activity today versus prior week
|
||||
// Rare activity today versus prior week
|
||||
let LastDay = startofday(ago(1d));
|
||||
let PrevDay = endofday(ago(2d));
|
||||
let Prev7Day = startofday(ago(8d));
|
||||
let ActivityLastDay = activity(LastDay, now())
|
||||
| summarize RareActivityStartTimeUtc = min(TimeGenerated),RareActivityEndTimeUtc = max(TimeGenerated), RareActivityCount = count()
|
||||
by DataType, AccountName, EventType, ClientIP, ServiceOrSystem, WinSecEventDomain;
|
||||
let Activity7day = activity(Prev7Day, PrevDay)
|
||||
| summarize HistoricalActivityCount = count() by DataType, AccountName, EventType, ClientIP, ServiceOrSystem, WinSecEventDomain;
|
||||
let NewActivityToday = ActivityLastDay | join kind=leftanti (Activity7day)
|
||||
on DataType, AccountName, ServiceOrSystem
|
||||
let ra_LastDay = activity(LastDay, now())
|
||||
| summarize ra_StartTime = min(TimeGenerated), ra_EndTime = max(TimeGenerated),
|
||||
ra_Count = count() by Type, AccountName, EventType, ClientIP, ServiceOrSystem, WinSecEventDomain;
|
||||
let a_7day = activity(Prev7Day, PrevDay)
|
||||
| summarize ha_Count = count() by Type, AccountName, EventType, ClientIP, ServiceOrSystem, WinSecEventDomain;
|
||||
let ra_Today = ra_LastDay | join kind=leftanti (a_7day) on Type, AccountName, ServiceOrSystem
|
||||
| extend RareServiceOrSystem = ServiceOrSystem;
|
||||
// Retrieve related activity as context
|
||||
let RelatedActivity =
|
||||
let a_Related =
|
||||
(union isfuzzy=true
|
||||
(
|
||||
// Make sure we at least publish the unusual activity we identified above - even if no related context activity is found in the subsequent union
|
||||
NewActivityToday
|
||||
),
|
||||
(// Make sure we at least publish the unusual activity we identified above - even if no related context activity is found in the subsequent union
|
||||
ra_Today),
|
||||
// Remaining elements of the union look for related activity
|
||||
(NewActivityToday
|
||||
| join kind=inner (
|
||||
OfficeActivity
|
||||
(ra_Today | join kind=inner
|
||||
(OfficeActivity
|
||||
| where TimeGenerated > LastDay
|
||||
| summarize RelatedActivityStartTimeUtc = min(TimeGenerated), RelatedActivityEndTimeUtc = max(TimeGenerated), RelatedActivityServiceOrSystemCount = dcount(OfficeWorkload), RelatedActivityServiceOrSystemSet = makeset(OfficeWorkload), RelatedActivityClientIPSet = makeset(ClientIP), RelatedActivityCount = count() by AccountName = tolower(UserId), RelatedActivityEventType = Operation
|
||||
| summarize rel_StartTime = min(TimeGenerated), rel_EndTime = max(TimeGenerated), rel_ServiceOrSystemCount = dcount(OfficeWorkload),
|
||||
rel_ServiceOrSystemSet = makeset(OfficeWorkload), rel_ClientIPSet = makeset(ClientIP),
|
||||
rel_Count = count() by AccountName = tolower(UserId), rel_EventType = Operation, Type
|
||||
) on AccountName),
|
||||
(NewActivityToday
|
||||
| join kind=innerunique (
|
||||
SecurityEvent | where TimeGenerated > LastDay
|
||||
(ra_Today | join kind=inner
|
||||
(SecurityEvent | where TimeGenerated > LastDay
|
||||
| where EventID in (4624, 4625)
|
||||
| where AccountType == "User"
|
||||
| summarize RelatedActivityStartTimeUtc = min(TimeGenerated), RelatedActivityEndTimeUtc = max(TimeGenerated), RelatedActivityServiceOrSystemCount = dcount(Computer), RelatedActivityServiceOrSystemSet = makeset(Computer), RelatedActivityClientIPSet = makeset("-"), RelatedActivityCount = count() by DomainSlashAccount = tolower(Account), RelatedActivityEventType = Activity
|
||||
| summarize rel_StartTime = min(TimeGenerated), rel_EndTime = max(TimeGenerated), rel_ServiceOrSystemCount = dcount(Computer),
|
||||
rel_ServiceOrSystemSet = makeset(Computer), rel_ClientIPSet = makeset("-"),
|
||||
rel_Count = count() by DomainSlashAccount = tolower(Account), rel_EventType = Activity, Type
|
||||
) on DomainSlashAccount),
|
||||
(NewActivityToday
|
||||
| join kind=inner (
|
||||
SecurityEvent | where TimeGenerated > LastDay
|
||||
(ra_Today | join kind=inner
|
||||
(SecurityEvent | where TimeGenerated > LastDay
|
||||
// 7045: A service was installed in the system
|
||||
| where EventID == 7045
|
||||
| summarize RelatedActivityStartTimeUtc = min(TimeGenerated), RelatedActivityEndTimeUtc = max(TimeGenerated), RelatedActivityServiceOrSystemCount = dcount(Computer), RelatedActivityServiceOrSystemSet = makeset(Computer), RelatedActivityClientIPSet = makeset("-"), RelatedActivityCount = count() by DomainSlashAccount = tolower(Account), RelatedActivityEventType = Activity
|
||||
| summarize rel_StartTime = min(TimeGenerated), rel_EndTime = max(TimeGenerated), rel_ServiceOrSystemCount = dcount(Computer),
|
||||
rel_ServiceOrSystemSet = makeset(Computer), rel_ClientIPSet = makeset("-"),
|
||||
rel_Count = count() by DomainSlashAccount = tolower(Account), rel_EventType = Activity, Type
|
||||
) on DomainSlashAccount),
|
||||
(NewActivityToday
|
||||
| join kind=inner (
|
||||
SecurityEvent | where TimeGenerated > LastDay
|
||||
(ra_Today | join kind=inner
|
||||
(SecurityEvent | where TimeGenerated > LastDay
|
||||
// 4720: Account created, 4726: Account deleted
|
||||
| where EventID in (4720,4726)
|
||||
| summarize RelatedActivityStartTimeUtc = min(TimeGenerated), RelatedActivityEndTimeUtc = max(TimeGenerated), RelatedActivityServiceOrSystemCount = dcount(UserPrincipalName), RelatedActivityServiceOrSystemSet = makeset(UserPrincipalName), RelatedActivityClientIPSet = makeset("-"), RelatedActivityCount = count() by DomainSlashAccount = tolower(Account), RelatedActivityEventType = Activity
|
||||
| summarize rel_StartTime = min(TimeGenerated), rel_EndTime = max(TimeGenerated), rel_ServiceOrSystemCount = dcount(UserPrincipalName),
|
||||
rel_ServiceOrSystemSet = makeset(UserPrincipalName), rel_ClientIPSet = makeset("-"),
|
||||
rel_Count = count() by DomainSlashAccount = tolower(Account), rel_EventType = Activity, Type
|
||||
) on DomainSlashAccount),
|
||||
(NewActivityToday
|
||||
| join kind=inner (
|
||||
SigninLogs | where TimeGenerated > LastDay
|
||||
(ra_Today | join kind=inner
|
||||
(SigninLogs | where TimeGenerated > LastDay
|
||||
| extend RemoteHost = tolower(tostring(parsejson(DeviceDetail.["displayName"])))
|
||||
| extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser
|
||||
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
|
||||
| extend State = tostring(LocationDetails.state)
|
||||
| summarize RelatedActivityStartTimeUtc = min(TimeGenerated), RelatedActivityEndTimeUtc = max(TimeGenerated), RelatedActivityRemoteHostSet = makeset(RemoteHost), RelatedActivityServiceOrSystemSet = makeset(AppDisplayName), RelatedActivityServiceOrSystemCount = dcount(AppDisplayName), RelatedActivityClientIPSet = makeset(IPAddress), RelatedActivityStateSet = makeset(State), RelatedActivityCount = count() by AccountAtDomain = tolower(UserPrincipalName), RelatedActivityEventType = iff(isnotempty(ResultDescription), ResultDescription, StatusDetails)
|
||||
| extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser, StatusCode = tostring(Status.errorCode),
|
||||
StatusDetails = tostring(Status.additionalDetails), State = tostring(LocationDetails.state)
|
||||
| summarize rel_StartTime = min(TimeGenerated), rel_EndTime = max(TimeGenerated), a_RelatedRemoteHostSet = makeset(RemoteHost),
|
||||
rel_ServiceOrSystemSet = makeset(AppDisplayName), rel_ServiceOrSystemCount = dcount(AppDisplayName), rel_ClientIPSet = makeset(IPAddress),
|
||||
rel_StateSet = makeset(State),
|
||||
rel_Count = count() by AccountAtDomain = tolower(UserPrincipalName), rel_EventType = iff(isnotempty(ResultDescription), ResultDescription, StatusDetails), Type
|
||||
) on AccountAtDomain),
|
||||
(NewActivityToday
|
||||
| join kind=inner (
|
||||
AWSCloudTrail | where TimeGenerated > LastDay
|
||||
| summarize RelatedActivityStartTimeUtc = min(TimeGenerated),RelatedActivityEndTimeUtc = max(TimeGenerated), RelatedActivityServiceOrSystemSet = makeset(EventSource), RelatedActivityServiceOrSystemCount = dcount(EventSource), RelatedActivityClientIPSet = makeset("-"), RelatedActivityCount= count() by AccountNameOnly = tolower(UserIdentityUserName), RelatedActivityEventType = EventName
|
||||
) on AccountNameOnly),
|
||||
(NewActivityToday
|
||||
| join kind=inner (
|
||||
SecurityAlert | where TimeGenerated > LastDay
|
||||
| extend ExtProps=parsejson(ExtendedProperties)
|
||||
(ra_Today | join kind=inner
|
||||
(AWSCloudTrail | where TimeGenerated > LastDay
|
||||
| summarize rel_StartTime = min(TimeGenerated),rel_EndTime = max(TimeGenerated), rel_ServiceOrSystemSet = makeset(EventSource),
|
||||
rel_ServiceOrSystemCount = dcount(EventSource), rel_ClientIPSet = makeset("-"),
|
||||
rel_Count= count() by AccountName = tolower(UserIdentityUserName), rel_EventType = EventName, Type
|
||||
) on AccountName),
|
||||
(ra_Today | join kind=inner
|
||||
(SecurityAlert | where TimeGenerated > LastDay
|
||||
| extend ExtProps=parsejson(ExtendedProperties)
|
||||
| extend AccountName = tostring(ExtProps.["user name"])
|
||||
| summarize RelatedActivityStartTimeUtc = min(TimeGenerated), RelatedActivityEndTimeUtc = max(TimeGenerated), RelatedActivityServiceOrSystemCount = dcount(AlertType), RelatedActivityServiceOrSystemSet = makeset(AlertType), RelatedActivityCount = count() by DomainSlashAccount = tolower(AccountName), RelatedActivityEventType = ProductName
|
||||
| summarize rel_StartTime = min(TimeGenerated), rel_EndTime = max(TimeGenerated), rel_ServiceOrSystemCount = dcount(AlertType),
|
||||
rel_ServiceOrSystemSet = makeset(AlertType),
|
||||
rel_Count = count() by DomainSlashAccount = tolower(AccountName), rel_EventType = ProductName, Type
|
||||
) on DomainSlashAccount)
|
||||
);
|
||||
RelatedActivity
|
||||
| project RareActivityStartTimeUtc, RareActivityEndTimeUtc, DataType, RareActivityCount, AccountName, WinSecEventDomain,
|
||||
EventType, RareServiceOrSystem, RelatedActivityStartTimeUtc, RelatedActivityEndTimeUtc, RelatedActivityEventType,
|
||||
RelatedActivityClientIPSet, RelatedActivityServiceOrSystemCount, RelatedActivityServiceOrSystemSet, RelatedActivityCount
|
||||
| extend timestamp = RareActivityStartTimeUtc, AccountCustomEntity = AccountName
|
||||
a_Related
|
||||
| project Type, RareActivtyStartTimeUtc = ra_StartTime, RareActivityEndTimeUtc = ra_EndTime, RareActivityCount = ra_Count,
|
||||
AccountName, WinSecEventDomain, EventType, RareServiceOrSystem, RelatedActivityStartTimeUtc = rel_StartTime,
|
||||
RelatedActivityEndTimeUtc = rel_EndTime, RelatedActivityEventType = rel_EventType, RelatedActivityClientIPSet = rel_ClientIPSet,
|
||||
RelatedActivityServiceOrSystemCount = rel_ServiceOrSystemCount, RelatedActivityServiceOrSystemSet = rel_ServiceOrSystemSet, RelatedActivityCount = rel_Count
|
||||
| extend timestamp = RareActivtyStartTimeUtc, AccountCustomEntity = AccountName
|
Загрузка…
Ссылка в новой задаче