Proteggere RDP: mettere in sicurezza Windows Server dagli attacchi
Un server Windows appena installato raccoglie in poche ore migliaia di accessi RDP falliti. Questa guida mostra quali misure funzionano davvero, quanto costa ciascuna e come evitare di chiuderti fuori da solo.
Un server Windows che va in rete con la porta 3389 aperta non viene attaccato prima o poi, ma nel giro di pochi minuti. Gli scanner girano in permanenza, conoscono ogni intervallo di indirizzi IPv4 e provano meccanicamente nomi utente e password. Quasi tutti gli incidenti ransomware sui server piccoli iniziano esattamente qui: un account chiamato Administrator, una password che qualcuno riteneva sufficiente e nessun blocco dopo il millesimo tentativo fallito.
Questa guida affronta le misure nell'ordine della loro efficacia reale, non nell'ordine in cui compaiono nella maggior parte degli articoli. Tutti i comandi girano in una sessione PowerShell con privilegi di amministratore, su Windows Server dal 2016 al 2025.
Perché RDP è la porta più attaccata
RDP non è un protocollo sbagliato. Il problema è che espone un modulo di accesso completo su internet e che Windows, per impostazione predefinita, lascia compilare quel modulo un numero illimitato di volte. Un attaccante non ha bisogno di una vulnerabilità, gli bastano pazienza e una lista di password.
Quanto è colpito il tuo server lo verifichi con una riga sola. Conta gli accessi falliti delle ultime 24 ore:
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=(Get-Date).AddDays(-1)} -ErrorAction SilentlyContinue | Measure-Object | Select-Object -ExpandProperty Count
Un server interno senza raggiungibilità da internet si attesta di solito su poche decine al giorno, per lo più password dimenticate e vecchi account di servizio. Un server con la 3389 aperta arriva in fretta a migliaia, se non a decine di migliaia. Questo numero è il tuo metro di paragone: dopo le misure qui sotto deve calare di diversi ordini di grandezza. Se Get-WinEvent risponde "Non sono stati trovati eventi corrispondenti ai criteri di selezione specificati", il tuo server non registra affatto gli accessi falliti. Lo sistemiamo più avanti.
Prima della prima modifica: mettere al sicuro la via di ritorno
Ognuna delle misure che seguono può lasciarti fuori. Su un server che raggiungi solo via RDP significa la fine della sessione e l'inizio di una lunga serata. Tre precauzioni costano cinque minuti ed evitano esattamente questo.
Primo: verifica di avere una console che funziona indipendentemente da RDP. Sui server root KVM di KernelHost trovi nell'area clienti una console VNC che guarda direttamente lo schermo della macchina virtuale. Funziona anche quando firewall, rete e servizio RDP sono rotti tutti insieme. Aprila una volta come prova prima di cambiare qualcosa, non dopo.
Secondo: crea un secondo account amministratore, così che un'utenza bloccata non equivalga a un server perso. I nomi dei gruppi dipendono dalla lingua, perciò lavoriamo con i SID fissi (S-1-5-32-544 è il gruppo degli amministratori locali, S-1-5-32-555 quello degli utenti Desktop remoto):
New-LocalUser -Name 'kh-adm' -Password (Read-Host -AsSecureString -Prompt 'Password') -FullName 'Account di manutenzione' -PasswordNeverExpires
Add-LocalGroupMember -SID 'S-1-5-32-544' -Member 'kh-adm'
Add-LocalGroupMember -SID 'S-1-5-32-555' -Member 'kh-adm'
La terza riga è a rigore superflua, perché i membri del gruppo degli amministratori locali entrano comunque via RDP. Non fa danni e rende visibile l'intenzione, nel caso in cui più avanti togliessi l'account dal gruppo degli amministratori.
Terzo: lascia aperta per tutta la durata della migrazione la sessione RDP in cui stai lavorando e prova ogni modifica con una seconda connessione, nuova. Una sessione esistente non viene interrotta subito dalle nuove regole firewall, una connessione nuova invece fallisce immediatamente. Così ti accorgi dell'errore finché puoi ancora annullarlo.
Imporre l'autenticazione a livello di rete
Senza autenticazione a livello di rete (in inglese Network Level Authentication, in breve NLA) il server apre prima una sessione, disegna una schermata di accesso e chiede le credenziali dopo. Ogni tentativo di connessione anonimo costa quindi RAM e CPU, e tutto il codice dietro la schermata di accesso è raggiungibile prima di qualsiasi autenticazione. È proprio lì che si annidavano le vulnerabilità RDP gravi del passato.
Con NLA il server verifica le credenziali tramite CredSSP prima ancora che nasca una sessione. Un bot che non possiede dati validi ottiene soltanto una connessione TLS rifiutata.
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -Value 1
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'SecurityLayer' -Value 2
SecurityLayer su 2 impone TLS per la negoziazione. Il valore 1 (negozia) permette a un client di ripiegare all'occorrenza sul vecchio livello di sicurezza RDP, 0 è il vecchio livello senza TLS. Per verificare:
Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' | Select-Object UserAuthentication, SecurityLayer
Non serve un riavvio, i valori valgono per ogni connessione stabilita da quel momento in poi. Le sessioni esistenti proseguono invariate, il che è comodo se ti stai giusto chiudendo fuori un client.
Quando poi non entra più nessuno
Dopo l'attivazione di NLA compaiono regolarmente due quadri di errore.
"Il computer remoto richiede l'autenticazione a livello di rete, che il computer in uso non supporta. Per assistenza, contattare l'amministratore di sistema o il supporto tecnico." Il client è troppo vecchio oppure non parla CredSSP. I client Windows attuali ne sono capaci da anni. Su Linux FreeRDP richiede l'opzione /sec:nla, Remmina l'impostazione del protocollo di sicurezza su NLA, e i client macOS molto vecchi falliscono comunque. La soluzione è sempre il client più recente, mai la disattivazione di NLA.
L'accesso fallisce nonostante la password sia corretta. È la trappola che la maggior parte delle guide tace: se su un account è impostata l'opzione "L'utente deve cambiare la password all'accesso successivo" oppure la password è scaduta, quell'account con NLA attivo non riesce più ad accedere affatto. CredSSP non conosce il cambio password, è previsto così. A seconda della versione il server segnala un errore di autenticazione oppure semplicemente credenziali non valide. La via d'uscita passa dalla console nell'area clienti, oppure esenti gli account di manutenzione dalla scadenza:
Set-LocalUser -Name 'kh-adm' -PasswordNeverExpires $true
Blocco account: il singolo interruttore più efficace
Una password robusta protegge da chi prova a indovinare, un blocco account protegge dai tentativi illimitati. Senza, un attaccante può fare milioni di tentativi a settimana, con il blocco sono dieci ogni quarto d'ora. Imposta soglia, durata del blocco e finestra di osservazione in un'unica chiamata, altrimenti Windows rifiuta la durata finché la soglia è ancora a 0:
net accounts /lockoutthreshold:10 /lockoutduration:15 /lockoutwindow:15
La durata del blocco deve essere sempre maggiore o uguale alla finestra di osservazione. Controlla il risultato:
net accounts
Le versioni recenti di Windows portano già un valore predefinito di questo ordine di grandezza, le installazioni più vecchie e molte immagini fornite dai provider no. Verificare non costa nulla.
Includere l'account Administrator integrato
Storicamente proprio l'account che ogni attaccante prova per primo era escluso dal blocco. Per questo esiste il criterio "Consenti blocco account Administrator". Presuppone però una versione recente, cioè Windows 11 22H2 oppure Windows Server 2025. Su un Windows Server 2022 (build 20348) l'esportazione del criterio di sicurezza non contiene affatto la riga AllowAdministratorLockout: nella sezione [System Access] compaiono solo valori come LockoutBadCount e MinimumPasswordLength. Verifica quindi prima di tutto che cosa esporta davvero il tuo sistema:
secedit /export /cfg C:\secpol.inf
Select-String -Path C:\secpol.inf -Pattern 'AllowAdministratorLockout'
Se non torna nessuna corrispondenza, la tua build non conosce il criterio e per te questa sezione è chiusa. Proprio qui sta la trappola che la maggior parte delle guide crea: la classica riga singola con -replace sostituisce una stringa che nel file non esiste affatto, ma non segnala alcun errore, e il successivo secedit /configure termina con esito positivo. Dopo credi che il blocco dell'account Administrator sia attivo, mentre non è cambiato nulla.
Su una build che conosce il criterio modifichi la riga se c'è, altrimenti la inserisci. Questa versione copre entrambi i casi:
$c = Get-Content C:\secpol.inf
if ($c -notmatch 'AllowAdministratorLockout') { $c = $c -replace '(LockoutBadCount = \d+)', "`$1`r`nAllowAdministratorLockout = 1" } else { $c = $c -replace 'AllowAdministratorLockout = 0','AllowAdministratorLockout = 1' }
$c | Set-Content C:\secpol.inf -Encoding Unicode
Il -Encoding Unicode è obbligatorio e non è un dettaglio: il file INF deve essere UTF-16 LE con byte order mark. In Windows PowerShell 5.1 Set-Content scrive altrimenti in ANSI, e secedit ne rifiuta l'acquisizione. Prima di riscrivere, secedit /validate C:\secpol.inf controlla il file, poi:
secedit /configure /db C:\Windows\security\local.sdb /cfg C:\secpol.inf /areas SECURITYPOLICY
E dopo rileggi assolutamente il risultato, perché a questo punto un'esecuzione riuscita di secedit non dimostra nulla. Esporta di nuovo il criterio in un secondo file e guarda se il valore è davvero impostato su 1.
Su un controller di dominio queste impostazioni locali non hanno effetto. Lì il criterio di blocco va nella Default Domain Policy, sotto Configurazione computer, Impostazioni di Windows, Impostazioni sicurezza, Criteri account.
Il rovescio della medaglia e come rientrare
Un blocco account è anche un'arma contro di te: chi conosce il tuo nome utente può tenerlo bloccato in permanenza inviando dieci password sbagliate ogni 15 minuti. Proprio per questo il blocco è solo la seconda linea di difesa, la prima è la limitazione per indirizzo IP della sezione successiva.
Se un account locale è bloccato, il client segnala in sostanza "L'account a cui si fa riferimento è attualmente bloccato e non può essere utilizzato per l'accesso". La via di ritorno più semplice è aspettare, perché allo scadere della durata del blocco Windows sblocca da solo. Chi non vuole aspettare sblocca dalla console:
$u = [ADSI]"WinNT://./kh-adm,user"; $u.IsAccountLocked = $false; $u.SetInfo()
In Active Directory è più breve con Unlock-ADAccount -Identity kh-adm.
Password che rendono sensato il blocco
Dieci tentativi ogni quarto d'ora sono un ostacolo solo se la password non è al terzo posto di ogni lista. Lunghezza minima e complessità le imposti così:
net accounts /minpwlen:14
La regola di complessità sta di nuovo nel criterio di sicurezza, sezione [System Access], chiave PasswordComplexity = 1. La strada è la stessa vista sopra con secedit.
Due punti dalla pratica. Primo, il cambio password forzato ogni 30 giorni serve dimostrabilmente a poco e, in combinazione con NLA, produce esattamente il problema di accesso della sezione precedente. Meglio password lunghe, impostate una volta sola e conservate in un password manager. Secondo, il criterio vale solo per le password impostate da quel momento in poi. Una password esistente di sei caratteri resta valida finché non la cambi.
Limitare l'accesso a indirizzi IP noti
È la misura che chiude davvero il traffico di attacco, e lo chiude del tutto. Tutte le regole del gruppo Desktop remoto si possono limitare a un elenco di indirizzi. Usa l'identificatore di gruppo neutro rispetto alla lingua, così lo script funziona anche su installazioni inglesi:
Get-NetFirewallRule -Group '@FirewallAPI.dll,-28752' | Select-Object Name, DisplayName, Enabled, Profile
Set-NetFirewallRule -Group '@FirewallAPI.dll,-28752' -RemoteAddress @('203.0.113.10','198.51.100.0/24')
Aspettati più risultati delle regole che hai in mente. -Group tocca tutte le regole del gruppo: su un sistema di prova erano sei, comprese le regole ombra e alcune copie aggiuntive con nome GUID nel profilo Public. È voluto ed è corretto, altrimenti una delle copie resterebbe aperta. L'elenco preliminare con Get-NetFirewallRule ti mostra in anticipo che cosa verrà toccato.
Controllo di quali indirizzi sono stati effettivamente salvati:
Get-NetFirewallRule -Group '@FirewallAPI.dll,-28752' | Get-NetFirewallAddressFilter
Se hai cambiato la porta, le regole integrate non fanno più presa, perché sono legate in modo fisso alla 3389. Allora ti serve una regola tua:
New-NetFirewallRule -DisplayName 'RDP limitato' -Direction Inbound -Protocol TCP -LocalPort 34567 -RemoteAddress '203.0.113.10' -Action Allow -Profile Any
L'ancora di salvezza contro la tua stessa regola firewall
Chi digita male il proprio indirizzo IP o inserisce un indirizzo dinamico si chiude fuori con certezza. Crea perciò in anticipo un'attività pianificata che revoca da sola la limitazione dopo dieci minuti:
Set-Content -Path C:\rdp-soccorso.ps1 -Value "Set-NetFirewallRule -Group '@FirewallAPI.dll,-28752' -RemoteAddress Any"
Register-ScheduledTask -TaskName 'RDP-Soccorso' -Action (New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-ExecutionPolicy Bypass -File C:\rdp-soccorso.ps1') -Trigger (New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(10)) -User 'SYSTEM' -RunLevel Highest
Se la nuova connessione funziona, rimuovi di nuovo l'attività:
Unregister-ScheduledTask -TaskName 'RDP-Soccorso' -Confirm:$false
Se non hai un indirizzo IP fisso, la soluzione pulita è non esporre affatto RDP su internet, ma raggiungerlo tramite una VPN. Come si imposta lo trovi nella nostra guida al server VPN WireGuard. Il server ascolta poi solo sull'indirizzo VPN e la porta 3389 sparisce completamente da internet.
Cambiare la porta e che cosa serve davvero
Una porta diversa non è una misura di sicurezza, è una misura contro il rumore. La grande massa dei bot scansiona esclusivamente la 3389 e dopo non ti trova più, il che di regola abbassa drasticamente il tuo numero di eventi 4625 e rende di nuovo leggibili i registri eventi. Chi cerca in modo mirato trova comunque il servizio: i motori di ricerca per servizi esposti riconoscono RDP dall'impronta di protocollo, indipendentemente dalla porta, e una scansione completa di 65535 porte dura pochi secondi.
Il cambio di porta ha quindi senso come integrazione, ma mai come sostituto di NLA, blocco account e limitazione degli indirizzi. La realizzazione pratica, compresa la parte che quasi tutte le guide sbagliano, cioè senza riavvio, l'abbiamo descritta a parte: cambiare la porta RDP senza riavvio. Ricordati in ogni caso di creare una regola firewall per la nuova porta prima di riconfigurare il servizio.
Analizzare i log di accesso
Prima di tutto la registrazione deve essere attiva. I nomi delle sottocategorie di auditpol sono tradotti, quindi un comando in inglese fallisce su un sistema italiano con "Si è verificato l'errore 0x00000057: parametro non corretto." Il GUID invece funziona su qualsiasi versione linguistica:
auditpol /set '/subcategory:{0CCE9215-69AE-11D9-BED3-505054503030}' /success:enable /failure:enable
auditpol /get '/subcategory:{0CCE9215-69AE-11D9-BED3-505054503030}'
Gli apici singoli intorno all'intero parametro non sono un vezzo, sono obbligatori. Altrimenti PowerShell interpreta le parentesi graffe come blocco di script e le rimuove, da un argomento ne diventano tre, e auditpol si interrompe con Si è verificato l'errore 0x00000057: parametro non corretto. e codice di uscita 87, seguito dal testo della guida. In cmd.exe la forma senza apici funziona, in PowerShell no. Con gli apici passano entrambe con codice di uscita 0, e la query di controllo risponde poi Accesso Riuscito e non riuscito.
Sotto attacco il registro di sicurezza si riempie in poche ore e sovrascrive proprio le voci che ti servono. Dagli più spazio:
wevtutil sl Security /ms:1073741824
Poi gli indirizzi di origine più frequenti dell'ultima settimana, ordinati per numero. La variante che passa dalla struttura XML è quella robusta, perché non dipende dall'ordine dei campi:
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=(Get-Date).AddDays(-7)} -ErrorAction SilentlyContinue | ForEach-Object { ([xml]$_.ToXml()).Event.EventData.Data | Where-Object { $_.Name -eq 'IpAddress' } | Select-Object -ExpandProperty '#text' } | Group-Object | Sort-Object Count -Descending | Select-Object -First 15 Count, Name
Il -ErrorAction SilentlyContinue va messo in ognuna di queste chiamate. Get-WinEvent si interrompe con un errore rosso non appena nel periodo indicato non esiste nemmeno un evento corrispondente: "Non sono stati trovati eventi corrispondenti ai criteri di selezione specificati." Su un server appena messo in sicurezza è proprio questo il caso normale, quindi il comando fallisce esattamente in corrispondenza del successo a cui questa guida punta.
La domanda decisiva però non è chi ci ha provato, ma se qualcuno ci è riuscito. Gli accessi Desktop remoto riusciti portano l'ID evento 4624 con tipo di accesso 10 (RemoteInteractive):
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624; StartTime=(Get-Date).AddDays(-7)} -ErrorAction SilentlyContinue | Where-Object { $_.Properties[8].Value -eq 10 } | Select-Object TimeCreated, @{n='Utente';e={$_.Properties[5].Value}}, @{n='Origine';e={$_.Properties[18].Value}} | Format-Table -AutoSize
Se lì compare un nome utente o un indirizzo di origine che non riesci a ricondurre a nulla, qualcuno ha trovato una password valida. Allora non serve più affinare i criteri, il server va reinstallato da zero.
Vale inoltre la pena dare un'occhiata al log RDP dedicato. L'evento 1149 riporta utente, dominio e indirizzo di origine di ogni connessione autorizzata in una sola riga:
Get-WinEvent -LogName 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational' -FilterXPath '*[System[EventID=1149]]' -MaxEvents 25 | Format-List TimeCreated, Message
Rinominare Administrator, meglio ancora disattivarlo
L'account Administrator integrato è l'unico nome utente che ogni attaccante conosce con certezza. Rinominarlo non costa nulla:
Rename-LocalUser -Name 'Administrator' -NewName 'kh-svc'
Sii però consapevole del limite: il SID dell'account continua a terminare con -500, e qualsiasi accesso autenticato può risalire da lì al nuovo nome. Contro i bot di massa la rinomina funziona, contro un attaccante mirato che ha già un piede dentro la porta no. Ecco come trovare l'account nonostante il nuovo nome:
Get-LocalUser | Where-Object { $_.SID.Value -like '*-500' } | Select-Object Name, Enabled
Molto più efficace è disattivare del tutto l'account integrato, dopo che il tuo account amministratore della sezione "mettere al sicuro la via di ritorno" funziona in modo dimostrato. Prova l'accesso con il nuovo account in una seconda sessione, solo allora:
Disable-LocalUser -Name 'kh-svc'
Limita inoltre l'accesso RDP alle persone che ne hanno bisogno. Per impostazione predefinita ogni membro del gruppo degli amministratori locali può entrare via RDP, anche account di servizio che non dovrebbero farlo mai. Chi ne ha diritto adesso lo mostra:
Get-LocalGroupMember -SID 'S-1-5-32-555'
Come capisci che ha funzionato davvero
Spuntare invece di sperare. Queste cinque verifiche ti dicono se la migrazione è andata a segno:
- NLA:
Get-ItemPropertysuRDP-TcprestituisceUserAuthentication : 1eSecurityLayer : 2. Una nuova connessione chiede ora le credenziali prima di stabilire la connessione, non più su una schermata di accesso dentro la finestra. - Blocco:
net accountsmostra una soglia di blocco diversa da "Mai". Controprova con un account usa e getta: dopo l'undicesima password sbagliata il client deve restituire il messaggio di blocco, non più quello sulle credenziali non valide. - Firewall:
Get-NetFirewallAddressFiltermostra i tuoi indirizzi al posto diAny. Un test di connessione da un indirizzo estraneo deve finire in un errore di timeout, non in una richiesta di accesso. Verificalo dall'esterno conTest-NetConnection -ComputerName tuoserver -Port 3389, il risultato deve essereTcpTestSucceeded : False. - Log:
auditpol /getsegnala per la sottocategoria successo e fallimento. - Il numero: conta di nuovo gli eventi 4625 24 ore dopo la migrazione. Deve essere nettamente più basso. Se resta alto, una delle tue regole non fa presa, di solito perché esiste una seconda regola firewall più permissiva per la porta 3389, creata da un'immagine di sistema del provider o dall'installazione di un software. Questa riga la trova:
Get-NetFirewallPortFilter | Where-Object { $_.LocalPort -eq 3389 } | Get-NetFirewallRule | Select-Object DisplayName, Enabled, Profile, Action. L'ordine nella pipeline è voluto. Se lo si inverte e si mandano prima tutte le regole firewall dentroGet-NetFirewallPortFilter, la chiamata dura, misurata, circa 12 secondi invece di uno, e soprattutto nell'output manca il nome della regola: vedi quattro risultati senza sapere di quali regole si tratti.
Se comunque stai installando un server da zero, affronta questi punti subito all'inizio invece di aggiungerli dopo. Per i sistemi Linux vale lo stesso schema con SSH al posto di RDP, descritto in mettere in sicurezza SSH e configurare il login con chiave, e l'ordine dei primi passi lo trovi nella nostra checklist per i nuovi server root.
Che cosa l'hardening di RDP non copre
Le misure qui sopra proteggono dai tentativi di accesso. Non proteggono dagli attacchi volumetrici che puntano a rendere il server irraggiungibile attraverso la connettività di rete. Contro quelli aiuta solo un filtraggio nella rete a monte. Come funzionano questi attacchi lo spieghiamo in che cos'è un attacco DDoS, e quali precauzioni restino sensate lato server in proteggere il server dagli attacchi DDoS. Tutti i server di KernelHost si trovano nel datacenter maincubes di Francoforte sul Meno, dietro un filtraggio che intercetta il traffico di attacco già prima del server.
E non sostituiscono un backup. Un server su cui qualcuno è riuscito ad accedere non è più affidabile, per quanto in fretta tu cambi la password subito dopo. L'unica via di ritorno affidabile è un backup precedente all'incidente.
Domande frequenti
Basta cambiare la porta RDP dalla 3389 a un'altra porta?
Mi sono chiuso fuori da solo dopo aver impostato una regola firewall. E adesso?
Perché un utente non riesce più ad accedere con la password corretta da quando NLA è attivo?
L'account Administrator integrato rientra nel blocco account?
Un attaccante può chiudermi fuori in permanenza tramite il blocco account?
Da che cosa capisco che un attacco è riuscito?
2026 KernelHost GmbH. Tutti i diritti riservati. Questa guida è protetta dal diritto d'autore. La ripubblicazione su altri siti web, anche parziale o in forma modificata, non è consentita senza il nostro consenso scritto. Le citazioni con indicazione della fonte e un link sono le benvenute.

