Proteger o RDP: defender o Windows Server contra ataques

Publicado a 18 min de leitura

Um servidor Windows acabado de instalar acumula milhares de inícios de sessão RDP falhados em poucas horas. Este guia mostra que medidas resultam mesmo, quanto custa cada uma e como não bloquear o seu próprio acesso.

Um servidor Windows que entra na rede com a porta 3389 aberta não é atacado um dia qualquer, é atacado em poucos minutos. Os scanners correm em permanência, conhecem todas as gamas de endereços IPv4 e percorrem cegamente nomes de utilizador e palavras-passe. Quase todos os incidentes de ransomware em servidores pequenos começam exatamente neste ponto: uma conta chamada Administrator, uma palavra-passe que alguém considerou suficiente e nenhum bloqueio depois da milésima tentativa falhada.

Este guia percorre as medidas pela ordem do efeito real que produzem, não pela ordem em que aparecem na maioria dos artigos. Todos os comandos correm numa sessão do PowerShell com direitos de administrador, em Windows Server de 2016 a 2025.

Porque é que o RDP é a porta de entrada mais atacada

O RDP não é um mau protocolo. O problema é que coloca um formulário de início de sessão completo na Internet aberta e que o Windows, por predefinição, deixa preencher esse formulário um número ilimitado de vezes. Um atacante não precisa de nenhuma falha, precisa apenas de paciência e de uma lista de palavras-passe.

Consegue ver numa única linha até que ponto o seu servidor está afetado. Conte os inícios de sessão falhados das últimas 24 horas:

Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=(Get-Date).AddDays(-1)} -ErrorAction SilentlyContinue | Measure-Object | Select-Object -ExpandProperty Count

Um servidor interno sem acessibilidade a partir da Internet situa-se tipicamente em poucas dezenas por dia, sobretudo palavras-passe esquecidas e contas de serviço antigas. Um servidor com a 3389 aberta chega depressa aos milhares, ou mesmo às dezenas de milhares. Esse número é a sua referência: depois das medidas abaixo deve cair várias ordens de grandeza. Se o Get-WinEvent responder com a mensagem "Não foram encontrados eventos que correspondam aos critérios de seleção especificados", o seu servidor nem sequer regista os inícios de sessão falhados. Isso corrigimos mais abaixo.

Antes da primeira alteração: garantir o caminho de volta

Cada uma das medidas seguintes pode bloquear o seu próprio acesso. Num servidor a que só chega por RDP, isso é o fim da sessão e o início de uma noite longa. Três precauções custam cinco minutos e evitam exatamente isso.

Primeiro: Verifique se tem uma consola que funciona independentemente do RDP. Nos servidores root KVM da KernelHost encontra na área de cliente uma consola VNC que olha diretamente para o ecrã da máquina virtual. Continua a funcionar mesmo quando a firewall, a rede e o serviço RDP estão avariados ao mesmo tempo. Abra-a uma vez a título de teste antes de alterar seja o que for, não depois.

Segundo: Crie uma segunda conta de administrador, para que uma credencial bloqueada não signifique um servidor perdido. Os nomes dos grupos dependem do idioma, por isso trabalhamos com os SID fixos (S-1-5-32-544 é o grupo de administradores locais, S-1-5-32-555 o dos utilizadores de ambiente de trabalho remoto):

New-LocalUser -Name 'kh-adm' -Password (Read-Host -AsSecureString -Prompt 'Palavra-passe') -FullName 'Conta de manutenção' -PasswordNeverExpires
Add-LocalGroupMember -SID 'S-1-5-32-544' -Member 'kh-adm'
Add-LocalGroupMember -SID 'S-1-5-32-555' -Member 'kh-adm'

A terceira linha é, a rigor, supérflua, porque os membros do grupo de administradores locais entram por RDP de qualquer maneira. Não faz mal nenhum e torna a intenção visível, caso mais tarde retire a conta do grupo de administradores.

Terceiro: Deixe aberta, durante toda a mudança, a sessão RDP em que está a trabalhar e teste cada alteração com uma segunda ligação, nova. Uma sessão existente não é cortada de imediato por novas regras de firewall, ao passo que uma ligação nova falha logo. Assim dá pelo erro enquanto ainda o consegue desfazer.

Impor a autenticação ao nível da rede

