16.8 Detection Engineering

3-5 hours · Module 16

Detection Engineering

Five analytics rules that detect insider threat patterns. These are fundamentally different from the external-attacker rules in M11-M14 — they detect anomalous behaviour by authorised users, not unauthorised access.

Required role: Microsoft Sentinel Contributor.


Rule 1: Bulk File Download Exceeding User Baseline

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Scheduled Rule: User downloads significantly more files than their 30-day average
// Schedule: 1 hour / Lookback: 24 hours
// Severity: Medium
// MITRE: T1530 (Data from Cloud Storage)
let UserBaselines = CloudAppEvents
| where TimeGenerated between(ago(60d) .. ago(1d))
| where ActionType == "FileDownloaded"
| summarize DailyAvg = count() / 60.0 by AccountDisplayName;
CloudAppEvents
| where TimeGenerated > ago(24h)
| where ActionType == "FileDownloaded"
| summarize TodayCount = count() by AccountDisplayName
| join kind=inner UserBaselines on AccountDisplayName
| where TodayCount > (DailyAvg * 5) and TodayCount > 20
// 5x baseline AND minimum 20 files (avoid noise from low-baseline users)
| project AccountDisplayName, TodayCount, DailyAvg,
    Multiplier = round(TodayCount / DailyAvg, 1)

Entity mapping: Account → AccountDisplayName (FullName). Custom details: TodayCount, DailyAvg, Multiplier.


Rule 2: File Transfer to Personal Cloud Storage

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Scheduled Rule: Network connection to personal cloud storage from corporate device
// Schedule: 15 minutes / Lookback: 20 minutes
// Severity: High
// MITRE: T1567 (Exfiltration Over Web Service)
DeviceNetworkEvents
| where RemoteUrl has_any ("dropbox.com", "drive.google.com", "icloud.com",
    "box.com", "mega.nz", "onedrive.live.com", "wetransfer.com",
    "sendanywhere.com", "filemail.com", "mediafire.com")
| where InitiatingProcessFileName in ("chrome.exe", "msedge.exe", "firefox.exe",
    "Dropbox.exe", "googledrivesync.exe")
| project TimeGenerated, DeviceName,
    AccountName = InitiatingProcessAccountName,
    RemoteUrl, InitiatingProcessFileName

Entity mapping: Account → AccountName (Name). Host → DeviceName (HostName).


Rule 3: USB Device File Copy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Scheduled Rule: Files written to removable storage
// Schedule: 15 minutes / Lookback: 20 minutes
// Severity: High
// MITRE: T1052 (Exfiltration Over Physical Medium)
DeviceFileEvents
| where ActionType == "FileCreated"
| where FolderPath matches regex @"^[D-Z]:\\"  // Non-system drive
| where FolderPath !startswith "C:\\"
| where FolderPath !has "WindowsApps"
| join kind=inner (
    DeviceEvents
    | where ActionType == "UsbDriveMounted"
    | extend MountTime = TimeGenerated
    | project DeviceName, MountTime
) on DeviceName
| where TimeGenerated between(MountTime .. (MountTime + 2h))
| project TimeGenerated, DeviceName,
    AccountName = InitiatingProcessAccountName,
    FileName, FolderPath, FileSize

Entity mapping: Host → DeviceName (HostName). File → FileName (Name).


Rule 4: Email with Attachments to Personal Address

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Scheduled Rule: Corporate email with attachments sent to personal domains
// Schedule: 15 minutes / Lookback: 20 minutes
// Severity: Medium
// MITRE: T1048 (Exfiltration Over Alternative Protocol)
let PersonalDomains = dynamic(["gmail.com", "yahoo.com", "hotmail.com",
    "outlook.com", "icloud.com", "protonmail.com", "aol.com", "live.com"]);
EmailEvents
| where SenderFromDomain == "northgateeng.com"
| where RecipientEmailAddress has_any (PersonalDomains)
| join kind=inner (
    EmailAttachmentInfo
    | where FileSize > 10240  // > 10KB  filter trivial attachments
) on NetworkMessageId
| project TimeGenerated, SenderFromAddress, RecipientEmailAddress,
    Subject, FileName, FileSize

Tuning note: This rule generates false positives for legitimate personal email to colleagues’ personal addresses (e.g., sending calendar invites to a personal email). Tune by: maintaining an exclusion list for known personal addresses of employees who routinely receive legitimate forwarded email, or by filtering on file extensions (.cad, .dwg, .xlsx, .pdf, .zip) that indicate sensitive documents vs casual attachments (.jpg, .png).


