Merge pull request #1069 from Azure/insights_folderRename

Insights folder rename
This commit is contained in:
Shain 2020-09-14 11:47:16 -07:00 коммит произвёл GitHub
Родитель 5fb2a432b1 e56bef0bfd
Коммит db24d633b0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
50 изменённых файлов: 2795 добавлений и 42 удалений

Просмотреть файл

@ -1,4 +1,4 @@
Id: guid
Id: guid
DisplayName: string
Description: string
InputEntityType: string

Просмотреть файл

@ -11,7 +11,7 @@ QueryPeriodBefore: 1d
QueryPeriodAfter: 1d
DataSources:
- SecurityEvent
Tactics:
Tactics:
- Persistence
- Discovery
- LateralMovement
@ -37,14 +37,14 @@ query: |
| extend p_Account_NTDomain = case(
v_Account_NTDomain has '\\', tostring(split(v_Account_UPNSuffix, '\\')[0]),
v_Account_NTDomain
)
)
// parse Account sections
| extend Account_UPNSuffix = iff(Account has '@', tostring(split(Account,'@')[1]),'')
| extend Account_NTDomain = iff(Account has '\\', tostring(split(Account,'\\')[0]),'')
| extend Account_Name = extract(@'^([^\\]*\\)?([^@]+)@?',2,Account)
// filter by account: Name has to match, NTDomain and UPNSuffix should not be different
| where ( (isnotempty(Account_Name) and Account_Name==p_Account_Name)
and
| where ( (isnotempty(Account_Name) and Account_Name==p_Account_Name)
and
iff(isnotempty(p_Account_NTDomain) and isnotempty(Account_NTDomain) ,p_Account_NTDomain==Account_NTDomain,true )
and
iff(isnotempty(p_Account_UPNSuffix) and isnotempty(Account_UPNSuffix) ,p_Account_UPNSuffix==Account_UPNSuffix,true )
@ -53,8 +53,8 @@ query: |
by Computer, Account
| top 10 by Host_Aux_FailedLoginsCount
| parse Computer with Host_NTDomain '\\' *
| extend Host_HostName = tostring(split(Computer,'.')[0]),
| extend Host_HostName = tostring(split(Computer,'.')[0]),
Host_DnsDomain = strcat_array(array_slice(split(Computer,'.'),1,256),'.')
| project-away Computer, Account
| project-away Computer, Account
};
MostFailedLogins('<Name>','<NTDomain>','<UPNSuffix>')

Просмотреть файл

@ -28,7 +28,7 @@ query: |
| where SyslogMessage has v_Account_Name
| extend info = pack('HostName', HostName, 'HostIP', HostIP)
| summarize Process_Aux_StartTime=min(EventTime), Process_Aux_EndTime=max(EventTime), count(), Process_Aux_info = makeset(info) by Computer, ProcessName, ProcessID
| top 10 by count_ asc nulls last
| top 10 by count_ asc nulls last
| project Process_Aux_StartTime, Process_Aux_EndTime, Process_Host_UnstructuredName=Computer, Process_ImageFile_FullPath=ProcessName, Process_ProcessId=ProcessID, Process_Aux_info
};
// change <Name> value below

Просмотреть файл

@ -44,7 +44,7 @@ query: |
| extend ServiceAccount = tostring(EventDataParse.DataItem.EventData.Data[4]['#text'])
| where ImagePath !has '\\ProgramData\\Microsoft\\Windows Defender\\Definition Updates\\'
| extend Process_Aux_Account_info = pack('ServiceName', ServiceName, 'ServiceType', ServiceType, 'StartType', StartType, 'ServiceAccount', ServiceAccount)
| summarize Process_Host_Aux_StartTimeUtc = min(TimeGenerated), Process_Host_Aux_EndTimeUtc = max(TimeGenerated) by Process_Host_UnstructuredName = Computer, Process_Account_Name,
| summarize Process_Host_Aux_StartTimeUtc = min(TimeGenerated), Process_Host_Aux_EndTimeUtc = max(TimeGenerated) by Process_Host_UnstructuredName = Computer, Process_Account_Name,
Process_Account_NTDomain, Process_Account_UnstructuredName = UserName, Process_ImageFile_FullPath = ImagePath, tostring(Process_Aux_Account_info)
| top 10 by Process_Host_Aux_StartTimeUtc desc nulls last
};

Просмотреть файл

@ -29,7 +29,7 @@ query: |
| summarize min(TimeGenerated), max(TimeGenerated), IP_Aux_info = makeset(info) by ClientIP
| project IP_Aux_StartTime = min_TimeGenerated, IP_Aux_EndTime = max_TimeGenerated, ClientIP, IP_Aux_info
| project-rename IP_Address=ClientIP
| top 10 by IP_Aux_StartTime desc nulls last
| top 10 by IP_Aux_StartTime desc nulls last
};
// change <Name> value below
GetAllIPbyAccount ('<Name>')

Просмотреть файл

@ -14,7 +14,7 @@ Tactics:
- Persistence
- Discovery
- LateralMovement
- Collection
- Collection
query: |
let GetAllHostsbyAccount = (v_Account_Name:string){
@ -32,7 +32,7 @@ query: |
| extend info = pack('UserDisplayName', UserDisplayName, 'UserPrincipalName', UserPrincipalName, 'AppDisplayName', AppDisplayName, 'ClientAppUsed', ClientAppUsed, 'Browser', tostring(Browser), 'IPAddress', IPAddress, 'ResultType', ResultType, 'ResultDescription', ResultDescription, 'Location', Location, 'State', State, 'City', City, 'StatusCode', StatusCode, 'StatusDetails', StatusDetails)
| summarize min(TimeGenerated), max(TimeGenerated), Host_Aux_info = makeset(info) by RemoteHost , tostring(OS)
| project min_TimeGenerated, max_TimeGenerated, RemoteHost, OS, Host_Aux_info
| top 10 by min_TimeGenerated desc nulls last
| top 10 by min_TimeGenerated desc nulls last
| project-rename Host_UnstructuredName=RemoteHost, Host_OSVersion=OS, Host_Aux_StartTime=min_TimeGenerated, Host_Aux_EndTime=max_TimeGenerated
};
// change <Name> value below

Просмотреть файл

@ -13,7 +13,7 @@ DataSources:
Tactics:
- LateralMovement
- CredentialAccess
query: |
query: |
let BRUTEFORCE_THRESHOLD = 10;
let SuccessfulLoginEventId = 4624;
@ -30,13 +30,13 @@ query: |
| where p_Host_HostName=~Host_HostName and (isempty(p_Host_DnsDomain) or isempty(Host_DnsDomain) or p_Host_DnsDomain=~Host_DnsDomain)
| extend Fails = (EventID == FailedLoginEventId), Success = (EventID == SuccessfulLoginEventId)
| extend Account = tolower(Account)
| summarize Account_Aux_SuccessPerMin = countif(Success), Account_Aux_FailPerMin = countif(Fails) by Account, bin(TimeGenerated, 1m)
| summarize Account_Aux_SuccessPerMin = countif(Success), Account_Aux_FailPerMin = countif(Fails) by Account, bin(TimeGenerated, 1m)
| where Account_Aux_FailPerMin > BRUTEFORCE_THRESHOLD and Account_Aux_SuccessPerMin > 0
| extend EventData = pack('FailPerMin',Account_Aux_FailPerMin, 'SuccessPerMin', Account_Aux_SuccessPerMin, 'Time', TimeGenerated )
| summarize Max = max(Account_Aux_FailPerMin), Account_Aux_EventsData=makeset(EventData) by Account
| top 10 by Max
| parse Account with Account_NTDomain '\\' *
| extend Account_Name = extract(@'^([^\\]*\\)?([^@]+)(@.*)?$',2,Account),
| extend Account_Name = extract(@'^([^\\]*\\)?([^@]+)(@.*)?$',2,Account),
Account_UPNSuffix = extract(@'^([^\\]*\\)?([^@]+)(@(.*))?$',4,Account)
| project Account_Name, Account_NTDomain, Account_UPNSuffix, Account_Aux_EventsData
};

Просмотреть файл

