Inbound RDP and VNC Listeners on an Employee Endpoint

WindowsmacOS

TTP Hunt medium DPRK IT Worker FraudLaptop Farm

A laptop-farm machine has to accept inbound connections. This looks for an endpoint listening on, or accepting traffic on, remote-desktop and remote-access ports, adapting the port checks published by mattysplo.it.

What this hunt looks for

Whatever the tooling, a laptop in a facilitator's rack must accept a connection from somewhere, and where it is not brokered by a cloud relay the machine has to be listening. The mattysplo.it write-up publishes narrow queries for inbound RDP on 3389 with private address space excluded, and broad ones for remote-access ports, processes, services and configuration files.

The port list is not only RDP: 3389 is RDP, 5900 to 5905 VNC, 5938 TeamViewer, 7070 AnyDesk and 8172 ScreenConnect, while 6568 appears in the source without a mapping. On macOS the equivalents are Screen Sharing and Remote Management on 5900 and Remote Login on 22.

This hunt keeps the port test, matched on the local side with private and loopback sources excluded, and leaves the process, service and configuration-file branches of the source to the remote-access sweep, where they share its false positives.

Technique
Remote Services (MITRE ATT&CK T1021.001 RDP, T1021.005 VNC) accepted inbound on an endpoint that should originate connections rather than receive them.
Data sources
EDR network telemetry, EDR process and service telemetry, File-write telemetry, Host network state, osquery, macOS launchd services
Last updated
2026-10-04

Indicators

The atomic values this hunt matches on, listed separately from the queries so they can be lifted into tooling that speaks none of the query languages below. Every value here is reported by one of the sources cited at the foot of this page.

TypeValueNote
Port 3389 RDP, per the source's own protocol mapping.
Port 5900-5905 VNC, per the source's own protocol mapping, which runs 5900 through 5905.
Port 5938 TeamViewer, per the source's own protocol mapping.
Port 7070 AnyDesk, per the source's own protocol mapping.
Port 8172 ScreenConnect, per the source's own protocol mapping.
Port 6568 Present in the source's port list but not assigned a protocol in its mapping. Commonly associated with AnyDesk elsewhere; this site does not assert that on the strength of a list membership.

Logic

Every query below implements these steps, so the same hunt returns the same result in each product.

  1. Collect: network listen and accepted-connection events on Windows and macOS endpoints
  2. Match: local port is 3389, 5900-5905, 5938, 7070, 8172 or 3283, with 22 and 6568 optional
  3. Exclude: accepted connections whose remote address is private (10/8, 172.16/12, 192.168/16) or loopback
  4. Report: hosts by local port and owning process, newest first

Queries

Defender / Sentinel KQL

Written for this site, from indicators reported by mattysplo.it

Listening and accepted-connection events per device, port and process; accepted connections from private or loopback addresses are excluded, listeners are kept.

// Hunt: inbound RDP, VNC and remote-access listeners on an employee endpoint (Windows and macOS).
DeviceNetworkEvents
| where Timestamp > ago(30d)
| where ActionType in ("InboundConnectionAccepted", "ListeningConnectionCreated")
| where LocalPort in (3389, 5900, 5901, 5902, 5903, 5904, 5905, 5938, 7070, 8172, 3283)
    // optional: Remote Login (22) is enabled deliberately by developers and IT teams
    or LocalPort == 22
    // optional: 6568 is in the source's port list without a protocol mapping
    or LocalPort == 6568
| where ActionType == "ListeningConnectionCreated"
    or not(coalesce(ipv4_is_private(RemoteIP), false) or RemoteIP startswith "127.")
| summarize Events = count(), RemoteAddresses = dcount(RemoteIP), FirstSeen = min(Timestamp), LastSeen = max(Timestamp)
        by DeviceName, LocalPort, InitiatingProcessFileName
| sort by LastSeen desc
Lower-fidelity parts (2)
  • port 22 (Remote Login) — developers and IT teams enable SSH deliberately. Remove: delete the // optional: Remote Login comment and the port 22 term next to it
  • port 6568 — the source lists it without a protocol mapping. Remove: delete the // optional: 6568 comment and the port 6568 term next to it

Sigma

Written for this site, from indicators reported by mattysplo.it

Inbound connection to a remote-access port from a public address; it needs a source that records Initiated, such as Sysmon event 3.

