You don’t get breached by a wizard. You get breached by an internet-exposed RDP port, a stale local admin password, and logs nobody ever looks at until the insurer asks for them.
RDP is still the workhorse remote access tool in a lot of Windows estates. It’s also one of the loudest attack magnets on the public internet. The good news: you can stop most RDP attacks with a baseline that’s boring, strict, and measurable. And if you do it right, you’ll sleep better—and your on-call phone will stop doing that “new credential stuffing wave” ringtone.
The threat model (what actually hits you)
Let’s be honest about what “RDP attacks” look like in real production environments:
- Mass scanning + brute force: Automated scanning finds TCP/3389 (or your “clever” alternate port) and tries common usernames and leaked passwords. This is the bulk of commodity noise.
- Password spraying: Low-and-slow attempts across many accounts to avoid lockouts. This is what hits organizations with weak policies and no MFA.
- Credential replay: Valid credentials from phishing, malware, or prior breaches. With RDP, a single working credential can be enough to establish a foothold.
- Misconfiguration harvesting: NLA off, old TLS settings, weak local admin practices, wide inbound firewall rules, “temporary” vendor access that became permanent.
- Post-login escalation: Once they’re in, they dump creds, pivot laterally, disable EDR, and start encrypting. RDP is just the front door; the house still needs internal locks.
The baseline in this article targets the part that causes the majority of RDP incidents: internet exposure + weak auth + weak policy + no monitoring. That’s the 80%. The other 20% is what keeps security teams employed.
Interesting facts and historical context (because patterns repeat)
- RDP has been around since the late 1990s (Windows NT 4.0 Terminal Server Edition era), long enough for every attacker and every defender to have muscle memory about it.
- TCP/3389 became “the new 22” for Windows estates: a default remote admin port that gets scanned continuously by bots, regardless of your industry.
- NLA (Network Level Authentication) was introduced to reduce pre-authentication attack surface by requiring authentication before a full session is created.
- Credential stuffing became mainstream once password dumps became routine and users reused passwords. RDP is a direct beneficiary of that bad habit.
- BlueKeep (CVE-2019-0708) was a wake-up call: pre-auth RDP vulnerabilities can be wormable. Even if you patched, you learned that “RDP exposed” is a lifestyle choice.
- RDP Gateway exists because enterprises learned the hard way that direct RDP from the internet is an incident generator, not an access strategy.
- Windows Event IDs for logon telemetry have been stable for years (e.g., 4624/4625), which is good: your detection and response shouldn’t depend on novelty.
- Account lockout policies were widely used long before modern MFA, but attackers adapted with spraying. That’s why lockouts are necessary but not sufficient.
- Security baselines became productized (Microsoft security baselines, CIS Benchmarks) because “tribal admin knowledge” doesn’t scale—and fails audits.
The baseline: the few decisions that crush most RDP attacks
This is the opinionated part. You want to reduce your attack surface, make authentication expensive for attackers, and make abuse obvious in logs. Do these, in order.
1) Stop exposing RDP to the public internet
If you do one thing, do this. Direct inbound RDP from the internet is like leaving your office lobby door open because the receptionist “is usually here.”
Pick one of these patterns:
- VPN + restricted RDP: Only allow RDP from VPN address space. Clean, common, effective.
- RD Gateway: Terminate HTTPS externally; RDP is proxied. Better for managed access, policy, and auditing.
- Privileged Access Workstations (PAW) + jump host: Admins RDP only from a controlled workstation to a jump box, then onward. Annoying. Correct.
- SSO-based remote access: If you have it, good. Still restrict inbound RDP at the firewall.
What not to do: “We changed the port from 3389 to 53389.” That’s not security; it’s just making the bots do one extra SYN scan.
2) Require NLA and modern TLS
NLA reduces the pre-auth RDP surface and cuts off some classes of abuse. Combine it with modern TLS configuration so you’re not negotiating antique cryptography out of nostalgia.
Also: disable “Allow connections only from computers running Remote Desktop with Network Level Authentication” only if you enjoy explaining yourself during incident review. You don’t.
3) Enforce MFA for remote admin paths
RDP itself doesn’t magically do MFA in a vacuum. You enforce MFA through the access path:
- VPN with MFA
- RD Gateway with MFA integration
- Conditional access / device compliance where applicable
MFA won’t fix terrible segmentation, but it stops a depressing number of “credential worked, game over” incidents.
4) Use strong account policy: lockouts, password length, and no shared admins
Local Administrator shared across servers is still painfully common. It’s also how one compromised box becomes forty. Use LAPS/Windows LAPS so each machine has a unique local admin password.
Set sane lockout thresholds. But understand the trade: lockouts can become a denial-of-service vector if you have lots of public exposure (another reason to eliminate public exposure).
5) Restrict who can RDP (and remove the rest)
Don’t let “Domain Users” or broad groups RDP. Use a dedicated group, least privilege, and keep it small. If you need vendor access, create a vendor-specific group, time-bound access, and audit it like you mean it.
6) Harden the endpoint: Defender/EDR, patching, and reduce lateral movement
RDP attacks are often just the beginning. Once a host is accessed, attackers try to escalate and pivot. Make that difficult:
- Patch regularly (especially RDP-related components and Windows auth stack)
- Disable legacy protocols (where feasible)
- Block inbound SMB from untrusted segments
- Use Windows Firewall rules that are explicit and scoped
7) Log like you’ll need it at 3 a.m.
RDP without logging is a haunted house: you can hear footsteps, but you can’t prove anything. Collect:
- Security logon events (success/failure, logon type, source IP)
- TerminalServices RemoteConnectionManager and LocalSessionManager logs
- Firewall logs (at least at the boundary; preferably host-based too)
One quote that belongs on every ops team wall:
“Hope is not a strategy.” — Gene Kranz
Joke #1: Changing the RDP port is like putting a fake mustache on your server and calling it “StealthHost01.” The internet is not fooled.
Practical tasks (commands, outputs, and what you decide)
These are designed to be runnable on Windows (locally or via remote management). The command prompt label is just a shell prompt convention—don’t overthink it.
Task 1: Is RDP enabled on this host?
cr0x@server:~$ reg query "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server
fDenyTSConnections REG_DWORD 0x0
What the output means: 0x0 means RDP connections are allowed. 0x1 means denied.
Decision: If this machine doesn’t have a business reason for RDP, set it to deny and remove any inbound firewall rules. If it does, continue hardening.
Task 2: Is NLA required?
cr0x@server:~$ reg query "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v UserAuthentication
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp
UserAuthentication REG_DWORD 0x1
What the output means: 0x1 generally indicates NLA required. 0x0 means not required.
Decision: If 0x0, enable NLA unless you have a specific legacy client requirement—and if you do, fix that requirement. Don’t enshrine it.
Task 3: What port is RDP listening on (and is it open)?
cr0x@server:~$ netstat -ano | findstr ":3389"
TCP 0.0.0.0:3389 0.0.0.0:0 LISTENING 1148
TCP [::]:3389 [::]:0 LISTENING 1148
What the output means: The host is listening on 3389 on all interfaces; PID 1148 owns the socket.
Decision: If the host listens on a public interface, you must restrict inbound network paths (VPN/RD Gateway/boundary firewall) and host firewall rules. “We’ll monitor it” is not a control.
Task 4: Confirm the Windows Firewall inbound rule posture for RDP
cr0x@server:~$ powershell -NoProfile -Command "Get-NetFirewallRule -DisplayGroup 'Remote Desktop' | Get-NetFirewallAddressFilter | Select-Object Name,RemoteAddress"
Name RemoteAddress
---- -------------
RemoteDesktop-UserMode-In-TCP Any
RemoteDesktop-UserMode-In-UDP Any
What the output means: The built-in RDP rules allow from Any remote address. That’s the default convenience setting, not a security setting.
Decision: Change RemoteAddress to your VPN subnet(s) or jump host IPs. If you can’t articulate the allowed source ranges, you’re not ready to allow RDP.
Task 5: Restrict RDP to known source subnets (host firewall)
cr0x@server:~$ powershell -NoProfile -Command "Set-NetFirewallRule -DisplayName 'Remote Desktop - User Mode (TCP-In)' -RemoteAddress 10.8.0.0/24,10.9.0.0/24; Get-NetFirewallRule -DisplayName 'Remote Desktop - User Mode (TCP-In)' | Get-NetFirewallAddressFilter | Select-Object RemoteAddress"
RemoteAddress
-------------
{10.8.0.0/24, 10.9.0.0/24}
What the output means: Only clients in those subnets can hit RDP TCP.
Decision: If you can’t restrict to known sources, you should not expose RDP. Implement VPN or RD Gateway first.
Task 6: Check who is allowed to log on via RDP
cr0x@server:~$ powershell -NoProfile -Command "Get-LocalGroupMember -Group 'Remote Desktop Users' | Select-Object Name,ObjectClass"
Name ObjectClass
---- ----------
CONTOSO\IT-RemoteAdmins Group
What the output means: Only the specified group is in the local “Remote Desktop Users” group.
Decision: If you see broad groups or individual users added ad hoc, tighten it. Use dedicated groups. Remove exceptions that exist only because someone didn’t want to file an access request.
Task 7: Check account lockout policy (domain or local)
cr0x@server:~$ net accounts
Force user logoff how long after time expires?: Never
Minimum password age (days): 1
Maximum password age (days): 90
Minimum password length: 14
Length of password history maintained: 24
Lockout threshold: 10
Lockout duration (minutes): 15
Lockout observation window (minutes): 15
What the output means: Password length, history, and lockout settings are visible. These might be overridden by domain policy.
Decision: If lockout threshold is 0 (never lock out) or password length is short, fix policy. If you’re internet-exposed, also fix exposure—lockouts alone are a bandage.
Task 8: Verify audit policy for logon events
cr0x@server:~$ auditpol /get /subcategory:"Logon"
System audit policy
Category/Subcategory Setting
Logon Success and Failure
What the output means: Logon events are recorded for both successful and failed attempts.
Decision: If it’s not “Success and Failure,” enable it. You can’t investigate what you didn’t log.
Task 9: Identify RDP failed logons in Security log (last 50)
cr0x@server:~$ powershell -NoProfile -Command "Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625} -MaxEvents 5 | Select-Object TimeCreated,@{n='User';e={$_.Properties[5].Value}},@{n='Ip';e={$_.Properties[19].Value}} | Format-Table -Auto"
TimeCreated User Ip
----------- ---- --
1/28/2026 2:11:05 AM Administrator 203.0.113.44
1/28/2026 2:10:58 AM admin 203.0.113.44
1/28/2026 2:10:52 AM test 203.0.113.44
1/28/2026 2:10:45 AM svc_backup 203.0.113.44
1/28/2026 2:10:39 AM user 203.0.113.44
What the output means: Event ID 4625 shows failed logons; the IP indicates the source. Seeing public IPs here is a smell if you intended RDP to be private.
Decision: If sources aren’t your VPN/jump ranges, your firewall story is wrong. Block at the edge and host. Then hunt for any successful logons from the same source.
Task 10: Find successful RDP logons (Logon Type 10) in Security log
cr0x@server:~$ powershell -NoProfile -Command "Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624} -MaxEvents 20 | Where-Object { $_.Properties[8].Value -eq 10 } | Select-Object TimeCreated,@{n='Account';e={$_.Properties[5].Value}},@{n='Ip';e={$_.Properties[18].Value}} | Format-Table -Auto"
TimeCreated Account Ip
----------- ------- --
1/27/2026 9:14:22 PM CONTOSO\j.smith 10.8.0.24
What the output means: Logon Type 10 typically indicates RemoteInteractive (RDP). The source IP should be your expected management path.
Decision: Any successful RDP logon from an unexpected IP is an incident until proven otherwise. Contain first, debate later.
Task 11: Check TerminalServices operational logs for connection attempts
cr0x@server:~$ powershell -NoProfile -Command "Get-WinEvent -LogName 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational' -MaxEvents 5 | Select-Object TimeCreated,Id,Message | Format-List"
TimeCreated : 1/28/2026 2:12:01 AM
Id : 1149
Message : Remote Desktop Services: User authentication succeeded: CONTOSO\j.smith
TimeCreated : 1/28/2026 2:11:55 AM
Id : 1149
Message : Remote Desktop Services: User authentication succeeded: CONTOSO\it-admin
What the output means: These logs help correlate RDP auth events with session behavior; they’re useful when Security logs are noisy.
Decision: If this log is disabled or not collected centrally, fix that. It’s cheap signal.
Task 12: Validate RDP service status
cr0x@server:~$ sc query TermService
SERVICE_NAME: TermService
TYPE : 20 WIN32_SHARE_PROCESS
STATE : 4 RUNNING
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
What the output means: Remote Desktop Services is running. If it’s running on machines that shouldn’t accept RDP, that’s drift.
Decision: For non-admin endpoints/servers, consider disabling RDP service and removing permissions rather than leaving it “available just in case.”
Task 13: Confirm that you are not accepting older, weaker RDP security layers
cr0x@server:~$ reg query "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v SecurityLayer
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp
SecurityLayer REG_DWORD 0x2
What the output means: Values differ by configuration; generally, you want TLS and not “RDP Security Layer” fallback. (Exact mapping can vary by build and policy.)
Decision: If you see settings that allow fallback to weaker security layers, enforce TLS-only via policy and remove exceptions. Exceptions are how “temporary compatibility” becomes “permanent exposure.”
Task 14: Verify Windows Defender Firewall logging (host visibility)
cr0x@server:~$ powershell -NoProfile -Command "Get-NetFirewallProfile | Select-Object Name,LogAllowed,LogBlocked,LogFileName | Format-Table -Auto"
Name LogAllowed LogBlocked LogFileName
---- ---------- ---------- -----------
Domain False True %systemroot%\system32\logfiles\firewall\pfirewall.log
Private False True %systemroot%\system32\logfiles\firewall\pfirewall.log
Public False True %systemroot%\system32\logfiles\firewall\pfirewall.log
What the output means: Blocked connections are logged. Allowed connections are not (often fine; logging everything can be noisy).
Decision: If you’re troubleshooting exposure or attacks, blocked logging helps validate whether rules work. If logs aren’t enabled anywhere, you’re debugging blind.
Task 15: Check for inbound 3389 exposure at the perimeter from a Linux jump (external view)
cr0x@server:~$ nmap -Pn -p 3389 198.51.100.10
Starting Nmap 7.80 ( https://nmap.org ) at 2026-01-28 02:14 UTC
Nmap scan report for 198.51.100.10
Host is up (0.020s latency).
PORT STATE SERVICE
3389/tcp open ms-wbt-server
Nmap done: 1 IP address (1 host up) scanned in 0.45 seconds
What the output means: The host is reachable on 3389 from wherever you ran this scan. If that location is the public internet or an untrusted network, you’ve got a problem.
Decision: Close it at the boundary firewall/security group. Do not rely only on host firewall. Defense in depth is not a slogan; it’s your future incident timeline.
Task 16: Enumerate active RDP sessions (who’s on the box?)
cr0x@server:~$ quser
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
j.smith rdp-tcp#12 2 Active . 1/27/2026 9:14 PM
it-admin rdp-tcp#13 3 Disc 8 1/28/2026 1:58 AM
What the output means: You can see active and disconnected sessions. Disconnected sessions can persist and be abused if you don’t manage them.
Decision: If you see unexpected admins or long-lived disconnected sessions, investigate and consider setting session timeouts and “logoff disconnected sessions” policies.
Task 17: Confirm the box isn’t in “Public” network profile (common in cloud images)
cr0x@server:~$ powershell -NoProfile -Command "Get-NetConnectionProfile | Select-Object InterfaceAlias,NetworkCategory"
InterfaceAlias NetworkCategory
-------------- ---------------
Ethernet0 DomainAuthenticated
What the output means: DomainAuthenticated usually means domain policies apply and firewall profiles are appropriate.
Decision: If it’s Public unexpectedly, fix network identification and firewall profile application; you may be missing domain GPO protections or applying the wrong rules.
Joke #2: RDP on the open internet is the security equivalent of “we’ll just keep the passwords in a spreadsheet, but it’s a hidden tab.” Attackers love hidden tabs.
Three corporate mini-stories from the trenches
Mini-story 1: The incident caused by a wrong assumption
The company had migrated a chunk of Windows servers into a cloud VPC. The network team swore up and down that “nothing is public unless we explicitly make it public.” Reasonable assumption. Also wrong in practice.
A project team needed a vendor to perform a one-time install on a legacy app server. Someone added a temporary inbound rule in a security group to allow TCP/3389 “from vendor IP range.” That range was broader than anyone realized—because the vendor used a rotating pool and the ticket got translated into “just allow from anywhere for now.” The rule was never removed. Nobody noticed because nothing broke. And nothing breaking is how risk gets promoted.
Two months later, the Security log filled with 4625 failures against local accounts. The SOC saw it but categorized it as background internet noise. After all, “RDP isn’t public.” The compromise came through a reused password for a local admin account that also existed on other servers. The attacker logged in once, then quietly enumerated the network, harvested creds, and used RDP laterally where it was allowed internally.
The post-incident write-up was brutal in the best way. The primary failure wasn’t “bad password.” It was the assumption that the environment made exposure impossible. The fix was equally blunt: automated checks that continuously verified which hosts were reachable on 3389 from outside the management network, plus a hard requirement that all RDP must come from VPN or RD Gateway, enforced at the boundary and on-host.
Wrong assumptions don’t announce themselves. They show up later as “unexpected logons” and a calendar full of uncomfortable meetings.
Mini-story 2: The optimization that backfired
An ops team decided to reduce helpdesk calls by relaxing account lockouts. Their thinking: remote workers often fat-finger passwords, and lockouts created a support queue. So they increased the lockout threshold substantially and shortened the lockout duration. It was framed as “improving productivity.” It did improve productivity—for attackers.
At the same time, they were onboarding contractors and didn’t want to deal with issuing managed devices. Contractors got VPN credentials (no MFA at the time) and were told to RDP to a jump host. The jump host had RDP open to the VPN subnet, which sounded fine. But the VPN subnet was huge, shared across partners, and not monitored well.
Then came password spraying. Not a fast brute force that triggers alarms. A patient, distributed spray across many accounts from many VPN IPs. With the relaxed lockouts, the attacker had plenty of runway. Eventually, a contractor account with a weak password hit. From there: RDP to the jump host, then into internal servers, then credential dumping.
The team reverted the lockout changes, implemented MFA on VPN, and segmented the VPN pools so “contractors” weren’t swimming in the same ocean as admins. The lesson wasn’t “lockouts solve everything.” The lesson was: optimizing for fewer tickets without understanding threat behavior is how you pay for silence with security.
Good operations is about reducing toil without increasing blast radius. If your optimization reduces toil and increases blast radius, you didn’t optimize. You borrowed trouble.
Mini-story 3: The boring but correct practice that saved the day
A different company had a stubborn, almost old-fashioned practice: every quarter, they ran an access review and a firewall exposure review. Not a slide deck. A real review with changes executed and validated.
They had a rule: RDP is not an access method; it’s an internal protocol. The access method is VPN with MFA, and only from managed devices. Host firewalls were configured to accept RDP only from jump hosts and a small admin VLAN. On top of that, the boundary firewall had a blanket deny for inbound 3389. If you wanted an exception, you had to explain yourself in writing to two teams who enjoy saying “no.”
One weekend, their SIEM alerted on a burst of 4625 failures against a handful of admin accounts on a server that shouldn’t have been reachable except from the admin VLAN. The on-call SRE checked the host firewall logs and saw blocked attempts from a partner network range. That should have been impossible.
It turned out a network change had accidentally routed the partner range into an internal segment that could reach the server. The host firewall blocked the attempts anyway, and the logs proved it. They fixed the routing mistake before any successful logon occurred. The incident report was short, boring, and satisfying—the best kind. The preventive control worked, the detective control told the truth, and recovery was “change a route, close the ticket.”
Boring practices don’t get conference talks. They do keep your name out of breach notifications.
Fast diagnosis playbook (find the bottleneck fast)
This is the “you’re on call, RDP is ‘acting weird,’ and someone is shouting” sequence. The goal is to quickly determine whether the issue is exposure, authentication, policy, or resource constraints.
First: verify exposure and path
- From outside the management network, test reachability to 3389 (or your configured port). If it’s reachable publicly, that’s not a “connectivity issue”—it’s a policy failure.
- On the server, verify listening sockets (
netstat -ano) and firewall scope (RemoteAddress not Any). - Confirm the client path: Are users connecting via VPN or RD Gateway? If they can’t answer, neither can you.
Second: check authentication and policy controls
- Is NLA required? Check
UserAuthenticationand group policy. - Are there account lockouts happening? Check failed logons (4625), lockout events (4740 in domain environments), and identify whether it’s fat-finger or attack traffic.
- Is MFA enforced on the access path (VPN/RD Gateway)? If not, assume credentials will eventually be replayed.
Third: confirm the service and session health
- Check
TermServicestate. If it’s stopped, ask why it stopped (crash? patch? policy?). - Check active/disconnected sessions (
quser). Too many sessions can degrade experience and mask abuse. - Check CPU and memory pressure (Task Manager/PerfMon). RDP “slowness” is often just a CPU-saturated VM.
Fourth: look for malicious intent, quickly
- Spike of 4625 failures from unexpected IPs → treat as attack traffic; block at boundary and host.
- Any 4624 Logon Type 10 from unexpected source IP → incident until cleared.
- Correlate with TerminalServices operational logs to identify user/session timeline.
Common mistakes: symptom → root cause → fix
1) “We disabled RDP, but it’s still reachable”
Symptom: Port 3389 still shows open from scans; users can still connect.
Root cause: You disabled user connections in one place (registry/UI) but left firewall rules or an RD Gateway path, or you’re scanning a different host/NAT target.
Fix: Validate on-host fDenyTSConnections, service state, and firewall rules; validate at boundary (security group/firewall) and confirm NAT mappings. Ensure there’s no alternate listener and no load balancer forwarding.
2) “We enabled NLA, but legacy clients can’t connect”
Symptom: Old thin clients or embedded systems fail to connect after change.
Root cause: Client doesn’t support NLA/CredSSP requirements.
Fix: Upgrade or replace the clients. If you absolutely must support them temporarily, isolate the target hosts, restrict source IPs tightly, and set a hard deadline. Don’t generalize the exception.
3) “RDP keeps locking out accounts”
Symptom: Users get locked out repeatedly; helpdesk tickets spike.
Root cause: Password spraying or a saved credential (scheduled task, service, mapped drive) repeatedly failing. Sometimes it’s an attacker; sometimes it’s your own automation.
Fix: Inspect Security logs: 4625 patterns and source IPs. If from external/unexpected ranges, block. If from internal hosts, find the stored credential source (task, service, cached RDP credential). Reset and update.
4) “We restricted firewall to VPN subnet, but people still connect from home without VPN”
Symptom: Connections succeed from public IPs.
Root cause: A different firewall rule is allowing it (higher priority), or there’s an upstream allow at the perimeter with NAT hairpinning, or you restricted the wrong rule (UDP vs TCP, or different display name).
Fix: Enumerate all inbound rules referencing 3389 and Remote Desktop groups; verify effective policy; disable broad rules; log blocked traffic and retest from an external network.
5) “We moved RDP to a different port and attacks stopped” (for now)
Symptom: Fewer log entries; fewer alerts.
Root cause: You reduced noise, not risk. Attackers scan all ports; you just fell off the lowest-hanging fruit list temporarily.
Fix: Put it back if you like standardization. More importantly: remove internet exposure, enforce NLA, enforce MFA via access path, restrict sources, and monitor. Noise reduction is not a control.
6) “RDP is slow and unreliable”
Symptom: Frequent disconnects, lag, black screens.
Root cause: Often resource starvation (CPU/memory), or network path issues (packet loss, MTU problems on VPN), or overloaded session host.
Fix: Check session count, CPU/memory, and event logs for disconnect reasons. Treat performance separately from security, but don’t let performance “fixes” loosen security controls.
7) “We have RD Gateway, so we’re safe”
Symptom: Direct RDP is still open “for emergencies,” and it gets used.
Root cause: Cultural workaround. The emergency path becomes the primary path because it’s easier.
Fix: Remove direct inbound RDP. If you must keep a break-glass method, it must be time-bound, logged, and require explicit approval with MFA.
Checklists / step-by-step plan
Phase 0: Decide what RDP is allowed to be
- Policy: “RDP is internal-only.” Write it down.
- Access method: Choose VPN+MFA or RD Gateway+MFA (or both).
- Admin model: Dedicated admin accounts; no shared local admin; use LAPS/Windows LAPS.
- Logging: Security + TerminalServices logs centrally collected, with alerting for anomalies.
Phase 1: Remove exposure
- At the perimeter, block inbound TCP/3389 (and UDP/3389 if applicable) from the internet.
- If you can’t block globally, block per-host and document exceptions with owners and expiration.
- On each host, scope RDP firewall rules to VPN/jump host subnets only.
- Validate from an external network that 3389 is closed.
Phase 2: Harden authentication
- Enable NLA everywhere.
- Implement MFA on VPN/RD Gateway.
- Enforce minimum password length and history; avoid weak exceptions for service accounts.
- Set lockout threshold/window/duration appropriate to your environment (and reduce exposure first to avoid lockout DoS).
Phase 3: Reduce blast radius
- Use separate admin accounts (no email, no browsing) and restrict where they can log on.
- Limit “Remote Desktop Users” membership to dedicated groups.
- Segment networks so user subnets can’t RDP to servers by default.
- Ensure EDR/Defender is enabled and tamper protection is configured per your org’s standard.
Phase 4: Make it observable
- Enable audit policy for logon success/failure.
- Collect 4624/4625 and TerminalServices operational logs centrally.
- Alert on:
- Spike in 4625 from a single IP or many IPs
- 4624 Logon Type 10 from non-management IP ranges
- RDP logons outside expected change windows for sensitive servers
- Run monthly exposure checks (external scan + internal verification).
FAQ
1) Does changing the RDP port help?
It reduces noise, not risk. It can be a minor speed bump, but scanners find it. Do it only if it supports standardization in your environment, not as a primary control.
2) Is NLA enough to secure RDP?
No. NLA is necessary. It is not sufficient. You still need restricted network access (VPN/RD Gateway), MFA, and proper account controls.
3) Should I allow RDP only from an IP allowlist?
Yes, if the allowlist is stable (VPN subnets, jump host IPs, admin VLANs). Avoid allowlisting random home IPs; that becomes unmaintainable and encourages exceptions.
4) What event IDs matter most for RDP investigations?
Start with Security log 4624 (successful logon) and 4625 (failed logon). In domain environments, also watch for account lockouts (4740). Add TerminalServices RemoteConnectionManager operational logs for RDP-specific correlation.
5) We need vendors to RDP in. What’s the least-bad approach?
Use RD Gateway or VPN with MFA, restrict by group membership, time-bound access, and restrict to specific target hosts. Log and review. “Permanent vendor local admin” is how you end up with permanent vendor incident response.
6) Do I need UDP/3389 open?
Many environments can function with TCP only, but performance features may use UDP. Security-wise, treat UDP exposure similarly: restrict sources and avoid internet exposure. Test with your client base before disabling.
7) What’s the quickest win if I inherited a messy environment?
Block inbound RDP at the perimeter, then scope host firewall rules to management networks. That instantly cuts off mass scanning and brute force from the internet.
8) If we use RD Gateway, can we leave direct RDP open as a backup?
Don’t. Backups become primary under pressure. If you require a break-glass path, make it time-limited, MFA-protected, and audited with explicit approval.
9) How do I tell if our “RDP attacks” are real or just internet noise?
If you see failed logons (4625) from public IPs, that’s noise but still a problem because it indicates exposure. If you see any successful logon (4624 type 10) from unexpected sources, that’s real.
10) Should we disable RDP entirely on servers?
If you can manage servers through safer channels (remote management tools, just-in-time admin, console access), yes—reduce the number of machines where RDP is even possible. For the rest, keep it private and controlled.
Practical next steps
Do this in the next two weeks, not “this quarter.” The attackers aren’t waiting for your change window calendar to feel emotionally ready.
- Inventory: Find every Windows host with RDP enabled and every path that can reach it (internal and external).
- Close exposure: Block inbound RDP at the perimeter. Then scope host firewall rules to VPN/jump sources.
- Enforce NLA: Turn it on everywhere, kill exceptions, upgrade clients.
- Put MFA in the access path: VPN or RD Gateway, with a policy that admins use managed devices.
- Fix admin hygiene: Separate admin accounts, remove broad RDP permissions, deploy LAPS/Windows LAPS.
- Operationalize telemetry: Enable logon auditing, collect logs centrally, and alert on the small number of signals that matter.
- Prove it: Re-run the commands above after changes. If you can’t prove the baseline, you don’t have a baseline—you have vibes.