Bonjour les amis !
Il existe de nombreuses façons de se connecter de chez soi à son poste de travail au bureau. L'une d'elles consiste à utiliser Microsoft Remote Desktop Gateway. Il s'agit d'un RDP sur HTTP. Je ne veux pas aborder ici la configuration de RDGW en elle-même, ni discuter de ses avantages ou inconvénients, considérons-le simplement comme un des outils d'accès à distance. Je souhaite parler de la protection de votre serveur RDGW contre les dangers d'internet. Lorsque j'ai configuré mon serveur RDGW, je me suis immédiatement préoccupé de sa protection, surtout contre les tentatives de brute force. J'ai été surpris de ne pas trouver d'articles sur internet expliquant comment procéder. Eh bien, il va falloir le faire soi-même.
RDGW en lui-même n'a pas de protection. Oui, il peut être exposé avec son interface à un réseau public et fonctionner parfaitement. Mais un bon administrateur ou un spécialiste en sécurité s'en inquiéterait. De plus, cela permet d'éviter une situation de blocage de compte, lorsque qu'un employé négligent se souvient du mot de passe de son compte professionnel sur son ordinateur personnel et change ensuite son mot de passe.
Une bonne façon de protéger des ressources internes contre l'environnement externe est d'utiliser divers proxies, systèmes de publication et autres WAF. Rappelons que RDGW utilise tout de même HTTP, il serait donc logique d'installer une solution spécialisée entre les serveurs internes et internet.
Je sais qu'il existe des solutions performantes comme F5, A10, Netscaler (ADC). En tant qu'administrateur d'un de ces systèmes, je peux dire qu'il est également possible de configurer la protection contre les tentatives de brute force sur ces systèmes. Et oui, ces systèmes vous protégeront également contre les attaques de type syn flood.
Mais toutes les entreprises ne peuvent pas se permettre d'acquérir une telle solution (et de trouver un administrateur pour ce système :), mais la sécurité peut être assurée !
Il est tout à fait possible d'installer une version gratuite de HAProxy sur un système d'exploitation gratuit. J'ai testé sur Debian 10, dans le dépôt stable, la version haproxy 1.8.19. J'ai également vérifié sur les versions 2.0.xx du dépôt testing.
La configuration de Debian elle-même restera en dehors de cet article. En résumé : sur l'interface publique, fermez tout sauf le port 443, sur l'interface grise — selon votre politique, par exemple fermez tout sauf le port 22. Ouvrez uniquement ce qui est nécessaire au bon fonctionnement (VRRP par exemple, pour une IP flottante).
Tout d'abord, j'ai configuré haproxy en mode SSL bridging (également connu sous le nom de mode http) et j'ai activé la journalisation pour voir ce qui se passe dans RDP. En quelque sorte, je me suis mis en milieu de chemin. Ainsi, le chemin mentionné dans « tous » les articles sur la configuration de RDGateway, /RDWeb, est absent. Tout ce qu'il y a, c'est /rpc/rpcproxy.dll et /remoteDesktopGateway/. De plus, les requêtes GET/POST standard ne sont pas utilisées, on utilise un type de requête personnalisé RDG_IN_DATA, RDG_OUT_DATA.
Pas grand-chose, mais c'est déjà ça.
Testons.
Je lance mstsc, je vais sur le serveur, dans les logs je vois quatre erreurs 401 (non autorisé), puis j'entre mon identifiant/mot de passe et je vois une réponse 200.
Je déconnecte, relance, dans les logs je vois les mêmes quatre erreurs 401. J'entre un identifiant/mot de passe erroné et je vois à nouveau quatre erreurs 401. C'est exactement ce qu'il nous faut. Nous allons traquer cela.
Puisque je n'ai pas réussi à déterminer l'url de connexion, et que je ne sais pas non plus comment traquer l'erreur 401 dans haproxy, je vais surveiller (en réalité, compter) toutes les erreurs 4xx. Cela suffira également pour résoudre le problème.
Le principe de sécurité consistera à compter le nombre d'erreurs 4xx (sur le backend) dans un certain laps de temps et si ce nombre dépasse un seuil spécifié, alors rejeter (sur le frontend) toutes les connexions ultérieures de cette ip pendant le temps spécifié.
Techniquement, cela ne protégera pas contre les attaques par force brute, mais ce sera une protection contre les erreurs 4xx. Par exemple, si l'on demande souvent une url inexistante (404), la protection sera également activée.
La manière la plus simple et fonctionnelle consiste à compter et à bloquer sur le backend, s'il y a quelque chose de superflu :
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
...
# créer une table, de type string, 1000 éléments, expira après 15 sec, enregistrer le nombre d'erreurs des 10 dernières secondes
stick-table type string len 128 size 1k expire 15s store http_err_rate(10s)
# mémoriser l'ip
http-request track-sc0 src
# interdire avec l'erreur http 429, si plus de 4 erreurs durant les 10 dernières secondes
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
Ce n'est pas la meilleure option, compliquons un peu. Nous allons compter sur le backend, et bloquer sur le frontend.
Avec l'attaquant, nous allons être rudes, nous allons couper sa connexion tcp.
frontend fe_rdp_tsc
bind *:443 ssl crt /etc/haproxy/cert/ertelecom_ru_2020_06_11.pem
mode http
...
# créer une table d'adresses IP, 1000 éléments, expirera après 15 secondes, conserver à partir du compteur global
stick-table type ip size 1k expire 15s store gpc0
# prendre la source
tcp-request connection track-sc0 src
# rejeter la connexion tcp si le compteur global >0
tcp-request connection reject if { sc0_get_gpc0 gt 0 }
...
default_backend be_rdp_tsc
backend be_rdp_tsc
...
mode http
...
# créer une table d'adresses IP, 1000 éléments, expirera après 15 secondes, conserver le nombre d'erreurs pendant 10 secondes
stick-table type ip size 1k expire 15s store http_err_rate(10s)
# beaucoup d'erreurs si le nombre d'erreurs pendant 10 secondes dépasse 8
acl errors_too_fast sc1_http_err_rate gt 8
# marquer l'attaque dans le compteur global (augmenter le compteur)
acl mark_as_abuser sc0_inc_gpc0(fe_rdp_tsc) gt 0
# réinitialiser le compteur global
acl clear_as_abuser sc0_clr_gpc0(fe_rdp_tsc) ge 0
# prendre la source
tcp-request content track-sc1 src
# rejeter, marquer comme attaque
tcp-request content reject if errors_too_fast mark_as_abuser
# permettre, réinitialiser le flag d'attaque
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
la même chose, mais poliment, nous retournerons une erreur http 429 (Trop de demandes)
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
...
Je vérifie : je lance mstsc et commence à entrer des mots de passe de manière désordonnée. Après la troisième tentative en 10 secondes, je suis expulsé, et mstsc renvoie une erreur. Comme on peut le voir dans les logs.
Explications. Je ne suis pas un expert en haproxy. Je ne comprends pas pourquoi, par exemple
http-request deny deny_status 429 if { sc_http_err_rate(0) gt 4 }
permet d'effectuer environ 10 erreurs avant que cela ne se déclenche.
Je me perds dans la numérotation des compteurs. Experts en haproxy, je serais heureux si vous me complétiez, corrigiez ou amélioriez.
Vous pouvez proposer d'autres méthodes de protection pour RD Gateway dans les commentaires, ce sera intéressant à étudier.
Concernant le client de bureau à distance Windows (mstsc), il convient de noter qu'il ne supporte pas TLS1.2 (du moins dans Windows 7), c'est pourquoi j'ai dû laisser TLS1 ; il ne prend pas en charge les cipher récents, donc j'ai également dû conserver les anciens.
Pour ceux qui ne comprennent rien et qui apprennent, mais qui veulent déjà bien faire, je vais fournir toute la configuration.
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
# Emplacements par défaut des matériaux SSL
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# Voir : 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
Pourquoi deux serveurs en backend ? Parce que cela permet d'assurer la haute disponibilité. Vous pouvez également mettre en place un HAProxy avec deux adresses IP flottantes.
Ressources informatiques : on peut commencer avec « deux gigas, deux cœurs, un PC de jeu ». Selon cela devrait être largement suffisant.
Liens :
Source : habr.com