title: Inbound Remote-Desktop or Remote-Access Connection from a Public Address
id: 6a2f3a58-4bb1-4c7e-b0aa-5c9e2a5d7b14
status: experimental
description: >
  Detects an endpoint accepting a connection on a remote-desktop or
  remote-access port (RDP 3389, VNC 5900-5905, TeamViewer 5938, AnyDesk 7070,
  ScreenConnect 8172, macOS Remote Management 3283) from a public address. A
  laptop in a facilitator's rack must accept a connection from somewhere, and
  where no cloud relay brokers it the machine has to be listening.
references:
  - https://mattysplo.it/2025/06/13/laptopfarms.html
  - https://cloud.google.com/blog/topics/threat-intelligence/mitigating-dprk-it-worker-threat
logsource:
  category: network_connection
detection:
  selection:
    Initiated: 'false'
    DestinationPort:
      - 3389
      - 5900
      - 5901
      - 5902
      - 5903
      - 5904
      - 5905
      - 5938
      - 7070
      - 8172
      - 3283
      # optional: Remote Login is enabled deliberately by developers and IT teams
      - 22
      # optional: 6568 is in the source's port list without a protocol mapping
      - 6568
  filter_private:
    SourceIp|cidr:
      - '10.0.0.0/8'
      - '172.16.0.0/12'
      - '192.168.0.0/16'
      - '127.0.0.0/8'
  condition: selection and not filter_private
falsepositives:
  - Supported help-desk or jump-host access to workstations
  - Screen Sharing or Remote Login enabled deliberately on a Mac
level: medium
Lower-fidelity parts (2)
  • port 22 (Remote Login) — developers and IT teams enable SSH deliberately. Remove: delete the # optional: Remote Login comment and the port 22 term next to it
  • port 6568 — the source lists it without a protocol mapping. Remove: delete the # optional: 6568 comment and the port 6568 term next to it

Splunk SPL

Written for this site, from indicators reported by mattysplo.it

Accepted connections from public addresses per endpoint and port; listeners carry no peer address and are not in this form.

```Placeholder: index=edr is EDR network events normalised to CIM-style fields; dest is the endpoint, dest_port its local port, src_ip the remote peer.```
index=edr (dest_port IN (3389, 5900, 5901, 5902, 5903, 5904, 5905, 5938, 7070, 8172, 3283)
    ```optional: Remote Login (22) is enabled deliberately by developers and IT teams``` OR dest_port=22
    ```optional: 6568 is in the source's port list without a protocol mapping``` OR dest_port=6568)
| where NOT (cidrmatch("10.0.0.0/8", src_ip) OR cidrmatch("172.16.0.0/12", src_ip) OR cidrmatch("192.168.0.0/16", src_ip) OR cidrmatch("127.0.0.0/8", src_ip))
| stats count AS events dc(src_ip) AS remote_addresses min(_time) AS first_seen max(_time) AS last_seen BY dest, dest_port
| convert ctime(first_seen) ctime(last_seen)
| sort - last_seen
Lower-fidelity parts (2)
  • port 22 (Remote Login) — developers and IT teams enable SSH deliberately. Remove: delete the ``` optional: Remote Login comment and the port 22 term next to it
  • port 6568 — the source lists it without a protocol mapping. Remove: delete the ``` optional: 6568 comment and the port 6568 term next to it

CrowdStrike LogScale

Written for this site, from indicators reported by mattysplo.it

Listen and accept events for the port list on both platforms; the private-range filter applies to accepted connections only.

