StealthTalk - After hours work
| Id | e3a8b2f1-5c7d-4d89-9b6e-0f1a2c3d4e5f |
| Rulename | StealthTalk - After hours work |
| Description | Identifies systematic off-hours activity for a single StealthTalk user - repeated authentications outside the user’s configured working hours OR on weekends, observed across at least two distinct calendar days within a 48-hour window. The pattern is a common indicator of credential misuse, insider threat, or compromise of the account by an attacker operating in a different timezone. An “off-hours event” is one where IsWeekend=true OR DeviationMinutes >= 180 (i.e. >= 3 hours after the configured working-hours end). Three or more such events on at least two distinct days are required for an incident to fire - a single late-evening login is not enough. |
| Severity | Low |
| Tactics | InitialAccess DefenseEvasion Persistence |
| Techniques | T1078 |
| Required data connectors | StealthTalkAnomalousAuth |
| Kind | Scheduled |
| Query frequency | 1h |
| Query period | 2d |
| Trigger threshold | 0 |
| Trigger operator | gt |
| Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/StealthTalk/Analytic Rules/AfterHoursWork.yaml |
| Version | 1.0.0 |
| Arm template | e3a8b2f1-5c7d-4d89-9b6e-0f1a2c3d4e5f.json |
let LookbackPeriod = 48h;
let MinAttempts = 3;
let MinDistinctDays = 2;
let OffHoursThreshold = 180;
StealthTalkAnomalousAuth_CL
| where TimeGenerated >= ago(LookbackPeriod)
| where EventType == "OffHoursLogin"
| where IsWeekend == true or DeviationMinutes >= OffHoursThreshold
| summarize
AttemptCount = count(),
DistinctDays = dcount(startofday(TimeGenerated)),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
DeviceIds = make_set(DeviceId),
MaxDeviation = max(DeviationMinutes),
WeekendsCount = countif(IsWeekend == true),
AppVersions = make_set(AppVersion)
by UserId
| where AttemptCount >= MinAttempts and DistinctDays >= MinDistinctDays
| extend
AlertName = "AfterHoursWork",
AlertDetails = strcat(
"User ", UserId,
" performed ", AttemptCount, " off-hours logins",
" across ", DistinctDays, " distinct days.",
" Max deviation from working hours: ", MaxDeviation, " min.",
" Weekend logins: ", WeekendsCount, "."
)
| project
TimeGenerated = LastSeen,
UserId, AttemptCount, DistinctDays, MaxDeviation,
WeekendsCount, DeviceIds, AppVersions, FirstSeen, LastSeen,
AlertName, AlertDetails
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: UserId
tactics:
- InitialAccess
- DefenseEvasion
- Persistence
suppressionEnabled: false
suppressionDuration: 5h
requiredDataConnectors:
- dataTypes:
- StealthTalkAnomalousAuth_CL
connectorId: StealthTalkAnomalousAuth
alertDetailsOverride:
alertDisplayNameFormat: 'StealthTalk: After-Hours Work - {{UserId}} ({{AttemptCount}} events / {{DistinctDays}} days)'
alertDescriptionFormat: '{{AlertDetails}}'
incidentConfiguration:
groupingConfiguration:
reopenClosedIncident: false
lookbackDuration: 5h
groupByEntities:
- Account
enabled: true
matchingMethod: Selected
createIncident: true
id: e3a8b2f1-5c7d-4d89-9b6e-0f1a2c3d4e5f
severity: Low
status: Available
customDetails:
AttemptCount: AttemptCount
LastSeen: LastSeen
FirstSeen: FirstSeen
DistinctDays: DistinctDays
MaxDeviation: MaxDeviation
WeekendsCount: WeekendsCount
query: |
let LookbackPeriod = 48h;
let MinAttempts = 3;
let MinDistinctDays = 2;
let OffHoursThreshold = 180;
StealthTalkAnomalousAuth_CL
| where TimeGenerated >= ago(LookbackPeriod)
| where EventType == "OffHoursLogin"
| where IsWeekend == true or DeviationMinutes >= OffHoursThreshold
| summarize
AttemptCount = count(),
DistinctDays = dcount(startofday(TimeGenerated)),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
DeviceIds = make_set(DeviceId),
MaxDeviation = max(DeviationMinutes),
WeekendsCount = countif(IsWeekend == true),
AppVersions = make_set(AppVersion)
by UserId
| where AttemptCount >= MinAttempts and DistinctDays >= MinDistinctDays
| extend
AlertName = "AfterHoursWork",
AlertDetails = strcat(
"User ", UserId,
" performed ", AttemptCount, " off-hours logins",
" across ", DistinctDays, " distinct days.",
" Max deviation from working hours: ", MaxDeviation, " min.",
" Weekend logins: ", WeekendsCount, "."
)
| project
TimeGenerated = LastSeen,
UserId, AttemptCount, DistinctDays, MaxDeviation,
WeekendsCount, DeviceIds, AppVersions, FirstSeen, LastSeen,
AlertName, AlertDetails
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/StealthTalk/Analytic Rules/AfterHoursWork.yaml
kind: Scheduled
queryPeriod: 2d
version: 1.0.0
name: StealthTalk - After hours work
queryFrequency: 1h
triggerThreshold: 0
relevantTechniques:
- T1078
description: |
Identifies systematic off-hours activity for a single StealthTalk user - repeated authentications
outside the user's configured working hours OR on weekends, observed across at least two
distinct calendar days within a 48-hour window. The pattern is a common indicator of credential
misuse, insider threat, or compromise of the account by an attacker operating in a different
timezone.
An "off-hours event" is one where IsWeekend=true OR DeviationMinutes >= 180 (i.e. >= 3 hours
after the configured working-hours end). Three or more such events on at least two distinct
days are required for an incident to fire - a single late-evening login is not enough.
triggerOperator: gt