Sem autenticação ao nível da rede (em inglês Network Level Authentication, abreviada NLA), o servidor começa por criar uma sessão, desenha um ecrã de início de sessão e só depois pede as credenciais. Cada tentativa de ligação anónima custa assim RAM e CPU, e todo o código que está por trás do ecrã de início de sessão fica acessível antes de qualquer autenticação. Foi exatamente aí que se alojaram as falhas graves de RDP do passado.

Com NLA, o servidor verifica as credenciais através de CredSSP antes de sequer existir uma sessão. Um bot que não tenha dados válidos não obtém nada além de uma ligação TLS recusada.

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 em 2 impõe TLS para a negociação. O valor 1 (negociar) permite que um cliente recue, em caso de necessidade, para a antiga camada de segurança do RDP, 0 é essa camada antiga sem TLS. Para verificar:

Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' | Select-Object UserAuthentication, SecurityLayer

Não é preciso reiniciar, os valores aplicam-se a cada ligação estabelecida de novo. As sessões existentes continuam a correr sem alteração, o que é prático se estiver precisamente a bloquear o acesso a um cliente.

Quando depois disto já ninguém entra

Dois quadros de erro aparecem regularmente depois de ligar o NLA.

"O computador remoto requer autenticação ao nível da rede, que o seu computador não suporta. Para obter assistência, contacte o administrador de sistema ou o suporte técnico." O cliente é demasiado antigo ou não fala CredSSP. Os clientes Windows atuais fazem-no há anos. Em Linux, o FreeRDP exige o parâmetro /sec:nla e o Remmina a definição do protocolo de segurança em NLA, ao passo que os clientes macOS muito antigos falham por princípio. A solução passa sempre pelo cliente mais recente, nunca por desligar o NLA.

O início de sessão falha com a palavra-passe correta. Esta é a armadilha que a maioria dos guias não menciona: se numa conta estiver definida a opção "O utilizador tem de alterar a palavra-passe no próximo início de sessão" ou se a palavra-passe tiver expirado, essa conta deixa de conseguir iniciar sessão de todo com o NLA ativo. O CredSSP não sabe fazer mudanças de palavra-passe, isso está previsto assim. Consoante a versão, o servidor comunica um erro de autenticação ou simplesmente credenciais incorretas. A saída passa pela consola na área de cliente, ou então isenta as contas de manutenção do prazo de expiração:

Set-LocalUser -Name 'kh-adm' -PasswordNeverExpires $true

Bloqueio de conta: o interruptor isolado mais eficaz

Uma palavra-passe forte protege contra a adivinhação, um bloqueio de conta protege contra a adivinhação ilimitada. Sem ele, um atacante pode fazer milhões de tentativas por semana, com ele são dez por cada quarto de hora. Defina o limiar, a duração do bloqueio e a janela de observação numa única chamada, caso contrário o Windows recusa a duração enquanto o limiar ainda estiver a 0:

net accounts /lockoutthreshold:10 /lockoutduration:15 /lockoutwindow:15

A duração do bloqueio tem de ser sempre maior ou igual à janela de observação. Controle o resultado:

net accounts

As versões mais recentes do Windows já trazem uma predefinição desta ordem de grandeza, as instalações mais antigas e muitas imagens de fornecedores não. Verificar não custa nada.

Incluir a conta de administrador integrada

Historicamente, era precisamente a conta que qualquer atacante experimenta primeiro que estava isenta do bloqueio. Para isso existe a política "Permitir bloqueio da conta de administrador". Ela pressupõe, no entanto, uma versão mais recente, nomeadamente o Windows 11 22H2 ou o Windows Server 2025. Num Windows Server 2022 (build 20348), a exportação da política de segurança não contém de todo a linha AllowAdministratorLockout, e na secção [System Access] estão lá apenas valores como LockoutBadCount e MinimumPasswordLength. Por isso, verifique primeiro o que o seu sistema exporta realmente:

secedit /export /cfg C:\secpol.inf
Select-String -Path C:\secpol.inf -Pattern 'AllowAdministratorLockout'

