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, RecentUsersOnSourceAbout 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.