@ -20,7 +20,7 @@ query: |
let GetWireDataInboundWithHost = (v_Host_HostName:string){
WireData
| where Direction == 'Inbound'
| where Direction == 'Inbound'
| where Computer has v_Host_HostName
| extend info = pack('Computer', Computer, 'LocalPortNumber', LocalPortNumber, 'RemoteIP', RemoteIP, 'Direction', Direction, 'ApplicationProtocol', ApplicationProtocol)
| summarize Process_Aux_Min_SessionStartTime=min(SessionStartTime), count(), IP_Aux_info = makeset(info) by ProcessName , LocalIP, ProcessID

Просмотреть файл

@ -20,7 +20,7 @@ query: |
let GetWireDataOutboundWithHost = (v_Host_HostName:string){
WireData
| where Direction == 'Outbound'
| where Direction == 'Outbound'
| where Computer has v_Host_HostName
| extend info = pack('Computer', Computer, 'LocalIP', LocalIP, 'LocalPortNumber', LocalPortNumber, 'Direction', Direction, 'ApplicationProtocol', ApplicationProtocol)
| summarize Process_Aux_Min_SessionStartTime=min(SessionStartTime), count(), IP_Aux_info = makeset(info) by ProcessName, RemoteIP, ProcessID

Просмотреть файл

@ -23,7 +23,7 @@ query: |
| where Computer has v_Host_HostName
| extend info = pack('HostName', HostName, 'HostIP', HostIP)
| summarize Process_Aux_StartTime=min(EventTime), Process_Aux_EndTime=max(EventTime), count(), Process_Aux_info = makeset(info) by Computer, ProcessName, ProcessID
| top 10 by count_ asc nulls last
| top 10 by count_ asc nulls last
| project Process_Aux_StartTime, Process_Aux_EndTime, Process_Host_UnstructuredName=Computer, Process_ProcessId=ProcessID, Process_ImageFile_FullPath=ProcessName, Process_Aux_info
};
// change <HostName> value below

Просмотреть файл

@ -18,8 +18,8 @@ Tactics:
query: |
let GetParentProcessesOnHost = (v_Host_HostName:string){
SecurityEvent
| where EventID == 4688
SecurityEvent
| where EventID == 4688
| where isnotempty(ParentProcessName)
// excluding well known processes, feel free to add more specific to the environment
| where NewProcessName !contains ':\\Windows\\System32\\conhost.exe' and ParentProcessName !contains ':\\Windows\\System32\\conhost.exe'

Просмотреть файл

@ -18,7 +18,7 @@ Tactics:
query: |
let GetActiveProcessesOnHost = (v_Host_HostName:string){
SecurityEvent
SecurityEvent
| where EventID == 4688
// excluding well known processes, feel free to add more specific to the environment
| where NewProcessName !contains ':\\Windows\\System32\\conhost.exe' and ParentProcessName !contains ':\\Windows\\System32\\conhost.exe'

Просмотреть файл

@ -19,13 +19,13 @@ query: |
let AccountActivity_byIP = (v_IP_Address:string){
AzureActivity
| where Caller != '' and CallerIpAddress =~ v_IP_Address
| summarize Account_Aux_StartTime = min(TimeGenerated),
Account_Aux_EndTime = max(TimeGenerated),
Count = count() by
| summarize Account_Aux_StartTime = min(TimeGenerated),
Account_Aux_EndTime = max(TimeGenerated),
Count = count() by
Caller, TenantId
| top 10 by Count asc nulls last
| top 10 by Count asc nulls last
| extend UPN = iff(Caller contains '@', Caller, ''), Account_AadUserId = iff(Caller !contains '@', Caller,'')
| extend Account_Name = split(UPN,'@')[0] , Account_UPNSuffix = split(UPN,'@')[1]
| project Account_Name, Account_UPNSuffix, Account_AadUserId, Account_AadTenantId=TenantId, Account_Aux_StartTime , Account_Aux_EndTime
| project Account_Name, Account_UPNSuffix, Account_AadUserId, Account_AadTenantId=TenantId, Account_Aux_StartTime , Account_Aux_EndTime
};
AccountActivity_byIP('<Address>')

Просмотреть файл

@ -19,13 +19,13 @@ query: |
let AccountActivity_byIP = (v_IP_Address:string){
AzureActivity
| where Caller != '' and CallerIpAddress =~ v_IP_Address
| summarize Account_Aux_StartTime = min(TimeGenerated),
Account_Aux_EndTime = max(TimeGenerated),
Count = count() by
| summarize Account_Aux_StartTime = min(TimeGenerated),
Account_Aux_EndTime = max(TimeGenerated),
Count = count() by
Caller, TenantId
| top 10 by Count desc nulls last
| top 10 by Count desc nulls last
| extend UPN = iff(Caller contains '@', Caller, ''), Account_AadUserId = iff(Caller !contains '@', Caller,'')
| extend Account_Name = split(UPN,'@')[0] , Account_UPNSuffix = split(UPN,'@')[1]
| project Account_Name, Account_UPNSuffix, Account_AadUserId, Account_AadTenantId=TenantId, Account_Aux_StartTime , Account_Aux_EndTime
| project Account_Name, Account_UPNSuffix, Account_AadUserId, Account_AadTenantId=TenantId, Account_Aux_StartTime , Account_Aux_EndTime
};
AccountActivity_byIP('<Address>')

Просмотреть файл

@ -19,7 +19,7 @@ query: |
let HostsReceivingDatafromIP = (v_IP_Address:string){
WireData
| parse Computer with HostName '.' Host_DnsDomain
| where SessionState == 'Disconnected'
| where SessionState == 'Disconnected'
| where RemoteIP =~ v_IP_Address
| extend Host_HostName = iff(Computer has '.', HostName, Computer)
| summarize Host_Aux_BytesReceived = sum(ReceivedBytes), Host_Aux_LocalIPs=make_set(LocalIP) by Host_HostName, Host_DnsDomain

Просмотреть файл

@ -10,7 +10,7 @@ QueryPeriodBefore: 6h
QueryPeriodAfter: 6h
DataSources:
- WindowsFirewall
Tactics:
Tactics:
- Exfiltration
- CommandAndControl
- Collection

Просмотреть файл

@ -18,7 +18,7 @@ Tactics:
query: |
let GetAllAccountByIP = (v_IP_Address:string){
OfficeActivity
OfficeActivity
| where ClientIP =~ v_IP_Address
| extend info = pack('ClientIP', ClientIP, 'UserType', UserType, 'Operation', Operation, 'OfficeWorkload', OfficeWorkload, 'ResultStatus', ResultStatus)
| summarize min(TimeGenerated), max(TimeGenerated), Account_Aux_Count=count(), Account_Aux_info = makeset(info) by UserId

Просмотреть файл

@ -21,7 +21,7 @@ query: |
let GetWireDataInboundWithIp = (v_IPAddress:string){
WireData
| where Direction == 'Inbound'
| where Direction == 'Inbound'
| where RemoteIP has v_IPAddress
| extend info = pack('LocalPortNumber', LocalPortNumber, 'RemoteIP', RemoteIP, 'Direction', Direction, 'ApplicationProtocol', ApplicationProtocol)
| summarize Process_Aux_EarliestSessionStartTime=min(SessionStartTime), count(), IP_Aux_info = makeset(info) by Computer, ProcessName , LocalIP, ProcessID

Просмотреть файл

@ -17,7 +17,7 @@ Tactics:
- Discovery
- LateralMovement
- Collection
query: |
query: |
let GetWireDataOutboundWithIp = (v_IP_Address:string){
WireData

Просмотреть файл

@ -26,7 +26,7 @@ query: |
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city)
| extend info = pack('AppDisplayName', AppDisplayName, 'ClientAppUsed', ClientAppUsed, 'Browser', tostring(Browser), 'IPAddress', IPAddress, 'ResultType', ResultType, 'ResultDescription', ResultDescription, 'Location', Location, 'State', State, 'City', City, 'StatusCode', StatusCode, 'StatusDetails', StatusDetails)
| summarize min(TimeGenerated), max(TimeGenerated), count(), Account_Aux_info = makeset(info) by RemoteHost , UserDisplayName, tostring(OS), UserPrincipalName, AADTenantId, UserId
| top 10 by count_ asc nulls last
| top 10 by count_ asc nulls last
| project Account_Aux_StartTime = min_TimeGenerated, Account_Aux_EndTime = max_TimeGenerated, RemoteHost, UserDisplayName, OS, UserPrincipalName, AADTenantId, UserId, Account_Aux_info
| project-rename Account_UnstructuredName=UserPrincipalName, Account_DisplayName=UserDisplayName, Account_AadTenantId=AADTenantId, Account_AadUserId=UserId, Account_Host_UnstructuredName=RemoteHost, Account_Host_OSVersion=OS
};

Просмотреть файл

@ -23,7 +23,7 @@ query: |
| where HostIP has v_IP_Address
| extend info = pack('HostIP', HostIP, 'ProcessName', ProcessName, 'SeverityLevel', SeverityLevel)
| summarize min(EventTime), max(EventTime), count(), Host_Aux_info = makeset(info) by Computer
| top 10 by count_ desc nulls last
| top 10 by count_ desc nulls last
| project Host_Aux_StartTime = min_EventTime, Host_Aux_EndTime = max_EventTime, Computer, Host_Aux_info
| project-rename Host_UnstructuredName=Computer
};

Просмотреть файл

@ -26,7 +26,7 @@ query: |
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city)
| extend info = pack('AppDisplayName', AppDisplayName, 'ClientAppUsed', ClientAppUsed, 'Browser', tostring(Browser), 'IPAddress', IPAddress, 'ResultType', ResultType, 'ResultDescription', ResultDescription, 'Location', Location, 'State', State, 'City', City, 'StatusCode', StatusCode, 'StatusDetails', StatusDetails)
| summarize min(TimeGenerated), max(TimeGenerated), count(), Account_Aux_info = makeset(info) by RemoteHost , UserDisplayName, tostring(OS), UserPrincipalName, AADTenantId, UserId
| top 10 by count_ desc nulls last
| top 10 by count_ desc nulls last
| project Account_Aux_StartTimeUtc = min_TimeGenerated, Account_Aux_EndTimeUtc = max_TimeGenerated, RemoteHost, UserDisplayName, OS, UserPrincipalName, AADTenantId, UserId, Account_Aux_info
| project-rename Account_UnstructuredName=UserPrincipalName, Account_DisplayName=UserDisplayName, Account_AadTenantId=AADTenantId, Account_AadUserId=UserId, Account_Host_UnstructuredName=RemoteHost, Account_Host_OSVersion=OS
};

Просмотреть файл

@ -26,7 +26,7 @@ query: |
| where ProcessName has v_Process_ImageFile_FullPath
| extend info = pack('HostName', HostName, 'HostIP', HostIP, 'ProcessName', ProcessName, 'SyslogMessage', SyslogMessage)
| summarize min(EventTime), max(EventTime), count(), Host_Aux_info = makeset(info) by Computer
| top 10 by count_ asc nulls last
| top 10 by count_ asc nulls last
| project Host_Aux_StartTime=min_EventTime, Host_Aux_EndTime=max_EventTime, Computer, Host_Aux_info
| project-rename Host_UnstructuredName=Computer
};

Просмотреть файл

@ -9,12 +9,12 @@ OutputEntityTypes:
- Process
QueryPeriodBefore: 30m
QueryPeriodAfter: 30m
DataSources:
DataSources:
- SecurityIoTRawEvent
query: |
let Process_byIoTDevice = (v_IotDevice_DeviceId:string, v_IoTDevice_IoTHub:string){
SecurityIoTRawEvent
SecurityIoTRawEvent
| where RawEventName =~ 'ProcessCreate'
| where AssociatedResourceId =~ parse_json(v_IoTDevice_IoTHub)['ResourceId'] and DeviceId =~ v_IotDevice_DeviceId
| extend Process_CommandLine = tostring(parse_json(EventDetails)['CommandLine'])

Просмотреть файл

@ -0,0 +1,70 @@
SchemaVersion: 1.0
DataTypes:
- DataType: AuditLogs
- DataType: SecurityEvent
Type: KQL
Provider: Sentinel
RequiredInputFieldsSets:
- - Account_Name
- Account_NTDomain
- - Account_Name
- Account_UPNSuffix
- - Account_AADUserId
- - Account_SID
BaseQuery: |
let GetAccountActions = (Account_Name:string, Account_NTDomain:string, Account_UPNSuffix:string, Account_AADUserId:string, Account_SID:string){
union isfuzzy=true
(
AuditLogs
| where tostring(bag_keys(InitiatedBy)[0]) == "user"
| where OperationName in~ ('Delete user', 'Change user password', 'Reset user password', 'Change password (self-service)', 'Reset password (by admin)', 'Reset password (self-service)', 'Update user')
| extend userPrincipalName = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend userName=tostring(split(userPrincipalName, '@')[0])
| extend userUpnSuffix=tostring(split(userPrincipalName, '@')[1])
| extend userId=tostring(parse_json(tostring(InitiatedBy.user)).id)
| where (userName == Account_Name and userUpnSuffix == Account_UPNSuffix ) or (userId == Account_AADUserId )
| extend Target_UPN = tostring(TargetResources[0].userPrincipalName)
| extend Target_Name = tostring(split(Target_UPN, '@')[0])
| extend Target_UPNSuffix = tostring(split(Target_UPN, '@')[1])
| extend Target_AADUserId = tostring(TargetResources[0].id)
| extend Action = tostring(parse_json(tostring(parse_json(tostring(TargetResources[0].modifiedProperties))[0])))
| extend ModifiedProperty = parse_json(Action).displayName
| extend ModifiedValue = parse_json(Action).newValue
| extend DisableUser = iif(ModifiedProperty =~ 'AccountEnabled' and ModifiedValue =~ '[false]', 'True', 'False')
),
(
SecurityEvent
| where EventID in (4720, 4722, 4723, 4724, 4725, 4726, 4740)
| where AccountType =~ "user" or isempty(AccountType)
| extend userName=tostring(split(Account,"\\")[1]), userNTDomain=tostring(split(Account,"\\")[0]), userSid=SubjectUserSid
| where (Account_Name==userName and userNTDomain==Account_NTDomain) or SubjectUserSid == Account_SID
| extend OperationName = tostring(EventID)
| extend Target_Name = TargetUserName, Target_NTDomain = TargetDomainName, Target_SID = TargetSid
)
};
GetAccountActions('{{Account_Name}}', '{{Account_NTDomain}}', '{{Account_UPNSuffix}}', '{{Account_AADUserId}}', '{{Account_SID}}')
Activities:
EnabledByDefault: true
Items:
- Id: d6d08c94-455f-4ea5-8f76-fc6c0c442cfa
Title: "The user has created an account"
Content: "The user has created the account {{Target_Name}} {{Count}} time(s)"
Description: "This activity displays account creation events performed by the user"
QueryDefinitions:
Filter: "where OperationName in~ ('Add user', '4720')"
SummarizeBy: "Target_Name, Target_NTDomain, Target_UPNSuffix"
- Id: e0459780-ac9d-4b72-8bd4-fecf6b46a0a1
Title: "The user has deleted an account"
Content: "The user has deleted the account {{Target_Name}} {{Count}} time(s)"
Description: "This activity displays account deletion events performed by the user"
QueryDefinitions:
Filter: "where OperationName in~ ('Delete user', '4726')"
SummarizeBy: "Target_Name, Target_NTDomain, Target_UPNSuffix"
- Id: ad1f4269-5418-4c46-a3b6-4ec01031de60
Title: "The user has reset an account's password"
Content: "The password for account {{Target_Name}} was reset by the user {{Count}} time(s)"
Description: "This activity displays password reset events performed by the user"
QueryDefinitions:
Filter: "where OperationName in~ ('Change user password', 'Reset user password', 'Change password (self-service)', 'Reset password (by admin)', 'Reset password (self-service)', '4724', '4723')"
SummarizeBy: "Target_Name, Target_NTDomain, Target_UPNSuffix"

Просмотреть файл

@ -0,0 +1,126 @@
SchemaVersion: 1.0
DataTypes:
- DataType: AuditLogs
- DataType: SecurityEvent
Type: KQL
Provider: Sentinel
RequiredInputFieldsSets:
- - Account_Name
- Account_NTDomain
- - Account_Name
- Account_UPNSuffix
- - Account_AADUserId
- - Account_SID
BaseQuery: |
let GetAccountActions = (v_Account_Name:string, v_Account_NTDomain:string, v_Account_UPNSuffix:string, v_Account_AADUserId:string, v_Account_SID:string){
AuditLogs
| where OperationName in~ ('Delete user', 'Change user password', 'Reset user password', 'Change password (self-service)', 'Reset password (by admin)', 'Reset password (self-service)', 'Update user')
| extend UserPrincipalName = tostring(TargetResources[0].userPrincipalName)
| extend Account_Name = tostring(split(UserPrincipalName, '@')[0])
| extend Account_UPNSuffix = tostring(split(UserPrincipalName, '@')[1])
| extend Action = tostring(parse_json(tostring(parse_json(tostring(TargetResources[0].modifiedProperties))[0])))
| extend ModifiedProperty = parse_json(Action).displayName
| extend ModifiedValue = parse_json(Action).newValue
| extend Account_AADUserId = tostring(TargetResources[0].id)
| extend DisableUser = iif(ModifiedProperty =~ 'AccountEnabled' and ModifiedValue =~ '[false]', 'True', 'False')
| union isfuzzy=true (
SecurityEvent
| where EventID in (4720, 4722, 4723, 4724, 4725, 4726, 4740)
| extend OperationName = tostring(EventID)
| where AccountType =~ "user" or isempty(AccountType)
| extend Account_Name = TargetUserName, Account_NTDomain = TargetDomainName, Account_SID = TargetSid
)
| where (Account_Name =~ v_Account_Name and (Account_UPNSuffix =~ v_Account_UPNSuffix or Account_NTDomain =~ v_Account_NTDomain)) or Account_AADUserId =~ v_Account_AADUserId or Account_SID =~ v_Account_SID
};
GetAccountActions('{{Account_Name}}', '{{Account_NTDomain}}', '{{Account_UPNSuffix}}', '{{Account_AADUserId}}', '{{Account_SID}}')
# The queries for the insights.
Insights:
Id: 6db7f5d1-f41e-46c2-b935-230b36a569e6
DisplayName: Actions on account
Description: |
Summary of actions taken on the specified account, grouped by action: password resets and changes, account lockouts (policy or admin), account creation and deletion, account enabled and disabled
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
TableQuery:
ColumnsDefinitions:
- Header: Action
OutputType: String
SupportDeepLink: false
- Header: Most Recent
OutputType: Date
SupportDeepLink: false
- Header: Count
OutputType: Number
SupportDeepLink: true
QueriesDefinitions:
# Account Password Resets
- Filter: "where OperationName in~ ('Change user password', 'Reset user password', 'Change password (self-service)', 'Reset password (by admin)', 'Reset password (self-service)', '4724', '4723')"
Summarize: "summarize MostRecent = max(TimeGenerated), Count = count() by OperationName"
Project: "project Title = OperationName, MostRecent, Count"
LinkColumnsDefinitions:
- ProjectedName: Count
Query: "{{BaseQuery}} | {{RowFilter}}"
# Account Disabled by Policy
- Filter: "where OperationName in~ ('Blocked from self-service password reset', '4740')"
Summarize: "summarize MostRecent = max(TimeGenerated), Count = count() by OperationName"
Project: "project Title = OperationName, MostRecent, Count"
LinkColumnsDefinitions:
- ProjectedName: Count
Query: "{{BaseQuery}} | {{RowFilter}}"
# Account Disabled by Admin
- Filter: "where OperationName == '4725' or (OperationName =~ 'Update user' and DisableUser =~ 'True')"
Summarize: "summarize MostRecent = max(TimeGenerated), Count = count() by OperationName"
Project: "project Title = OperationName, MostRecent, Count"
LinkColumnsDefinitions:
- ProjectedName: Count
Query: "{{BaseQuery}} | {{RowFilter}}"
# Account Created
- Filter: "where OperationName in~ ('Add user', '4720')"
Summarize: "summarize MostRecent = max(TimeGenerated), Count = count() by OperationName"
Project: "project Title = OperationName, MostRecent, Count"
LinkColumnsDefinitions:
- ProjectedName: Count
Query: "{{BaseQuery}} | {{RowFilter}}"
# Account Deleted
- Filter: "where OperationName in~ ('Delete user', '4726')"
Summarize: "summarize MostRecent = max(TimeGenerated), Count = count() by OperationName"
Project: "project Title = OperationName, MostRecent, Count"
LinkColumnsDefinitions:
- ProjectedName: Count
Query: "{{BaseQuery}} | {{RowFilter}}"
# Account Deleted or Disabled
- Filter: "where OperationName in~ ('4725', 'Blocked from self-service password reset', '4740') or (OperationName =~ 'Update user' and DisableUser =~ 'True')"
Summarize: "summarize MostRecent = max(TimeGenerated), Count = count() by OperationName"
Project: "project Title = OperationName, MostRecent, Count"
LinkColumnsDefinitions:
- ProjectedName: Count
Query: "{{BaseQuery}} | {{RowFilter}}"
# Account Created or Enabled
- Filter: "where OperationName in~ ('4722', '4767') or (OperationName =~ 'Update user' and DisableUser =~ 'False')"
Summarize: "summarize MostRecent = max(TimeGenerated), Count = count() by OperationName"
Project: "project Title = OperationName, MostRecent, Count"
LinkColumnsDefinitions:
- ProjectedName: Count
Query: "{{BaseQuery}} | {{RowFilter}}"
# Account Setting Changed
- Filter: "where OperationName in~ ('Update user','4738')"
Summarize: "summarize MostRecent = max(TimeGenerated), Count = count() by OperationName"
Project: "project Title = OperationName, MostRecent, Count"
LinkColumnsDefinitions:
- ProjectedName: Count
Query: "{{BaseQuery}} | {{RowFilter}}"
ChartQuery:
Title: "Actions by type"
DataSets:
- Query: "summarize Count = count() by bin(TimeGenerated, 1h), OperationName"
XColumnName: "TimeGenerated"
YColumnName: "Count"
LegendColumnName: "OperationName"
Type: BarChart
AdditionalQuery:
Text: "See all account activity"
Query: "project TimeGenerated, UserPrincipalName, Account_Name, OperationName, Activity, DisableUser, TargetSid, AADUserId, InitiatedBy, AADTenantId, AccountType, Computer, SubjectAccount, SubjectUserSid, EventData"

Просмотреть файл

@ -0,0 +1,32 @@
SchemaVersion: 1.0
Provider: Sentinel
Type: KQL
DataTypes:
- DataType: AuditLogs
RequiredInputFieldsSets:
- - Account_Name
- Account_UPNSuffix
- - Account_AadUserId
BaseQuery: |-
let UserConsentToApplication = (Account_Name:string, Account_UPNSuffix:string, Account_AadUserId:string){
let account_upn = iff(Account_Name != "" and Account_UPNSuffix != ""
, strcat(Account_Name,"@",Account_UPNSuffix)
,"" );
AuditLogs
| where OperationName == "Consent to application"
| extend Source_Account_UPNSuffix = tostring(todynamic(InitiatedBy) ["user"]["userPrincipalName"]), Source_Account_AadUserId = tostring(todynamic(InitiatedBy) ["user"]["id"])
| where (account_upn != "" and account_upn =~ Source_Account_UPNSuffix)
or (Account_AadUserId != "" and Account_AadUserId =~ Source_Account_AadUserId)
| extend Target_CloudApplication_Name = tostring(todynamic(TargetResources)[0]["displayName"]), Target_CloudApplication_AppId = tostring(todynamic(TargetResources)[0]["id"])
};
UserConsentToApplication('{{Account_Name}}', '{{Account_UPNSuffix}}', '{{Account_AadUserId}}')
Activities:
EnabledByDefault: true
Title: "The user consented to OAuth application"
Content: "The user consented to the OAuth application named {{Target_CloudApplication_Name}} {{Count}} time(s)"
Items:
- Id: 5e9ecee5-e7a4-4a2a-94c4-9c0e22e1b673
Description: This activity lists user's consents to an OAuth applications.
QueryDefinitions:
SummarizeBy: Target_CloudApplication_AppId, Target_CloudApplication_Name

Просмотреть файл

@ -0,0 +1,33 @@
SchemaVersion: 1.0
Provider: Sentinel
Type: KQL
DataTypes:
- DataType: AzureActivity
RequiredInputFieldsSets:
- - Account_Name
- Account_UPNSuffix
- - Account_AadUserId
BaseQuery: |-
let AzureRunProcess = (Account_Name:string, Account_UPNSuffix:string,Account_AadUserId:string){
let upn = strcat(Account_Name,"@",Account_UPNSuffix);
AzureActivity
| where (isnotempty(Account_AadUserId) and Caller =~ Account_AadUserId) or Caller =~ upn
| where OperationName contains "Run Command on Virtual Machine"
or (OperationName == "List Storage Account Keys" and ActivityStatus == "Succeeded")
or OperationName == "Create or Update Virtual Machine"
or OperationName == "Create Deployment"
or OperationName == "Create role assignment"
| project-rename Target_AzureResource_ResourceId = _ResourceId, Source_IP_Address = CallerIpAddress
| extend shortResourceId = tostring(split(ResourceId,'/')[-1])
};
AzureRunProcess('{{Account_Name}}', '{{Account_UPNSuffix}}', '{{Account_AadUserId}}')
Activities:
EnabledByDefault: true
Title: "User performed operation on azure resource from IP"
Content: "User performed operation {{OperationName}} on azure resource: {{shortResourceId}} from IP {{Source_IP_Address}} {{Count}} time(s)"
Items:
- Id: cab4058a-0707-4a02-b76f-cf96270823ed
Description: This activity lists the user's activities on Azure.
QueryDefinitions:
SummarizeBy: Target_AzureResource_ResourceId, Source_IP_Address, shortResourceId, OperationName

Просмотреть файл

@ -0,0 +1,88 @@
SchemaVersion: 1.0
Type: KQL
Provider: Sentinel
DataTypes:
- DataType: SecurityEvent
- DataType: Event
RequiredInputFieldsSets:
- - Account_Name
- Account_NTDomain
- - Account_SID
BaseQuery: |
let UserClearedEventLog = (v_Account_Name:string, v_Account_NTDomain:string, v_Account_SID:string){
SecurityEvent
// 1102 - This event generates every time Windows Security audit log was cleared
| where EventID == 1102 and EventSourceName =~ 'Microsoft-Windows-Eventlog'
| parse EventData with * 'SubjectUserName>' SubjectUserName '<' *
| parse EventData with * 'SubjectUserSid>' SubjectUserSid '<' *
| parse EventData with * 'SubjectLogonId>' SubjectLogonId '<' *
| parse EventData with * 'SubjectDomainName>' SubjectDomainName '<' *
| parse EventData with * 'BackupPath>' BackupPath'<' *
| extend ClearedLog = Type
| extend Account_Name = SubjectUserName, Account_NTDomain = SubjectDomainName
| union isfuzzy=true
( Event
// 104 - This event generates every time a Windows Event log was cleared
| where EventID == 104 and Source == "Microsoft-Windows-Eventlog"
| parse EventData with * 'SubjectUserName>' SubjectUserName '<' *
| parse EventData with * 'SubjectUserSid>' SubjectUserSid '<' *
| parse EventData with * 'SubjectLogonId>' SubjectLogonId '<' *
| parse EventData with * 'SubjectDomainName>' SubjectDomainName '<' *
| parse EventData with * 'BackupPath>' BackupPath'<' *
| parse RenderedDescription with * 'The' ClearedLog 'log' *
| extend Account_Name = SubjectUserName, Account_NTDomain = SubjectDomainName, Account_SID = SubjectUserSid
)
| project TimeGenerated, Computer, EventID, Account_Name, SubjectUserName, SubjectUserSid, Account_SID, SubjectLogonId, Account_NTDomain, SubjectDomainName, SourceComputerId, _ResourceId, ClearedLog, BackupPath
| where (Account_Name =~ v_Account_Name and Account_NTDomain =~ v_Account_NTDomain) or (isnotempty(v_Account_SID) and Account_SID =~ v_Account_SID)
};
UserClearedEventLog('{{Account_Name}}', '{{Account_NTDomain}}', '{{Account_SID}}')
# The queries for the insights.
Insights:
Id: 5a70a68d-25d4-4012-b73e-4f302a16c06a
DisplayName: Event Logs cleared by user
Description: |
Provides the datetime & count of number of times any event log was cleared by the user.
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
TableQuery:
ColumnsDefinitions:
- Header: Cleared Log
OutputType: String
- Header: Times Cleared
OutputType: Number
- Header: Host Count
OutputType: Number
SupportDeepLink: true
- Header: Host(s)
OutputType: String
QueriesDefinitions:
# SecurityEvent cleared
- Filter: "where ClearedLog =~ 'SecurityEvent'"
Summarize: "summarize Hosts = makeset(Computer), HostCount = dcount(Computer), EventCount = count() by ClearedLog"
Project: "project Title = ClearedLog, EventCount, HostCount, Hosts = case(array_length(Hosts) == 1, tostring(Hosts[0]), array_length(Hosts) > 1, 'Many', 'None')"
LinkColumnsDefinitions:
- ProjectedName: HostCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# Other Event Cleared
- Filter: "where ClearedLog !~ 'SecurityEvent'"
Summarize: "summarize Hosts = makeset(Computer), HostCount = dcount(Computer), EventCount = count() by ClearedLog"
Project: "project Title = ClearedLog, EventCount, HostCount, Hosts = case(array_length(Hosts) == 1, tostring(Hosts[0]), array_length(Hosts) > 1, 'Many', 'None')"
LinkColumnsDefinitions:
- ProjectedName: HostCount
Query: "{{BaseQuery}} | {{RowFilter}}"
ChartQuery:
Title: "Log clear activity over time"
DataSets:
- Query: "summarize EventCount = count() by bin(TimeGenerated, 1d) | extend Legend = 'EventCount'"
XColumnName: TimeGenerated
YColumnName: EventCount
LegendColumnName: Legend
- Query: "summarize HostCount = dcount(Computer) by bin(TimeGenerated, 1d) | extend Legend = 'HostCount'"
XColumnName: TimeGenerated
YColumnName: HostCount
LegendColumnName: Legend
Type: BarChart
AdditionalQuery:
Text: See All Log Clear Activity
Query: "project TimeGenerated, Computer, EventID, Account_Name, SubjectUserName, SubjectUserSid, SubjectLogonId, Account_NTDomain, SubjectDomainName, SourceComputerId, _ResourceId, ClearedLog, BackupPath"

Просмотреть файл

@ -0,0 +1,163 @@
SchemaVersion: 1.0
Type: KQL
Provider: Sentinel
DataTypes:
- DataType: SecurityEvent
RequiredInputFieldsSets:
- - Account_Name
- Account_NTDomain
- - Account_SID
BaseQuery: |
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$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-498$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1000$';
let GetGroupAddForUser = (v_Account_Name:string, v_Account_NTDomain:string, v_Account_SID:string){
SecurityEvent
| where EventID in (4728, 4732, 4756)
| where AccountType =~ 'User'
| extend Account_Name = case(
// Handles mixed use scenario of NTDomain\AccountName@UPNSuffix
SubjectUserName has '@' and SubjectUserName has '\\', tostring(split(tostring(split(SubjectUserName, '\\')[1]),'@')[0]),
SubjectUserName has '@', tostring(split(SubjectUserName, '@')[0]),
SubjectUserName has '\\', tostring(split(SubjectUserName, '\\')[1]),
SubjectUserName
)
| extend Account_NTDomain = case(
SubjectDomainName has '\\', tostring(split(SubjectDomainName, '\\')[0]),
// Handles UPN scenario of AccountName@UPNSuffix to pull potential NTDomain from
SubjectDomainName has '@', tostring(split(tostring(split(SubjectDomainName, '@')[1]),'.')[0]),
SubjectDomainName
)
| extend MemberAdded = case( MemberName has 'CN=', tostring(split(tostring(split(MemberName, ',')[0]),'CN=')[1]), MemberName == '-', MemberSid, MemberName)
| extend MemberNameMatch = iff(isnotempty(v_Account_Name) and MemberAdded has v_Account_Name, true, false)
| extend MemberNTDomainMatch = iff(isnotempty(v_Account_NTDomain) and MemberAdded has v_Account_NTDomain, true, false)
| extend MemberSidMatch = iff(isnotempty(v_Account_SID) and MemberSid =~ v_Account_SID, true, false)
| extend SubjectNameMatch = iff(isnotempty(v_Account_Name) and SubjectUserName =~ v_Account_Name, true, false)
| extend SubjectNTDomainMatch = iff(isnotempty(v_Account_NTDomain) and SubjectDomainName =~ v_Account_NTDomain, true, false)
| extend SubjectSidMatch = iff(isnotempty(v_Account_SID) and SubjectUserSid has v_Account_SID, true, false)
| where (MemberNameMatch == true and MemberNTDomainMatch == true) or MemberSidMatch == true or (SubjectNameMatch == true and SubjectNTDomainMatch == true) or SubjectSidMatch == true
| project TimeGenerated, EventID, Activity, Computer, MemberName, MemberAdded, MemberSid, TargetUserName, TargetDomainName, TargetSid, UserPrincipalName, SubjectAccount, SubjectUserName, SubjectUserSid, WellKnownGroupSID, WellKnownLocalSID,
MemberNameMatch, MemberNTDomainMatch, MemberSidMatch, SubjectNameMatch, SubjectNTDomainMatch, SubjectSidMatch
| extend GroupName = TargetUserName, AddedBy = SubjectAccount
//support for Activities
| extend timestamp = TimeGenerated, AccountCustomEntity = SubjectAccount
};
GetGroupAddForUser('{{Account_Name}}', '{{Account_NTDomain}}', '{{Account_SID}}')
# The queries for the insights.
Insights:
Id: bec9d204-c96c-4a34-8042-b49e89dbff89
DisplayName: Group additions
Description: |
Summary of user additions to Groups for the specific user. Specifically, we provide the count of All Groups, [Privileged Groups](https://docs.microsoft.com/windows/security/identity-protection/access-control/active-directory-security-groups) and Remote Desktop Users Group.
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
TableQuery:
ColumnsDefinitions:
- Header: "Groups"
OutputType: String
SupportDeepLink: false
- Header: GroupCount
OutputType: Number
SupportDeepLink: true
- Header: HostCount
OutputType: Number
SupportDeepLink: true
QueriesDefinitions:
# UserAddedToPrivilegedGroups
- Filter: "where ((MemberNameMatch == true and MemberNTDomainMatch == true) or MemberSidMatch == true) and (TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID)"
Summarize: "summarize GroupCount = dcount(GroupName), HostCount = dcount(Computer) by SubjectAccount"
Project: "project Title = 'Privileged', GroupCount, HostCount"
LinkColumnsDefinitions:
- ProjectedName: GroupCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: HostCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# UserAddedToRemoteDesktopGroup
- Filter: "where ((MemberNameMatch == true and MemberNTDomainMatch == true) or MemberSidMatch == true) and TargetSid in ('S-1-5-32-555')"
Summarize: "summarize GroupCount = dcount(GroupName), HostCount = dcount(Computer) by SubjectAccount"
Project: "project Title = 'Remote Desktop', GroupCount, HostCount"
LinkColumnsDefinitions:
- ProjectedName: GroupCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: HostCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# UserAddedToPrivilegedGroupsExcludeRDP
- Filter: "where ((MemberNameMatch == true and MemberNTDomainMatch == true) or MemberSidMatch == true) and (TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID) | where TargetSid !in ('S-1-5-32-555')"
Summarize: "summarize GroupCount = dcount(GroupName), HostCount = dcount(Computer) by SubjectAccount"
Project: "project Title = 'Privileged(non-RDP)', GroupCount, HostCount"
LinkColumnsDefinitions:
- ProjectedName: GroupCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: HostCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# UserAddedToGroups
- Filter: "where (MemberNameMatch == true and MemberNTDomainMatch == true) or MemberSidMatch == true"
Summarize: "summarize GroupCount = dcount(GroupName), HostCount = dcount(Computer) by SubjectAccount"
Project: "project Title = 'All Groups', GroupCount, HostCount"
LinkColumnsDefinitions:
- ProjectedName: GroupCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: HostCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# GroupsAddedUsersTo
- Filter: "where (SubjectNameMatch == true and SubjectNTDomainMatch == true) or SubjectSidMatch == true"
Summarize: "summarize GroupCount = dcount(GroupName), HostCount = dcount(Computer) by SubjectAccount"
Project: "project Title = 'Groups this user added users to', GroupCount, HostCount"
LinkColumnsDefinitions:
- ProjectedName: GroupCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: HostCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# UsersAddedToGroups
- Filter: "where (SubjectNameMatch == true and SubjectNTDomainMatch == true) or SubjectSidMatch == true "
Summarize: "summarize GroupCount = dcount(MemberAdded), HostCount = dcount(Computer) by SubjectAccount"
Project: "project Title = 'Users this user added to Groups', GroupCount, HostCount"
LinkColumnsDefinitions:
- ProjectedName: GroupCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: HostCount
Query: "{{BaseQuery}} | {{RowFilter}}"
ChartQuery:
Title: "Group additions per hour"
DataSets:
- Query: "summarize Count = count() by bin(TimeGenerated, 1h) | extend Legend = 'Total'"
XColumnName: "TimeGenerated"
YColumnName: "Count"
LegendColumnName: "Legend"
Type: LineChart
AdditionalQuery:
Text: "See all group additions related to user"
Query: "project TimeGenerated, EventID, Activity, Computer, MemberName, MemberAdded, MemberSid, TargetUserName, TargetDomainName, TargetSid, UserPrincipalName, SubjectAccount, SubjectUserName, SubjectUserSid, WellKnownGroupSID, WellKnownLocalSID | order by TimeGenerated desc"
Activities:
EnabledByDefault: true
Items:
- Id: febba410-e7d6-4c63-8fe5-2b93f448b7a1
Title: "The user has added an account to a privileged group"
Content: "The user has added an account to the privileged group, {{TargetDomainName}}{{TargetUserName}}, {{Count}} time(s)"
Description: "This activity displays the user that added an account and the account that was added to a privileged group"
QueryDefinitions:
Filter: "where ((MemberNameMatch == true and MemberNTDomainMatch == true) or MemberSidMatch == true) and (TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID) | where TargetSid !in ('S-1-5-32-555')"
SummarizeBy: "SubjectAccount, TargetUserName, TargetSid"
- Id: 5ae2baf4-de7b-40f0-a861-8852266bfcd0
Title: "The user has added an account to the Remote Desktop Users group"
Content: "The user has added an account to the Remote Desktop Users group {{Count}} time(s)"
Description: "This activity displays the account was added to Remote Desktop group"
QueryDefinitions:
Filter: "where ((MemberNameMatch == true and MemberNTDomainMatch == true) or MemberSidMatch == true) and TargetSid in ('S-1-5-32-555')"
SummarizeBy: "SubjectAccount, TargetUserName, TargetSid"
- Id: bf56473d-b9bd-4eb1-96d0-8569ec7a9003
Title: "The user has added an account to a security group"
Content: "The user has added {{SubjectAccount}} to the {{TargetDomainName}}\\{{TargetUserName}} group"
Description: "This activity displays the user that added an account and the account that was added to a security group"
QueryDefinitions:
Filter: "where (SubjectNameMatch == true and SubjectNTDomainMatch == true) or SubjectSidMatch == true"
SummarizeBy: "SubjectAccount, TargetUserName, TargetSid"

Просмотреть файл

@ -0,0 +1,95 @@
SchemaVersion: 1.0
DataTypes:
- DataType: OfficeActivity
Provider: Sentinel
Type: KQL
BaseQuery: |
let AScoreThresh = 3;
let maxAnomalies = 3;
let BeforeRange = 14d;
let EndTime = todatetime('{{End_Time_UTC}}');
let StartTime = todatetime('{{Start_Time_UTC}}');
let numDays = tolong((EndTime-StartTime)/1d);
let userData = (v_Account_Name:string, v_Account_UPNSuffix:string) {
OfficeActivity
| extend splitUserId=split(UserId, '@')
| extend Account_Name = tostring(splitUserId[0]), Account_UPNSuffix = tostring(splitUserId[1])
| where Account_Name =~ v_Account_Name and Account_UPNSuffix =~ v_Account_UPNSuffix };
userData('{{Account_Name}}', '{{Account_UPNSuffix}}')
RequiredInputFieldsSets:
- - Account_Name
- Account_UPNSuffix
Insights:
Id: 0a5d7b14-b485-450a-a0ac-4100c860ac32
DisplayName: Anomalously high office operation count
Description: Highlight office operations of the user with anomalously high count compared to those observed in the preceding 14 days.
DefaultTimeRange:
BeforeRange: 1d
AfterRange: 0d
ReferenceTimeRange:
BeforeRange: 14d
TableQuery:
ColumnsDefinitions:
- Header: Operation
OutputType: String
SupportDeepLink: true
- Header: Expected Count
OutputType: Number
SupportDeepLink: false
- Header: Actual Count
OutputType: Number
SupportDeepLink: false
QueriesDefinitions:
- Filter: |
make-series count() default=0 on TimeGenerated from (StartTime - BeforeRange) to EndTime step 1d by Operation
| extend (anomalies,anomalyScore, expectedCount)=series_decompose_anomalies(count_,AScoreThresh,7,'linefit',numDays, 'ctukey')
| extend count1=count_, TimeGenerated1=TimeGenerated, anomalyScore1=anomalyScore
| mv-apply count1 to typeof(long), TimeGenerated1 to typeof(datetime), anomalyScore1 to typeof(double), anomalies to typeof(long) on (summarize totAnomalies=sumif(abs(anomalies), TimeGenerated1 < StartTime), baseStd=stdevif(count1, TimeGenerated1 < StartTime), baseAvg=avgif(count1, TimeGenerated1 < StartTime), maxCountPost=maxif(count1,TimeGenerated1 >= StartTime), maxAnomalyScorePost=maxif(anomalyScore1, TimeGenerated1 >= StartTime))
| extend count1=count_
| mv-apply count1 to typeof(long), anomalyScore to typeof(double), expectedCount to typeof(double) on ( summarize (dummy, postExpectedCount, postActualCount)=arg_min(abs(anomalyScore-maxAnomalyScorePost), expectedCount, count1) )
| where totAnomalies < maxAnomalies
| extend postAnomalyScore=iff(baseStd == 0 and maxCountPost > tolong(count_[0]),1000.0,maxAnomalyScorePost), postExpectedCount=iff(postExpectedCount < 0,0.0,postExpectedCount)
| where maxAnomalyScorePost > AScoreThresh
| order by maxAnomalyScorePost desc
Summarize: take 1
Project: project Operation, expectedCount=round(postExpectedCount,2), actualCount=postActualCount, anomalyScore=round(postAnomalyScore,2)
LinkColumnsDefinitions:
- ProjectedName: Operation
Query: |
{{BaseQuery}}
| where TimeGenerated between (StartTime .. EndTime)
| where Operation == '{{RowValue_Operation}}'
ChartQuery:
Title: Anomalous operation timeline
DataSets:
- Query: |
make-series count() default=0 on TimeGenerated from (StartTime - BeforeRange) to EndTime step 1d by Operation
| extend (anomalies,anomalyScore, expectedCount)=series_decompose_anomalies(count_,AScoreThresh,7,'linefit',numDays, 'ctukey')
| extend count1=count_, TimeGenerated1=TimeGenerated, anomalyScore1=anomalyScore
| mv-apply count1 to typeof(long), TimeGenerated1 to typeof(datetime), anomalyScore1 to typeof(double), anomalies to typeof(long) on (summarize totAnomalies=sumif(abs(anomalies), TimeGenerated1 < StartTime), baseStd=stdevif(count1, TimeGenerated1 < StartTime), baseAvg=avgif(count1, TimeGenerated1 < StartTime), maxCountPost=maxif(count1,TimeGenerated1 >= StartTime), maxAnomalyScorePost=maxif(anomalyScore1, TimeGenerated1 >= StartTime))
| extend count1=count_
| mv-apply count1 to typeof(long), anomalyScore to typeof(double), expectedCount to typeof(double) on ( summarize (dummy, postExpectedCount, postActualCount)=arg_min(abs(anomalyScore-maxAnomalyScorePost), expectedCount, count1) )
| where totAnomalies < maxAnomalies
| extend postAnomalyScore=iff(baseStd == 0 and maxCountPost > tolong(count_[0]),1000.0,maxAnomalyScorePost), postExpectedCount=iff(postExpectedCount < 0,0.0,round(postExpectedCount,2))
| where maxAnomalyScorePost > AScoreThresh
| order by maxAnomalyScorePost desc
| take 1
| project Operation, TimeGenerated, count_
| mvexpand TimeGenerated, count_ | project todatetime(TimeGenerated), toint(count_), Operation
XColumnName: TimeGenerated
YColumnName: count_
LegendColumnName: Operation
Type: LineChart
AdditionalQuery:
Text: Query all anomalous operations
Query: |
make-series count() default=0 on TimeGenerated from (StartTime - BeforeRange) to EndTime step 1d by Operation
| extend (anomalies,anomalyScore, expectedCount)=series_decompose_anomalies(count_,AScoreThresh,7,'linefit',numDays, 'ctukey')
| extend count1=count_, TimeGenerated1=TimeGenerated, anomalyScore1=anomalyScore
| mv-apply count1 to typeof(long), TimeGenerated1 to typeof(datetime), anomalyScore1 to typeof(double), anomalies to typeof(long) on (summarize totAnomalies=sumif(abs(anomalies), TimeGenerated1 < StartTime), baseStd=stdevif(count1, TimeGenerated1 < StartTime), baseAvg=avgif(count1, TimeGenerated1 < StartTime), maxCountPost=maxif(count1,TimeGenerated1 >= StartTime), maxAnomalyScorePost = maxif(anomalyScore1, TimeGenerated1 >= StartTime))
| extend count1=count_
| mv-apply count1 to typeof(long), anomalyScore to typeof(double), expectedCount to typeof(double) on ( summarize (dummy, postExpectedCount, postActualCount)=arg_min(abs(anomalyScore - maxAnomalyScorePost), expectedCount, count1) )
| where totAnomalies < maxAnomalies
| extend postAnomalyScore=iff(baseStd == 0 and maxCountPost > tolong(count_[0]),1000.0,maxAnomalyScorePost), postExpectedCount=iff(postExpectedCount < 0,0.0,postExpectedCount)
| where maxAnomalyScorePost > AScoreThresh | order by maxAnomalyScorePost desc
| project Operation, expectedCount=round(postExpectedCount,2), actualCount=postActualCount, anomalyScore=round(postAnomalyScore,2)

Просмотреть файл

@ -0,0 +1,31 @@
SchemaVersion: 1.0
Provider: Sentinel
Type: KQL
DataTypes:
- DataType: OfficeActivity
RequiredInputFieldsSets:
- - Account_Name
- Account_UPNSuffix
- - Account_Sid
BaseQuery: |-
let TLQ_UserActedOnForeignMailbox = (Account_Name:string, Account_UPNSuffix:string, account_sid:string){
let account_upn = iff(Account_Name!="" and Account_UPNSuffix != ""
,strcat(Account_Name,"@",Account_UPNSuffix)
,"");
OfficeActivity
| where RecordType == "ExchangeItem" and UserType =="Regular" and Operation !contains "InboxRule"
| where LogonUserSid != MailboxOwnerSid
| where ((account_sid != "" and LogonUserSid =~ account_sid)
or ( account_upn != "" and UserId =~ account_upn ))
};
TLQ_UserActedOnForeignMailbox('{{Account_Name}}', '{{Account_UPNSuffix}}', '{{Account_Sid}}')
Activities:
EnabledByDefault: true
Title: "The user acted on another accounts mailbox"
Content: "The user acted on mailbox {{MailboxOwnerUPN}} {{Count}} time(s)"
Items:
- Id: 1f82f263-d694-469a-9717-1b3edf9d3bb2
Description: This activity lists user's activities on others' mailbox
QueryDefinitions:
SummarizeBy: MailboxOwnerSid, MailboxOwnerUPN

Просмотреть файл

@ -0,0 +1,32 @@
SchemaVersion: 1.0
Provider: Sentinel
Type: KQL
DataTypes:
- DataType: OfficeActivity
RequiredInputFieldsSets:
- - Account_Name
- Account_UPNSuffix
- - Account_Sid
BaseQuery: |-
let ruleChangeRecordTypes = dynamic( ["ExchangeAdmin", "ExchangeItem"]);
let TLQ_UserModifiedinboxRules = (Account_Name: string, Account_UPNSuffix: string, Account_Sid: string){
let upn = iff(Account_Name != "" and Account_UPNSuffix != ""
, strcat(Account_Name, "@", Account_UPNSuffix)
, "");
OfficeActivity
| where RecordType in~ (ruleChangeRecordTypes) and Operation contains "InboxRule"
| where((Account_Sid != "" and LogonUserSid == Account_Sid)
or(upn != "" and UserId == upn )
)
};
TLQ_UserModifiedinboxRules('{{Account_Name}}', '{{Account_UPNSuffix}}', '{{Account_Sid}}')
Activities:
EnabledByDefault: true
Title: "The user modified inbox rules on another accounts mailbox"
Content: "User Modified {{Count}} inbox rules on {{MailboxOwnerUPN}}'s Mailbox"
Items:
- Id: e480efd0-016d-428e-b892-84b9d586d004
Description: User modified inbox rules on a mailbox
QueryDefinitions:
SummarizeBy: MailboxOwnerSid, MailboxOwnerUPN

Просмотреть файл

@ -0,0 +1,34 @@
SchemaVersion: 1.0
Provider: Sentinel
Type: KQL
DataTypes:
- DataType: OfficeActivity
RequiredInputFieldsSets:
- - Account_Name
- Account_UPNSuffix
BaseQuery: |-
let TLQ_UserUploadFiles = (Account_Name:string, Account_UPNSuffix:string){
let upn = strcat(Account_Name,"@",Account_UPNSuffix);
OfficeActivity
| where RecordType =~ "SharePointFileOperation" and Operation in~ ("FileUploaded", "FileDownloaded")
| where upn =~UserId
| extend Subject_File_Directory = tostring(split(OfficeObjectId,SourceFileName)[0]), Op = iff (Operation != "FileUploaded", "uploaded", "downloaded")
| project-rename Source_IP_Address = ClientIP
};
TLQ_UserUploadFiles('{{Account_Name}}', '{{Account_UPNSuffix}}')
Activities:
EnabledByDefault: true
Title: "User {{Op}} files to SharePoint"
Content: "User Uploaded {{Count}} File(s) To SharePoint From {{Source IPAddress}}"
Items:
- Id: 0eabec03-51e7-4909-b0cb-1adc76759e93
Description: This activity lists the user's SharePoint uploads.
QueryDefinitions:
Filter: where Operation =~ "FileUploaded"
SummarizeBy: Source_IP_Address, Op
- Id: df564e7b-bf6d-4dc4-a32d-79b00bd2cc7b
Description: This activity lists the user's SharePoint downloads.
QueryDefinitions:
Filter: where Operation =~ "FileDownloaded"
SummarizeBy: Source_IP_Address, Op

Просмотреть файл

@ -0,0 +1,75 @@
SchemaVersion: 1.0
DataTypes:
- DataType: OfficeActivity
Provider: Sentinel
Type: KQL
RequiredInputFieldsSets:
- - Account_Name
- Account_UPNSuffix
- - Account_AADUserId
BaseQuery: |
let Operations = dynamic(["FileDownloaded", "FileUploaded"]);
let UserOperationToSharePoint = (v_Account_Name:string, v_Account_UPNSuffix:string) {
OfficeActivity
// Select sharepoint activity that is relevant
| where RecordType in~ ('SharePointFileOperation')
| where Operation in~ (Operations)
| extend Account_Name = tostring(split(UserId, '@')[0])
| extend Account_UPNSuffix = tostring(split(UserId, '@')[1])
| where Account_Name =~ v_Account_Name and Account_UPNSuffix =~ v_Account_UPNSuffix
| project TimeGenerated, Account_Name, Account_UPNSuffix, UserId, OfficeId, RecordType, Operation, OrganizationId, UserType, UserKey, OfficeWorkload, OfficeObjectId, ClientIP, ItemType, UserAgent, Site_Url, SourceRelativeUrl, SourceFileName, SourceFileExtension , Start_Time , ElevationTime , TenantId, SourceSystem , Type
};
UserOperationToSharePoint ('{{Account_Name}}','{{Account_UPNSuffix}}')
Insights:
Id: e6cf68e6-1eca-4fbb-9fad-6280f2a9476e
DisplayName: Resource access
Description: |
Provides the count and distinct resource accesses by a given user account
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
TableQuery:
ColumnsDefinitions:
- Header: Resource Type
OutputType: String
- Header: Distinct Resources
OutputType: Number
SupportDeepLink: true
- Header: Total Resources
OutputType: Number
SupportDeepLink: true
- Header: IPAddress(es)
OutputType: string
QueriesDefinitions:
- Filter: "where Operation =~ 'FileUploaded'"
Summarize: "summarize DistinctResources = dcount(SourceFileName), TotalResources = count(SourceFileName), IPAddresses = make_set(ClientIP) by Operation"
Project: "project Title = Operation, DistinctResources, TotalResources, IPAddresses = case(array_length(IPAddresses) == 1, tostring(IPAddresses[0]), array_length(IPAddresses) > 1, 'Many', 'None')"
LinkColumnsDefinitions:
- ProjectedName: DistinctResources
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: TotalResources
Query: "{{BaseQuery}} | {{RowFilter}}"
- Filter: "where Operation =~ 'FileDownloaded'"
Summarize: "summarize DistinctResources = dcount(SourceFileName), TotalResources = count(SourceFileName), IPAddresses = make_set(ClientIP) by Operation"
Project: "project Title = Operation, DistinctResources, TotalResources, IPAddresses = case(array_length(IPAddresses) == 1, tostring(IPAddresses[0]), array_length(IPAddresses) > 1, 'Many', 'None')"
LinkColumnsDefinitions:
- ProjectedName: DistinctResources
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: TotalResources
Query: "{{BaseQuery}} | {{RowFilter}}"
ChartQuery:
Title: "Resource access over time"
DataSets:
- Query: "summarize DistinctResources = dcountif(Operation, Operation =~ 'FileUploaded'), TotalResources = countif(Operation =~ 'FileUploaded') by bin(TimeGenerated, 1h) | extend Legend = 'File Uploads'"
XColumnName: TimeGenerated
YColumnName: TotalResources
LegendColumnName: Legend
- Query: "summarize DistinctResources = dcountif(Operation, Operation =~ 'FileDownloaded'), TotalResources = countif(Operation =~ 'FileDownloaded') by bin(TimeGenerated, 1h) | extend Legend = 'File Downloads'"
XColumnName: TimeGenerated
YColumnName: TotalResources
LegendColumnName: Legend
Type: LineChart
AdditionalQuery:
Text: "See all resource activity"
Query: "where Operation in~ (Operations)"

Просмотреть файл

@ -0,0 +1,99 @@
SchemaVersion: 1.0
DataTypes:
- DataType: SigninLogs
Type: KQL
Provider: Sentinel
BaseQuery: |
let AScoreThresh=3;
let maxAnomalies=3;
let BeforeRange = 14d;
let EndTime=todatetime('{{End_Time_UTC}}');
let StartTime = todatetime('{{Start_Time_UTC}}');
let numDays = tolong((EndTime-StartTime)/1d);
let userData = (v_Account_Name:string, v_Account_UPNSuffix:string, v_Account_AADUserId:string) {
SigninLogs
| where TimeGenerated between ((StartTime-BeforeRange) .. EndTime)
| extend splitUserId=split(UserPrincipalName, '@')
| extend Account_Name = tostring(splitUserId[0]), Account_UPNSuffix = tostring(splitUserId[1])
| where (Account_Name =~ v_Account_Name and Account_UPNSuffix =~ v_Account_UPNSuffix) or UserId =~ v_Account_AADUserId };
userData('{{Account_Name}}', '{{Account_UPNSuffix}}', '{{Account_AADUserId}}')
RequiredInputFieldsSets:
- - Account_Name
- Account_UPNSuffix
- - Account_AADUserId
Insights:
Id: cae8d0aa-aa45-4d53-8d88-17dd64ffd4e4
DisplayName: Anomalously high Azure sign-in result count
Description: Highlight Azure sign-in results by the user principal with anomalously high count compared to those observed in the preceding 14 days.
DefaultTimeRange:
BeforeRange: 1d
AfterRange: 0d
ReferenceTimeRange:
BeforeRange: 14d
TableQuery:
ColumnsDefinitions:
- Header: Result Description
OutputType: String
SupportDeepLink: true
- Header: Expected Count
OutputType: Number
SupportDeepLink: false
- Header: Actual Count
OutputType: Number
SupportDeepLink: false
QueriesDefinitions:
- Filter: |
make-series count() default=0 on TimeGenerated from (StartTime - BeforeRange) to EndTime step 1d by ResultDescription
| extend (anomalies,anomalyScore, expectedCount)=series_decompose_anomalies(count_,AScoreThresh,7,'linefit',numDays, 'ctukey')
| extend count1=count_, TimeGenerated1=TimeGenerated, anomalyScore1=anomalyScore
| mv-apply count1 to typeof(long), TimeGenerated1 to typeof(datetime), anomalyScore1 to typeof(double), anomalies to typeof(long) on (summarize totAnomalies=sumif(abs(anomalies), TimeGenerated1 < StartTime), baseStd=stdevif(count1, TimeGenerated1 < StartTime), baseAvg=avgif(count1, TimeGenerated1 < StartTime), maxCountPost=maxif(count1,TimeGenerated1 >= StartTime), maxAnomalyScorePost = maxif(anomalyScore1, TimeGenerated1 >= StartTime))
| extend count1=count_
| mv-apply count1 to typeof(long), anomalyScore to typeof(double), expectedCount to typeof(double) on ( summarize (dummy, postExpectedCount, postActualCount)=arg_min(abs(anomalyScore - maxAnomalyScorePost), expectedCount, count1) )
| where totAnomalies < maxAnomalies
| extend postAnomalyScore=iff(baseStd == 0 and maxCountPost > tolong(count_[0]),1000.0,maxAnomalyScorePost), postExpectedCount=iff(postExpectedCount < 0,0.0,postExpectedCount)
| where maxAnomalyScorePost > AScoreThresh
| order by maxAnomalyScorePost desc
Summarize: take 1
Project: project ResultDescription, expectedCount=round(postExpectedCount,2), actualCount=postActualCount, anomalyScore=round(postAnomalyScore,2)
LinkColumnsDefinitions:
- ProjectedName: ResultDescription
Query: |
{{BaseQuery}}
| where TimeGenerated between (StartTime .. EndTime)
| where ResultDescription == '{{RowValue_ResultDescription}}'
ChartQuery:
Title: Anomalous sign-in result timeline
DataSets:
- Query: |
make-series count() default=0 on TimeGenerated from (StartTime - BeforeRange) to EndTime step 1d by ResultDescription
| extend (anomalies,anomalyScore, expectedCount)=series_decompose_anomalies(count_,AScoreThresh,7,'linefit',numDays, 'ctukey')
| extend count1=count_, TimeGenerated1=TimeGenerated, anomalyScore1=anomalyScore
| mv-apply count1 to typeof(long), TimeGenerated1 to typeof(datetime), anomalyScore1 to typeof(double), anomalies to typeof(long) on (summarize totAnomalies=sumif(abs(anomalies), TimeGenerated1 < StartTime), baseStd=stdevif(count1, TimeGenerated1 < StartTime), baseAvg=avgif(count1, TimeGenerated1 < StartTime), maxCountPost=maxif(count1,TimeGenerated1 >= StartTime), maxAnomalyScorePost = maxif(anomalyScore1, TimeGenerated1 >= StartTime))
| extend count1=count_
| mv-apply count1 to typeof(long), anomalyScore to typeof(double), expectedCount to typeof(double) on ( summarize (dummy, postExpectedCount, postActualCount)=arg_min(abs(anomalyScore - maxAnomalyScorePost), expectedCount, count1) )
| where totAnomalies < maxAnomalies
| extend postAnomalyScore=iff(baseStd == 0 and maxCountPost > tolong(count_[0]),1000.0,maxAnomalyScorePost), postExpectedCount=iff(postExpectedCount < 0,0.0,round(postExpectedCount,2))
| where maxAnomalyScorePost > AScoreThresh
| order by maxAnomalyScorePost desc
| take 1
| project ResultDescription, TimeGenerated, count_
| mvexpand TimeGenerated, count_
| project todatetime(TimeGenerated), toint(count_), ResultDescription
XColumnName: TimeGenerated
YColumnName: count_
LegendColumnName: ResultDescription
Type: LineChart
AdditionalQuery:
Text: Query all anomalous sign-in results
Query: |
make-series count() default=0 on TimeGenerated from (StartTime - BeforeRange) to EndTime step 1d by ResultDescription
| extend (anomalies,anomalyScore, expectedCount)=series_decompose_anomalies(count_,AScoreThresh,7,'linefit',numDays, 'ctukey')
| extend count1=count_, TimeGenerated1=TimeGenerated, anomalyScore1=anomalyScore
| mv-apply count1 to typeof(long), TimeGenerated1 to typeof(datetime), anomalyScore1 to typeof(double), anomalies to typeof(long) on (summarize totAnomalies=sumif(abs(anomalies), TimeGenerated1 < StartTime), baseStd=stdevif(count1, TimeGenerated1 < StartTime), baseAvg=avgif(count1, TimeGenerated1 < StartTime), maxCountPost=maxif(count1,TimeGenerated1 >= StartTime), maxAnomalyScorePost = maxif(anomalyScore1, TimeGenerated1 >= StartTime))
| extend count1=count_
| mv-apply count1 to typeof(long), anomalyScore to typeof(double), expectedCount to typeof(double) on ( summarize (dummy, postExpectedCount, postActualCount)=arg_min(abs(anomalyScore - maxAnomalyScorePost), expectedCount, count1) )
| where totAnomalies < maxAnomalies
| extend postAnomalyScore=iff(baseStd == 0 and maxCountPost > tolong(count_[0]),1000.0,maxAnomalyScorePost), postExpectedCount=iff(postExpectedCount < 0,0.0,postExpectedCount)
| where maxAnomalyScorePost > AScoreThresh
| order by maxAnomalyScorePost desc
| project ResultDescription, expectedCount=round(postExpectedCount,2), actualCount=postActualCount, anomalyScore=round(postAnomalyScore,2)

Просмотреть файл

@ -0,0 +1,28 @@
SchemaVersion: 1.0
Provider: Sentinel
Type: KQL
DataTypes:
- DataType: SigninLogs
RequiredInputFieldsSets:
- - Account_Name
- Account_UPNSuffix
- - Account_AadUserId
BaseQuery: |-
let SignInsByResource = (Account_Name:string, Account_UPNSuffix:string, Account_AadUserId:string){
let acc_upn = iff(Account_Name != "" and Account_UPNSuffix != "" ,strcat(Account_Name,"@" ,Account_UPNSuffix),"");
SigninLogs
| where (acc_upn != "" and UserPrincipalName =~ acc_upn) or
   (Account_AadUserId != "" and Account_AadUserId =~ UserId) // UserPrincipalName, UserId
| extend shortResourceId = tostring(split(ResourceId,"/")[-1])
};
SignInsByResource('{{Account_Name}}', '{{Account_UPNSuffix}}', '{{Account_AadUserId}}')
Activities:
EnabledByDefault: true
Title: "The user signed in to an Azure resource"
Content: "The user signed in to {{shortResourceId}} {{Count}} time(s)"
Items:
- Id: 1f82f263-d694-469a-9717-1b3edf9d3bb2
Description: This activity lists user's sign ins to Azure Resources
QueryDefinitions:
SummarizeBy: shortResourceId, ResourceId

Просмотреть файл

@ -0,0 +1,272 @@
SchemaVersion: 1.0
DataTypes:
- DataType: SecurityEvent
Type: KQL
Provider: Sentinel
RequiredInputFieldsSets:
- - Account_Name
- Account_NTDomain
BaseQuery: |
let GetAllLogonsForUser = (v_Account_Name:string, v_Account_NTDomain:string){
let AllEvents = SecurityEvent
| extend p_Account_Name = case(
// Handles mixed use scenario of NTDomain\AccountName@UPNSuffix
v_Account_Name has '@' and v_Account_Name has '\\', tostring(split(tostring(split(v_Account_Name, '\\')[1]),'@')[0]),
v_Account_Name has '@', tostring(split(v_Account_Name, '@')[0]),
v_Account_Name has '\\', tostring(split(v_Account_Name, '\\')[1]),
v_Account_Name
)
| extend p_Account_NTDomain = case(
v_Account_NTDomain has '\\', tostring(split(v_Account_NTDomain, '\\')[0]),
// Handles UPN scenario of AccountName@UPNSuffix to pull potential NTDomain from
v_Account_NTDomain has '@', tostring(split(tostring(split(v_Account_NTDomain, '@')[1]),'.')[0]),
v_Account_NTDomain
)
| where EventID in (4624, 4625, 4672)
| where AccountType =~ 'User'
| where TargetUserName =~ p_Account_Name and TargetDomainName =~ p_Account_NTDomain
| extend PassedInAccountName = p_Account_Name, PassedInNTDomain = p_Account_NTDomain, RelatedRowSet = 'AllEvents'
| extend HourOfLogin = hourofday(TimeGenerated), DayNumberofWeek = dayofweek(TimeGenerated)
| extend DayofWeek = case(
DayNumberofWeek == "00:00:00", "Sunday",
DayNumberofWeek == "1.00:00:00", "Monday",
DayNumberofWeek == "2.00:00:00", "Tuesday",
DayNumberofWeek == "3.00:00:00", "Wednesday",
DayNumberofWeek == "4.00:00:00", "Thursday",
DayNumberofWeek == "5.00:00:00", "Friday",
DayNumberofWeek == "6.00:00:00", "Saturday","InvalidTimeStamp")
// map the most common ntstatus codes
| extend StatusDesc = case(
Status =~ "0x80090302", "SEC_E_UNSUPPORTED_FUNCTION",
Status =~ "0x80090308", "SEC_E_INVALID_TOKEN",
Status =~ "0x8009030E", "SEC_E_NO_CREDENTIALS",
Status =~ "0xC0000008", "STATUS_INVALID_HANDLE",
Status =~ "0xC0000017", "STATUS_NO_MEMORY",
Status =~ "0xC0000022", "STATUS_ACCESS_DENIED",
Status =~ "0xC0000034", "STATUS_OBJECT_NAME_NOT_FOUND",
Status =~ "0xC000005E", "STATUS_NO_LOGON_SERVERS",
Status =~ "0xC000006A", "STATUS_WRONG_PASSWORD",
Status =~ "0xC000006D", "STATUS_LOGON_FAILURE",
Status =~ "0xC000006E", "STATUS_ACCOUNT_RESTRICTION",
Status =~ "0xC0000073", "STATUS_NONE_MAPPED",
Status =~ "0xC00000FE", "STATUS_NO_SUCH_PACKAGE",
Status =~ "0xC000009A", "STATUS_INSUFFICIENT_RESOURCES",
Status =~ "0xC00000DC", "STATUS_INVALID_SERVER_STATE",
Status =~ "0xC0000106", "STATUS_NAME_TOO_LONG",
Status =~ "0xC000010B", "STATUS_INVALID_LOGON_TYPE",
Status =~ "0xC000015B", "STATUS_LOGON_TYPE_NOT_GRANTED",
Status =~ "0xC000018B", "STATUS_NO_TRUST_SAM_ACCOUNT",
Status =~ "0xC0000224", "STATUS_PASSWORD_MUST_CHANGE",
Status =~ "0xC0000234", "STATUS_ACCOUNT_LOCKED_OUT",
Status =~ "0xC00002EE", "STATUS_UNFINISHED_CONTEXT_DELETED",
EventID == 4624 or EventID == 4672, "Success",
"See - https://docs.microsoft.com/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55"
)
| extend SubStatusDesc = case(
SubStatus =~ "0x80090325", "SEC_E_UNTRUSTED_ROOT",
SubStatus =~ "0xC0000008", "STATUS_INVALID_HANDLE",
SubStatus =~ "0xC0000022", "STATUS_ACCESS_DENIED",
SubStatus =~ "0xC0000064", "STATUS_NO_SUCH_USER",
SubStatus =~ "0xC000006A", "STATUS_WRONG_PASSWORD",
SubStatus =~ "0xC000006D", "STATUS_LOGON_FAILURE",
SubStatus =~ "0xC000006E", "STATUS_ACCOUNT_RESTRICTION",
SubStatus =~ "0xC000006F", "STATUS_INVALID_LOGON_HOURS",
SubStatus =~ "0xC0000070", "STATUS_INVALID_WORKSTATION",
SubStatus =~ "0xC0000071", "STATUS_PASSWORD_EXPIRED",
SubStatus =~ "0xC0000072", "STATUS_ACCOUNT_DISABLED",
SubStatus =~ "0xC0000073", "STATUS_NONE_MAPPED",
SubStatus =~ "0xC00000DC", "STATUS_INVALID_SERVER_STATE",
SubStatus =~ "0xC0000133", "STATUS_TIME_DIFFERENCE_AT_DC",
SubStatus =~ "0xC000018D", "STATUS_TRUSTED_RELATIONSHIP_FAILURE",
SubStatus =~ "0xC0000193", "STATUS_ACCOUNT_EXPIRED",
SubStatus =~ "0xC0000380", "STATUS_SMARTCARD_WRONG_PIN",
SubStatus =~ "0xC0000381", "STATUS_SMARTCARD_CARD_BLOCKED",
SubStatus =~ "0xC0000382", "STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED",
SubStatus =~ "0xC0000383", "STATUS_SMARTCARD_NO_CARD",
SubStatus =~ "0xC0000384", "STATUS_SMARTCARD_NO_KEY_CONTAINER",
SubStatus =~ "0xC0000385", "STATUS_SMARTCARD_NO_CERTIFICATE",
SubStatus =~ "0xC0000386", "STATUS_SMARTCARD_NO_KEYSET",
SubStatus =~ "0xC0000387", "STATUS_SMARTCARD_IO_ERROR",
SubStatus =~ "0xC0000388", "STATUS_DOWNGRADE_DETECTED",
SubStatus =~ "0xC0000389", "STATUS_SMARTCARD_CERT_REVOKED",
EventID == 4624 or EventID == 4672, "Success",
"See - https://docs.microsoft.com/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55"
)
| project StartTime = TimeGenerated, DayofWeek, HourOfLogin, EventID, Activity, IpAddress, WorkstationName, Computer, TargetUserName, TargetDomainName, ProcessName, SubjectUserName, PrivilegeList, PassedInAccountName, PassedInNTDomain, LogonTypeName, StatusDesc, SubStatusDesc, RelatedRowSet
;
let UserSigninToSystems = AllEvents
| where EventID == 4624
| project-away StatusDesc, SubStatusDesc, PrivilegeList
| summarize Total= count(), max(HourOfLogin), min(HourOfLogin), historical_DayofWeek=make_set(DayofWeek), StartTime=max(StartTime), EndTime = min(StartTime), SourceIP = make_set(IpAddress), SourceHost = make_set(WorkstationName), SubjectUserName = make_set(SubjectUserName), HostLoggedOn = make_set(Computer) by EventID, Activity, TargetDomainName, TargetUserName , ProcessName , LogonTypeName
| extend RelatedRowSet = 'UserSigninToSystems' ;
let UserFailedSigninToSystems = AllEvents
| where EventID == 4625
| project-away PrivilegeList
| summarize Total= count(), max(HourOfLogin), min(HourOfLogin), historical_DayofWeek=make_set(DayofWeek), StartTime=max(StartTime), EndTime = min(StartTime), SourceIP = make_set(IpAddress), SourceHost = make_set(WorkstationName), SubjectUserName = make_set(SubjectUserName), HostLoggedOn = make_set(Computer) by EventID, Activity, TargetDomainName, TargetUserName , ProcessName , LogonTypeName
| extend RelatedRowSet = 'UserFailedSigninToSystems' ;
let UserSigninDuringAbnormalHours = AllEvents
| where StartTime between (ago(14d)..ago(2d))
| where EventID in (4624,4625)
| where LogonTypeName in~ ('2 - Interactive','10 - RemoteInteractive')
| summarize max(HourOfLogin), min(HourOfLogin), historical_DayofWeek=make_set(DayofWeek) by TargetUserName
| join kind= inner
(
AllEvents
| where StartTime > ago(2d)
| where LogonTypeName in~ ('2 - Interactive','10 - RemoteInteractive')
)
on TargetUserName
| where HourOfLogin > max_HourOfLogin or HourOfLogin < min_HourOfLogin
| extend historical_DayofWeek = tostring(historical_DayofWeek)
| summarize Total= count(), max(HourOfLogin), min(HourOfLogin), current_DayofWeek =make_set(DayofWeek), StartTime=max(StartTime), EndTime = min(StartTime), SourceIP = make_set(IpAddress), SourceHost = make_set(WorkstationName), SubjectUserName = make_set(SubjectUserName), HostLoggedOn = make_set(Computer) by EventID, Activity, TargetDomainName, TargetUserName , ProcessName , LogonTypeName, StatusDesc, SubStatusDesc, historical_DayofWeek
| extend historical_DayofWeek = todynamic(historical_DayofWeek)
| extend RelatedRowSet = 'UserSigninDuringAbnormalHour';
let UserHadPrivilegedLogonSessions = AllEvents
| where EventID == 4672
| where PrivilegeList contains 'SeDebugPrivilege'
| project-away StatusDesc, SubStatusDesc
| summarize Total= count(), max(HourOfLogin), min(HourOfLogin), historical_DayofWeek=make_set(DayofWeek), StartTime=max(StartTime), EndTime = min(StartTime), SourceIP = make_set(IpAddress), SourceHost = make_set(WorkstationName), SubjectUserName = make_set(SubjectUserName), HostLoggedOn = make_set(Computer) by EventID, Activity, PrivilegeList
// Notice! summarize removes the TimeGenerated field, which is required for Activities.
| extend RelatedRowSet = 'UserHadPrivilegedLogonSessions' ;
union isfuzzy=true AllEvents, UserSigninToSystems, UserFailedSigninToSystems, UserSigninDuringAbnormalHours, UserHadPrivilegedLogonSessions
};
// change {{Account_Name}} value below to the username you are interested in and {{Account_NTDomain}} to the domain of the user you are interested in
GetAllLogonsForUser('{{Account_Name}}', '{{Account_NTDomain}}')
Insights:
Id: 8d209299-cb14-4f22-b5c5-6813f2d1ed2e
DisplayName: Windows sign-in activity
Description: |
Summary of successful and failed sign-ins along with anamalous sign-in patterns for the specific user. Successful sign-ins currently only include interactive and limited to LogonType 2 and 10.
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
TableQuery:
ColumnsDefinitions:
- Header: "Signin Type"
OutputType: String
- Header: TotalLogons
OutputType: Number
SupportDeepLink: true
- Header: HostCount
OutputType: Number
SupportDeepLink: true
QueriesDefinitions:
# UserSigninToSystems
- Filter: "where RelatedRowSet =~ 'UserSigninToSystems' | extend NumberOfHostsLoggedOn = array_length(HostLoggedOn) "
Summarize: "summarize TotalLogons = sum(Total), HostCount = sum(NumberOfHostsLoggedOn)"
Project: "project Title = 'Successful Signins', TotalLogons, HostCount"
LinkColumnsDefinitions:
- ProjectedName: TotalLogons
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: HostCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# UserFailedSigninToSystems
- Filter: "where RelatedRowSet =~ 'UserFailedSigninToSystems'| extend NumberOfHostsLoggedOn = array_length(HostLoggedOn) "
Summarize: "summarize TotalLogons = sum(Total), HostCount = sum(NumberOfHostsLoggedOn)"
Project: "project Title = 'Failed Signins', TotalLogons, HostCount"
LinkColumnsDefinitions:
- ProjectedName: TotalLogons
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: HostCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# UserSigninDuringAbnormalHours
- Filter: "where RelatedRowSet =~ 'UserSigninDuringAbnormalHour' | extend NumberOfHostsLoggedOn = array_length(HostLoggedOn)"
Summarize: "summarize TotalLogons = sum(Total), HostCount = sum(NumberOfHostsLoggedOn)"
Project: "project Title = 'Abnormal Time Signins', TotalLogons, HostCount"
LinkColumnsDefinitions:
- ProjectedName: TotalLogons
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: HostCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# UserHadPrivilegedLogonSessions
- Filter: "where RelatedRowSet =~ 'UserHadPrivilegedLogonSessions' | extend NumberOfHostsLoggedOn = array_length(HostLoggedOn) "
Summarize: "summarize TotalLogons = sum(Total), HostCount = sum(NumberOfHostsLoggedOn)"
Project: "project Title = 'Privileged Signins', TotalLogons, HostCount"
LinkColumnsDefinitions:
- ProjectedName: TotalLogons
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: HostCount
Query: "{{BaseQuery}} | {{RowFilter}}"
ChartQuery:
Title: "Sign-ins over time"
DataSets:
- Query: "summarize Count=countif(EventID==4624) by Time = bin(StartTime, 1h) | extend Legend = 'Success'"
XColumnName: "Time"
YColumnName: "Count"
LegendColumnName: "Legend"
- Query: "summarize Count=countif(EventID==4625) by Time = bin(StartTime, 1h) | extend Legend = 'Failed'"
XColumnName: "Time"
YColumnName: "Count"
LegendColumnName: "Legend"
Type: LineChart
AdditionalQuery:
Text: "See all Windows sign-ins"
Query: "where RelatedRowSet =~ 'AllEvents' | where EventID in (4624,4625,4672) | extend SubjectUserName = columnifexists('SubjectUserName', 'EventDoesNotContain') | summarize Total= count(), max(HourOfLogin), min(HourOfLogin), historical_DayofWeek=make_set(DayofWeek), StartTime=max(StartTime), EndTime = min(StartTime), SourceIP = make_set(IpAddress), SourceHost = make_set(WorkstationName), SubjectUserName = make_set(SubjectUserName), HostLoggedOn = make_set(Computer) by Activity, TargetDomainName, TargetUserName, ProcessName, LogonTypeName | extend NumberOfHostsLoggedOn = array_length(HostLoggedOn)"
Activities:
EnabledByDefault: true
Title: "{{LogonTypeName}} log-ins to a host"
Content: "The user {{v_Account_Name}} logged on to host {{WorkstationName}} {{Count}} time(s)"
Items:
- Id: 0d4ec12e-e44a-40a4-bb87-3db84d2a8057
Title: "{{LogonTypeName}} log-ins to a host"
Content: "The user {{v_Account_Name}} logged on to host {{WorkstationName}} {{Count}} time(s)"
Description: "This activity lists the user's interactive log-ins grouped by Host."
QueryDefinitions:
Filter: "where RelatedRowSet == 'UserSigninToSystems' and LogonTypeName == '2 - Interactive' | extend TimeGenerated=StartTime"
SummarizeBy: "Computer, WorkstationName, LogonTypeName"
- Id: c9da5786-6c3c-45b5-9a46-53200ed9df09
Title: "{{LogonTypeName}} log-ins to a host"
Content: "The user {{v_Account_Name}} logged on to host {{WorkstationName}} {{Count}} time(s)"
Description: "This activity lists the user's network log-ins, grouped by Host."
QueryDefinitions:
Filter: "where RelatedRowSet == 'UserSigninToSystems' and LogonTypeName == '3 - Network' | extend TimeGenerated=StartTime"
SummarizeBy: "Computer, WorkstationName, LogonTypeName"
- Id: 8a302bfc-00e3-43b3-a516-102fd0cb0dbc
Title: "{{LogonTypeName}} log-ins to a host"
Content: "The user {{v_Account_Name}} logged on to host {{WorkstationName}} {{Count}} time(s)"
Description: "This activity lists the user's remote interactive log-ins, grouped by Host."
QueryDefinitions:
Filter: "where RelatedRowSet == 'UserSigninToSystems' and LogonTypeName == '10 - RemoteInteractive'| extend TimeGenerated=StartTime"
SummarizeBy: "Computer, WorkstationName, LogonTypeName"
- Id: ec87b066-17ad-4f9b-97c2-c2f2ee2d99e0
Title: "{{LogonTypeName}} log-ins to a host"
Content: "The user {{v_Account_Name}} logged on to host {{WorkstationName}} {{Count}} time(s)"
Description: "This activity lists the user's log-ins with new credentials, grouped by Host."
QueryDefinitions:
Filter: "where RelatedRowSet == 'UserSigninToSystems' and LogonTypeName == '9 - NewCredentials'| extend TimeGenerated=StartTime"
SummarizeBy: "Computer, WorkstationName, LogonTypeName"
- Id: e1c4c03c-2b40-47cf-9b8c-49e0a37a6da6
Title: "'Privileged log-ins to a host"
Content: "The user {{v_Account_Name}} logged on to host {{WorkstationName}} {{Count}} time(s)"
Description: "This activity lists the user's privileged log-ins, grouped by Host."
QueryDefinitions:
Filter: "where RelatedRowSet = 'UserHadPrivilegedLogonSessions'"
SummarizeBy: "Computer, WorkstationName, LogonTypeName"
- Id: a6fc3ad9-1a61-41f5-a5e2-bd1f5a6fe44d
Title: "'Failed {{LogonTypeName}}' log-ins to a host"
Content: "The user {{v_Account_Name}} logged on to host {{WorkstationName}} {{Count}} time(s)"
Description: "This activity lists the user's failed interactive log-ins grouped by Host."
QueryDefinitions:
Filter: "where RelatedRowSet =~ 'UserFailedSigninToSystems' and LogonTypeName == '2 - Interactive' | extend TimeGenerated=StartTime"
SummarizeBy: "Computer, WorkstationName, LogonTypeName"
- Id: 11449689-6542-4867-86dc-56264abbd90c
Title: "'Failed {{LogonTypeName}}' log-ins to a host"
Content: "The user {{v_Account_Name}} logged on to host {{WorkstationName}} {{Count}} time(s)"
Description: "This activity lists the user's failed network log-ins, grouped by Host."
QueryDefinitions:
Filter: "where RelatedRowSet =~ 'UserFailedSigninToSystems' and LogonTypeName == '3 - Network' | extend TimeGenerated=StartTime"
SummarizeBy: "Computer, WorkstationName, LogonTypeName"
- Id: 686cf7e8-87c7-4391-8898-25adf1033a54
Title: "Failed {{LogonTypeName}} log-ins to a host"
Content: "The user {{v_Account_Name}} failed to logged on to host {{WorkstationName}} {{Count}} time(s)"
Description: "This activity lists the user's failed remote interactive log-ins, grouped by Host."
QueryDefinitions:
Filter: "where RelatedRowSet =~ 'UserFailedSigninToSystems' and LogonTypeName == '10 - RemoteInteractive' | extend TimeGenerated=StartTime"
SummarizeBy: "Computer, WorkstationName, LogonTypeName"

Просмотреть файл

@ -0,0 +1,136 @@
SchemaVersion: 1.0
DataTypes:
- DataType: SecurityEvent
Type: KQL
Provider: Sentinel
EntitiesFilter:
Host_OsFamily:
- Windows
RequiredInputFieldsSets:
- - Host_HostName
- Host_NTDomain
- - Host_HostName
- Host_DnsDomain
- - Host_AzureID
- - Host_OMSAgentID
BaseQuery: |
let GetAccountActions = (v_Host_Name:string, v_Host_NTDomain:string, v_Host_DnsDomain:string, v_Host_AzureID:string, v_Host_OMSAgentID:string){
SecurityEvent
| where EventID in (4725, 4726, 4767, 4720, 4722, 4723, 4724)
// parsing for Host to handle variety of conventions coming from data
| extend Host_HostName = case(
Computer has '@', tostring(split(Computer, '@')[0]),
Computer has '\\', tostring(split(Computer, '\\')[1]),
Computer has '.', tostring(split(Computer, '.')[0]),
Computer
)
| extend Host_NTDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', tostring(split(Computer, '.')[-2]),
Computer
)
| extend Host_DnsDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', strcat_array(array_slice(split(Computer,'.'),-2,-1),'.'),
Computer
)
| where (Host_HostName =~ v_Host_Name and Host_NTDomain =~ v_Host_NTDomain)
or (Host_HostName =~ v_Host_Name and Host_DnsDomain =~ v_Host_DnsDomain)
or v_Host_AzureID =~ _ResourceId
or v_Host_OMSAgentID == SourceComputerId
| project TimeGenerated, EventID, Activity, Computer, TargetAccount, TargetUserName, TargetDomainName, TargetSid, SubjectUserName, SubjectUserSid, _ResourceId, SourceComputerId
| extend AddedBy = SubjectUserName
// Future support for Activities
| extend timestamp = TimeGenerated, HostCustomEntity = Computer, AccountCustomEntity = TargetAccount
};
GetAccountActions('{{Host_HostName}}', '{{Host_NTDomain}}', '{{Host_DnsDomain}}', '{{Host_AzureID}}', '{{Host_OMSAgentID}}')
# The queries for the insights.
Insights:
Id: e29ee1ef-7445-455e-85f1-269f2d536d61
DisplayName: Actions on accounts
Description: |
Summary of actions taken on the specified host, grouped by action: password resets and changes, account lockouts (policy or admin), account creation and deletion, account enabled and disabled
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
TableQuery:
ColumnsDefinitions:
- Header: "Action"
OutputType: String
SupportDeepLink: false
- Header: "NameCount"
OutputType: Number
SupportDeepLink: true
- Header: "SIDCount"
OutputType: Number
SupportDeepLink: true
QueriesDefinitions:
# Account Created or Enabled
- Filter: "where EventID in (4720, 4722)"
Summarize: "summarize NameCount = dcount(TargetAccount), SIDCount = dcount(TargetSid)"
Project: "project Title = 'Created or Enabled', NameCount, SIDCount"
LinkColumnsDefinitions:
- ProjectedName: NameCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: SIDCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# Account Deleted or Disabled
- Filter: "where EventID in (4725, 4726)"
Summarize: "summarize NameCount = dcount(TargetAccount), SIDCount = dcount(TargetSid)"
Project: "project Title = 'Deleted or Disabled', NameCount, SIDCount"
LinkColumnsDefinitions:
- ProjectedName: NameCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: SIDCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# Account Password Changes
- Filter: "where EventID in (4723, 4724)"
Summarize: "summarize NameCount = dcount(TargetAccount), SIDCount = dcount(TargetSid)"
Project: "project Title = 'Password Reset', NameCount, SIDCount"
LinkColumnsDefinitions:
- ProjectedName: NameCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: SIDCount
Query: "{{BaseQuery}} | {{RowFilter}}"
ChartQuery:
Title: "Actions on accounts over time"
DataSets:
- Query: "where EventID in (4720, 4722) | summarize Count = dcount(TargetSid) by bin(TimeGenerated, 1h) | extend Legend = 'Created/Enabled'"
XColumnName: "TimeGenerated"
YColumnName: "Count"
LegendColumnName: "Legend"
- Query: "where EventID in (4725, 4726) | summarize Count = dcount(TargetSid) by bin(TimeGenerated, 1h) | extend Legend = 'Deleted/Disabled'"
XColumnName: "TimeGenerated"
YColumnName: "Count"
LegendColumnName: "Legend"
- Query: "where EventID in (4723, 4724) | summarize Count = dcount(TargetSid) by bin(TimeGenerated, 1h) | extend Legend = 'Reset'"
XColumnName: "TimeGenerated"
YColumnName: "Count"
LegendColumnName: "Legend"
Type: BarChart
AdditionalQuery:
Text: "See all actions on accounts"
Query: "project TimeGenerated, EventID, Activity, Computer, TargetAccount, TargetUserName, TargetDomainName, TargetSid, SubjectUserName, SubjectUserSid, _ResourceId, SourceComputerId, timestamp, HostCustomEntity, AccountCustomEntity | order by TimeGenerated desc"
Activities:
EnabledByDefault: true
Items:
- Id: 307c85ee-39a2-4da3-952e-4fd79aa46d3a
Description: Account created on host
Title: "An account was created on this host"
Content: "On '{{Computer}}' the account '{{TargetAccount}}' was created by '{{AddedBy}}'"
QueryDefinitions:
Filter: where EventID == 4720
SummarizeBy: Computer
- Id: 31529548-dbd2-4d5d-8270-710330cdcec7
Description: Account deleted on host
Title: "An account was deleted on this host"
Content: "On '{{Computer}}' the account '{{TargetAccount}}' was deleted by '{{AddedBy}}'"
QueryDefinitions:
Filter: where EventID == 4726
SummarizeBy: Computer

Просмотреть файл

@ -0,0 +1,180 @@
SchemaVersion: '1.0'
Type: KQL
Provider: Sentinel
DataTypes:
- DataType: SecurityEvent
- DataType: Event
EntitiesFilter:
Host_OsFamily:
- Windows
RequiredInputFieldsSets:
- - Host_HostName
- Host_NTDomain
- - Host_HostName
- Host_DnsDomain
- - Host_AzureID
- - Host_OMSAgentID
BaseQuery: |
let SystemAccount = datatable(AccountName:string)['NT AUTHORITY\\SYSTEM', 'NT AUTHORITY\\NETWORK SERVICE', 'NT AUTHORITY\\LOCAL SERVICE', 'NT AUTHORITY\\IUSR', 'NTAUTHORITY\\ANONYMOUS LOGON'];
let SvcAcctList = dynamic(["Local SYSTEM","Local SERVICE","Network SERVICE","NT AUTHORITY"]);
let ServiceAccount = SecurityEvent
| where EventID == '4624' and LogonType == '5' and not(Account has_any (SvcAcctList))
| extend AccountName = Account
| distinct AccountName;
let MachineAccount = SecurityEvent
| where EventID == '4624' and AccountType == "Machine" and not(Account has_any (SvcAcctList))
| extend AccountName = Account
| distinct AccountName;
let Accounts = union isfuzzy=true SystemAccount, ServiceAccount, MachineAccount;
let source = 'Microsoft-Windows-Eventlog';
let tableFunc = (tableName:string, event:int){
table(tableName)
| where EventID == event
| extend Host_HostName = case(
Computer has '@', tostring(split(Computer, '@')[0]),
Computer has '\\', tostring(split(Computer, '\\')[1]),
Computer has '.', tostring(split(Computer, '.')[0]),
Computer
)
| extend Host_NTDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', tostring(split(Computer, '.')[-2]),
Computer
)
| extend Host_DnsDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', strcat_array(array_slice(split(Computer,'.'),-2,-1),'.'),
Computer
)
| extend SourceComputerId = column_ifexists("SourceComputerId", "NotAvailable"), EventOriginId = column_ifexists("EventOriginId", "NotAvailable")
| parse EventData with * 'SubjectUserName>' SubjectUserName '<' *
| parse EventData with * 'SubjectUserSid>' SubjectUserSid '<' *
| parse EventData with * 'SubjectLogonId>' SubjectLogonId '<' *
| parse EventData with * 'SubjectDomainName>' SubjectDomainName '<' *
| extend SubjectAccount = strcat(SubjectDomainName, '\\', SubjectUserName)
};
let HostClearedEventLog = (v_Host_Name:string, v_Host_NTDomain:string, v_Host_DnsDomain:string, v_Host_AzureID:string, v_Host_OMSAgentID:string)
{
let Event104 = tableFunc('Event', event=104)
| where Source =~ source
| where (Host_HostName =~ v_Host_Name and Host_NTDomain =~ v_Host_NTDomain)
or (Host_HostName =~ v_Host_Name and Host_DnsDomain =~ v_Host_DnsDomain)
or v_Host_AzureID =~ _ResourceId
or v_Host_OMSAgentID == SourceComputerId
| parse RenderedDescription with * 'The' LogName 'log' *
| project TimeGenerated, Computer, EventID, SubjectAccount, SubjectUserName, SubjectDomainName, LogName, SubjectUserSid, SubjectLogonId, SourceComputerId, EventOriginId, _ResourceId
| extend timestamp = TimeGenerated, AccountCustomEntity = SubjectAccount, HostCustomEntity = Computer;
let Event1102 = tableFunc('SecurityEvent', event=1102)
| where EventSourceName == source
| where (Host_HostName =~ v_Host_Name and Host_NTDomain =~ v_Host_NTDomain)
or (Host_HostName =~ v_Host_Name and Host_DnsDomain =~ v_Host_DnsDomain)
or v_Host_AzureID =~ _ResourceId
or v_Host_OMSAgentID == SourceComputerId
| extend LogName = 'Security'
| project TimeGenerated, Computer, EventID, SubjectAccount, SubjectUserName, SubjectDomainName, LogName, SubjectUserSid, SubjectLogonId, SourceComputerId, EventOriginId, _ResourceId
| extend timestamp = TimeGenerated, AccountCustomEntity = SubjectAccount, HostCustomEntity = Computer;
union isfuzzy=true Event104, Event1102
};
HostClearedEventLog('{{Host_HostName}}', '{{Host_NTDomain}}', '{{Host_DnsDomain}}', '{{Host_AzureID}}', '{{Host_OMSAgentID}}')
Insights:
Id: 9a70a72d-25d4-7212-b73e-4f302a90c06a
DisplayName: Event Logs cleared on host
Description: |
'Provides the number of times event logs were cleared on the host.'
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
SingleValuesQuery: {}
TableQuery:
ColumnsDefinitions:
- Header: "Cleared By"
OutputType: String
SupportDeepLink: false
- Header: "Security Log"
OutputType: Number
SupportDeepLink: true
- Header: "Other Logs"
OutputType: Number
SupportDeepLink: true
- Header: "Total"
OutputType: Number
SupportDeepLink: true
QueriesDefinitions:
# LogCleared_User
- Filter: "where SubjectUserName !in (Accounts)"
Summarize: "summarize Security = countif(LogName =~ 'Security'), Other = countif(LogName !~ 'Security'), All = count() by SubjectAccount"
Project: "project SubjectAccount, Security, Other, All"
LinkColumnsDefinitions:
- ProjectedName: Security
Query: "{{BaseQuery}} | {{RowFilter}} | where LogName =~ 'Security'"
- ProjectedName: Other
Query: "{{BaseQuery}} | {{RowFilter}} | where LogName !~ 'Security'"
- ProjectedName: All
Query: "{{BaseQuery}} | {{RowFilter}}"
# LogCleared_System
- Filter: "where AccountCustomEntity in (SystemAccount)"
Summarize: "summarize Security = countif(LogName =~ 'Security'), Other = countif(LogName !~ 'Security'), All = count() by SubjectAccount"
Project: "project SubjectAccount, Security, Other, All"
LinkColumnsDefinitions:
- ProjectedName: Security
Query: "{{BaseQuery}} | {{RowFilter}} | where LogName =~ 'Security'"
- ProjectedName: Other
Query: "{{BaseQuery}} | {{RowFilter}} | where LogName !~ 'Security'"
- ProjectedName: All
Query: "{{BaseQuery}} | {{RowFilter}}"
# LogCleared_Service
- Filter: "where AccountCustomEntity in (ServiceAccount)"
Summarize: "summarize Security = countif(LogName =~ 'Security'), Other = countif(LogName !~ 'Security'), All = count() by SubjectAccount"
Project: "project SubjectAccount, Security, Other, All"
LinkColumnsDefinitions:
- ProjectedName: Security
Query: "{{BaseQuery}} | {{RowFilter}} | where LogName =~ 'Security'"
- ProjectedName: Other
Query: "{{BaseQuery}} | {{RowFilter}} | where LogName !~ 'Security'"
- ProjectedName: All
Query: "{{BaseQuery}} | {{RowFilter}}"
# LogCleared_Machine
- Filter: "where AccountCustomEntity in (MachineAccount)"
Summarize: "summarize Security = countif(LogName =~ 'Security'), Other = countif(LogName !~ 'Security'), All = count() by SubjectAccount"
Project: "project SubjectAccount, Security, Other, All"
LinkColumnsDefinitions:
- ProjectedName: Security
Query: "{{BaseQuery}} | {{RowFilter}} | where LogName =~ 'Security'"
- ProjectedName: Other
Query: "{{BaseQuery}} | {{RowFilter}} | where LogName !~ 'Security'"
- ProjectedName: All
Query: "{{BaseQuery}} | {{RowFilter}}"
ChartQuery:
Title: "Log clear activity over time"
DataSets:
- Query: "summarize LogClearOnHost = count() by Time = bin(TimeGenerated, 12h), SubjectAccount"
XColumnName: Time
YColumnName: LogClearOnHost
LegendColumnName: SubjectAccount
Type: BarChart
AdditionalQuery:
Text: "See all log clear activity"
Query: "project TimeGenerated, LogName, Computer, SubjectAccount, SubjectUserName, SubjectDomainName, EventID, SubjectUserSid, SubjectLogonId, SourceComputerId, _ResourceId, EventOriginId"
Activities:
EnabledByDefault: true
Items:
- Id: 2fcda698-9526-454f-8fe0-4a0fd7af13f2
Description: Security Event log cleared by account
Title: "Security Event log cleared by account on this host"
Content: "On '{{Computer}}' the user '{{SubjectAccount}}' cleared the '{{LogName}}' log, EventID: '{{EventID}}'"
QueryDefinitions:
Filter: where LogName =~ 'Security'
SummarizeBy: SubjectAccount
- Id: 3ff675ee-3052-4e0b-88ad-f34ed1732adc
Description: Event logs cleared by account
Title: "Event log(s) cleared by account on this host"
Content: "On '{{Computer}}' the user '{{SubjectAccount}}' cleared the '{{LogName}}' log, EventID: '{{EventID}}'"
QueryDefinitions:
Filter: where LogName !~ 'Security'
SummarizeBy: SubjectAccount