Se aqui não vier nenhuma ocorrência, a sua build não conhece a política e esta secção fica arrumada. É precisamente aqui que está a armadilha que a maioria dos guias cria: a habitual linha única com -replace substitui uma cadeia de carateres que nem sequer existe no ficheiro, mas não comunica qualquer erro, e o secedit /configure seguinte termina com êxito. Fica depois a acreditar que o bloqueio do administrador está ativo, embora nada tenha mudado.

Numa build que conhece a política, altera a linha caso ela exista e insere-a caso contrário. Esta versão cobre os dois casos:

$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

O -Encoding Unicode é obrigatório e não é um pormenor: o ficheiro INF tem de estar em UTF-16 LE com byte order mark. Caso contrário, o Set-Content escreve em ANSI no Windows PowerShell 5.1 e o secedit recusa aceitá-lo. Antes de o reescrever, o secedit /validate C:\secpol.inf verifica o ficheiro, depois:

secedit /configure /db C:\Windows\security\local.sdb /cfg C:\secpol.inf /areas SECURITYPOLICY

E depois releia sem falta, porque uma execução bem-sucedida do secedit não prova nada neste ponto. Exporte a política outra vez para um segundo ficheiro e veja se o valor está mesmo lá com 1.

Num controlador de domínio, estas definições locais não pegam. Aí a política de bloqueio pertence à Default Domain Policy, em Configuração do Computador, Definições do Windows, Definições de Segurança, Políticas de Conta.

O reverso da medalha e como voltar a entrar

Um bloqueio de conta é também uma arma contra si: quem conhece o seu nome de utilizador consegue mantê-lo permanentemente bloqueado, enviando dez palavras-passe erradas a cada 15 minutos. É exatamente por isso que o bloqueio é apenas a segunda linha de defesa, a primeira é a restrição por IP da secção seguinte.

Se uma conta local estiver bloqueada, o cliente comunica, em substância, "A conta referenciada está atualmente bloqueada e não pode ser utilizada para iniciar sessão". O caminho de volta mais simples é esperar, porque o Windows desbloqueia sozinho depois de decorrida a duração do bloqueio. Quem não quiser esperar, desbloqueia através da consola:

$u = [ADSI]"WinNT://./kh-adm,user"; $u.IsAccountLocked = $false; $u.SetInfo()

No Active Directory é mais curto com Unlock-ADAccount -Identity kh-adm.

Palavras-passe que tornam um bloqueio realmente útil

Dez tentativas por quarto de hora só são um obstáculo se a palavra-passe não estiver no terceiro lugar de todas as listas. O comprimento mínimo e a complexidade definem-se assim:

net accounts /minpwlen:14

A regra de complexidade está de novo na política de segurança, secção [System Access], chave PasswordComplexity = 1. O caminho é o mesmo de cima, com secedit.

Dois pontos vindos da prática. Primeiro, a mudança forçada de palavra-passe a cada 30 dias traz comprovadamente pouco e, em combinação com NLA, gera exatamente o problema de início de sessão da secção anterior. Palavras-passe longas, definidas uma única vez e guardadas num gestor de palavras-passe, são melhores. Segundo, a política só se aplica a palavras-passe definidas de novo. Uma palavra-passe existente com seis carateres continua válida até a alterar.

Limitar o acesso a endereços IP conhecidos

Esta é a medida que acaba mesmo com o tráfego de ataque, e de forma completa. Todas as regras do grupo do ambiente de trabalho remoto podem ser restringidas a uma lista de endereços. Utilize o identificador de grupo neutro em relação ao idioma, para que o script funcione também em instalações em inglês:

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')

Espere mais resultados do que as regras que tem em mente. O -Group mexe em todas as regras do grupo, o que num sistema de teste eram seis, incluindo as regras sombra e cópias adicionais com nome em GUID no perfil Public. Isso é intencional e está correto, porque de outro modo uma das cópias ficaria aberta. A listagem prévia com Get-NetFirewallRule mostra-lhe de antemão o que vai ser afetado.

Controlo dos endereços que ficaram efetivamente guardados:

Get-NetFirewallRule -Group '@FirewallAPI.dll,-28752' | Get-NetFirewallAddressFilter

Se alterou a porta, as regras integradas deixam de pegar, porque estão fixas na 3389. Nesse caso precisa de uma regra própria:

New-NetFirewallRule -DisplayName 'RDP restrito' -Direction Inbound -Protocol TCP -LocalPort 34567 -RemoteAddress '203.0.113.10' -Action Allow -Profile Any

