id: 69a45b05-71f5-45ca-8944-2e038747fb39
name: RDP Nesting
description: |
  'Query detects potential lateral movement within a network by identifying when an RDP connection (EventID 4624, LogonType 10) is made to an initial system, followed by a subsequent RDP connection from that system to another, using the same account within a 60-minute window.
   To reduce false positives, it excludes scenarios where the same account has made 5 or more connections to the same set of computers in the previous 7 days. This approach focuses on highlighting unusual RDP behaviour that suggests lateral movement, which is often associated with attacker tactics during a network breach.'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
queryFrequency: 1d
queryPeriod: 8d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - LateralMovement
relevantTechniques:
  - T1021
query: |
  let endtime = 1d;
  // Function to resolve hostname to IP address using DNS logs or a lookup table (example syntax)
  let rdpConnections =
  (union isfuzzy=true
  (
  SecurityEvent
  | where TimeGenerated >= ago(endtime)
  | where EventID == 4624 and LogonType == 10
  // Labeling the first RDP connection time, computer and ip
  | extend
  FirstHop = bin(TimeGenerated, 1m),
  FirstComputer = toupper(Computer),
  FirstRemoteIPAddress = IpAddress,
  Account = tolower(Account)
  ),
  (
  WindowsEvent
  | where TimeGenerated >= ago(endtime)
  | where EventID == 4624 and EventData has ("10")
  | extend LogonType = tostring(EventData.LogonType)
  | where LogonType == 10 // Labeling the first RDP connection time, computer and ip
  | extend Account = strcat(tostring(EventData.TargetDomainName), "", tostring(EventData.TargetUserName))
  | extend IpAddress = tostring(EventData.IpAddress)
  | extend
  FirstHop = bin(TimeGenerated, 1m),
  FirstComputer = toupper(Computer),
  FirstRemoteIPAddress = IpAddress,
  Account = tolower(Account)
  ))
  | join kind=inner (
  (union isfuzzy=true
  (
  SecurityEvent
  | where TimeGenerated >= ago(endtime)
  | where EventID == 4624 and LogonType == 10
  // Labeling the second RDP connection time, computer and ip
  | extend
  SecondHop = bin(TimeGenerated, 1m),
  SecondComputer = toupper(Computer),
  SecondRemoteIPAddress = IpAddress,
  Account = tolower(Account)
  ),
  (
  WindowsEvent
  | where TimeGenerated >= ago(endtime)
  | where EventID == 4624 and EventData has ("10")
  | extend LogonType = toint(EventData.LogonType)
  | where LogonType == 10 // Labeling the second RDP connection time, computer and ip
  | extend Account = strcat(tostring(EventData.TargetDomainName), "", tostring(EventData.TargetUserName))
  | extend IpAddress = tostring(EventData.IpAddress)
  | extend
  SecondHop = bin(TimeGenerated, 1m),
  SecondComputer = toupper(Computer),
  SecondRemoteIPAddress = IpAddress,
  Account = tolower(Account)
  ))
  )
  on Account
  | distinct
  Account,
  FirstHop,
  FirstComputer,
  FirstRemoteIPAddress,
  SecondHop,
  SecondComputer,
  SecondRemoteIPAddress,
  AccountType,
  Activity,
  LogonTypeName,
  ProcessName;
  // Resolve hostnames to IP addresses device network Ip's
  let listOfFirstComputer = rdpConnections | distinct FirstComputer;
  let listOfSecondComputer = rdpConnections | distinct SecondComputer;
  let resolvedIPs =
  DeviceNetworkInfo
  | where TimeGenerated >= ago(endtime)
  | where isnotempty(ConnectedNetworks) and NetworkAdapterStatus == "Up"
  | extend ClientIP = tostring(parse_json(IPAddresses[0]).IPAddress)
  | where isnotempty(ClientIP)
  | where DeviceName in~ (listOfFirstComputer) or DeviceName in~ (listOfSecondComputer)
  | summarize arg_max(TimeGenerated, ClientIP) by Computer= DeviceName
  | project Computer=toupper(Computer), ResolvedIP = ClientIP;
  // Join resolved IPs with the RDP connections
  rdpConnections
  | join kind=inner (resolvedIPs) on $left.FirstComputer == $right.Computer
  | join kind=inner (resolvedIPs) on $left.SecondComputer == $right.Computer
  // | where ResolvedIP != ResolvedIP1
  | distinct
  Account,
  FirstHop,
  FirstComputer,
  FirstComputerIP = ResolvedIP,
  FirstRemoteIPAddress,
  SecondHop,
  SecondComputer,
  SecondComputerIP = ResolvedIP1,
  SecondRemoteIPAddress,
  AccountType,
  Activity,
  LogonTypeName,
  ProcessName
  // Ensure the first connection is before the second connection
  // Identify only RDP to another computer from within the first RDP connection by only choosing matches where the Computer names do not match
  // Ensure the IPAddresses do not match by excluding connections from the same computers with first hop RDP connections to multiple computers
  | where FirstComputer != SecondComputer
  and FirstRemoteIPAddress != SecondRemoteIPAddress
  and SecondHop > FirstHop
  // Ensure the second hop occurs within 30 minutes of the first hop
  | where SecondHop <= FirstHop + 30m
  | where SecondRemoteIPAddress == FirstComputerIP
  | summarize FirstHopFirstSeen = min(FirstHop), FirstHopLastSeen = max(FirstHop)
  by
  Account,
  FirstComputer,
  FirstComputerIP,
  FirstRemoteIPAddress,
  SecondHop,
  SecondComputer,
  SecondComputerIP,
  SecondRemoteIPAddress,
  AccountType,
  Activity,
  LogonTypeName,
  ProcessName
  | extend
  AccountName = tostring(split(Account, @"")[1]),
  AccountNTDomain = tostring(split(Account, @"")[0])
  | extend
  HostName1 = tostring(split(FirstComputer, ".")[0]),
  DomainIndex = toint(indexof(FirstComputer, '.'))
  | extend HostNameDomain1 = iff(DomainIndex != -1, substring(FirstComputer, DomainIndex + 1), FirstComputer)
  | extend
  HostName2 = tostring(split(SecondComputer, ".")[0]),
  DomainIndex = toint(indexof(SecondComputer, '.'))
  | extend HostNameDomain2 = iff(DomainIndex != -1, substring(SecondComputer, DomainIndex + 1), SecondComputer)
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: FirstComputer
      - identifier: HostName
        columnName: HostName1
      - identifier: NTDomain
        columnName: HostNameDomain1
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: SecondComputer
      - identifier: HostName
        columnName: HostName2
      - identifier: NTDomain
        columnName: HostNameDomain2
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: FirstIPAddress
version: 1.2.8
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection" ]