diff --git a/Detections/SecurityEvent/gte_6_FailedLogons_10m.yaml b/Detections/SecurityEvent/gte_6_FailedLogons_10m.yaml deleted file mode 100644 index 810aab6cea7..00000000000 --- a/Detections/SecurityEvent/gte_6_FailedLogons_10m.yaml +++ /dev/null @@ -1,134 +0,0 @@ -id: 0777f138-e5d8-4eab-bec1-e11ddfbc2be2 -name: Failed logon attempts by valid accounts within 10 mins -description: | - 'Identifies when failed logon attempts are 20 or higher during a 10 minute period (2 failed logons per minute minimum) from valid account.' -severity: Low -requiredDataConnectors: - - connectorId: SecurityEvents - dataTypes: - - SecurityEvent - - connectorId: WindowsSecurityEvents - dataTypes: - - SecurityEvent - - connectorId: WindowsForwardedEvents - dataTypes: - - WindowsEvent -queryFrequency: 10m -queryPeriod: 10m -triggerOperator: gt -triggerThreshold: 0 -tactics: - - CredentialAccess -relevantTechniques: - - T1110 -query: | - let threshold = 20; - let ReasontoSubStatus = datatable(SubStatus: string, Reason: string) [ - "0xc000005e", "There are currently no logon servers available to service the logon request.", - "0xc0000064", "User logon with misspelled or bad user account", - "0xc000006a", "User logon with misspelled or bad password", - "0xc000006d", "Bad user name or password", - "0xc000006e", "Unknown user name or bad password", - "0xc000006f", "User logon outside authorized hours", - "0xc0000070", "User logon from unauthorized workstation", - "0xc0000071", "User logon with expired password", - "0xc0000072", "User logon to account disabled by administrator", - "0xc00000dc", "Indicates the Sam Server was in the wrong state to perform the desired operation", - "0xc0000133", "Clocks between DC and other computer too far out of sync", - "0xc000015b", "The user has not been granted the requested logon type (aka logon right) at this machine", - "0xc000018c", "The logon request failed because the trust relationship between the primary domain and the trusted domain failed", - "0xc0000192", "An attempt was made to logon, but the Netlogon service was not started", - "0xc0000193", "User logon with expired account", - "0xc0000224", "User is required to change password at next logon", - "0xc0000225", "Evidently a bug in Windows and not a risk", - "0xc0000234", "User logon with account locked", - "0xc00002ee", "Failure Reason: An Error occurred during Logon", - "0xc0000413", "Logon Failure: The machine you are logging onto is protected by an authentication firewall. The specified account is not allowed to authenticate to the machine" - ]; - (union isfuzzy=true - (SecurityEvent - | where EventID == 4625 - | where AccountType =~ "User" - | where SubStatus !~ '0xc0000064' and Account !in ('\\', '-\\-') - // SubStatus '0xc0000064' signifies 'Account name does not exist' - | extend - ResourceId = column_ifexists("_ResourceId", _ResourceId), - SourceComputerId = column_ifexists("SourceComputerId", SourceComputerId), - SubStatus = tolower(SubStatus) - | lookup ReasontoSubStatus on SubStatus - | extend coalesce(Reason, strcat('Unknown reason substatus: ', SubStatus)) - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), FailedLogonCount = count() by bin(TimeGenerated,10m), EventID, - Activity, Computer, Account, TargetAccount, TargetUserName, TargetDomainName, - LogonType, LogonTypeName, LogonProcessName, Status, SubStatus, Reason, ResourceId, SourceComputerId, WorkstationName, IpAddress - | where FailedLogonCount >= threshold - ), - ( - (WindowsEvent - | where EventID == 4625 and not(EventData has '0xc0000064') - | extend TargetAccount = strcat(tostring(EventData.TargetDomainName), "\\", tostring(EventData.TargetUserName)) - | extend TargetUserSid = tostring(EventData.TargetUserSid) - | extend AccountType=case(EventData.TargetUserName endswith "$" or TargetUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(TargetUserSid), "", "User") - | where AccountType =~ "User" - | extend SubStatus = tostring(EventData.SubStatus) - | where SubStatus !~ '0xc0000064' and TargetAccount !in ('\\', '-\\-') - // SubStatus '0xc0000064' signifies 'Account name does not exist' - | extend - ResourceId = column_ifexists("_ResourceId", _ResourceId), - SourceComputerId = column_ifexists("SourceComputerId", ""), - SubStatus = tolower(SubStatus) - | lookup ReasontoSubStatus on SubStatus - | extend coalesce(Reason, strcat('Unknown reason substatus: ', SubStatus)) - | extend Activity="4625 - An account failed to log on." - | extend TargetUserName = tostring(EventData.TargetUserName) - | extend TargetDomainName = tostring(EventData.TargetDomainName) - | extend LogonType = tostring(EventData.LogonType) - | extend Status= tostring(EventData.Status) - | extend LogonProcessName = tostring(EventData.LogonProcessName) - | extend WorkstationName = tostring(EventData.WorkstationName) - | extend IpAddress = tostring(EventData.IpAddress) - | extend LogonTypeName=case( - LogonType == 2, "2 - Interactive", - LogonType == 3, "3 - Network", - LogonType == 4, "4 - Batch", - LogonType == 5, "5 - Service", - LogonType == 7, "7 - Unlock", - LogonType == 8, "8 - NetworkCleartext", - LogonType == 9, "9 - NewCredentials", - LogonType == 10, "10 - RemoteInteractive", - LogonType == 11, "11 - CachedInteractive", - tostring(LogonType) - ) - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), FailedLogonCount = count() by bin(TimeGenerated,10m), EventID, - Activity, Computer, TargetAccount, TargetUserName, TargetDomainName, - LogonType, LogonTypeName, LogonProcessName, Status, SubStatus, Reason, ResourceId, SourceComputerId, WorkstationName, IpAddress - | where FailedLogonCount >= threshold - ))) - | summarize arg_max(TimeGenerated, *) by Computer, TargetUserName, TargetDomainName -entityMappings: - - entityType: Account - fieldMappings: - - identifier: Name - columnName: TargetUserName - - identifier: NTDomain - columnName: TargetDomainName - - entityType: Host - fieldMappings: - - identifier: HostName - columnName: Computer - - identifier: NTDomain - columnName: TargetDomainName - - entityType: IP - fieldMappings: - - identifier: Address - columnName: IpAddress -version: 1.2.4 -kind: Scheduled -metadata: - source: - kind: Community - author: - name: Microsoft Security Research - support: - tier: Community - categories: - domains: [ "Security - Others", "Identity" ] diff --git a/Detections/SigninLogs/AuthenticationAttemptfromNewCountry.yaml b/Detections/SigninLogs/AuthenticationAttemptfromNewCountry.yaml deleted file mode 100644 index a10cb846f66..00000000000 --- a/Detections/SigninLogs/AuthenticationAttemptfromNewCountry.yaml +++ /dev/null @@ -1,105 +0,0 @@ -id: ef895ada-e8e8-4cf0-9313-b1ab67fab69f -name: Authentication Attempt from New Country -description: | - Detects when there is a login attempt from a country that has not seen a successful login in the previous 14 days. - Threat actors may attempt to authenticate with credentials from compromised accounts - monitoring attempts from anomalous locations may help identify these attempts. - Authentication attempts should be investigated to ensure the activity was legitimate and if there is other similar activity. - Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-failed-unusual-sign-ins -severity: Medium -requiredDataConnectors: - - connectorId: AzureActiveDirectory - dataTypes: - - SigninLogs - - AADNonInteractiveUserSignInLogs -queryFrequency: 1d -queryPeriod: 14d -triggerOperator: gt -triggerThreshold: 0 -tactics: - - InitialAccess -relevantTechniques: - - T1078.004 -tags: - - AADSecOpsGuide -query: | - let CombinedSignInLogs = union isfuzzy=True AADNonInteractiveUserSignInLogs, SigninLogs; - // Combine AADNonInteractiveUserSignInLogs and SigninLogs into a single table - // Fetch Azure IP address ranges data from a JSON file hosted on GitHub - let AzureRanges = externaldata(changeNumber: string, cloud: string, values: dynamic) - ["https://raw.githubusercontent.com/microsoft/mstic/master/PublicFeeds/MSFTIPRanges/ServiceTags_Public.json"] with(format='multijson') - // Load Azure IP address ranges from the JSON file hosted on GitHub - | mv-expand values - // Expand the values column into separate rows - | extend Name = values.name, AddressPrefixes = tostring(values.properties.addressPrefixes); - // Create additional columns for the name and address prefixes - // Identify known locations to be excluded from analysis - let ExcludedKnownLocations = CombinedSignInLogs - // Filter the combined logs based on the specified time range - | where TimeGenerated between (ago(14d)..ago(1d)) - // Filter by specific ResultType - | where ResultType == 0 - // Summarize the logs by location - | summarize by Location; - // Find sign-in locations matching specific criteria - let MatchedLocations = materialize(CombinedSignInLogs - // Filter the combined logs based on the specified time range - | where TimeGenerated > ago(1d) - // Exclude specific ResultTypes - | where ResultType !in (50126, 50053, 50074, 70044) - // Exclude known locations - | where Location !in (ExcludedKnownLocations)); - // Match IP addresses of matched locations with Azure IP address ranges - let MatchedIPs = MatchedLocations - // Use the 'ipv4_lookup' function to match IP addresses with Azure IP address ranges - | evaluate ipv4_lookup(AzureRanges, IPAddress, AddressPrefixes) - // Project only the IPAddress column - | project IPAddress; - // Exclude IP addresses that are already matched with Azure IP address ranges - let MaxSetSize = 5; // Set the maximum size limit for make_set - let ExcludedIPs = MatchedLocations - // Filter out IP addresses that are already matched - | where not (IPAddress in (MatchedIPs)) - // Exclude empty or null Location values - | where isnotempty(Location) - // Handle dynamic and string column values for LocationDetails and DeviceDetail - | extend LocationDetails_dynamic = column_ifexists("LocationDetails_dynamic", "") - | extend DeviceDetail_dynamic = column_ifexists("DeviceDetail_dynamic", "") - | extend LocationDetails = iif(isnotempty(LocationDetails_dynamic), LocationDetails_dynamic, parse_json(LocationDetails_string)) - | extend DeviceDetail = iif(isnotempty(DeviceDetail_dynamic), DeviceDetail_dynamic, parse_json(DeviceDetail_string)) - // Extract location details (city and state) - | extend City = tostring(LocationDetails.city) - | extend State = tostring(LocationDetails.state) - | extend Place = strcat(City, " - ", State) - | extend DeviceId = tostring(DeviceDetail.deviceId) - | extend Result = strcat(tostring(ResultType), " - ", ResultDescription) - // Summarize the data based on UserPrincipalName, Location, and Category - | summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), - make_set(Result, MaxSetSize), make_set(IPAddress, MaxSetSize), - make_set(UserAgent, MaxSetSize), make_set(Place, MaxSetSize), - make_set(DeviceId, MaxSetSize) by UserPrincipalName, Location, Category - // Extract the username prefix and suffix from UserPrincipalName - | extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0]); - ExcludedIPs // Output the final result set - | extend IP = set_IPAddress[0] -entityMappings: - - entityType: Account - fieldMappings: - - identifier: Name - columnName: Name - - identifier: UPNSuffix - columnName: UPNSuffix - - entityType: IP - fieldMappings: - - identifier: Address - columnName: IP -version: 1.1.1 -kind: Scheduled -metadata: - source: - kind: Community - author: - name: Microsoft Security Research - support: - tier: Community - categories: - domains: [ "Security - Others" ] diff --git a/Solutions/Amazon Web Services/Analytic Rules/AWS_GuardDuty_template.yaml b/Solutions/Amazon Web Services/Analytic Rules/AWS_GuardDuty_template.yaml deleted file mode 100644 index 691826806a4..00000000000 --- a/Solutions/Amazon Web Services/Analytic Rules/AWS_GuardDuty_template.yaml +++ /dev/null @@ -1,151 +0,0 @@ -id: bf0cde21-0c41-48f6-a40c-6b5bd71fa106 -name: AWS Guard Duty Alert -description: 'Amazon GuardDuty is a threat detection service that continuously monitors your AWS accounts and workloads for malicious activity and delivers detailed security findings for visibility and remediation. This templates create an alert for each Amazon GuardDuty finding.' -severity: Medium -status: Available -requiredDataConnectors: - - connectorId: AWSS3 - dataTypes: - - AWSGuardDuty -queryFrequency: 5h -queryPeriod: 5h -triggerOperator: gt -triggerThreshold: 0 -tactics: [] -relevantTechniques: [] -query: | - // https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings.html - AWSGuardDuty - // Parse the finding - // https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_finding-format.html - // Example: "ThreatPurpose:ResourceTypeAffected/ThreatFamilyName.DetectionMechanism!Artifact" - | extend findingTokens = split(ActivityType, ":") - | extend ThreatPurpose=findingTokens[0], findingTokens=split(findingTokens[1], "/") - | extend ResourceTypeAffected=findingTokens[0], findingTokens= split(findingTokens[1], ".") - | extend ThreatFamilyName=findingTokens[0], findingTokens=split(findingTokens[1], "!") - | extend DetectionMechanism=findingTokens[0], Artifact=findingTokens[1] - // Assign severity level - // https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings.html#guardduty_findings-severity - | extend Severity = - case ( - Severity >= 7.0, "High", - Severity between (4.0 .. 6.9), "Medium", - Severity between (1.0 .. 3.9), "Low", - "Unknown" - ) - // Pull out any available resource details we can extract entities from. These may not exist in the alert. - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_Resource.html - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_AccessKeyDetails.html - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_RdsDbUserDetails.html - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_KubernetesDetails.html - | extend AccessKeyDetails=ResourceDetails.accessKeyDetails - | extend RdsDbUserDetails=ResourceDetails.rdsDbUserDetails - | extend KubernetesDetails=ResourceDetails.kubernetesDetails - // Pull out any available action details we can extract entities from. These may not exist in the alert. - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_Action.html - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_AwsApiCallAction.html - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_KubernetesApiCallAction.html - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_NetworkConnectionAction.html - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_RdsLoginAttemptAction.html - | extend ServiceAction = - case( - isnotempty(ServiceDetails.action.awsApiCallAction), ServiceDetails.action.awsApiCallAction, - isnotempty(ServiceDetails.action.kubernetesApiCallAction), ServiceDetails.action.kubernetesApiCallAction, - isnotempty(ServiceDetails.action.networkConnectionAction), ServiceDetails.action.networkConnectionAction, - isnotempty(ServiceDetails.action.rdsLoginAttemptAction), ServiceDetails.action.rdsLoginAttemptAction, - dynamic(null) - ) - // The IPv4 remote address of the connection - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_RemoteIpDetails.html - // or - // The IP of the Kubernetes API caller and the IPs of any proxies or load balancers between the caller and the API endpoint - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_KubernetesApiCallAction.html - | extend RemoteIpAddress = - coalesce( - tostring(ServiceAction.remoteIpDetails.ipAddressV4), - tostring(parse_json(ServiceAction.sourceIPs)[0]) - ) - // The IPv4 local address of the connection - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_LocalIpDetails.html - | extend LocalIpAddress = ServiceAction.localIpDetails.ipAddressV4 - // The AWS account ID of the remote API caller. - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_AwsApiCallAction.html - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_RemoteAccountDetails.html - | extend RemoteAWSAccountId = ServiceAction.remoteAccountDetails.accountId - // The IAM access key details (user information) of a user that engaged in the activity that prompted GuardDuty to generate a finding - // https://docs.aws.amazon.com/guardduty/latest/APIReference/API_AccessKeyDetails.html - | extend AccountUpn = - case( - AccessKeyDetails.userType == "IAMUser", AccessKeyDetails.userName, - AccessKeyDetails.userType == "AssumedRole", split(AccessKeyDetails.principalId, ":", 1)[0], - isnotempty(RdsDbUserDetails.user), RdsDbUserDetails.user, - isnotempty(KubernetesDetails.kubernetesUserDetails.username), KubernetesDetails.kubernetesUserDetails.username, - "" - ) - | extend AccountName = split(AccountUpn, "@", 0)[0] - | extend UPNSuffix = split(AccountUpn, "@", 1)[0] - // Clean up the output - | extend GuardDutyDetails = - bag_pack( - "DetectorId", ServiceDetails.detectorId, - "Partition", Partition, - "Region", Region - ) - | extend FindingLink = - iff( - isnotempty(Region) and isnotempty(Id), - strcat("https://", Region, ".console.aws.amazon.com/guardduty/home?region=", Region, "#/findings?fId=", Id), - "" - ) - | extend FindingLinkDescription = - iff( - isnotempty(FindingLink), - strcat("Link to GuardDuty finding (AWS): ", FindingLink), - "" - ) - | project-rename - FindingArn=Arn, - FindingId=Id, - AWSAccountId=AccountId - | project-away - ActivityType, - findingTokens, - Partition, - Region, - SchemaVersion, - TimeGenerated, - Type -entityMappings: - - entityType: Account - fieldMappings: - - identifier: Name - columnName: AccountName - - identifier: UPNSuffix - columnName: UPNSuffix - - identifier: ObjectGuid - columnName: RemoteAWSAccountId - - entityType: IP - fieldMappings: - - identifier: Address - columnName: RemoteIpAddress - - entityType: IP - fieldMappings: - - identifier: Address - columnName: LocalIpAddress - - entityType: URL - fieldMappings: - - identifier: URL - columnName: FindingLink -customDetails: - ThreatPurpose: ThreatPurpose - ResourceTypeAffected: ResourceTypeAffected - ThreatFamilyName: ThreatFamilyName - DetectionMechanism: DetectionMechanism - Artifact: Artifact -alertDetailsOverride: - alertDisplayNameFormat: '{{Title}}' - alertDescriptionFormat: '{{Description}}' - alertTacticsColumnName: ThreatPurpose - alertSeverityColumnName: Severity -kind: Scheduled -version: 1.0.4 \ No newline at end of file diff --git a/Solutions/Azure Active Directory/Analytic Rules/ExplicitMFADeny.yaml b/Solutions/Azure Active Directory/Analytic Rules/ExplicitMFADeny.yaml deleted file mode 100644 index 922326a0583..00000000000 --- a/Solutions/Azure Active Directory/Analytic Rules/ExplicitMFADeny.yaml +++ /dev/null @@ -1,58 +0,0 @@ -id: a22740ec-fc1e-4c91-8de6-c29c6450ad00 -name: Explicit MFA Deny -description: | - 'User explicitly denies MFA push, indicating that login was not expected and the account's password may be compromised.' -severity: Medium -requiredDataConnectors: - - connectorId: AzureActiveDirectory - dataTypes: - - SigninLogs - - connectorId: AzureActiveDirectory - dataTypes: - - AADNonInteractiveUserSignInLogs -queryFrequency: 1d -queryPeriod: 1d -triggerOperator: gt -triggerThreshold: 0 -status: Available -tactics: - - CredentialAccess -relevantTechniques: - - T1110 -query: | - let aadFunc = (tableName:string){ - table(tableName) - | where ResultType == 500121 - | where Status has "MFA Denied; user declined the authentication" or Status has "MFA denied; Phone App Reported Fraud" - | extend Type = Type - | extend timestamp = TimeGenerated, Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0]) - }; - let aadSignin = aadFunc("SigninLogs"); - let deviceInfo = DeviceInfo - | where OnboardingStatus == "Onboarded" and SensorHealthState == "Active" - | project PublicIP, AadDeviceId; - let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs"); - union isfuzzy=true aadSignin, aadNonInt - | project-rename PublicIP = IPAddress - | join kind=leftouter deviceInfo on PublicIP -entityMappings: - - entityType: Account - fieldMappings: - - identifier: Name - columnName: Name - - identifier: UPNSuffix - columnName: UPNSuffix - - entityType: IP - fieldMappings: - - identifier: Address - columnName: IPAddress - - entityType: AzureResource - fieldMappings: - - identifier: ResourceID - columnName: ResourceID - - entityType: URL - fieldMappings: - - identifier: Url - columnName: ClientAppUsed -version: 1.0.3 -kind: Scheduled diff --git a/Solutions/DNS Essentials/Analytic Rules/MultipleErrorsReportedForSameDNSQueryStaticThresholdBased.yaml b/Solutions/DNS Essentials/Analytic Rules/MultipleErrorsReportedForSameDNSQueryStaticThresholdBased.yaml deleted file mode 100644 index a0cdb2cc58a..00000000000 --- a/Solutions/DNS Essentials/Analytic Rules/MultipleErrorsReportedForSameDNSQueryStaticThresholdBased.yaml +++ /dev/null @@ -1,72 +0,0 @@ -id: 5b8344eb-fa28-4ac3-bcff-bc19d5d63089 -name: Detect DNS queries reporting multiple errors from different clients - Static threshold based (ASIM DNS Solution) -description: | - 'This rule creates an alert when multiple clients report errors for the same DNS query. This helps in identifying possible similar C2 communications originating from different clients. It utilizes [ASIM](https://aka.ms/AboutASIM) normalization and is applied to any source that supports the ASIM DNS schema.' -severity: Medium -status: Available -tags: - - Schema: ASimDns - SchemaVersion: 0.1.6 -requiredDataConnectors: [] -queryFrequency: 1h -queryPeriod: 1h -triggerOperator: gt -triggerThreshold: 0 -tactics: - - CommandAndControl -relevantTechniques: - - T1568 - - T1573 - - T1008 -query: | - let lookback=1h; - let threshold = 2; - let errors = dynamic(['NXDOMAIN', 'SERVFAIL', 'REFUSED']); - _Im_Dns(starttime=ago(lookback)) - | where EventResultDetails has_any (errors) - | summarize SrcIPs = make_set(SrcIpAddr, 100), Dvcs = make_set(Dvc, 100), ResourceIds = make_set(_ResourceId, 100) by DnsQuery, bin(TimeGenerated, 10min) - | where array_length(SrcIPs) >= threshold - | extend TotalIPs = array_length(SrcIPs),IPCountthreshold = threshold - | extend DomainName = strcat(split(DnsQuery, ".")[1], ".", split(DnsQuery, ".")[2]) - | mv-expand SrcIPs - | extend SrcIP = tostring(SrcIPs) - | mv-expand Dvcs - | extend Dvc = tostring(Dvcs) - | mv-expand ResourceIds - | extend ResourceId = tostring(ResourceIds) - | extend Dvc = strcat(split(Dvc, ".")[0]) - | summarize Start=min(TimeGenerated), End=max(TimeGenerated) by SrcIP, Dvc, ResourceId, DnsQuery, DomainName -entityMappings: - - entityType: DNS - fieldMappings: - - identifier: DomainName - columnName: DnsQuery - - entityType: IP - fieldMappings: - - identifier: Address - columnName: SrcIP - - entityType: AzureResource - fieldMappings: - - identifier: ResourceId - columnName: ResourceId - - entityType: Url - fieldMappings: - - identifier: Url - columnName: DnsQuery - - entityType: Host - fieldMappings: - - identifier: HostName - columnName: Dvc - - identifier: DnsDomain - columnName: DomainName -eventGroupingSettings: - aggregationKind: SingleAlert -customDetails: - SrcIPs: SrcIPs - IPCountthreshold: IPCountthreshold - TotalIPs: TotalIPs -alertDetailsOverride: - alertDisplayNameFormat: "[Static threshold] Multiple errors for the same DNS query has been detected - '{{DnsQuery}}'" - alertDescriptionFormat: "Multiple errors were detected on different clients for the same DNS query. These unsuccessful responses can be an indication of C2 communication. \n\nThreshold for total clients reporting errors: '{{IPCountthreshold}}'\n\nCurrent count of clients reporting errors for this DNS query: '{{TotalIPs}}'\n\nClients requesting this DNSQuery include:\n\n'{{SrcIPs}}'" -version: 1.0.3 -kind: Scheduled \ No newline at end of file diff --git a/Solutions/Threat Intelligence/Analytic Rules/IPEntity_AppServiceHTTPLogs.yaml b/Solutions/Threat Intelligence/Analytic Rules/IPEntity_AppServiceHTTPLogs.yaml deleted file mode 100644 index 254f81bf3af..00000000000 --- a/Solutions/Threat Intelligence/Analytic Rules/IPEntity_AppServiceHTTPLogs.yaml +++ /dev/null @@ -1,91 +0,0 @@ -id: f9949656-473f-4503-bf43-a9d9890f7d08 -name: TI map IP entity to AppServiceHTTPLogs -description: | - Identifies a match in AppServiceHTTPLogs from any IP IOC from TI -severity: Medium -requiredDataConnectors: - - connectorId: ThreatIntelligence - dataTypes: - - ThreatIntelligenceIndicator - - connectorId: ThreatIntelligenceTaxii - dataTypes: - - ThreatIntelligenceIndicator - - connectorId: MicrosoftDefenderThreatIntelligence - dataTypes: - - ThreatIntelligenceIndicator -queryFrequency: 1h -queryPeriod: 14d -triggerOperator: gt -triggerThreshold: 0 -tactics: - - Impact -query: | - let dt_lookBack = 1h; // Look back 1 hour for AppServiceHTTPLogs - let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators - // Fetch threat intelligence indicators related to IP addresses - let IP_Indicators = ThreatIntelligenceIndicator - | where TimeGenerated >= ago(ioc_lookBack) - | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId - | where Active == true and ExpirationDateTime > now() - // Filter out indicators without relevant IP address fields - | where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP) - // Select the IP entity based on availability of different IP fields - | extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP) - | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity) - | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity) - // Filtering out rows where the Confidence Score is less than 50 as they would not have an Alert Priority label. - | where ConfidenceScore > 50 - // Determine AlertPriority based on ConfidenceScore - | extend AlertPriority = case(ConfidenceScore > 82, "High", - ConfidenceScore > 74, "Medium", - "Low") - // Exclude local addresses using the ipv4_is_private operator and filtering out specific address prefixes - | where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."; - // Perform a join between IP indicators and AppServiceHTTPLogs to identify potential malicious activity - IP_Indicators - // Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation - | join kind=innerunique ( - AppServiceHTTPLogs | where TimeGenerated >= ago(dt_lookBack) - | where isnotempty(CIp) - | extend WebApp = split(_ResourceId, '/')[8] - | extend AppService_TimeGenerated = TimeGenerated // Rename time column for clarity - ) - on $left.TI_ipEntity == $right.CIp - // Filter out logs that occurred after the expiration of the corresponding indicator - | where AppService_TimeGenerated < ExpirationDateTime - // Group the results by IndicatorId and CIp, and keep the log entry with the latest timestamp - | summarize AppService_TimeGenerated = arg_max(AppService_TimeGenerated, *) by IndicatorId, CIp - // Select the desired output fields - | project AppService_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore, TI_ipEntity, CsUsername, WebApp = split(_ResourceId, '/')[8], CIp, CsHost, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, _ResourceId, Type - // Extract hostname and DNS domain from the CsHost field - | extend HostName = tostring(split(CsHost, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(CsHost, '.'), 1, -1), '.')) - // Rename the timestamp field - | extend timestamp = AppService_TimeGenerated -entityMappings: - - entityType: Host - fieldMappings: - - identifier: HostName - columnName: HostName - - identifier: DnsDomain - columnName: DnsDomain - - entityType: Account - fieldMappings: - - identifier: Name - columnName: CsUsername - - entityType: IP - fieldMappings: - - identifier: Address - columnName: CIp - - entityType: URL - fieldMappings: - - identifier: Url - columnName: Url - - entityType: AzureResource - fieldMappings: - - identifier: ResourceId - columnName: _ResourceId -alertDetailsOverride: - alertSeverityColumnName: AlertPriority -version: 1.5.0 -kind: Scheduled - \ No newline at end of file