A tábua de salvação contra a sua própria regra de firewall

Quem escreve mal o seu endereço IP ou introduziu um endereço dinâmico bloqueia o próprio acesso com toda a certeza. Crie por isso, antes, uma tarefa que retire a restrição por si própria ao fim de dez minutos:

Set-Content -Path C:\rdp-resgate.ps1 -Value "Set-NetFirewallRule -Group '@FirewallAPI.dll,-28752' -RemoteAddress Any"
Register-ScheduledTask -TaskName 'RDP-Resgate' -Action (New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-ExecutionPolicy Bypass -File C:\rdp-resgate.ps1') -Trigger (New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(10)) -User 'SYSTEM' -RunLevel Highest

Se a nova ligação funcionar, volte a remover a tarefa:

Unregister-ScheduledTask -TaskName 'RDP-Resgate' -Confirm:$false

Se não tiver um endereço IP fixo, a solução limpa é não colocar o RDP na Internet de todo, mas alcançá-lo através de uma VPN. Como montar isso está no nosso guia do servidor VPN WireGuard. O servidor passa então a escutar apenas no endereço da VPN, e a porta 3389 desaparece por completo da Internet.

Mudar a porta e o que isso traz na realidade

Outra porta não é uma medida de segurança, é uma medida de redução de ruído. A grande massa dos bots faz scan exclusivamente à 3389 e deixa de o encontrar depois disso, o que em regra baixa drasticamente o seu número de eventos 4625 e volta a tornar os registos de eventos legíveis. Quem procura de forma dirigida encontra o serviço à mesma: os motores de busca de serviços expostos reconhecem o RDP pela impressão digital do protocolo, independentemente da porta, e um scan completo às 65535 portas demora segundos.

A mudança de porta faz portanto sentido como complemento, mas nunca como substituto do NLA, do bloqueio de conta e da restrição de endereços. Descrevemos a execução prática à parte, incluindo a parte que quase todos os guias fazem mal, nomeadamente sem reiniciar: mudar a porta RDP sem reiniciar. Em qualquer caso, lembre-se de criar uma regra de firewall para a nova porta antes de mudar o serviço.

Analisar os registos de início de sessão

Primeiro, o registo tem de estar sequer ligado. Os nomes das subcategorias do auditpol estão traduzidos, e um comando em inglês falha num sistema em português com "Ocorreu o erro 0x00000057: O parâmetro está incorreto." O GUID, esse, funciona em qualquer versão linguística:

auditpol /set '/subcategory:{0CCE9215-69AE-11D9-BED3-505054503030}' /success:enable /failure:enable
auditpol /get '/subcategory:{0CCE9215-69AE-11D9-BED3-505054503030}'

As plicas à volta de todo o parâmetro não são uma questão de gosto, são obrigatórias. Caso contrário, o PowerShell interpreta as chavetas como um bloco de script e remove-as, de um argumento passam a três, e o auditpol interrompe com Ocorreu o erro 0x00000057: O parâmetro está incorreto. e o código de saída 87, seguido do texto de ajuda. No cmd.exe a escrita sem plicas funciona, no PowerShell não. Com plicas, ambos passam com o código de saída 0, e a consulta de controlo responde então Início de Sessão Êxito e Falha.

Sob fogo, o registo de segurança dá a volta em poucas horas e sobrescreve exatamente as entradas de que precisa. Dê-lhe mais espaço:

wevtutil sl Security /ms:1073741824

A seguir, os endereços de origem mais frequentes da última semana, ordenados por número. A variante que passa pela estrutura XML é a robusta, porque não depende da ordem dos campos:

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

O -ErrorAction SilentlyContinue pertence a cada uma destas chamadas. O Get-WinEvent interrompe com um erro vermelho assim que não existe um único evento correspondente no período: "Não foram encontrados eventos que correspondam aos critérios de seleção especificados." Num servidor acabado de proteger, esse é precisamente o caso normal, ou seja, o comando falha justamente no sucesso a que este guia aspira.

A pergunta decisiva não é, porém, quem tentou, mas se alguém conseguiu. Os inícios de sessão de ambiente de trabalho remoto bem-sucedidos levam o ID de evento 4624 com o tipo de início de sessão 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='Utilizador';e={$_.Properties[5].Value}}, @{n='Origem';e={$_.Properties[18].Value}} | Format-Table -AutoSize

