Privileged RDP Session Source Mismatch
Query
// ============================================================================
// Configuration variables — adjust these to match your environment
// ============================================================================
// Regex pattern to exclude shared terminal servers (would cause excessive false positives)
let ExcludeTerminalServerRegex = @"^tsar[ae][0-9]{3}";
// Regex pattern to identify privileged accounts by naming convention
let PrivUserPattern = @"sys[ae]\.[a-z]+";
// Regex pattern to identify privileged accounts in the recent users list (for short name extraction)
let PrivRecentUserPattern = @"^sys[ae]\.";
// Service account domains to exclude from recent user enrichment
let ExcludedAccountDomains = dynamic(["nt service", "font driver host", "window manager", "nt-autorität", "autorite nt", "nt authority"]);
// Generic account names to exclude from recent user enrichment
let ExcludedAccountNames = dynamic(["-", "admin"]);
// IP ranges to exclude from device IP mapping (link-local, private)
let ExcludeIPRange1 = "169.254.0.0/16";
let ExcludeIPRange2 = "192.168.0.0/16";
// ============================================================================
// Time Settings
// ============================================================================
// Detection window for new privileged RDP logons (aligned with scheduled run interval)
let DetectionWindow = 1h;
// Lookback window for IP resolution and recent user context
let LookbackWindow = 1d;
// Maximum allowed time difference (in seconds) between logon and IP observation for device resolution
let MaxTimeDiffSeconds = 3600;
// ============================================================================
// Query start
// ============================================================================
// Collect all known device-to-IP mappings within lookback window (broad window to handle IP changes)
let DeviceIPs = DeviceNetworkEvents
| where Timestamp > ago(LookbackWindow)
| where isnotempty(DeviceName)
| where not(ipv4_is_in_range(LocalIP, ExcludeIPRange1))
| where not(ipv4_is_in_range(LocalIP, ExcludeIPRange2))
| where LocalIP != "0.0.0.0"
| project DeviceIP_Timestamp = Timestamp, DeviceName, LocalIP;
// Privileged RDP logons within detection window
let PrivLogons = DeviceLogonEvents
| where Timestamp > ago(DetectionWindow)
| where LogonType == "RemoteInteractive"
| where not(DeviceName matches regex ExcludeTerminalServerRegex)
| where AccountName matches regex PrivUserPattern
| where isnotempty(RemoteIP)
| project-rename PrivAccountName = AccountName, LogonTimestamp = Timestamp;
// Resolve RemoteIP to the closest matching source device (within configured tolerance)
PrivLogons
| join kind=inner DeviceIPs on $left.RemoteIP == $right.LocalIP
| extend TimeDiff = abs(datetime_diff('second', LogonTimestamp, DeviceIP_Timestamp))
| where TimeDiff < MaxTimeDiffSeconds
| summarize arg_min(TimeDiff, *) by LogonTimestamp, DeviceName, PrivAccountName, RemoteIP
// Enrich with recent interactive users on the resolved source device
| join kind=leftouter (
DeviceLogonEvents
| where Timestamp > ago(LookbackWindow)
| where LogonType in ("Interactive", "RemoteInteractive")
| where AccountDomain !in (ExcludedAccountDomains)
| where AccountName !in (ExcludedAccountNames)
| summarize LastLogon = max(Timestamp),
RecentUsers = make_set(AccountName, 3)
by DeviceName
) on $left.DeviceName1 == $right.DeviceName
| summarize arg_max(LogonTimestamp, *) by DeviceName1, PrivAccountName
| where isnotempty(RecentUsers)
// Expand each recent user and extract comparable short name
| mv-expand RecentUser = RecentUsers to typeof(string)
| extend UNameshort1 = extract(@"\.(.+)$", 1, PrivAccountName)
// For priv accounts take only the surname, for regular accounts take first initial + surname
| extend UNameshort2 = iif(
RecentUser matches regex PrivRecentUserPattern,
extract(@"\.(.+)$", 1, RecentUser),
strcat(substring(RecentUser, 0, 1), extract(@"\.(.+)$", 1, RecentUser))
)
// Check if any recent user on the source device matches the priv account owner
| summarize MatchFound = countif(UNameshort2 has UNameshort1),
arg_max(LogonTimestamp, *)
by DeviceName1, PrivAccountName
// Alert only when no matching user was found — potential unauthorized priv account usage
| where MatchFound == 0
| extend RecentUsersOnSource = strcat_array(RecentUsers, ", ")
| project LogonTimestamp, DeviceName, DeviceName1, PrivAccountName, RemoteIP, RecentUsersOnSource```About this query
Explanation
This query is designed to detect potential security issues involving the use of privileged accounts for Remote Desktop Protocol (RDP) sessions. Here's a simplified breakdown of what the query does:
-
Purpose: It identifies situations where a privileged account (like an admin account) is used to start an RDP session from a device, but the owner of that privileged account hasn't recently logged into that device using their regular account. This could indicate unauthorized access or misuse of credentials.
-
How It Works:
- Data Collection: The query gathers data about device logins and network events within a specified time frame.
- Privileged Account Detection: It looks for RDP logins using privileged accounts, identified by specific naming patterns.
- Source Device Verification: It checks the source device of the RDP session to see if the privileged account owner has recently logged in with their standard account.
- Anomaly Detection: If the privileged account is used from a device where the owner hasn't logged in recently, it flags this as suspicious.
-
Why It's Important: This pattern can indicate:
- Lateral Movement: An attacker moving through the network using stolen credentials.
- Credential Theft: Unauthorized use of admin credentials.
- Policy Violations: Sharing of accounts, which goes against security policies.
-
Exclusions and Configurations:
- The query excludes certain IP ranges and account types to reduce false positives.
- It uses regular expressions to identify privileged accounts and exclude shared terminal servers.
-
Outcome: The query generates alerts for potential unauthorized use of privileged accounts, helping security teams to investigate and respond to possible security breaches.
Details

Benjamin Zulliger
Released: March 31, 2026
Tables
Keywords
Operators