Rule 5: Departing Employee Anomalous Activity (Watchlist-Correlated)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Scheduled Rule: Activity anomaly for users on the departing employee watchlist
// Schedule: 4 hours / Lookback: 24 hours
// Severity: High
// MITRE: T1530, T1567
let DepartingEmployees = _GetWatchlist('DepartingEmployees')
    | project SearchKey;
let UserBaselines = CloudAppEvents
| where TimeGenerated between(ago(60d) .. ago(1d))
| where ActionType in ("FileDownloaded", "FileCopied")
| summarize DailyAvg = count() / 60.0 by AccountDisplayName;
CloudAppEvents
| where TimeGenerated > ago(24h)
| where ActionType in ("FileDownloaded", "FileCopied")
| where AccountDisplayName in (DepartingEmployees)
| summarize TodayCount = count() by AccountDisplayName
| join kind=inner UserBaselines on AccountDisplayName
| where TodayCount > (DailyAvg * 3) and TodayCount > 10
// Lower threshold (3x) for departing employees vs general population (5x)
| project AccountDisplayName, TodayCount, DailyAvg,
    Multiplier = round(TodayCount / DailyAvg, 1)

Watchlist requirement: Create a Sentinel watchlist named DepartingEmployees containing the UPNs of employees who have submitted their resignation or been given notice. HR populates this watchlist as part of the offboarding process. This rule applies a lower anomaly threshold (3x baseline vs 5x for general population) to departing employees — because the risk profile is higher during the notice period.

This is the most operationally powerful rule in this module. It turns the HR offboarding process into a detection signal — any departing employee who deviates from their baseline triggers an alert.


Deployment checklist

For each rule: create in Sentinel Analytics, configure entity mapping, set MITRE technique, test against historical data, enable, monitor.

Watchlist setup: Create DepartingEmployees in Sentinel → Watchlists. Columns: SearchKey (UPN), DepartureDate, Department, Manager. HR updates the watchlist during the offboarding process.

Subsection artifact: 5 deployable insider threat detection rules + the DepartingEmployees watchlist specification. Combined with M11-M14: 29 detection rules total covering external attacks, financial fraud, token abuse, consent phishing, and insider threat.

Compliance mapping: NIST CSF DE.AE-2. ISO 27001 A.8.16. SOC 2 CC6.8 (Prevent unauthorized data removal).


Watchlist operations — maintaining the DepartingEmployees list

The DepartingEmployees watchlist is the operational backbone of Rule 5. Without it, the rule has nothing to correlate against. Define the operational process:

Who maintains the watchlist: HR. The security team does not know who has resigned — HR does. The watchlist update should be part of the standard HR offboarding checklist: “When an employee submits their resignation: add their UPN to the DepartingEmployees watchlist in Microsoft Sentinel.”

What to include: SearchKey (UPN), DepartureDate, Department, Manager, RiskLevel (Low/Medium/High — based on access to sensitive data and destination employer). RiskLevel determines the detection threshold: High-risk employees (joining a competitor, with access to IP) trigger on 3x baseline deviation. Low-risk employees (moving to a different industry, no sensitive access) trigger on 5x.

When to add: At resignation acceptance — not at departure. The highest-risk period is between resignation and last day (typically 2-4 weeks). Adding at departure is too late — the exfiltration has already occurred.

When to remove: 30 days after departure date. Keep the entry for 30 days post-departure because: automated processes may continue accessing resources briefly after departure (OneDrive sync, cached credentials), and post-departure login attempts are a separate detection signal (the former employee may attempt to access resources using saved credentials).

1
2
3
4
5
6
7
8
9
// Verify watchlist health: are departing employees current?
_GetWatchlist('DepartingEmployees')
| extend DepartureDate = todatetime(DepartureDate)
| extend Status = case(
    DepartureDate > now(), "Active — pre-departure",
    DepartureDate between(ago(30d) .. now()), "Post-departure — monitoring",
    DepartureDate < ago(30d), "STALE — should be removed",
    "Unknown")
| summarize count() by Status

Run this monthly. “STALE” entries should be removed. “Active” entries should match HR’s current departing employee list. Discrepancies indicate a process gap — HR is not consistently updating the watchlist.

Try it yourself

Create the DepartingEmployees watchlist in your test Sentinel workspace. Columns: SearchKey (UPN), DepartureDate, Department, RiskLevel. Add 3 test entries. Then run Rule 5 against your workspace — does it correlate the watchlist with CloudAppEvents? If you have test user activity in your lab, adjust the thresholds to trigger an alert. This validates the watchlist-rule integration end-to-end.

What you should observe

The watchlist appears in Sentinel → Watchlists. The _GetWatchlist function returns your test entries. Rule 5 joins the watchlist with CloudAppEvents and applies the lower threshold (3x for departing employees). If your test user has any file download activity, the rule should evaluate it — even if it does not trigger (below threshold).