Se aí estiver um nome de utilizador ou um endereço de origem que não consiga atribuir, alguém encontrou uma palavra-passe válida. Nessa altura já não ajuda apertar mais as políticas, o servidor tem de ser reinstalado de raiz.

Vale ainda a pena um olhar ao registo próprio do RDP. O evento 1149 nomeia o utilizador, o domínio e o endereço de origem de cada ligação autorizada, numa só linha:

Get-WinEvent -LogName 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational' -FilterXPath '*[System[EventID=1149]]' -MaxEvents 25 | Format-List TimeCreated, Message

Mudar o nome do administrador, melhor ainda desativá-lo

A conta de administrador integrada é o único nome de utilizador que qualquer atacante conhece com certeza. Mudar-lhe o nome não custa nada:

Rename-LocalUser -Name 'Administrator' -NewName 'kh-svc'

Tenha porém consciência do limite: o SID da conta continua a terminar em -500, e qualquer acesso autenticado consegue resolver o novo nome por essa via. A mudança de nome funciona contra bots em massa, contra um atacante dirigido que já tem um pé na porta não. É assim que encontra a conta apesar do novo nome:

Get-LocalUser | Where-Object { $_.SID.Value -like '*-500' } | Select-Object Name, Enabled

Bastante mais eficaz é desativar por completo a conta integrada, depois de a sua própria conta de administrador da secção "garantir o caminho de volta" funcionar comprovadamente. Teste o início de sessão com a nova conta numa segunda sessão, e só depois:

Disable-LocalUser -Name 'kh-svc'

Restrinja além disso o acesso RDP às pessoas que precisam dele. Por predefinição, qualquer membro do grupo de administradores locais pode entrar por RDP, incluindo contas de serviço que nunca o deveriam fazer. Quem pode atualmente mostra-o:

Get-LocalGroupMember -SID 'S-1-5-32-555'

Como reconhece que resultou mesmo

Riscar da lista em vez de esperar pelo melhor. Estas cinco verificações dizem-lhe se a mudança chegou a bom porto:

  1. NLA: O Get-ItemProperty em RDP-Tcp devolve UserAuthentication : 1 e SecurityLayer : 2. Uma nova ligação pede agora as credenciais antes do estabelecimento da ligação, já não num ecrã de início de sessão dentro da janela.
  2. Bloqueio: O net accounts mostra um limiar de bloqueio diferente de "Nunca". Contraprova com uma conta descartável: depois da décima primeira palavra-passe errada, o cliente tem de apresentar a mensagem de bloqueio, já não a mensagem sobre credenciais incorretas.
  3. Firewall: O Get-NetFirewallAddressFilter mostra os seus endereços em vez de Any. Um teste de ligação a partir de um endereço alheio tem de acabar num erro de tempo esgotado, não num pedido de início de sessão. Verifique isso a partir do exterior com Test-NetConnection -ComputerName oseuservidor -Port 3389, o resultado tem de ser TcpTestSucceeded : False.
  4. Registos: O auditpol /get comunica êxito e falha para a subcategoria.
  5. O número: Volte a contar os eventos 4625 24 horas depois da mudança. Tem de estar bastante mais baixo. Se se mantiver alto, uma das suas regras não pega, normalmente porque existe uma segunda regra de firewall, mais aberta, para a porta 3389, criada por uma imagem de fornecedor ou pela instalação de um software. Esta linha encontra-a: Get-NetFirewallPortFilter | Where-Object { $_.LocalPort -eq 3389 } | Get-NetFirewallRule | Select-Object DisplayName, Enabled, Profile, Action. A ordem no pipeline é intencional. Se a inverter e enviar primeiro todas as regras de firewall através do Get-NetFirewallPortFilter, a chamada demora, em medições reais, cerca de 12 segundos em vez de um, e sobretudo falta o nome da regra na saída: vê então quatro resultados sem ficar a saber de que regras se trata.

Se estiver de qualquer forma a instalar um servidor de raiz, trate destes pontos logo no início, em vez de os acrescentar depois. Para sistemas Linux vale o mesmo padrão, com SSH em vez de RDP, descrito em proteger o SSH e configurar o início de sessão por chave, e a ordem dos primeiros passos encontra-a na nossa checklist para servidores root novos.

