MS Remote Desktop Gateway, HAProxy et attaque par force brute

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 Wikipedia cela devrait être largement suffisant.

Liens :

Configuration du rdp-gateway à partir de HAProxy.
Le seul article que j'ai trouvé où l'on s'est préoccupé du craquage de mot de passe

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster