16 noviembre, 2021

Detectar, alertar y bloquear intentos de conexión por fuerza bruta a escritorio remoto RDP

Antes de nada comentar que es una muy mala práctica y está totalmente desaconsejado publicar hacia internet servicios de escritorio remoto. RDP es de los servicios más comprometidos por atacantes externos, con más intentos de ataques y que facilitan un vector de entrada directo a la red de una empresa si el acceso se configura de esta forma.

En el caso de tener que publicar RDP a internet -sin una VPN o una conexión intermedia hacia un equipo bastión- aunque sea de una manera temporal, hay que hacerlo de la forma "más segura" posible.

Lista de acceso de usuarios mediante NLA y privilegios

Bloqueo de cuenta por intentos fallidos de inicio de sesión

Este artículo no se centra en la identificación, investigación y seguimiento de los eventos relacionados con el servicio RDP, para eso dejo esta buena referencia de estudio sobre logs RDP. Al igual que unas diapositivas que representan los diagramas de flujo de conexiones RDP

Está enfocado especialmente a los intentos de conexión originados desde máquinas externas hacia servicios RDP expuestos de forma pública a internet, aunque también es aplicable a conexiones de equipos remotos entre redes internas.

Detectar evento de intento de conexión TCP hacia RDP

Cuando se realiza un intento de conexión RDP hacia un equipo y este tiene la autenticación NLA habilitada lo primero que se genera son eventos de conexión de red, después de autenticación y finalmente un login satisfactorio, fallido o reconexiones de sesión.

Haciendo referencia al conjunto de diapositivas anteriores de diagramas de flujo de conexiones RDP. Se destacan principalmente dos eventos del ProviderName "Microsoft-Windows-TerminalServices-RemoteConnectionManager" el event ID 261 y 1149.
  • Event ID 261: Se trata del primer evento que se produce indicando que el puerto RDP-Tcp que estaba escuchando recibió una conexión.
  • Event ID 1149: NO indica una autenticación exitosa a un objetivo, simplemente una conexión de red RDP exitosa.
Figura 1: Event ID 261 y 1149.

El evento 1149 es interesante ya que nos muestra el login de usuario empleado en el intento de conexión hacia los servicios RDP, pero ninguno de estos eventos nos da información sobre la IP origen remota que realizó esta conexión al intentar autenticarse.

En el siguiente flujograma se puede observar que con NLA habilitado el primer evento de conexión de red que se genera es el event ID 131 correspondiente al ProviderName "Microsoft-Windows-RemoteDesktopServices-RdpCoreTS".

Figura 2: Flujograma de alerta de eventos y bloqueo de una conexión RDP (fuente imagen).

El event ID 131 ya nos aporta la dirección IP remota y puerto TCP origen que realizó la solicitud de conexión a los servicios de escritorio remoto. Teniendo esta información ya podremos automatizar una detección, generar una alerta y bloquear la conexión localmente en el Firewall de Windows como veremos más adelante.

Figura 3: Detectando evento de conexión TCP hacia RDP (event id 131).

Automatizar las detecciones y crear alertas de los intentos de conexión RDP

Para detectar este evento de forma automática y alertarnos de ello, primero se creará un fichero por lotes .bat el cual simplemente se usará para realizar la llamada al script Powershell a través de la acción de la tarea programada que se creará más adelante.

Script .bat.
powershell.exe -ExecutionPolicy Bypass -File "C:\Users\adrian\Tools\Scripts\RDPBruteForce\Hunting_RDPBruteForce.ps1"
Comentaré dos formas para generar las notificaciones de alerta: vía Slack o correo Gmail y aunque se trate de una cuenta sin importancia y dedicada a ello tenemos que intentar no hardcodear passwords en texto plano en los scripts por lo que crearemos una password cifrada y almacenada en un fichero que solo podrá ser leída desde el equipo local que la creó.
"MiPassword" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString | Out-File "C:\Users\adrian\Tools\Scripts\RDPBruteForce\email.pass"
El siguiente script Powershell enviará una alerta de email notificando un nuevo intento de conexión correspondiente al event ID 131.
$PSDefaultParameterValues['*:Encoding'] = 'utf8'
$usuarioEmail = "EMAIL@gmail.com" 
$passwdEmailFile = "C:\PATH\email.pass"
$secPasswdEmail = Get-Content $passwdEmailFile | ConvertTo-SecureString
$credencialesEmail = New-Object System.Management.Automation.PSCredential ($usuarioEmail, $secPasswdEMail)
$asuntoEmail = "Intento de conexión RDP"

$cuerpoEmail = Get-WinEvent -ProviderName "Microsoft-Windows-RemoteDesktopServices-RdpCoreTS" | Where-Object {$_.Id -eq "131"} | Format-List | Select -First 3 | Out-String

Send-MailMessage -From $usuarioEmail -To $usuarioEmail -Subject "$asuntoEmail - $fechaHoraActual" -Body "$cuerpoEmail" -SmtpServer smtp.gmail.com -UseSsl -Credential $credencialesEmail
Creamos un filtro indicando el ID de evento 131 a partir de ahí adjuntamos este filtro personalizado a una nueva tarea programada de modo que la tarea se ejecutará solamente cuando se produzca dicho evento. 

Figura 4: Crear tarea programada a partir de un filtro personalizado.

Cargar el fichero de llamada .bat. Es opcional pero recomiendo especificar el path absoluto donde inicia el script.

Figura 5: Iniciar el script .bat de llamada al script Powershell en la acción de la tarea programada. 

En las opciones de seguridad la tarea se ejecutará tanto si el usuario inició sesión somo si no.