O que o endurecimento do RDP não cobre

As medidas acima protegem contra tentativas de início de sessão. Não protegem contra ataques volumétricos, que procuram tornar o servidor inalcançável pela sua ligação de rede. Contra isso só ajuda a filtragem na rede a montante. Como funcionam estes ataques está em o que é um ataque DDoS, e que precauções continuam a fazer sentido do lado do servidor está em proteger servidores contra ataques DDoS. Todos os servidores da KernelHost estão no datacenter maincubes em Frankfurt am Main, atrás de uma filtragem que intercepta o tráfego de ataque ainda antes do servidor.

E não substituem um backup. Um servidor em que alguém iniciou sessão com sucesso deixa de ser fidedigno, por muito depressa que altere a palavra-passe a seguir. O único caminho de volta fiável é uma cópia de segurança anterior ao incidente.

Perguntas frequentes

Basta mudar a porta do RDP de 3389 para outra porta?
Não. Outra porta afasta a massa dos bots, que fazem scan exclusivamente à 3389, e baixa bastante o número de tentativas de ataque nos registos. Os scanners dirigidos reconhecem, porém, o RDP pela impressão digital do protocolo em qualquer porta. A mudança de porta é um complemento útil à NLA, ao bloqueio de contas e à restrição por IP, mas nunca um substituto.
Bloqueei o meu próprio acesso depois de definir uma regra de firewall. E agora?
Ligue-se pela consola VNC na área de cliente, que funciona independentemente do RDP e da rede, e reponha a restrição com Set-NetFirewallRule -Group '@FirewallAPI.dll,-28752' -RemoteAddress Any. Para nunca chegar a esse ponto, crie antes da mudança uma tarefa agendada que execute automaticamente esse mesmo comando ao fim de dez minutos.
Porque é que um utilizador já não consegue iniciar sessão com a palavra-passe correta desde que a NLA está ativa?
Provavelmente a palavra-passe da conta expirou ou está definida a opção "O utilizador tem de alterar a palavra-passe no próximo início de sessão". O CredSSP, o protocolo por trás da NLA, não consegue executar mudanças de palavra-passe, por isso o início de sessão falha por completo. Altere a palavra-passe através da consola ou isente as contas de manutenção do prazo de expiração com Set-LocalUser -PasswordNeverExpires $true.
A conta de administrador integrada é abrangida pelo bloqueio de contas?
Só se a política "Permitir bloqueio da conta de administrador" estiver ativa, e essa pressupõe o Windows 11 22H2 ou o Windows Server 2025. No Windows Server 2022 (build 20348), a exportação com secedit /export nem sequer contém a chave AllowAdministratorLockout: uma substituição de texto cai no vazio, não comunica qualquer erro, e o secedit /configure termina mesmo assim com êxito. Verifique por isso com o Select-String na exportação se a sua build conhece sequer a política. Ela não se define com net accounts, mas sim com secedit e AllowAdministratorLockout = 1 na secção [System Access]. O bloqueio atua apenas sobre inícios de sessão em rede como o RDP, o início de sessão na consola local continua possível.
Um atacante consegue bloquear-me o acesso de forma permanente através do bloqueio de contas?
Sim, esse é o reverso da medalha. Quem conhece o nome de utilizador consegue manter a conta permanentemente bloqueada com tentativas falhadas dirigidas. É exatamente por isso que a restrição a endereços IP conhecidos ou o acesso através de uma VPN é a primeira linha de defesa e o bloqueio de contas apenas a segunda. Uma segunda conta de administrador com outro nome é adicionalmente sensata.
Como reconheço que um ataque teve êxito?
Procure no registo de segurança o ID de evento 4624 com o tipo de início de sessão 10 e compare o nome de utilizador e o endereço de origem com as suas próprias ligações. Se algo não bater certo, o início de sessão foi alheio. Vale ainda a pena olhar para o registo Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational, evento 1149. Perante um resultado desses, já não ajuda apertar mais nada, o servidor tem de ser reinstalado a partir de uma cópia de segurança anterior ao incidente.

RDP Windows Server Segurança PowerShell Remote Desktop Firewall Força bruta Endurecimento