Multiple Email Entity Signin Logs
Query
// This query assumes a feed of threat indicators is ingested/synchronized periodically, and each synchronization ingests new indicators and only old indicators that have been modified.
// Active threat indicators in Sentinel are renovated as ThreatIntelligenceIndicator events every ~12 days.
let query_frequency = 1h;
let query_period = 14d;
let query_wait = 0h;
let table_query_lookback = 14d;
let _TIBenignProperty =
_GetWatchlist('ID-TIBenignProperty')
| where Notes has_any ("[SourceEmailAddress]")
| project IndicatorId, BenignProperty
;
let _TIExcludedSources = toscalar(
_GetWatchlist('Activity-ExpectedSignificantActivity')
| where Activity == "ThreatIndicatorSource"
| summarize make_list(Auxiliar)
);
let _UncompromisedFailureResultTypes = toscalar(
_GetWatchlist('ResultType-SignInLogsErrorCodes')
| where isnotempty(ResultDescription) and not(Notes has_any ("[Success]", "[Expired]"))
| summarize make_list(ResultType)
);
let _TITableMatch = (table_start: datetime, table_end: datetime, only_new_ti: boolean, ti_start: datetime = datetime(null)) {
// Scheduled Analytics rules have a query period limit of 14d
let _Indicators =// materialize(
ThreatIntelligenceIndicator
| where TimeGenerated > ago(query_period)
// Take the earliest TimeGenerated and the latest column info
| summarize hint.strategy=shuffle
minTimeGenerated = min(TimeGenerated),
arg_max(TimeGenerated, Active, Description, ActivityGroupNames, IndicatorId, ThreatType, DomainName, Url, ExpirationDateTime, ConfidenceScore, AdditionalInformation, ExternalIndicatorId, EmailSenderAddress)
by IndicatorId
// Remove inactive or expired indicators
| where not(not(Active) or ExpirationDateTime < now())
// Pick indicators that contain the desired entity type
| where isnotempty(EmailSenderAddress)
| extend EmailAddress = tolower(EmailSenderAddress)
// Remove indicators from specific sources
| where not(AdditionalInformation has_any (_TIExcludedSources) or Description has_any (_TIExcludedSources))
// Remove excluded indicators with benign properties
| join kind=leftanti _TIBenignProperty on IndicatorId, $left.EmailAddress == $right.BenignProperty
// Deduplicate indicators by EmailAddress column, equivalent to using join kind=innerunique afterwards
| summarize hint.strategy=shuffle
minTimeGenerated = min(minTimeGenerated),
take_any(*)
by EmailAddress
// If we want only new indicators, remove indicators received previously
| where not(only_new_ti and minTimeGenerated < ti_start)
//)
;
//let _IndicatorsLength = toscalar(_Indicators | summarize count());
//let _IndicatorsPrefilter = toscalar(
// _Indicators
// | extend AuxiliarField = tostring(split(EmailAddress, ".")[-1])
// | summarize make_set_if(AuxiliarField, isnotempty(AuxiliarField))
//);
//let _IndicatorsPrefilterLength = array_length(_IndicatorsPrefilter);
let _TableEvents =
SigninLogs
| where TimeGenerated between (table_start .. table_end)
// Filter events that may contain indicators
//| where not(_IndicatorsPrefilterLength < 10000 and not(UserPrincipalName has_any (_IndicatorsPrefilter))) // valid TLD ~1500 , "has_any" limit 10000
| extend EmailAddress = tolower(UserPrincipalName)
//| where not(_IndicatorsLength < 1000000 and not(EmailAddress in (toscalar(_Indicators | summarize make_list(EmailAddress))))) // "in" limit 1.000.000
| project-rename SigninLogs_TimeGenerated = TimeGenerated
;
_Indicators
| join kind=inner hint.strategy=shuffle _TableEvents on EmailAddress
// Take only a single event by key columns
//| summarize hint.strategy=shuffle take_any(*) by EmailAddress, UserId
| extend
DeviceDetail = tostring(DeviceDetail),
ConditionalAccessPolicies = tostring(ConditionalAccessPolicies)
| project
SigninLogs_TimeGenerated,
Description, ActivityGroupNames, IndicatorId, ThreatType, DomainName, Url, ExpirationDateTime, ConfidenceScore, AdditionalInformation, EmailSenderAddress,
Category, UserPrincipalName, UserDisplayName, IPAddress, Location, ResultType, ResultDescription, ClientAppUsed, AppDisplayName, ResourceDisplayName, DeviceDetail, UserAgent, AuthenticationDetails, ConditionalAccessPolicies, RiskState, RiskEventTypes, RiskLevelDuringSignIn, RiskLevelAggregated, UserId, OriginalRequestId, CorrelationId
};
union// isfuzzy=true
// Match current table events all indicators available
_TITableMatch(ago(query_frequency + query_wait), ago(query_wait), false),
// Match past table events new indicators since last query execution
_TITableMatch(ago(table_query_lookback + query_wait), ago(query_frequency + query_wait), true, ago(query_frequency))
| summarize arg_max(SigninLogs_TimeGenerated, *) by IndicatorId, UserId
| extend
DeviceDetail = tostring(DeviceDetail),
ConditionalAccessPolicies = tostring(ConditionalAccessPolicies)
| extend
timestamp = SigninLogs_TimeGenerated,
IPCustomEntity = IPAddress,
AccountCustomEntity = EmailSenderAddressExplanation
This KQL query is designed to identify and analyze suspicious sign-in activities based on threat intelligence indicators. Here's a simplified breakdown of what the query does:
-
Setup and Configuration:
- Defines various time periods and parameters for the query, such as how often it should run (
query_frequency), the period it should cover (query_period), and a wait time (query_wait).
- Defines various time periods and parameters for the query, such as how often it should run (
-
Threat Intelligence Indicators:
- Retrieves a list of threat indicators from a data source, focusing on email addresses associated with threats.
- Filters out inactive or expired indicators and those from specific excluded sources.
- Removes indicators with benign properties, ensuring only relevant threat indicators are considered.
-
Sign-in Logs Analysis:
- Retrieves sign-in logs within a specified time range.
- Matches these logs against the threat indicators based on email addresses.
- Filters and processes the logs to identify potential security incidents.
-
Data Processing and Output:
- Joins the threat indicators with sign-in logs to find matches.
- Ensures that only unique and relevant events are considered.
- Projects and extends various fields to provide detailed information about each incident, such as user details, device information, and risk levels.
-
Final Output:
- Combines current and past events with new threat indicators to provide a comprehensive view of potential threats.
- Summarizes the results to highlight the most recent and relevant incidents, including details like IP addresses and user accounts involved.
Overall, this query is used to detect and analyze sign-in activities that may be linked to known threat indicators, helping security teams to identify and respond to potential security threats effectively.
Details

Jose Sebastián Canós
Released: December 13, 2023
Tables
ThreatIntelligenceIndicatorSigninLogs
Keywords
ThreatIntelligenceIndicatorSigninLogsEmailAddressUserPrincipalNameUserIdDeviceDetailConditionalAccessPoliciesIPAddressLocationUserAgentAuthenticationDetailsRiskStateRiskEventTypesRiskLevelDuringSignInRiskLevelAggregatedOriginalRequestIdCorrelationId
Operators
let|has_anyprojecttoscalarwhereisnotemptyandnotsummarizemake_listarg_max<nowextendtolowerjoinkind=leftanti==take_anybybetween..project-renamekind=inneruniontostring