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
version: 1.0.0
severity: Low
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
queryPeriod: 2d
status: Available
alertDetailsOverride:
alertDescriptionFormat: '{{AlertDetails}}'
alertDisplayNameFormat: 'StealthTalk: After-Hours Work - {{UserId}} ({{AttemptCount}} events / {{DistinctDays}} days)'
suppressionEnabled: false
customDetails:
MaxDeviation: MaxDeviation
AttemptCount: AttemptCount
DistinctDays: DistinctDays
LastSeen: LastSeen
WeekendsCount: WeekendsCount
FirstSeen: FirstSeen
tactics:
- InitialAccess
- DefenseEvasion
- Persistence
triggerOperator: gt
queryFrequency: 1h
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/StealthTalk/Analytic Rules/AfterHoursWork.yaml
entityMappings:
- fieldMappings:
- columnName: UserId
identifier: Name
entityType: Account
suppressionDuration: 5h
name: StealthTalk - After hours work
triggerThreshold: 0
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.
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
lookbackDuration: 5h
matchingMethod: Selected
groupByEntities:
- Account
reopenClosedIncident: false
id: e3a8b2f1-5c7d-4d89-9b6e-0f1a2c3d4e5f
kind: Scheduled
relevantTechniques:
- T1078
requiredDataConnectors:
- connectorId: StealthTalkAnomalousAuth
dataTypes:
- StealthTalkAnomalousAuth_CL