¡Hola, amigos!
Existen muchas formas de conectarse desde casa al lugar de trabajo en la oficina. Una de ellas es utilizar Microsoft Remote Desktop Gateway. Esto es RDP sobre HTTP. No quiero abordar aquí la configuración del RDGW en sí, ni discutir por qué es bueno o malo; tratemos este tema como una de las herramientas de acceso remoto. Quiero hablar sobre cómo proteger su servidor RDGW del malvado internet. Cuando configuré servidores RDGW, inmediatamente me preocupé por la seguridad, especialmente por protegerlo contra ataques de fuerza bruta. Me sorprendió no encontrar en internet artículos sobre cómo hacerlo. Bueno, tendré que hacerlo por mí mismo.
El RDGW en sí no tiene ninguna protección. Sí, se puede exponer con una interfaz desnuda a la red pública y funcionará perfectamente. Pero un buen administrador o especialista en seguridad se sentiría incómodo con eso. Además, esto ayudará a evitar la situación de bloqueo de cuentas, cuando un empleado descuidado recuerda la contraseña de la cuenta corporativa en su computadora doméstica y luego cambia su contraseña.
Una buena manera de proteger los recursos internos del entorno externo es a través de varios proxies, sistemas de publicación y otros WAF. Recordemos que el RDGW, después de todo, es http, por lo que se sugiere colocar una solución especializada entre los servidores internos y la internet.
Sé que hay excelentes soluciones como F5, A10 y Netscaler (ADC). Como administrador de uno de estos sistemas, diré que también es posible configurar la protección contra ataques de fuerza bruta en estos sistemas. Y sí, estos sistemas también lo protegerán de cualquier inundación de SYN.
Pero no todas las empresas pueden permitirse adquirir una solución así (y encontrar un administrador para ese sistema :), pero ¡se puede cuidar la seguridad!
Es posible instalar una versión gratuita de HAProxy en un sistema operativo gratuito. Lo he probado en Debian 10, en el repositorio estable la versión de haproxy es 1.8.19. También lo probé en versiones 2.0.xx del repositorio testing.
Dejaremos la configuración de Debian fuera del artículo. En breve: en la interfaz pública, cerrar todo, excepto el puerto 443; en la interfaz gris, según su política, por ejemplo, también cerrar todo, excepto el puerto 22. Abrir solo lo necesario para el funcionamiento (VRRP, por ejemplo, para IP flotante).
Primero configuré haproxy en modo SSL bridging (también conocido como modo http) y activé el registro para ver qué datos se manejan dentro de RDP. Así que, de alguna manera, intervine. El camino indicado en "todos" los artículos sobre la configuración de RDGateway, /RDWeb, está ausente. Todo lo que hay es /rpc/rpcproxy.dll y /remoteDesktopGateway/. No se utilizan las solicitudes estándar GET/POST, se utiliza un tipo de solicitud propio RDG_IN_DATA, RDG_OUT_DATA.
No mucho, pero al menos algo.
Probemos.
Ejecutando mstsc, me dirijo al servidor y en los registros veo cuatro errores 401 (no autorizado), luego ingreso el usuario/contraseña y veo respuesta 200.
Desconecto, reinicio, y en los registros veo los mismos cuatro errores 401. Ingreso un usuario/contraseña incorrectos y veo nuevamente cuatro errores 401. Eso es lo que necesitamos. Eso es lo que vamos a rastrear.
Dado que no logré determinar la URL de inicio de sesión y además no sé cómo rastrear en haproxy un error 401 específico, rastrearé (en realidad no rastrearé, contaré) todos los errores 4xx. También funcionará para resolver el problema.
La esencia de la protección consistirá en contar el número de errores 4xx (en el backend) en un período de tiempo y si supera el límite especificado, denegar (en el frontend) todas las conexiones posteriores desde esa IP durante el tiempo especificado.
Técnicamente, esto no será una protección contra ataques de fuerza bruta; será una protección contra los errores 4xx. Por ejemplo, si se solicita frecuentemente una URL inexistente (404), la protección también se activará.
La forma más sencilla y efectiva — es contar en el backend y denegar si aparece algo innecesario:
frontend fe_rdp_tsc
bind *:443 ssl crt /etc/haproxy/cert/desktop.example.com.pem
mode http
...
default_backend be_rdp_tsc
backend be_rdp_tsc
...
mode http
...
#crear tabla, de tipo string, 1000 elementos, caduca en 15 seg, registrar cantidad de errores en los últimos 10 seg
stick-table type string len 128 size 1k expire 15s store http_err_rate(10s)
#recordar ip
http-request track-sc0 src
#denegar con error http 429, si en los últimos 10 seg hay más de 4 errores
http-request deny deny_status 429 if { sc_http_err_rate(0) gt 4 }
...
server rdgw01 192.168.1.33:443 maxconn 1000 weight 10 ssl check cookie rdgw01
server rdgw02 192.168.2.33:443 maxconn 1000 weight 10 ssl check cookie rdgw02
No es la mejor opción, así que lo complicaremos. Contaremos en el backend y bloquearemos en el frontend.
Seremos duros con el atacante, cortaremos su conexión TCP.
frontend fe_rdp_tsc
bind *:443 ssl crt /etc/haproxy/cert/ertelecom_ru_2020_06_11.pem
mode http
...
#crear una tabla de direcciones IP, 1000 elementos, caduca en 15 segundos, almacenar desde un contador global
stick-table type ip size 1k expire 15s store gpc0
#tomar la fuente
tcp-request connection track-sc0 src
#rechazar la conexión TCP si el contador global >0
tcp-request connection reject if { sc0_get_gpc0 gt 0 }
...
default_backend be_rdp_tsc
backend be_rdp_tsc
...
mode http
...
#crear una tabla de direcciones IP, 1000 elementos, caduca en 15 segundos, almacenar el número de errores en 10 segundos
stick-table type ip size 1k expire 15s store http_err_rate(10s)
#muchos errores si el número de errores en 10 segundos supera 8
acl errors_too_fast sc1_http_err_rate gt 8
#marcar el ataque en el contador global (aumentar el contador)
acl mark_as_abuser sc0_inc_gpc0(fe_rdp_tsc) gt 0
#restablecer el contador global
acl clear_as_abuser sc0_clr_gpc0(fe_rdp_tsc) ge 0
#tomar la fuente
tcp-request content track-sc1 src
#rechazar, marcar como ataque
tcp-request content reject if errors_too_fast mark_as_abuser
#permitir, restablecer la bandera de ataque
tcp-request content accept if !errors_too_fast clear_as_abuser
...
server rdgw01 192.168.1.33:443 maxconn 1000 weight 10 ssl check cookie rdgw01
server rdgw02 192.168.2.33:443 maxconn 1000 weight 10 ssl check cookie rdgw02
Lo mismo, pero de forma cortés, devolveremos el error http 429 (Demasiadas solicitudes)
frontend fe_rdp_tsc
...
stick-table type ip size 1k expire 15s store gpc0
http-request track-sc0 src
http-request deny deny_status 429 if { sc0_get_gpc0 gt 0 }
...
default_backend be_rdp_tsc
backend be_rdp_tsc
...
stick-table type ip size 1k expire 15s store http_err_rate(10s)
acl errors_too_fast sc1_http_err_rate gt 8
acl mark_as_abuser sc0_inc_gpc0(fe_rdp_tsc) gt 0
acl clear_as_abuser sc0_clr_gpc0(fe_rdp_tsc) ge 0
http-request track-sc1 src
http-request allow if !errors_too_fast clear_as_abuser
http-request deny deny_status 429 if errors_too_fast mark_as_abuser
...
Verificando: inicio mstsc y empiezo a introducir contraseñas al azar. Después del tercer intento en 10 segundos, me expulsa y mstsc muestra un error. Como se puede ver en los registros.
Explicaciones. No soy un experto en haproxy. No entiendo por qué, por ejemplo
http-request deny deny_status 429 if { sc_http_err_rate(0) gt 4 }
permite hacer alrededor de 10 errores antes de que se active.
Me confundo con la numeración de los contadores. Expertos en haproxy, estaría agradecido si me complementan, corrigen o mejoran.
En los comentarios pueden sugerir otros métodos de protección para RD Gateway, sería interesante estudiarlos.
Respecto al cliente de escritorio remoto de Windows (mstsc), cabe destacar que no soporta TLS1.2 (al menos en Windows 7), por lo que tuve que dejar TLS1; tampoco soporta los cifrados actuales, así que también tuve que dejar los antiguos.
Para aquellos que no han entendido y están aprendiendo, y ya quieren hacerlo bien, aquí está toda la configuración.
haproxy.conf
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
# Ubicaciones predeterminadas del material SSL
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# Ver: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
#ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE
-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
#ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
ssl-default-bind-options no-sslv3
ssl-server-verify none
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 15m
timeout server 15m
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
frontend fe_rdp_tsc
bind *:443 ssl crt /etc/haproxy/cert/dektop.example.com.pem
mode http
capture request header Host len 32
log global
option httplog
timeout client 300s
maxconn 1000
stick-table type ip size 1k expire 15s store gpc0
tcp-request connection track-sc0 src
tcp-request connection reject if { sc0_get_gpc0 gt 0 }
acl rdweb_domain hdr(host) -i beg dektop.example.com
http-request deny deny_status 400 if !rdweb_domain
default_backend be_rdp_tsc
backend be_rdp_tsc
balance source
mode http
log global
stick-table type ip size 1k expire 15s store http_err_rate(10s)
acl errors_too_fast sc1_http_err_rate gt 8
acl mark_as_abuser sc0_inc_gpc0(fe_rdp_tsc) gt 0
acl clear_as_abuser sc0_clr_gpc0(fe_rdp_tsc) ge 0
tcp-request content track-sc1 src
tcp-request content reject if errors_too_fast mark_as_abuser
tcp-request content accept if !errors_too_fast clear_as_abuser
option forwardfor
http-request add-header X-CLIENT-IP %[src]
option httpchk GET /
cookie RDPWEB insert nocache
default-server inter 3s rise 2 fall 3
server rdgw01 192.168.1.33:443 maxconn 1000 weight 10 ssl check cookie rdgw01
server rdgw02 192.168.2.33:443 maxconn 1000 weight 10 ssl check cookie rdgw02
frontend fe_stats
mode http
bind *:8080
acl ip_allow_admin src 192.168.66.66
stats enable
stats uri /stats
stats refresh 30s
#stats admin if LOCALHOST
stats admin if ip_allow_admin
¿Por qué hay dos servidores en el backend? Porque así se puede lograr alta disponibilidad. Haproxy también puede configurarse con dos IPs flotantes.
Recursos computacionales: se puede comenzar con "dos gigas, dos núcleos, PC para juegos". Según esto será más que suficiente.
Enlaces:
Fuente: habr.com