Просмотреть файл

@ -0,0 +1,149 @@
SchemaVersion: 1.0
DataTypes:
- DataType: SecurityEvent
Type: KQL
Provider: Sentinel
EntitiesFilter:
Host_OsFamily:
- Windows
RequiredInputFieldsSets:
- - Host_HostName
- Host_NTDomain
- - Host_HostName
- Host_DnsDomain
- - Host_AzureID
- - Host_OMSAgentID
BaseQuery: |
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$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-498$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1000$';
let GetGroupAddForHost = (v_Host_Name:string, v_Host_NTDomain:string, v_Host_DnsDomain:string, v_Host_AzureID:string, v_Host_OMSAgentID:string){
SecurityEvent
| where EventID in (4728, 4732, 4756)
// parsing for Host to handle variety of conventions coming from data
| extend Host_HostName = case(
Computer has '@', tostring(split(Computer, '@')[0]),
Computer has '\\', tostring(split(Computer, '\\')[1]),
Computer has '.', tostring(split(Computer, '.')[0]),
Computer
)
| extend Host_NTDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', tostring(split(Computer, '.')[-2]),
Computer
)
| extend Host_DnsDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', strcat_array(array_slice(split(Computer,'.'),-2,-1),'.'),
Computer
)
| where (Host_HostName =~ v_Host_Name and Host_NTDomain =~ v_Host_NTDomain)
or (Host_HostName =~ v_Host_Name and Host_DnsDomain =~ v_Host_DnsDomain)
or v_Host_AzureID =~ _ResourceId
or v_Host_OMSAgentID == SourceComputerId
| extend MemberAdded = case( MemberName has 'CN=', tostring(split(tostring(split(MemberName, ',')[0]),'CN=')[1]), MemberName == '-', MemberSid, MemberName)
| project TimeGenerated, EventID, Activity, Computer, MemberAdded, MemberName, MemberSid, TargetUserName, TargetDomainName, TargetSid, UserPrincipalName, SubjectUserName, SubjectUserSid, WellKnownGroupSID, WellKnownLocalSID, _ResourceId, SourceComputerId
| extend GroupName = TargetUserName, AddedBy = SubjectUserName
//support for Activities
| extend timestamp = TimeGenerated, HostCustomEntity = Computer
};
GetGroupAddForHost('{{Host_HostName}}', '{{Host_NTDomain}}', '{{Host_DnsDomain}}', '{{Host_AzureID}}', '{{Host_OMSAgentID}}')
# The queries for the insights.
Insights:
Id: 44c9d0fe-c131-4080-a34f-da0e349da336
DisplayName: Group additions
Description: |
Summary of user additions to Groups on the specific host. Specifically, we provide the count of All Groups, [Privileged Groups](https://docs.microsoft.com/windows/security/identity-protection/access-control/active-directory-security-groups) and Remote Desktop Users Group.
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
TableQuery:
ColumnsDefinitions:
- Header: "Groups"
OutputType: String
SupportDeepLink: false
- Header: GroupCount
OutputType: Number
SupportDeepLink: true
- Header: UsersAddedCount
OutputType: Number
SupportDeepLink: true
QueriesDefinitions:
# UserAddedToPrivilegedGroups
- Filter: "where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID"
Summarize: "summarize GroupCount = dcount(GroupName), UsersAddedCount = dcount(MemberAdded) by Computer"
Project: "project Title = 'Privileged', GroupCount, UsersAddedCount"
LinkColumnsDefinitions:
- ProjectedName: GroupCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: UsersAddedCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# UserAddedToRemoteDesktopGroup
- Filter: "where TargetSid in ('S-1-5-32-555')"
Summarize: "summarize GroupCount = dcount(GroupName), UsersAddedCount = dcount(MemberAdded)"
Project: "project Title = 'Remote Desktop', GroupCount, UsersAddedCount by Computer"
LinkColumnsDefinitions:
- ProjectedName: GroupCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# UserAddedToPrivilegedGroupsExcludeRDP
- Filter: "where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID | where TargetSid !in ('S-1-5-32-555')"
Summarize: "summarize GroupCount = dcount(GroupName), UsersAddedCount = dcount(MemberAdded) by Computer"
Project: "project Title = 'Privileged(non-RDP)', GroupCount, UsersAddedCount"
LinkColumnsDefinitions:
- ProjectedName: GroupCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# UsersAddedToGroups
- Filter: "order by GroupName"
Summarize: "summarize GroupCount = dcount(GroupName), UsersAddedCount = dcount(MemberAdded) by Comptuer"
Project: "project Title = 'All', GroupCount, UsersAddedCount"
LinkColumnsDefinitions:
- ProjectedName: GroupCount
Query: "{{BaseQuery}}"
ChartQuery:
Title: "Group additions per hour"
DataSets:
- Query: "summarize Count = count() by bin(TimeGenerated, 1h) | extend Legend = 'Total'"
XColumnName: "TimeGenerated"
YColumnName: "Count"
LegendColumnName: "Legend"
Type: LineChart
AdditionalQuery:
Text: "See all group additions"
Query: "project TimeGenerated, EventID, Activity, Computer, MemberAdded, MemberName, MemberSid, TargetUserName, TargetDomainName, TargetSid, UserPrincipalName, SubjectUserName, SubjectUserSid, WellKnownGroupSID, WellKnownLocalSID, _ResourceId, SourceComputerId, timestamp, HostCustomEntity | order by TimeGenerated desc"
Activities:
EnabledByDefault: true
Items:
- Id: b880ad94-f905-4ba8-8a3f-9088b19b12fa
Description: Account added to local Administrators group
Title: "An account was added to the local Administrators group"
Content: "On '{{Computer}}' the user '{{MemberAdded}}' was added by '{{AddedBy}}' to group: '{{GroupName}}'"
QueryDefinitions:
Filter: where TargetSid == 'S-1-5-32-544'
SummarizeBy: Computer
- Id: aaad22c3-be50-465f-b258-8570d629c3db
Description: Account added to the Domain Admins group
Title: "An account was added to the Domain Admins group"
Content: "On '{{Computer}}' the user '{{MemberAdded}}' was added by '{{AddedBy}}' to group: '{{GroupName}}'"
QueryDefinitions:
Filter: where TargetSid matches regex 'S-1-5-21-[0-9]*-[0-9]*-[0-9]*-512$'
SummarizeBy: Computer
- Id: cf3469b3-f64c-4ae2-9900-289617443d74
Description: Account added to the Enterprise Admins group
Title: "An account was added to the Enterprise Admins group"
Content: "On '{{Computer}}' the user '{{MemberAdded}}' was added by '{{AddedBy}}' to group: '{{GroupName}}'"
QueryDefinitions:
Filter: where TargetSid matches regex 'S-1-5-21-[0-9]*-[0-9]*-[0-9]*-519$'
SummarizeBy: Computer
- Id: 5ba7b064-c667-4bb9-b8ac-7e87872ae479
Description: Account added to privileged group.
Title: "Account added to a privileged group"
Content: "On '{{Computer}}' the user '{{MemberAdded}}' was added by '{{AddedBy}}' to group: '{{GroupName}}'"
QueryDefinitions:
Filter: where (TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID) and TargetSid != 'S-1-5-32-544' and not(TargetSid matches regex 'S-1-5-21-[0-9]*-[0-9]*-[0-9]*-512$') and not(TargetSid matches regex 'S-1-5-21-[0-9]*-[0-9]*-[0-9]*-519$')
SummarizeBy: MemberAdded, AddedBy, GroupName

Просмотреть файл

@ -0,0 +1,86 @@
SchemaVersion: '1.0'
Type: KQL
Provider: Sentinel
DataTypes:
- DataType: Syslog
EntitiesFilter:
Host_OsFamily:
- Linux
RequiredInputFieldsSets:
- - Host_HostName
- - Host_AzureID
BaseQuery: |
let AllUserEvents = (v_Host_Name:string, v_Host_AzureID:string) {
Syslog
| where Computer == v_Host_Name or v_Host_AzureID == _ResourceId
| where Facility == 'authpriv'
| where ProcessName in~ ('useradd','userdel')
| where SyslogMessage startswith 'new user:' or SyslogMessage startswith 'delete user '
| extend User = case(SyslogMessage startswith 'new user:', tostring(split(tostring(split(SyslogMessage, 'name=')[1]), ',')[0]),
SyslogMessage startswith 'delete user ', tostring(split(SyslogMessage, "'")[1]),
'Not Available')
| extend Action = case( SyslogMessage startswith 'new user', 'new user', SyslogMessage startswith 'delete user', 'delete user', 'None')
| project TimeGenerated, Computer, HostIP, User, Facility, ProcessName, Action, SyslogMessage, _ResourceId
| extend timestamp = TimeGenerated, HostCustomEntity = Computer, IPCustomEntity = HostIP, AccountCustomEntity = User
};
AllUserEvents('{{Host_HostName}}', '{{Host_AzureID}}')
Insights:
Id: e7144614-84b3-4884-bc14-cba1b9bac0de
DisplayName: Linux new or deleted users on host
Description: |
'Summary of actions taken by Sudo on the specified host grouped by newly created or deleted users'
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
SingleValuesQuery: {}
TableQuery:
ColumnsDefinitions:
- Header: Action
OutputType: String
- Header: User
OutputType: String
- Header: UserCount
OutputType: Number
SupportDeepLink: true
- Header: ActionCount
OutputType: Number
SupportDeepLink: true
QueriesDefinitions:
# UsersCreated
- Filter: "where Action == 'new user'"
Summarize: "summarize UserCount = dcount(User), NewUsers = makeset(User), ActionCount = count() by Computer, Action"
Project: "project Action, User = case(UserCount == 1, tostring(NewUsers[0]), UserCount > 1, 'Many', 'None'), UserCount, ActionCount"
# UsersDeleted
- Filter: "where Action == 'delete user'"
Summarize: "summarize UserCount = dcount(User), DelUsers = makeset(User), ActionCount = count() by Computer, Action"
Project: "project Action, User = case(UserCount == 1, tostring(DelUsers[0]), UserCount > 1, 'Many', 'None'), UserCount, ActionCount"
ChartQuery:
Title: "New or deleted users over time"
DataSets:
- Query: "summarize SudoUsage = count(User) by Time = bin(TimeGenerated, 1h), Action | extend Legend = Action"
XColumnName: Time
YColumnName: SudoUsage
LegendColumnName: Legend
Type: BarChart
AdditionalQuery:
Text: "See all new or deleted users"
Query: "project TimeGenerated, Computer, HostIP, User, Facility, ProcessName, Action, SyslogMessage, _ResourceId, timestamp, HostCustomEntity, AccountCustomEntity, IPCustomEntity"
Activities:
EnabledByDefault: true
Items:
- Id: 290032e9-c52e-4e66-841a-7428f0b356bb
Description: Account created on Host
Title: "An account was created on this host"
Content: "On '{{Computer}}' the account '{{User}}' was created by sudo"
QueryDefinitions:
Filter: where Action == 'new user'
SummarizeBy: Computer
- Id: ce9e87c7-2ffa-42cb-92e5-f1a4f21f007a
Description: Account deleted on Host
Title: "An account was deleted on this host"
Content: "On '{{Computer}}' the account '{{User}}' was deleted by sudo"
QueryDefinitions:
Filter: where Action == 'delete user'
SummarizeBy: Computer

Просмотреть файл

@ -0,0 +1,115 @@
SchemaVersion: '1.0'
Type: KQL
Provider: Sentinel
DataTypes:
- DataType: Syslog
EntitiesFilter:
Host_OsFamily:
- Linux
RequiredInputFieldsSets:
- - Host_HostName
- - Host_AzureID
BaseQuery: |
let AllUserEvents = (v_Host_Name:string, v_Host_AzureID:string) {
Syslog
| where Computer == v_Host_Name or v_Host_AzureID == _ResourceId
| where Facility == 'authpriv'
| where SyslogMessage !startswith "omsagent"
| where SyslogMessage has 'COMMAND' or ProcessName in~ ('gpasswd', 'useradd', 'userdel')
| parse SyslogMessage with * 'user ' User ' ' Verb ' by ' AcctMakingChange ' ' Preposition ' group ' Group
| extend Group = case(
SyslogMessage startswith 'removed group' or SyslogMessage startswith 'removed shadow group', tostring(split(SyslogMessage, "'")[1]),
SyslogMessage startswith 'new group', tostring(split(tostring(split(SyslogMessage, '=')[1]),',')[0]),
Group)
| extend Action = case(
isnotempty(Verb) or isnotempty(Preposition), strcat(Verb, ' ', Preposition),
SyslogMessage startswith 'new group', 'new group',
SyslogMessage startswith 'removed group', 'removed group',
SyslogMessage startswith 'removed shadow group', 'removed shadow group',
'None')
| where isnotempty(Action) and Action != 'None' and isnotempty(Group)
| project TimeGenerated, Computer, HostIP, User, Action, Group, Facility, ProcessName, AcctMakingChange, SyslogMessage, _ResourceId
| extend timestamp = TimeGenerated, HostCustomEntity = Computer, IPCustomEntity = HostIP, AccountCustomEntity = User
};
AllUserEvents('{{Host_HostName}}', '{{Host_AzureID}}')
Insights:
Id: f1607751-8784-4a69-a91b-45b56683bc77
DisplayName: Linux group actions on host
Description: |
'Summary of additions or removals to groups by Sudo on the specified host, specifically the count for Sudo Group, Any group, Group creations and deletions'
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
SingleValuesQuery: {}
TableQuery:
ColumnsDefinitions:
- Header: Action
OutputType: String
- Header: Group(s)
OutputType: String
- Header: GroupCount
OutputType: Number
SupportDeepLink: true
- Header: UserCount
OutputType: Number
SupportDeepLink: true
- Header: User(s)
OutputType: String
QueriesDefinitions:
# UsersAddedtoSudoGroup
- Filter: "where Action =~ 'added to' and Group =~ 'sudo' | extend Action = strcat('users ', Action)"
Summarize: "summarize UserCount = dcount(User), UsersAdded = makeset(User) by Computer, Action"
Project: "project Action, Groups = 'Sudo', GroupCount = 1, UserCount, Users = case(UserCount == 1, tostring(UsersAdded[0]), UserCount > 1, 'Many', 'None')"
# UsersRemovedFromSudoGroup
- Filter: "where Action =~ 'removed from' and Group =~ 'sudo' | extend Action = strcat('users ', Action)"
Summarize: "summarize UserCount = dcount(User), UsersAdded = makeset(User) by Computer, Action"
Project: "project Action, Groups = 'Sudo', GroupCount = 1, UserCount, Users = case(UserCount == 1, tostring(UsersAdded[0]), UserCount > 1, 'Many', 'None')"
# UsersAddedtoAnyGroup
- Filter: "where Action =~ 'added to' | extend Action = strcat('users ', Action)"
Summarize: "summarize GroupCount = dcount(Group), UserCount = dcount(User), UsersAdded = makeset(User) by Computer, Action"
Project: "project Action, Groups = 'Any', GroupCount, UserCount, Users = case(UserCount == 1, tostring(UsersAdded[0]), UserCount > 1, 'Many', 'None')"
# UsersRemovedFromAnyGroup
- Filter: "where Action =~ 'removed from' | extend Action = strcat('users ', Action)"
Summarize: "summarize GroupCount = dcount(Group), UserCount = dcount(User), UsersAdded = makeset(User) by Computer, Action"
Project: "project Action, Groups = 'Any', GroupCount, UserCount, Users = case(UserCount == 1, tostring(UsersAdded[0]), UserCount > 1, 'Many', 'None')"
# GroupAdded
- Filter: "where Action =~ 'new group' | extend Action = strcat(Action, 's created')"
Summarize: "summarize Groups = make_set(Group), GroupCount = dcount(Group), UserCount = dcount(User), UsersAdded = makeset(User) by Computer, Action"
Project: "project Action, Groups = case(GroupCount == 1, tostring(Groups[0]), GroupCount > 1, 'Many', 'None'), GroupCount, UserCount = 0, Users = 'None'"
# GroupDeleted
- Filter: "where Action =~ 'removed group'"
Summarize: "summarize Groups = make_set(Group), GroupCount = dcount(Group), UserCount = dcount(User), UsersAdded = makeset(User) by Computer, Action"
Project: "project Action, Groups = case(GroupCount == 1, tostring(Groups[0]), GroupCount > 1, 'Many', 'None'), GroupCount, UserCount = 0, Users = 'None'"
# ShadowGroupDeleted
- Filter: "where Action =~ 'removed shadow group'"
Summarize: "summarize Groups = make_set(Group), GroupCount = dcount(Group), UserCount = dcount(User), UsersAdded = makeset(User) by Computer, Action"
Project: "project Action, Groups = case(GroupCount == 1, tostring(Groups[0]), GroupCount > 1, 'Many', 'None'), GroupCount, UserCount = 0, Users = 'None'"
ChartQuery:
Title: "Group actions over time"
DataSets:
- Query: "summarize SudoUsage = count(User) by Time = bin(TimeGenerated, 1h), Action | extend Legend = Action"
XColumnName: Time
YColumnName: SudoUsage
LegendColumnName: Legend
Type: BarChart
AdditionalQuery:
Text: "See all group actions"
Query: "project TimeGenerated, Computer, HostIP, User, Action, Group, Facility, ProcessName, AcctMakingChange, SyslogMessage, _ResourceId, timestamp, HostCustomEntity, AccountCustomEntity, IPCustomEntity"
Activities:
EnabledByDefault: true
Items:
- Id: 46aeae2d-187c-41f9-b8d6-9d75c43bce0a
Description: Account added to the sudo group
Title: "An account was added to the sudo group"
Content: "On '{{Computer}}' the user '{{User}}' was added by '{{AcctMakingChange}}' to group: '{{Group}}'"
QueryDefinitions:
Filter: where Action =~ 'added to' and Group =~ 'sudo'
SummarizeBy: Computer
- Id: e24dd437-c65e-40e1-8d59-cd303ad4496a
Description: Account removed from sudo group
Title: "An account was removed from the sudo group"
Content: "On '{{Computer}}' the user '{{User}}' was added by '{{AcctMakingChange}}' to group: '{{Group}}'"
QueryDefinitions:
Filter: where Action =~ 'removed from' and Group =~ 'sudo'
SummarizeBy: Computer

Просмотреть файл

@ -0,0 +1,110 @@
SchemaVersion: '1.0'
Type: KQL
Provider: Sentinel
DataTypes:
- DataType: Syslog
EntitiesFilter:
Host_OsFamily:
- Linux
RequiredInputFieldsSets:
- - Host_HostName
- - Host_AzureID
BaseQuery: |
let SigninResults =
Syslog
| where Facility =~ 'auth'
| where SyslogMessage startswith 'Accepted' or SyslogMessage startswith 'Failed';
let AllSigninResults = (v_Host_Name:string, v_Host_AzureID:string)
{
let HostSpecificResults = SigninResults
| where Computer == v_Host_Name or v_Host_AzureID == _ResourceId;
let AcceptedAuth = HostSpecificResults
| where SyslogMessage startswith 'Accepted';
let LongAuth = AcceptedAuth
| where SyslogMessage has ':'
| parse SyslogMessage with * 'Accepted ' LogonMethod ' for ' User ' from ' ExternalIP ' port ' Port ' ' ConnectionType ':' TrimExtra
| project TimeGenerated = EventTime, HostName, HostIP, User, LogonMethod, ExternalIP, Port, ConnectionType, ProcessName, Result = 'SuccessfulSignin', _ResourceId;
let ShortAuth = AcceptedAuth
| where SyslogMessage !has ':'
| parse SyslogMessage with * 'Accepted ' LogonMethod ' for ' User ' from ' ExternalIP ' port ' Port ' ' ConnectionType
| project TimeGenerated = EventTime, HostName, HostIP, User, LogonMethod, ExternalIP, Port, ConnectionType, ProcessName, Result = 'SuccessfulSignin', _ResourceId;
let InitialFailedAuth = HostSpecificResults
| where SyslogMessage startswith 'Failed';
let FailedAuth = InitialFailedAuth
| where SyslogMessage !has 'invalid'
| parse SyslogMessage with * 'Failed ' LogonMethod ' for ' User ' from ' ExternalIP ' port ' Port ' ' ConnectionType
| project TimeGenerated = EventTime, HostName, HostIP, User, LogonMethod, ExternalIP, Port, ConnectionType, ProcessName, Result = 'FailedSignin', _ResourceId;
let ShortInvalidAuth = InitialFailedAuth
| where SyslogMessage has 'invalid user from'
| parse SyslogMessage with * 'Failed ' LogonMethod ' for invalid user from ' ExternalIP ' port ' Port ' ' ConnectionType
| project TimeGenerated = EventTime, HostName, HostIP, User = ' ', LogonMethod, ExternalIP, Port, ConnectionType, ProcessName, Result = 'InvalidSignin', _ResourceId;
let LongInvalidAuth = InitialFailedAuth
| where SyslogMessage has 'invalid' and SyslogMessage !has ' user from'
| parse SyslogMessage with * 'Failed ' LogonMethod ' for invalid user ' User ' from ' ExternalIP ' port ' Port ' ' ConnectionType
| project TimeGenerated = EventTime, HostName, HostIP, User, LogonMethod, ExternalIP, Port, ConnectionType, ProcessName, Result = 'InvalidSignin', _ResourceId;
union isfuzzy=true LongAuth, ShortAuth, FailedAuth, ShortInvalidAuth, LongInvalidAuth
| extend timestamp = TimeGenerated, HostCustomEntity = HostName, IPCustomEntity = HostIP, AccountCustomEntity = User
};
AllSigninResults('{{Host_HostName}}', '{{Host_AzureID}}')
Insights:
Id: d4ca45db-254b-46f0-98fa-d1d104c26e0c
DisplayName: Linux sign-in activity
Description: |
'Summary of successful, failed or invalid signins, along with most frequent and least frequent signins.'
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
SingleValuesQuery: {}
TableQuery:
ColumnsDefinitions:
- Header: "Signin Result"
OutputType: String
- Header: "Signin Count"
OutputType: Number
SupportDeepLink: true
- Header: "User Count"
OutputType: Number
SupportDeepLink: true
- Header: "User(s)"
OutputType: String
QueriesDefinitions:
# UserSigninsToHost
- Filter: "where Result == 'SuccessfulSignin'"
Summarize: "summarize UserCount = dcount(User), Users = makeset(User), SigninCount = count() by HostName"
Project: "project Title = 'Success', SigninCount = case(UserCount == 0, 0, isempty(SigninCount), 0, SigninCount), UserCount, Users = case(UserCount == 1, tostring(Users[0]), UserCount > 1, 'Many', 'None')"
# UserFailedSigninsToHost
- Filter: "where Result == 'FailedSignin'"
Summarize: "summarize UserCount = dcount(User), Users = makeset(User), SigninCount = count() by HostName"
Project: "project Title = 'Fail', SigninCount = case(UserCount == 0, 0, isempty(SigninCount), 0, SigninCount), UserCount, Users = case(UserCount == 1, tostring(Users[0]), UserCount > 1, 'Many', 'None')"
# InvalidSigninsToHost
- Filter: "where Result == 'InvalidSignin'"
Summarize: "summarize UserCount = dcount(User), Users = makeset(User), SigninCount = count() by HostName"
Project: "project Title = 'Invalid', SigninCount = case(UserCount == 0, 0, isempty(SigninCount), 0, SigninCount), UserCount, Users = case(UserCount == 1, tostring(Users[0]), UserCount > 1, 'Many', 'None')"
# MostFrequent
- Filter: "where Result == 'SuccessfulSignin'"
Summarize: "summarize StartTime=min(TimeGenerated), EndTime = max(TimeGenerated), SigninCount = count() by User | top 1 by SigninCount desc"
Project: "project Title = 'Most Frequent', SigninCount, UserCount = 1, Users = User"
# LeastFrequent
- Filter: "where Result == 'SuccessfulSignin'"
Summarize: "summarize StartTime=min(TimeGenerated), EndTime = max(TimeGenerated), SigninCount = count() by User | top 1 by SigninCount asc"
Project: "project Title = 'Least Frequent', SigninCount, UserCount = 1, Users = User"
ChartQuery:
Title: "Sign-ins over time"
DataSets:
- Query: "where Result == 'SuccessfulSignin' | summarize SigninCount = count() by Time = bin(TimeGenerated, 1h) | extend Legend = 'Success'"
XColumnName: Time
YColumnName: SigninCount
LegendColumnName: Legend
- Query: "where Result == 'FailedSignin' | summarize SigninCount = count() by Time = bin(TimeGenerated, 1h) | extend Legend = 'Fail'"
XColumnName: Time
YColumnName: SigninCount
LegendColumnName: Legend
- Query: "where Result == 'InvalidSignin' | summarize SigninCount = count() by Time = bin(TimeGenerated, 1h) | extend Legend = 'Invalid'"
XColumnName: Time
YColumnName: SigninCount
LegendColumnName: Legend
Type: LineChart
AdditionalQuery:
Text: "See all Linux sign-ins"
Query: "summarize StartTime=min(TimeGenerated), EndTime = max(TimeGenerated), SigninCount = count() by HostName, HostIP, User, LogonMethod, ExternalIP, Port, ConnectionType, ProcessName, Result, _ResourceId, timestamp, HostCustomEntity, AccountCustomEntity, IPCustomEntity"

Просмотреть файл

@ -0,0 +1,83 @@
SchemaVersion: '1.0'
Type: KQL
Provider: Sentinel
DataTypes:
- DataType: Syslog
EntitiesFilter:
Host_OsFamily:
- Linux
RequiredInputFieldsSets:
- - Host_HostName
- - Host_AzureID
BaseQuery: |
let AllUserEvents = (v_Host_Name:string, v_Host_AzureID:string) {
Syslog
| where Computer == v_Host_Name or v_Host_AzureID == _ResourceId
| where Facility == "authpriv"
| where SyslogMessage !startswith "omsagent"
| where SyslogMessage has 'COMMAND'
| parse SyslogMessage with User ' : TTY=' TTY ' PWD=' WorkingDirectory ' USER=' CmdRunAs ' COMMAND=' Commandline
| where User != 'omsagent'
| parse Commandline with Command ' ' *
| extend Command = case(isempty(Command), Commandline, Command)
| project TimeGenerated, Computer, HostIP, User, CmdRunAs, WorkingDirectory, Command, Commandline, SyslogMessage, _ResourceId
| extend timestamp = TimeGenerated, HostCustomEntity = Computer, IPCustomEntity = HostIP, AccountCustomEntity = User
};
AllUserEvents('{{Host_HostName}}', '{{Host_AzureID}}')
Insights:
Id: a9191fbe-ca33-400c-8036-18caac59271c
DisplayName: Linux sudo usage on host
Description: |
'Sudo usage on host by users, most/least events by commands, most/least events by user'
DefaultTimeRange:
BeforeRange: 7d
AfterRange: 7d
SingleValuesQuery: {}
TableQuery:
ColumnsDefinitions:
- Header: "Sudo Usage"
OutputType: String
- Header: User
OutputType: String
- Header: "Usage Count"
OutputType: Number
SupportDeepLink: true
- Header: Command(s)
OutputType: String
SupportDeepLink: true
- Header: "Distinct Commands"
OutputType: Number
SupportDeepLink: true
QueriesDefinitions:
# MostSudoEventsByCommand
- Filter: "project TimeGenerated, Computer, HostIP, User, CmdRunAs, WorkingDirectory, Command, Commandline, SyslogMessage"
Summarize: "summarize User = make_set(User), UserCount = dcount(User), UsageCount = count() by Command | top 1 by UsageCount desc"
Project: "project Title = 'Most by Command', User = case(UserCount == 1, tostring(User[0]), UserCount > 1, 'Many', 'None'), UsageCount = case(UsageCount == 0, 0, UsageCount), Commands = Command, CommandCount = 1"
# LeastSudoEventsByCommand
- Filter: "project TimeGenerated, Computer, HostIP, User, CmdRunAs, WorkingDirectory, Command, Commandline, SyslogMessage"
Summarize: "summarize User = make_set(User), UserCount = dcount(User), UsageCount = count() by Command | top 1 by UsageCount asc"
Project: "project Title = 'Least by Command', User = case(UserCount == 1, tostring(User[0]), UserCount > 1, 'Many', 'None'), UsageCount = case(UsageCount == 0, 0, UsageCount), Commands = Command, CommandCount = 1"
# MostSudoEventsByUser
- Filter: "project TimeGenerated, Computer, HostIP, User, CmdRunAs, WorkingDirectory, Command, Commandline, SyslogMessage"
Summarize: "summarize CommandCount = dcount(Command), Commands = make_set(Command), UsageCount = count() by User | top 1 by UsageCount desc"
Project: "project Title = 'Most by User', User = case(isnotempty(User), User, 'None'), UsageCount = case(UsageCount == 0, 0, UsageCount), Commands = case(CommandCount == 1, tostring(Commands[0]), CommandCount > 1, 'Many', 'None'), CommandCount = case(CommandCount == 0, 0, CommandCount)"
# LeastSudoEventsByUser
- Filter: "project TimeGenerated, Computer, HostIP, User, CmdRunAs, WorkingDirectory, Command, Commandline, SyslogMessage"
Summarize: "summarize CommandCount = dcount(Command), Commands = make_set(Command),UsageCount = count() by User | top 1 by UsageCount asc"
Project: "project Title = 'Least by User', User = case(isnotempty(User), User, 'None'), UsageCount = case(UsageCount == 0, 0, UsageCount), Commands = case(CommandCount == 1, tostring(Commands[0]), CommandCount > 1, 'Many', 'None'), CommandCount = case(CommandCount == 0, 0, CommandCount)"
# AllSudoEvents
- Filter: "project TimeGenerated, Computer, HostIP, User, CmdRunAs, WorkingDirectory, Command, Commandline, SyslogMessage"
Summarize: "summarize CommandCount = dcount(Command), Commands = make_set(Command), User = make_set(User), UserCount = dcount(User), UsageCount = count() by Computer"
Project: "project Title = 'All', User = case(UserCount == 1, tostring(User[0]), UserCount > 1, 'Many', 'None'), UsageCount = case(UsageCount == 0, 0, UsageCount), Commands = case(CommandCount == 1, tostring(Commands[0]), CommandCount > 1, 'Many', 'None'), CommandCount = case(CommandCount == 0, 0, CommandCount)"
ChartQuery:
Title: "Sudo usage over time"
DataSets:
- Query: "summarize UsageCount = count(User) by Time = bin(TimeGenerated, 1h), Command | extend Legend = Command"
XColumnName: Time
YColumnName: UsageCount
LegendColumnName: Legend
Type: BarChart
AdditionalQuery:
Text: "See all sudo usage"
Query: "project TimeGenerated, Computer, HostIP, User, CmdRunAs, WorkingDirectory, Command, Commandline, SyslogMessage, _ResourceId, timestamp, HostCustomEntity, AccountCustomEntity, IPCustomEntity"

Просмотреть файл

@ -0,0 +1,199 @@
SchemaVersion: 1.0
DataTypes:
- DataType: SecurityEvent
Type: KQL
Provider: Sentinel
EntitiesFilter:
Host_OsFamily:
- Windows
RequiredInputFieldsSets:
- - Host_HostName
- Host_NTDomain
- - Host_HostName
- Host_DnsDomain
- - Host_AzureID
- - Host_OMSAgentID
BaseQuery: |
// exclude when over # of machines have the process
let excludeThreshold = 10;
// exclude when more than percent (default 10%)
let ratioHighCount = 0.1;
// exclude when less than percent (default 3%)
let ratioMidCount = 0.03;
// Process count limit in one day, perf improvement (default every minute for 24 hours - 60*24 = 1440)
let procLimit = 60*24;
let SecEvents = SecurityEvent
| where EventID == 4688;
let EntropyCalc = (v_Host_Name:string, v_Host_NTDomain:string, v_Host_DnsDomain:string, v_Host_AzureID:string, v_Host_OMSAgentID:string)
{
let Exclude = SecEvents
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), ExcludeCompCount = dcount(Computer), ExcludeProcCount = count() by Process
// Removing general limit for noise in one day
| extend timediff = iff(datetime_diff('day', EndTime, StartTime) > 0, datetime_diff('day', EndTime, StartTime), 1)
// Default exclude of 1440 (1 per min) or more executions in 24 hours on a given machine
| where ExcludeProcCount < procLimit*timediff
// Removing noisy processes for an environment, adjust as needed
| extend compRatio = ExcludeCompCount/toreal(ExcludeProcCount)
| where compRatio == 0 or (ExcludeCompCount > excludeThreshold and compRatio < ratioHighCount) or (ExcludeCompCount between (2 .. excludeThreshold) and compRatio < ratioMidCount);
let AllSecEvents = SecEvents
// Removing general limit for noise in one day
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), procCount = count() by Computer, Process
| extend timediff = iff(datetime_diff('day', EndTime, StartTime) > 0, datetime_diff('day', EndTime, StartTime), 1)
// Default include the 1440 (1 per min) or more executions in 24 hours on a given machine to remove them from overall comparison list
| where procCount > procLimit*timediff
| join kind= rightanti (
SecEvents | project Computer, Process
) on Computer, Process
| project Computer, Process;
// Removing noisy process from full list
let Include = materialize(Exclude
| join kind= rightanti (
AllSecEvents
) on Process);
// Identifying prevalence for a given process in the environment
let DCwPC = materialize(Include
| summarize DistinctComputersWithProcessCount = dcount(Computer) by Process
| join kind=inner (
Include
) on Process
| distinct Computer, Process, DistinctComputersWithProcessCount);
// Getting the Total process count on each host to use as the denominator in the entropy calc
let TPCoH = materialize(Include
| summarize TotalProcessCountOnHost = count(Process) by Computer
| join kind=inner (
Include
) on Computer
| distinct Computer, Process, TotalProcessCountOnHost
//Getting a decimal value for later computation
| extend TPCoHValue = todecimal(TotalProcessCountOnHost));
// Need the count of each class in my bucket or also said as count of ProcName(Class) per Host(Bucket) for use in the entropy calc
let PCoH = Include
| summarize ProcessCountOnHost = count(Process) by Computer, Process
| join kind=inner (
Include
) on Computer,Process
| distinct Computer, Process, ProcessCountOnHost
//Getting a decimal value for later computation
| extend PCoHValue = todecimal(ProcessCountOnHost);
let Combined = DCwPC
| join (
TPCoH
) on Computer, Process
| join (
PCoH
) on Computer, Process;
let Results = Combined
// Entropy calculation
| extend ProcessEntropy = -log2(PCoHValue/TPCoHValue)*(PCoHValue/TPCoHValue)
| extend NormalizedProcessEntropy = toreal(ProcessEntropy*10000)
// Calculating Weight, see details in description
| extend Weight = toreal((ProcessEntropy*10000)*ProcessCountOnHost*DistinctComputersWithProcessCount)
// Remove or increase value to see processes with low entropy, meaning more common.
| where Weight <= 1000
| project Computer, Process, Weight , ProcessEntropy, TotalProcessCountOnHost, ProcessCountOnHost, DistinctComputersWithProcessCount, NormalizedProcessEntropy;
// Join back full entry
Results
| join kind= inner (
SecEvents
| project TimeGenerated, EventID, Computer, SubjectUserSid, Account, AccountType, Process, NewProcessName, CommandLine, ParentProcessName, _ResourceId, SourceComputerId
) on Computer, Process
| extend Host_HostName = case(
Computer has '@', tostring(split(Computer, '@')[0]),
Computer has '\\', tostring(split(Computer, '\\')[1]),
Computer has '.', tostring(split(Computer, '.')[0]),
Computer
)
| extend Host_NTDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', tostring(split(Computer, '.')[-2]),
Computer
)
| extend Host_DnsDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', strcat_array(array_slice(split(Computer,'.'),-2,-1),'.'),
Computer
)
| where (Host_HostName =~ v_Host_Name and Host_NTDomain =~ v_Host_NTDomain)
or (Host_HostName =~ v_Host_Name and Host_DnsDomain =~ v_Host_DnsDomain)
or v_Host_AzureID =~ _ResourceId
or v_Host_OMSAgentID == SourceComputerId
// removing common items that may still show up in small environments, add here is you have additional exclusions
| where NewProcessName !endswith ':\\Windows\\System32\\conhost.exe' and ParentProcessName !endswith ':\\Windows\\System32\\conhost.exe'
| where ParentProcessName !endswith ':\\Windows\\System32\\wuauclt.exe' and NewProcessName !endswith ':\\Windows\\System32\\wuauclt.exe' and NewProcessName !startswith 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_'
| where ParentProcessName !has ':\\WindowsAzure\\GuestAgent_' and NewProcessName !has ':\\WindowsAzure\\GuestAgent_'
| where ParentProcessName !has ':\\WindowsAzure\\WindowsAzureNetAgent_' and NewProcessName !has ':\\WindowsAzure\\WindowsAzureNetAgent_'
| where ParentProcessName !has ':\\ProgramData\\Microsoft\\Windows Defender\\platform\\'
| where NewProcessName !has ':\\ProgramData\\Microsoft\\Windows Defender\\platform\\'
| where NewProcessName !has ':\\Windows\\Microsoft.NET\\Framework' and not(NewProcessName endswith '\\ngentask.exe' or NewProcessName endswith '\\ngen.exe') and not(ParentProcessName endswith ':\\Windows\\System32\\taskhostw.exe')
| where NewProcessName !endswith '\\MpSigStub.exe' and ParentProcessName !has ':\\Windows\\SoftwareDistribution\\Download\\Install\\'
| where ParentProcessName !has ':\\Program Files\\Microsoft Monitoring Agent\\Agent\\MonitoringHost.exe'
| where NewProcessName !endswith ':\\Windows\\servicing\\trustedinstaller.exe'
| project TimeGenerated, EventID, Computer, SubjectUserSid, Account, AccountType, Weight, NormalizedProcessEntropy, FullDecimalProcessEntropy = ProcessEntropy,
Process, NewProcessName, CommandLine, ParentProcessName, TotalProcessCountOnHost, ProcessCountOnHost, DistinctComputersWithProcessCount, _ResourceId, SourceComputerId
| sort by Weight asc, NormalizedProcessEntropy asc, NewProcessName asc
| extend timestamp = TimeGenerated, HostCustomEntity = Computer, AccountCustomEntity = Account
};
EntropyCalc('{{Host_HostName}}', '{{Host_NTDomain}}', '{{Host_DnsDomain}}', '{{Host_AzureID}}', '{{Host_OMSAgentID}}')
Insights:
Id: 2b59ee1f-5098-4061-a868-e49c39ed2245
DisplayName: Process rarity via entropy calculation
Description: |
Entropy calculation used to help identify Hosts where they have a high variety of processes(a high entropy process list on a given Host over time).
This helps us identify rare processes on a given Host. Rare here means a process shows up on the Host relatively few times in the the last 7days.
The Weight is calculated based on the Entropy, Process Count and Distinct Hosts with that Process. The lower the Weight/ProcessEntropy the, more interesting.
The Weight calculation increases the Weight if the process executes more than once on the Host or has executed on more than 1 Hosts.
In general, this should identify processes on a Host that are rare and rare for the environment. A Weight lower than 100 is considered very rare.
References: https://medium.com/udacity/shannon-entropy-information-gain-and-picking-balls-from-buckets-5810d35d54b4
https://en.wiktionary.org/wiki/Shannon_entropy
DefaultTimeRange:
BeforeRange: 7d
AfterRange: 7d
TableQuery:
ColumnsDefinitions:
- Header: Label
OutputType: String
- Header: "Rare Process(es)"
OutputType: String
- Header: "Entropy Weight"
OutputType: Number
- Header: "Process Count"
OutputType: Number
SupportDeepLink: true
QueriesDefinitions:
# TrulyRareProcess
- Filter: "extend Weight = round(Weight, 3) | where Weight <= 75"
Summarize: "summarize Weight = make_list(Weight), avgWeight = avg(round(Weight, 3)), Process = make_list(Process), ProcessCountOnHost = sum(ProcessCountOnHost) by Computer "
Project: "project Title = 'Rare Process(es)', Process = case(array_length(Process) > 1, 'Many', array_length(Process) == 1, tostring(Process[0]), 'None'), EntropyWeight = case(array_length(Weight) > 1, strcat(tostring(round(avgWeight, 3)), ' avg'), array_length(Weight) == 1, tostring(Weight[0]), 'None'), ProcessCountOnHost = iff(isempty(Process), 0, ProcessCountOnHost)"
# LeastPrevalentProcesses
- Filter: "extend Weight = round(Weight, 3)"
Summarize: "sort by Weight asc, ProcessCountOnHost asc, TimeGenerated desc | take 3 | extend row = row_number()"
Project: "project Title = case(row == 1, strcat(row,'st Least prevalent'), row == 2, strcat(row,'nd Least prevalent'), row == 3, strcat(row,'rd Least prevalent'), 'None'), Process = case(row == 1, Process, row == 2, Process, row == 3, Process, 'None'), EntropyWeight = tostring(case(row == 1, Weight, row == 2, Weight, row == 3, Weight, 0.000)), ProcessCountOnHost = case(row == 1, ProcessCountOnHost, row == 2, ProcessCountOnHost, row == 3, ProcessCountOnHost, 0) | sort by Title"
# ServiceParentProcesses
- Filter: "where ParentProcessName has_any (':\\\\windows\\\\system32\\\\svchost.exe',':\\\\windows\\\\system32\\\\services.exe')"
Summarize: "summarize Process = make_set(Process), Weight = make_set(round(Weight, 3)), avgWeight = avg(round(Weight, 3)), ProcessCountOnHost = count(Process) by Computer"
Project: "project Title = 'Services as Parent', Process = case(array_length(Process) > 1, 'Many', array_length(Process) == 1, tostring(Process[0]), 'None'), EntropyWeight = case(array_length(Weight) > 1, strcat(tostring(round(avgWeight, 3)), ' avg'), array_length(Weight) == 1, tostring(Weight[0]), 'None'), ProcessCountOnHost = iff(isempty(Process), 0, ProcessCountOnHost)"
# MachineExecutions
- Filter: "where AccountType =~ 'machine'"
Summarize: "summarize Weight = make_set(round(Weight, 3)), avgWeight = avg(round(Weight, 3)), Process = make_set(Process), ProcessCountOnHost = count(Process) by Computer"
Project: "project Title = 'Execution by Machine Accounts', Process = case(array_length(Process) > 1, 'Many', array_length(Process) == 1, tostring(Process[0]), 'None'), EntropyWeight = case(array_length(Weight) > 1, strcat(tostring(round(avgWeight, 3)), ' avg'), array_length(Weight) == 1, tostring(Weight[0]), 'None'), ProcessCountOnHost = iff(isempty(Process), 0, ProcessCountOnHost)"
# UserExecutions
- Filter: "where AccountType =~ 'user'"
Summarize: "summarize Weight = make_set(round(Weight, 3)), avgWeight = avg(round(Weight, 3)), Process = make_set(Process), ProcessCountOnHost = count(Process) by Computer"
Project: "project Title = 'Execution by User Accounts', Process = case(array_length(Process) > 1, 'Many', array_length(Process) == 1, tostring(Process[0]), 'None'), EntropyWeight = case(array_length(Weight) > 1, strcat(tostring(round(avgWeight, 3)), ' avg'), array_length(Weight) == 1, tostring(Weight[0]), 'None'), ProcessCountOnHost = iff(isempty(Process), 0, ProcessCountOnHost)"
ChartQuery:
Title: "Rare processes over time (Weight <= 75)"
DataSets:
- Query: "where Weight <= 75 | summarize RareProcessCount = sum(ProcessCountOnHost) by Time = bin(TimeGenerated, 6h), Process"
XColumnName: Time
YColumnName: RareProcessCount
LegendColumnName: Process
Type: BarChart
AdditionalQuery:
Text: "See entropy weight levels 1000 and under"
Query: "where Weight <= 1000"