// Hunt: inbound RDP, VNC and remote-access listeners (Windows and macOS).
(#event_simpleName=NetworkReceiveAcceptIP4 or #event_simpleName=NetworkListenIP4)
| in(LocalPort, values=[3389, 5900, 5901, 5902, 5903, 5904, 5905, 5938, 7070, 8172, 3283])
    // optional: Remote Login (22) is enabled deliberately by developers and IT teams
    or LocalPort = 22
    // optional: 6568 is in the source's port list without a protocol mapping
    or LocalPort = 6568
| !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8"])
| groupBy([aid, ComputerName, LocalPort, #event_simpleName], function=[count(as=events), max(@timestamp, as=lastSeen)])
| sort(lastSeen, order=desc)
Lower-fidelity parts (2)
  • port 22 (Remote Login) — developers and IT teams enable SSH deliberately. Remove: delete the // optional: Remote Login comment and the port 22 term next to it
  • port 6568 — the source lists it without a protocol mapping. Remove: delete the // optional: 6568 comment and the port 6568 term next to it

Elastic

Written for this site, from indicators reported by mattysplo.it

Inbound events on the port list from public addresses, per host, port and process.

FROM logs-endpoint.events.network-*
| WHERE @timestamp > NOW() - 30 days
| WHERE destination.port IN (3389, 5900, 5901, 5902, 5903, 5904, 5905, 5938, 7070, 8172, 3283, 22, 6568)
| WHERE network.direction IN ("inbound", "incoming", "ingress")
| WHERE NOT CIDR_MATCH(source.ip, "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8")
| STATS connections = COUNT(*), sources = COUNT_DISTINCT(source.ip), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp)
    BY host.name, destination.port, process.name
| SORT last_seen DESC
Lower-fidelity parts (1)
  • port 22 and port 6568 — SSH is enabled deliberately by developers, and 6568 has no protocol mapping in the source. Remove: delete 22 and 6568 from the destination.port IN list

Cortex XQL

Written for this site, from indicators reported by mattysplo.it

Inbound connections on the port list from public addresses, per host, port and actor process.

// Hunt: inbound RDP, VNC and remote-access listeners. action_network_inbound is the field in the published mattysplo.it query; confirm it in your tenant.
dataset = xdr_data
| filter event_type = ENUM.NETWORK and action_network_inbound = true
| filter action_local_port in (3389, 5900, 5901, 5902, 5903, 5904, 5905, 5938, 7070, 8172, 3283)
    // optional: Remote Login (22) is enabled deliberately by developers and IT teams
    or action_local_port = 22
    // optional: 6568 is in the source's port list without a protocol mapping
    or action_local_port = 6568
| filter not (incidr(action_remote_ip, "10.0.0.0/8") or incidr(action_remote_ip, "172.16.0.0/12") or incidr(action_remote_ip, "192.168.0.0/16") or incidr(action_remote_ip, "127.0.0.0/8"))
| comp count() as events, count_distinct(action_remote_ip) as remote_addresses, min(_time) as first_seen, max(_time) as last_seen by agent_hostname, action_local_port, actor_process_image_name
| sort desc last_seen
Lower-fidelity parts (2)
  • port 22 (Remote Login) — developers and IT teams enable SSH deliberately. Remove: delete the // optional: Remote Login comment and the port 22 term next to it
  • port 6568 — the source lists it without a protocol mapping. Remove: delete the // optional: 6568 comment and the port 6568 term next to it

osquery

Written for this site, from indicators reported by mattysplo.it

Local listeners on the remote-access ports with the owning process, on Windows and macOS alike.

-- Hunt: remote-desktop and remote-access listeners on this endpoint (Windows and macOS).
SELECT lp.port, lp.address, lp.protocol, p.name, p.path
FROM listening_ports lp
JOIN processes p USING (pid)
WHERE lp.port IN (3389, 5900, 5901, 5902, 5903, 5904, 5905, 5938, 7070, 8172, 3283)
   OR lp.port = 22  -- optional: Remote Login is enabled deliberately by developers and IT teams
   OR lp.port = 6568  -- optional: 6568 is in the source's port list without a protocol mapping
Lower-fidelity parts (1)
  • port 22 and port 6568 — SSH is enabled deliberately by developers, and 6568 has no protocol mapping in the source. Remove: delete the two lines ending in -- optional

False positives and tuning

Where RDP to workstations is a supported help-desk path the narrow queries return that path; baseline and exclude legitimate jump hosts and support sources. The private-address exclusions do much of this work.

The process and file wildcards of the source, which match unrelated software, are left to the remote-access sweep and share its false positives.

On macOS, Screen Sharing and Remote Login are enabled deliberately by some users and IT teams, so compare against the management baseline.

Port 6568 appears in the source's list without a protocol mapping, and port 22 is enabled deliberately by many developers, so both are optional here.

Sources

Tuning required before use This hunt is a starting point. Adapt the queries to your schema, validate them against your baseline and tune them for the false positives noted above. Indicators go stale as operators rotate hosts.