Figura 6: Tipo de usuario para la ejecución de la tarea programada.

Notificación de alertas de intentos de conexión RDP

Configuración de alertas vía Gmail

Una vez se genere un nuevo evento 131 se ejecutará la tarea y el script asociado que disparará la alerta de correo. Para el envío de alerta por correo vía Gmail a través del script Powershell es necesario activar el acceso a "Aplicaciones menos seguras" en la cuenta de Google. Por seguridad se debería crear una cuenta específica para esta finalidad.

Figura 7: Ejemplo de notificación de alerta vía Gmail alerta intento de conexión RDP.

Configuración de alertas vía Slack


Figura 8: Crear canal, app y añadir nuevo Webhook a Slack. 

Sustituiríamos el anterior script Powershell de notificaciones vía email Gmail por este otro siendo este al que llamaría el fichero .bat en la tarea programada.
$eventRdp = Get-WinEvent -ProviderName "Microsoft-Windows-RemoteDesktopServices-RdpCoreTS" | Where-Object {$_.Id -eq "131"} | Format-List | Select -First 3 | Out-String
$uriSlack = "Webhook_URI"
$menssage = ConvertTo-Json @{ 
    text = $eventRdp
}
try {
    Invoke-RestMethod -uri $uriSlack -Method Post -body $menssage -ContentType 'application/json' | Out-Null
} catch {
    Write-Warning "Notification to Slack went wrong."
Figura 9: Ejemplo de notificación vía Slack alerta intento de conexión RDP.

Bloqueo de IPs externas en intentos de fuerza bruta a RDP

Al tener el servicio de RDP activo en WFAS (Windows Defender Firewall con seguridad avanzada) podemos encontrar habilitada la regla por defecto de Escritorio remoto (TCP de entrada) permitiendo la conexión por el puerto 3389/tcp por defecto. 

Figura 10: Regla por defecto RDP puerto TCP 3389.

Manualmente creamos una nueva regla pero esta vez en un modo de acción de "Bloquear la conexión". Las reglas de bloquear conexión o denegación tendrán un orden de preferencia frente a las reglas de permitir conexión.

Figura 11: Crear nueva regla de bloqueo de conexión.

Para este caso no se han cambiando los puertos por defecto por lo que el protocolo y puerto será el mismo que la regla por defecto 3389/tcp.

Figura 12: Regla de bloqueo de conexión protocolo TCP y puerto local 3389.

Una vez conocemos la dirección IP remota (pública en este caso) vamos agregando estas direcciones IP en el ámbito específico de "Dirección IP remota" estas se refieren a direcciones IP remotas dentro de la misma red o direcciones IP públicas-externas. Las direcciones IP locales se refieren a las propias de la máquina en caso de tener múltiples interfaces de red.

Al estar esta regla en un modo de acción de bloqueo de conexión denegará para esa IP origen el intento de conexión TCP hacia el servicio RDP no permitiendo la conexión inicial desde el primer intento.

Figura 13: Ámbito de regla - incluir IPs remotas de intento de conexión a bloquear.
 

Publicar escritorio remoto RDP de una forma más segura

Por lo general los servicios RDP suelen estar publicados en equipos servidores o máquinas que ofrecen uno o varios servicios y que remotamente necesitan gestionarse, por definición un servidor no debería de disponer de acceso a internet. Sino se disponen de tecnologías de control de seguridad SIEM o HIPS que puedan alertarnos de estos accesos, ni de arquitecturas más avanzadas y seguras para conectarse a una infraestructura interna como puedan ser servicios del tipo RD-Gateway, VDI o una VPN bien configurada con protocolos seguros, perfiles definidos y segmentación entre redes internas. 

Pero es necesario publicar el servicio RDP de una máquina interna de forma temporal, podemos hacerlo de una forma mucho más económica, sencilla de implementar y un poco más segura que tenerlo expuesto a través de un port forwarding local de puertos entre el router o firewall frontera hacia la IP del servidor interno.
  1. Crear una máquina puente o bastión. Adquirir un servicio en cloud (AWS, Azure, GCP) y creamos una nueva máquina limpia con una dirección IP pública fija (esto es importante) y únicamente con el servicio de SSH disponible de forma pública. Por defecto la forma de conexión SSH a este tipo de máquinas cloud se realiza mediante clave pública-privada.
  2. En la configuración avanzada del firewall de Windows editamos la regla RDP del propio servidor. En el ámbito de conexión del rango de IPs internas añadimos el rango de red interno (en el caso de que también queramos permitir conexiones de los equipos internos de la red).
  3. Agregamos también la dirección IP pública asignada a la máquina bastión creada en el primer punto.
  4. Para conectarnos si usamos una máquina Windows como cliente:
    1. Agregar la clave privada de la conexión SSH de la máquina bastión.
    2. Desde un cliente SSH como PuTTY establecemos una nueva conexión SSH port forwarding local.
  5. Para conectarnos si usamos una máquina Linux como cliente:
    1. Agregar la clave privada de la conexión SSH de la máquina bastión.
    2. ssh -L LOCAL_IP:LOCAL_PORT:DESTINATION:DESTINATION_PORT USER@SSH_SERVER
    3. Ejemplo: ssh -L 127.0.0.1:9999:192.168.1.10:3389 user@90.80.60.34
Estos recursos son económicos pero tampoco es una solución a largo plazo, lo ideal sería no exponer ningún servicio RDP ni SSH aunque se autentique con una PKI. Pero si por el momento no se disponen de otras implementaciones alternativas, hacerlo de este modo será más seguro que tenerlo expuesto directamente.

Saludos!

Autor: Adrián Lois