Просмотреть файл

@ -0,0 +1,124 @@
SchemaVersion: 1.0
DataTypes:
- DataType: SecurityEvent
Provider: Sentinel
Type: KQL
EntitiesFilter:
Host_OsFamily:
- Windows
BaseQuery: |
let AScoreThresh=3;
let maxAnomalies=3;
let BeforeRange = 14d;
let EndTime = todatetime('{{End_Time_UTC}}');
let StartTime = todatetime('{{Start_Time_UTC}}');
let numDays = tolong((EndTime-StartTime)/1d);
let computerData = (v_Host_Name:string, v_Host_NTDomain:string, v_Host_DnsDomain:string, v_Host_AzureID:string, v_Host_OMSAgentID:string) {
SecurityEvent
| extend SourceComputerId = column_ifexists("SourceComputerId", "NotAvailable"), _ResourceId = column_ifexists("_ResourceId", "NotAvailable")
| extend Host_HostName = case(
Computer has '@', tostring(split(Computer, '@')[0]),
Computer has '\\', tostring(split(Computer, '\\')[1]),
Computer has '.', tostring(split(Computer, '.')[0]),
Computer
)
| extend Host_NTDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', tostring(split(Computer, '.')[-2]),
Computer
)
| extend Host_DnsDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', strcat_array(array_slice(split(Computer,'.'),-2,-1),'.'),
Computer
)
| where (Host_HostName =~ v_Host_Name and Host_NTDomain =~ v_Host_NTDomain)
or (Host_HostName =~ v_Host_Name and Host_DnsDomain =~ v_Host_DnsDomain)
or v_Host_AzureID =~ _ResourceId
or v_Host_OMSAgentID == SourceComputerId };
computerData('{{Host_HostName}}', '{{Host_NTDomain}}', '{{Host_DnsDomain}}', '{{Host_AzureID}}', '{{Host_OMSAgentID}}')
RequiredInputFieldsSets:
- - Host_HostName
- Host_NTDomain
- - Host_HostName
- Host_DnsDomain
- - Host_AzureID
- - Host_OMSAgentID
Insights:
Id: 4191a4d7-e72b-4564-b2fb-25580630384b
DisplayName: Anomalously high number of a security event
Description: Highlight security events of the host with anomalously high count compared to those observed in the preceding 14 days.
DefaultTimeRange:
BeforeRange: 1d
AfterRange: 0d
ReferenceTimeRange:
BeforeRange: 14d
TableQuery:
ColumnsDefinitions:
- Header: Activity
OutputType: String
SupportDeepLink: true
- Header: Expected Count
OutputType: Number
SupportDeepLink: false
- Header: Actual Count
OutputType: Number
SupportDeepLink: false
QueriesDefinitions:
- Filter: |
make-series count() default=0 on TimeGenerated from (StartTime - BeforeRange) to EndTime step 1d by Activity
| extend (anomalies,anomalyScore, expectedCount)=series_decompose_anomalies(count_,AScoreThresh,7,'linefit',numDays, 'ctukey')
| extend count1=count_, TimeGenerated1=TimeGenerated, anomalyScore1=anomalyScore
| mv-apply count1 to typeof(long), TimeGenerated1 to typeof(datetime), anomalyScore1 to typeof(double), anomalies to typeof(long) on (summarize totAnomalies=sumif(abs(anomalies), TimeGenerated1 < StartTime), baseStd=stdevif(count1, TimeGenerated1 < StartTime), baseAvg=avgif(count1, TimeGenerated1 < StartTime), maxCountPost=maxif(count1,TimeGenerated1 >= StartTime), maxAnomalyScorePost = maxif(anomalyScore1, TimeGenerated1 >= StartTime))
| extend count1=count_
| mv-apply count1 to typeof(long), anomalyScore to typeof(double), expectedCount to typeof(double) on ( summarize (dummy, postExpectedCount, postActualCount)=arg_min(abs(anomalyScore - maxAnomalyScorePost), expectedCount, count1) )
| where totAnomalies < maxAnomalies
| extend postAnomalyScore=iff(baseStd == 0 and maxCountPost > tolong(count_[0]),1000.0,maxAnomalyScorePost), postExpectedCount=iff(postExpectedCount < 0,0.0,postExpectedCount)
| where maxAnomalyScorePost > AScoreThresh
| order by maxAnomalyScorePost desc
Summarize: take 1
Project: project Activity, expectedCount=round(postExpectedCount,2), actualCount=postActualCount, anomalyScore=round(postAnomalyScore,2)
LinkColumnsDefinitions:
- ProjectedName: Activity
Query: |
{{BaseQuery}}
| where TimeGenerated between (StartTime .. EndTime)
| where Activity == '{{RowValue_Activity}}'
ChartQuery:
Title: Anomalous activity timeline
DataSets:
- Query: |
make-series count() default=0 on TimeGenerated from (StartTime - BeforeRange) to EndTime step 1d by Activity
| extend (anomalies,anomalyScore, expectedCount)=series_decompose_anomalies(count_,AScoreThresh,7,'linefit',numDays, 'ctukey')
| extend count1=count_, TimeGenerated1=TimeGenerated, anomalyScore1=anomalyScore
| mv-apply count1 to typeof(long), TimeGenerated1 to typeof(datetime), anomalyScore1 to typeof(double), anomalies to typeof(long) on (summarize totAnomalies=sumif(abs(anomalies), TimeGenerated1 < StartTime), baseStd=stdevif(count1, TimeGenerated1 < StartTime), baseAvg=avgif(count1, TimeGenerated1 < StartTime), maxCountPost=maxif(count1,TimeGenerated1 >= StartTime), maxAnomalyScorePost = maxif(anomalyScore1, TimeGenerated1 >= StartTime))
| extend count1=count_
| mv-apply count1 to typeof(long), anomalyScore to typeof(double), expectedCount to typeof(double) on ( summarize (dummy, postExpectedCount, postActualCount)=arg_min(abs(anomalyScore - maxAnomalyScorePost), expectedCount, count1) )
| where totAnomalies < maxAnomalies
| extend postAnomalyScore=iff(baseStd == 0 and maxCountPost > tolong(count_[0]),1000.0,maxAnomalyScorePost), postExpectedCount=iff(postExpectedCount < 0,0.0,round(postExpectedCount,2))
| where maxAnomalyScorePost > AScoreThresh
| order by maxAnomalyScorePost desc
| take 1
| project Activity, TimeGenerated, count_
| mvexpand TimeGenerated, count_
| project todatetime(TimeGenerated), toint(count_), Activity
XColumnName: TimeGenerated
YColumnName: count_
LegendColumnName: Activity
Type: LineChart
AdditionalQuery:
Text: Query all anomalous activities
Query: |
make-series count() default=0 on TimeGenerated from (StartTime - BeforeRange) to EndTime step 1d by Activity
| extend (anomalies,anomalyScore, expectedCount)=series_decompose_anomalies(count_,AScoreThresh,7,'linefit',numDays, 'ctukey')
| extend count1=count_, TimeGenerated1=TimeGenerated, anomalyScore1=anomalyScore
| mv-apply count1 to typeof(long), TimeGenerated1 to typeof(datetime), anomalyScore1 to typeof(double), anomalies to typeof(long) on (summarize totAnomalies=sumif(abs(anomalies), TimeGenerated1 < StartTime), baseStd=stdevif(count1, TimeGenerated1 < StartTime), baseAvg=avgif(count1, TimeGenerated1 < StartTime), maxCountPost=maxif(count1,TimeGenerated1 >= StartTime), maxAnomalyScorePost = maxif(anomalyScore1, TimeGenerated1 >= StartTime))
| extend count1=count_
| mv-apply count1 to typeof(long), anomalyScore to typeof(double), expectedCount to typeof(double) on ( summarize (dummy, postExpectedCount, postActualCount)=arg_min(abs(anomalyScore - maxAnomalyScorePost), expectedCount, count1) )
| where totAnomalies < maxAnomalies
| extend postAnomalyScore=iff(baseStd == 0 and maxCountPost > tolong(count_[0]),1000.0,maxAnomalyScorePost), postExpectedCount=iff(postExpectedCount < 0,0.0,postExpectedCount)
| where maxAnomalyScorePost > AScoreThresh
| order by maxAnomalyScorePost desc
| project Activity, expectedCount=round(postExpectedCount,2), actualCount=postActualCount, anomalyScore=round(postAnomalyScore,2)

Просмотреть файл

@ -0,0 +1,121 @@
SchemaVersion: '1.0'
Type: KQL
Provider: Sentinel
DataTypes:
- DataType: SecurityEvent
EntitiesFilter:
Host_OsFamily:
- Windows
RequiredInputFieldsSets:
- - Host_HostName
- Host_NTDomain
- - Host_HostName
- Host_DnsDomain
- - Host_AzureID
- - Host_OMSAgentID
BaseQuery: |
let excludeProc = dynamic([':\\Windows\\System32\\svchost.exe', ':\\Windows\\System32\\sppsvc.exe', ':\\Windows\\system32\\wbem\\WmiApSrv.exe', ':\\Windows\\System32\\conhost.exe', ':\\Windows\\System32\\wuauclt.exe', ':\\Windows\\SoftwareDistribution\\Download\\Install\\', ':\\WindowsAzure\\GuestAgent_', ':\\WindowsAzure\\WindowsAzureNetAgent_',
':\\ProgramData\\Microsoft\\Windows Defender\\platform\\', ':\\Windows\\System32\\taskhostw.exe', '\\MpSigStub.exe',':\\Program Files\\Microsoft Monitoring Agent\\Agent\\MonitoringHost.exe', ':\\Windows\\servicing\\trustedinstaller.exe', ':\\Windows\\System32\\WerFault.exe', ':\\Windows\\CCM\\CcmExec.exe']);
let starttime = todatetime('{{Start_Time_ISO}}');
let endtime = todatetime('{{End_Time_ISO}}');
let includeScope = 1d;
let historicalScope = 14d;
let Procs=(v_Host_Name:string, v_Host_NTDomain:string, v_Host_DnsDomain:string, v_Host_AzureID:string, v_Host_OMSAgentID:string){
let processEvents=SecurityEvent
| where TimeGenerated >= (starttime - historicalScope)
| where EventID==4688
// removing common items that may still show up in small environments, add here if you have additional exclusions
| where not(NewProcessName has_any (excludeProc)) and not(ParentProcessName has_any (excludeProc))
// parsing for Host to handle variety of conventions coming from data
| extend Host_HostName = case(
Computer has '@', tostring(split(Computer, '@')[0]),
Computer has '\\', tostring(split(Computer, '\\')[1]),
Computer has '.', tostring(split(Computer, '.')[0]),
Computer
)
| extend Host_NTDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', tostring(split(Computer, '.')[-2]),
Computer
)
| extend Host_DnsDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', strcat_array(array_slice(split(Computer,'.'),-2,-1),'.'),
Computer
)
| where (Host_HostName =~ v_Host_Name and Host_NTDomain =~ v_Host_NTDomain)
or (Host_HostName =~ v_Host_Name and Host_DnsDomain =~ v_Host_DnsDomain)
or v_Host_AzureID =~ _ResourceId
or v_Host_OMSAgentID == SourceComputerId
| project TimeGenerated, Computer, SubjectAccount, NewProcessName, Process, CommandLine, ParentProcessName, ParentProcess = tostring(split(ParentProcessName, '\\')[-1]), _ResourceId, SourceComputerId;
let processes = materialize(processEvents
| where TimeGenerated >= endtime - includeScope
| join kind=leftanti (
processEvents
| where TimeGenerated between ((starttime - historicalScope) .. (endtime - includeScope))
| distinct NewProcessName
) on NewProcessName
| extend ExecType = 'Processes');
let parents = materialize(processEvents
| where TimeGenerated >= endtime - includeScope
| join kind=leftanti (
processEvents
| where TimeGenerated between ((starttime - historicalScope) .. (endtime - includeScope))
| distinct ParentProcessName
) on ParentProcessName
| extend ExecType = 'Parents');
union isfuzzy=true processes, parents
| extend timestamp = TimeGenerated, HostCustomEntity = Computer, AccountCustomEntity = SubjectAccount
};
Procs('{{Host_HostName}}', '{{Host_NTDomain}}', '{{Host_DnsDomain}}', '{{Host_AzureID}}', '{{Host_OMSAgentID}}')
Insights:
Id: 37a420dc-8d03-4a1f-b579-d96d5c5f5fe4
DisplayName: Windows process execution info
Description: |
'Identifies new processes and new parent processes, along with low or high execution counts.'
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
SingleValuesQuery: {}
TableQuery:
ColumnsDefinitions:
- Header: ProcessType
OutputType: String
- Header: ProcessName
OutputType: Number
SupportDeepLink: true
- Header: Executions
OutputType: Number
SupportDeepLink: true
- Header: User(s)
OutputType: String
QueriesDefinitions:
# NewProcessExec
- Filter: "where ExecType == 'Processes'"
Summarize: "summarize UserCount = dcount(SubjectAccount), Users = make_set(SubjectAccount), Processes = make_set(Process), ProcCount = count()"
Project: "project Title = 'New Process(es)', Processes = case(array_length(Processes) == 1, tostring(Processes[0]), array_length(Processes) > 1, 'Many', 'None'), ProcCount, Users = case(array_length(Users) == 1, tostring(Users[0]), array_length(Users) > 1, 'Many', 'None')"
# NewParentProcessExec
- Filter: "where ExecType == 'Parents'"
Summarize: "summarize UserCount = dcount(SubjectAccount), Users = make_set(SubjectAccount), Processes = make_set(ParentProcess), ProcCount = count()"
Project: "project Title = 'New Parent(s)', Processes = case(array_length(Processes) == 1, tostring(Processes[0]), array_length(Processes) > 1, 'Many', 'None'), ProcCount, Users = case(array_length(Users) == 1, tostring(Users[0]), array_length(Users) > 1, 'Many', 'None')"
# LeastNewProcessExecs
- Filter: "where ExecType == 'Processes' | project TimeGenerated, Computer, SubjectAccount, Process"
Summarize: "summarize UserCount = dcount(SubjectAccount), Users = make_set(SubjectAccount), ProcCount = count() by Process | order by ProcCount, UserCount asc | top 1 by ProcCount"
Project: "project Title = 'Least New Process Executions', Processes = Process, ProcCount, Users = case(array_length(Users) == 1, tostring(Users[0]), array_length(Users) > 1, 'Many', 'None')"
# MostNewProcessExecs
- Filter: "where ExecType == 'Processes' | project TimeGenerated, Computer, SubjectAccount, Process"
Summarize: "summarize UserCount = dcount(SubjectAccount), Users = make_set(SubjectAccount), ProcCount = count() by Process | order by ProcCount, UserCount desc | top 1 by ProcCount"
Project: "project Title = 'Most New Process Executions', Processes = Process, ProcCount, Users = case(array_length(Users) == 1, tostring(Users[0]), array_length(Users) > 1, 'Many', 'None')"
ChartQuery:
Title: "Process executions over time"
DataSets:
- Query: "summarize ProcessCount = count() by Time = bin(TimeGenerated, 1h), Process | extend Legend = Process"
XColumnName: Time
YColumnName: ProcessCount
LegendColumnName: Legend
Type: BarChart
AdditionalQuery:
Text: "See all new process information"
Query: "project TimeGenerated, Computer, SubjectAccount, NewProcessName, Process, CommandLine, ParentProcessName, ParentProcess, ExecType, _ResourceId, SourceComputerId, timestamp, HostCustomEntity, AccountCustomEntity"

Просмотреть файл

@ -0,0 +1,271 @@
SchemaVersion: 1.0
DataTypes:
- DataType: SecurityEvent
Type: KQL
Provider: Sentinel
EntitiesFilter:
Host_OsFamily:
- Windows
RequiredInputFieldsSets:
- - Host_HostName
- Host_NTDomain
- - Host_HostName
- Host_DnsDomain
- - Host_AzureID
- - Host_OMSAgentID
BaseQuery: |
let starttime = todatetime('{{Start_Time_ISO}}');
let endtime = todatetime('{{End_Time_ISO}}');
let includeScope = 2d;
let historicalScope = 8d;
let GetAllLogonsForHost = (v_Host_Name:string, v_Host_NTDomain:string, v_Host_DnsDomain:string, v_Host_AzureID:string, v_Host_OMSAgentID:string){
let DomainAllowList = dynamic(["NT AUTHORITY", "NT SERVICE", "Font Driver Host", "Window Manager"]);
let AccountAllowList = dynamic(["SYSTEM","NETWORK SERVICE", "LOCAL SERVICE"]);
let AllEvents = SecurityEvent
| where TimeGenerated >= (starttime - historicalScope)
| where EventID in (4624, 4625, 4672)
// parsing for Host to handle variety of conventions coming from data
| extend Host_HostName = case(
Computer has '@', tostring(split(Computer, '@')[0]),
Computer has '\\', tostring(split(Computer, '\\')[1]),
Computer has '.', tostring(split(Computer, '.')[0]),
Computer
)
| extend Host_NTDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', tostring(split(Computer, '.')[-2]),
Computer
)
| extend Host_DnsDomain = case(
Computer has '\\', tostring(split(Computer, '\\')[0]),
Computer has '.', strcat_array(array_slice(split(Computer,'.'),-2,-1),'.'),
Computer
)
| where (Host_HostName =~ v_Host_Name and Host_NTDomain =~ v_Host_NTDomain)
or (Host_HostName =~ v_Host_Name and Host_DnsDomain =~ v_Host_DnsDomain)
or v_Host_AzureID =~ _ResourceId
or v_Host_OMSAgentID == SourceComputerId
| extend RelatedRowSet = 'AllEvents'
| extend HourOfLogin = hourofday(TimeGenerated), DayNumberofWeek = dayofweek(TimeGenerated)
| extend DayofWeek = case(
DayNumberofWeek == "00:00:00", "Sunday",
DayNumberofWeek == "1.00:00:00", "Monday",
DayNumberofWeek == "2.00:00:00", "Tuesday",
DayNumberofWeek == "3.00:00:00", "Wednesday",
DayNumberofWeek == "4.00:00:00", "Thursday",
DayNumberofWeek == "5.00:00:00", "Friday",
DayNumberofWeek == "6.00:00:00", "Saturday","InvalidTimeStamp")
// map the most common ntstatus codes
| extend StatusDesc = case(
Status =~ "0x80090302", "SEC_E_UNSUPPORTED_FUNCTION",
Status =~ "0x80090308", "SEC_E_INVALID_TOKEN",
Status =~ "0x8009030E", "SEC_E_NO_CREDENTIALS",
Status =~ "0xC0000008", "STATUS_INVALID_HANDLE",
Status =~ "0xC0000017", "STATUS_NO_MEMORY",
Status =~ "0xC0000022", "STATUS_ACCESS_DENIED",
Status =~ "0xC0000034", "STATUS_OBJECT_NAME_NOT_FOUND",
Status =~ "0xC000005E", "STATUS_NO_LOGON_SERVERS",
Status =~ "0xC000006A", "STATUS_WRONG_PASSWORD",
Status =~ "0xC000006D", "STATUS_LOGON_FAILURE",
Status =~ "0xC000006E", "STATUS_ACCOUNT_RESTRICTION",
Status =~ "0xC0000073", "STATUS_NONE_MAPPED",
Status =~ "0xC00000FE", "STATUS_NO_SUCH_PACKAGE",
Status =~ "0xC000009A", "STATUS_INSUFFICIENT_RESOURCES",
Status =~ "0xC00000DC", "STATUS_INVALID_SERVER_STATE",
Status =~ "0xC0000106", "STATUS_NAME_TOO_LONG",
Status =~ "0xC000010B", "STATUS_INVALID_LOGON_TYPE",
Status =~ "0xC000015B", "STATUS_LOGON_TYPE_NOT_GRANTED",
Status =~ "0xC000018B", "STATUS_NO_TRUST_SAM_ACCOUNT",
Status =~ "0xC0000224", "STATUS_PASSWORD_MUST_CHANGE",
Status =~ "0xC0000234", "STATUS_ACCOUNT_LOCKED_OUT",
Status =~ "0xC00002EE", "STATUS_UNFINISHED_CONTEXT_DELETED",
EventID == 4624 or EventID == 4672, "Success",
"See - https://docs.microsoft.com/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55"
)
| extend SubStatusDesc = case(
SubStatus =~ "0x80090325", "SEC_E_UNTRUSTED_ROOT",
SubStatus =~ "0xC0000008", "STATUS_INVALID_HANDLE",
SubStatus =~ "0xC0000022", "STATUS_ACCESS_DENIED",
SubStatus =~ "0xC0000064", "STATUS_NO_SUCH_USER",
SubStatus =~ "0xC000006A", "STATUS_WRONG_PASSWORD",
SubStatus =~ "0xC000006D", "STATUS_LOGON_FAILURE",
SubStatus =~ "0xC000006E", "STATUS_ACCOUNT_RESTRICTION",
SubStatus =~ "0xC000006F", "STATUS_INVALID_LOGON_HOURS",
SubStatus =~ "0xC0000070", "STATUS_INVALID_WORKSTATION",
SubStatus =~ "0xC0000071", "STATUS_PASSWORD_EXPIRED",
SubStatus =~ "0xC0000072", "STATUS_ACCOUNT_DISABLED",
SubStatus =~ "0xC0000073", "STATUS_NONE_MAPPED",
SubStatus =~ "0xC00000DC", "STATUS_INVALID_SERVER_STATE",
SubStatus =~ "0xC0000133", "STATUS_TIME_DIFFERENCE_AT_DC",
SubStatus =~ "0xC000018D", "STATUS_TRUSTED_RELATIONSHIP_FAILURE",
SubStatus =~ "0xC0000193", "STATUS_ACCOUNT_EXPIRED",
SubStatus =~ "0xC0000380", "STATUS_SMARTCARD_WRONG_PIN",
SubStatus =~ "0xC0000381", "STATUS_SMARTCARD_CARD_BLOCKED",
SubStatus =~ "0xC0000382", "STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED",
SubStatus =~ "0xC0000383", "STATUS_SMARTCARD_NO_CARD",
SubStatus =~ "0xC0000384", "STATUS_SMARTCARD_NO_KEY_CONTAINER",
SubStatus =~ "0xC0000385", "STATUS_SMARTCARD_NO_CERTIFICATE",
SubStatus =~ "0xC0000386", "STATUS_SMARTCARD_NO_KEYSET",
SubStatus =~ "0xC0000387", "STATUS_SMARTCARD_IO_ERROR",
SubStatus =~ "0xC0000388", "STATUS_DOWNGRADE_DETECTED",
SubStatus =~ "0xC0000389", "STATUS_SMARTCARD_CERT_REVOKED",
EventID == 4624 or EventID == 4672, "Success",
"See - https://docs.microsoft.com/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55"
)
| project TimeGenerated, DayofWeek, HourOfLogin, EventID, Activity, IpAddress, WorkstationName, Computer, TargetAccount, TargetUserName, TargetDomainName, ProcessName, SubjectUserName, PrivilegeList, LogonTypeName, StatusDesc, SubStatusDesc, RelatedRowSet, _ResourceId, SourceComputerId
;
let HostSigninToSystems = materialize(AllEvents
| where EventID == 4624
| project-away StatusDesc, SubStatusDesc, PrivilegeList
| summarize SigninCount= count(), max(HourOfLogin), min(HourOfLogin), historical_DayofWeek=make_set(DayofWeek), StartTime=min(TimeGenerated), EndTime = max(TimeGenerated), SourceIP = make_set(IpAddress), SourceHost = make_set(WorkstationName), SubjectUserName = make_set(SubjectUserName) by EventID, Activity, Computer, TargetAccount, TargetDomainName, TargetUserName , ProcessName , LogonTypeName, _ResourceId, SourceComputerId
| extend RelatedRowSet = 'HostSigninToSystems', DomainAllowList = dynamic(["NT AUTHORITY", "NT SERVICE", "Font Driver Host", "Window Manager"]));
let HostFailedSigninToSystems = materialize(AllEvents
| where EventID == 4625
| project-away PrivilegeList
| summarize SigninCount= count(), max(HourOfLogin), min(HourOfLogin), historical_DayofWeek=make_set(DayofWeek), StartTime=min(TimeGenerated), EndTime = max(TimeGenerated), SourceIP = make_set(IpAddress), SourceHost = make_set(WorkstationName), SubjectUserName = make_set(SubjectUserName) by EventID, Activity, Computer, TargetAccount, TargetDomainName, TargetUserName , ProcessName , LogonTypeName, _ResourceId, SourceComputerId
| extend RelatedRowSet = 'HostFailedSigninToSystems');
let HostSigninDuringAbnormalHours = materialize(AllEvents
| where TimeGenerated between ((starttime - historicalScope) .. (endtime - includeScope))
| where EventID in (4624,4625)
| where LogonTypeName in~ ('2 - Interactive','10 - RemoteInteractive')
| summarize max(HourOfLogin), min(HourOfLogin), historical_DayofWeek=make_set(DayofWeek) by Computer, _ResourceId, SourceComputerId
| join kind= inner
(
AllEvents
| where TimeGenerated > endtime - includeScope
| where LogonTypeName in~ ('2 - Interactive','10 - RemoteInteractive')
)
on Computer
| where HourOfLogin > max_HourOfLogin or HourOfLogin < min_HourOfLogin
| extend historical_DayofWeek = tostring(historical_DayofWeek)
| summarize SigninCount= count(), max(HourOfLogin), min(HourOfLogin), current_DayofWeek =make_set(DayofWeek), StartTime=min(TimeGenerated), EndTime = max(TimeGenerated), SourceIP = make_set(IpAddress), SourceHost = make_set(WorkstationName), SubjectUserName = make_set(SubjectUserName) by EventID, Activity, Computer, TargetAccount, TargetDomainName, TargetUserName , ProcessName , LogonTypeName, StatusDesc, SubStatusDesc, historical_DayofWeek, _ResourceId, SourceComputerId
| extend historical_DayofWeek = todynamic(historical_DayofWeek)
| extend RelatedRowSet = 'HostSigninDuringAbnormalHour', DomainAllowList = dynamic(["NT AUTHORITY", "NT SERVICE", "Font Driver Host", "Window Manager"]));
let HostHadPrivilegedLogonSessions = materialize(AllEvents
| where EventID == 4672
| where PrivilegeList contains 'SeDebugPrivilege'
| project-away StatusDesc, SubStatusDesc
| summarize SigninCount= count(), max(HourOfLogin), min(HourOfLogin), historical_DayofWeek=make_set(DayofWeek), StartTime=min(TimeGenerated), EndTime = max(TimeGenerated), SourceIP = make_set(IpAddress), SourceHost = make_set(WorkstationName), SubjectUserName = make_set(SubjectUserName) by EventID, Activity, Computer, TargetAccount, TargetDomainName, TargetUserName, PrivilegeList, _ResourceId, SourceComputerId
| extend RelatedRowSet = 'HostHadPrivilegedLogonSessions', DomainAllowList = dynamic(["NT AUTHORITY", "NT SERVICE", "Font Driver Host", "Window Manager"]));
union isfuzzy=true AllEvents, HostSigninToSystems, HostFailedSigninToSystems, HostSigninDuringAbnormalHours, HostHadPrivilegedLogonSessions
| extend timestamp = StartTime, HostCustomEntity = Computer, AccountCustomEntity = TargetAccount
};
// change {{Host_HostName}} value below to the HostName you are interested in
GetAllLogonsForHost('{{Host_HostName}}', '{{Host_NTDomain}}', '{{Host_DnsDomain}}', '{{Host_AzureID}}', '{{Host_OMSAgentID}}')
# The queries for the insights.
Insights:
Id: 4ecc2229-5cbf-4b04-a2ab-0842c5e4d1cd
DisplayName: Windows sign-in activity
Description: |
Summary of successful and failed sign-ins along with anamalous sign-in patterns for the specific host. Successful sign-ins currently only include interactive and limited to LogonType 2 and 10.
DefaultTimeRange:
BeforeRange: 12h
AfterRange: 12h
TableQuery:
ColumnsDefinitions:
- Header: "Signin Type"
OutputType: String
- Header: "Signin Count"
OutputType: Number
SupportDeepLink: true
- Header: "User Count"
OutputType: Number
SupportDeepLink: true
- Header: "User(s)"
OutputType: String
QueriesDefinitions:
# HostSigninToSystems
- Filter: "where RelatedRowSet =~ 'HostSigninToSystems' and TargetDomainName !in ('NT AUTHORITY', 'NT SERVICE', 'Font Driver Host', 'Window Manager')"
Summarize: "summarize SigninCount = sum(SigninCount), UserCount = dcount(TargetUserName), Users = make_set(TargetUserName)"
Project: "project Title = 'Successful', SigninCount, UserCount, Users = case(array_length(Users) > 1, 'Many', array_length(Users) == 1, tostring(Users[0]), 'None')"
LinkColumnsDefinitions:
- ProjectedName: SigninCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: UserCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# HostFailedSigninToSystems
- Filter: "where RelatedRowSet =~ 'HostFailedSigninToSystems'"
Summarize: "summarize SigninCount= sum(SigninCount), UserCount = dcount(TargetUserName), Users = make_set(TargetUserName)"
Project: "project Title = 'Failed', SigninCount, UserCount, Users = case(array_length(Users) > 1, 'Many', array_length(Users) == 1, tostring(Users[0]), 'None')"
LinkColumnsDefinitions:
- ProjectedName: SigninCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: UserCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# HostSigninDuringAbnormalHours
- Filter: "where RelatedRowSet =~ 'HostSigninDuringAbnormalHour' and TargetDomainName !in ('NT AUTHORITY', 'NT SERVICE', 'Font Driver Host', 'Window Manager')"
Summarize: "summarize SigninCount = sum(SigninCount), UserCount = dcount(TargetUserName), Users = make_set(TargetUserName)"
Project: "project Title = 'Abnormal Time', SigninCount, UserCount, Users = case(array_length(Users) > 1, 'Many', array_length(Users) == 1, tostring(Users[0]), 'None')"
LinkColumnsDefinitions:
- ProjectedName: SigninCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: UserCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# HostHadPrivilegedLogonSessions
- Filter: "where RelatedRowSet =~ 'HostHadPrivilegedLogonSessions' "
Summarize: "summarize SigninCount = sum(SigninCount), UserCount = dcount(TargetUserName), Users = make_set(TargetUserName)"
Project: "project Title = 'Privileged', SigninCount, UserCount, Users = case(array_length(Users) > 1, 'Many', array_length(Users) == 1, tostring(Users[0]), 'None')"
LinkColumnsDefinitions:
- ProjectedName: SigninCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: UserCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# MostFrequent
- Filter: "where RelatedRowSet =~ 'AllEvents' | where EventID == 4624 and TargetDomainName !in ('NT AUTHORITY', 'NT SERVICE', 'Font Driver Host', 'Window Manager')"
Summarize: "summarize StartTime=min(TimeGenerated), EndTime = max(TimeGenerated), SourceIP = make_set(IpAddress), SigninCount = count() by TargetDomainName, TargetUserName, EventID | top 1 by SigninCount desc"
Project: "project Title = 'Most Frequent', SigninCount, UserCount = 1, Users = TargetUserName"
LinkColumnsDefinitions:
- ProjectedName: SigninCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: UserCount
Query: "{{BaseQuery}} | {{RowFilter}}"
# LeastFrequent
- Filter: "where RelatedRowSet =~ 'AllEvents' | where EventID == 4624 and TargetDomainName !in ('NT AUTHORITY', 'NT SERVICE', 'Font Driver Host', 'Window Manager')"
Summarize: "summarize StartTime=min(TimeGenerated), EndTime = max(TimeGenerated), SourceIP = make_set(IpAddress), SigninCount = count() by TargetDomainName, TargetUserName, EventID | top 1 by SigninCount asc"
Project: "project Title = 'Least Frequent', SigninCount, UserCount = 1, Users = TargetUserName"
LinkColumnsDefinitions:
- ProjectedName: SigninCount
Query: "{{BaseQuery}} | {{RowFilter}}"
- ProjectedName: UserCount
Query: "{{BaseQuery}} | {{RowFilter}}"
ChartQuery:
Title: "Sign-ins over time"
DataSets:
- Query: "where RelatedRowSet =~ 'AllEvents' and EventID == 4624 and TargetDomainName !in ('NT AUTHORITY', 'NT SERVICE', 'Font Driver Host', 'Window Manager') | summarize Count=count() by Time = bin(TimeGenerated, 1h) | extend Legend = 'Success'"
XColumnName: "Time"
YColumnName: "Count"
LegendColumnName: "Legend"
- Query: "where RelatedRowSet =~ 'AllEvents' and EventID == 4625 | summarize Count=count() by Time = bin(TimeGenerated, 1h) | extend Legend = 'Failed'"
XColumnName: "Time"
YColumnName: "Count"
LegendColumnName: "Legend"
Type: LineChart
AdditionalQuery:
Text: "See all windows sign-ins"
Query: "where RelatedRowSet =~ 'AllEvents' | extend SubjectUserName = columnifexists('SubjectUserName', 'EventDoesNotContain') | summarize SigninCount= count(), max(HourOfLogin), min(HourOfLogin), historical_DayofWeek=make_set(DayofWeek), StartTime=min(TimeGenerated), EndTime = max(TimeGenerated), SourceIP = make_set(IpAddress), SourceHost = make_set(WorkstationName), SubjectUserName = make_set(SubjectUserName), UserCount = dcount(TargetUserName), Users = make_set(TargetUserName) by Activity, TargetDomainName, TargetUserName, ProcessName, LogonTypeName, timestamp, HostCustomEntity, AccountCustomEntity"
Activities:
EnabledByDefault: true
Items:
- Id: cfba48ac-49dd-4d8a-8a65-70eaf4aafb61
Description: Privileged logon by account
Title: "Privileged logon by account on this host"
Content: "On '{{Computer}}' the user '{{TargetUserName}}' logged on at least once with the SeDebugPrivilege"
QueryDefinitions:
Filter: where RelatedRowSet =~ 'HostHadPrivilegedLogonSessions'
SummarizeBy: TargetUserName
- Id: 8d639acf-f55f-40cd-8ada-bf81b277bb73
Description: Logon outside normal hours
Title: "An account has logged on outside of their normal hours on this host"
Content: "On '{{Computer}}' the user '{{TargetUserName}}' logged on at least once outside of their normal logon hours"
QueryDefinitions:
Filter: where RelatedRowSet =~ 'HostSigninDuringAbnormalHour' and TargetDomainName !in ('NT AUTHORITY', 'NT SERVICE', 'Font Driver Host', 'Window Manager')
SummarizeBy: TargetUserName

1
Insights/readme.md Normal file
Просмотреть файл

@ -0,0 +1 @@
Insights feature preview