Brama usług pulpitu zdalnego MS, HAProxy i brutalna siła haseł

Przyjaciele, cześć!

Istnieje wiele sposobów na połączenie domu z biurem. Jednym z nich jest wykorzystanie usługi Microsoft Remote Desktop Gateway. To jest RDP przez HTTP. Nie chcę tu poruszać tematu samego konfigurowania RDGW, nie chcę dyskutować dlaczego jest to dobre, a co złe, potraktujmy to jako jedno z narzędzi zdalnego dostępu. Chcę porozmawiać o ochronie twojego serwera RDGW przed złym Internetem. Kiedy konfigurowałem serwer RDGW, od razu zacząłem martwić się bezpieczeństwem, zwłaszcza ochroną przed brutalną siłą haseł. Zaskoczyło mnie, że w Internecie nie znalazłem żadnych artykułów na temat tego, jak to zrobić. Cóż, będziesz musiał to zrobić sam.

Sam RDGW nie posiada żadnych zabezpieczeń. Tak, można go wystawić gołym interfejsem na białą sieć i będzie działać świetnie. Ale to sprawi, że właściwy administrator lub specjalista ds. bezpieczeństwa informacji poczuje się nieswojo. Dodatkowo pozwoli uniknąć sytuacji zablokowania konta, gdy nieostrożny pracownik przypomniał sobie hasło do konta firmowego na swoim domowym komputerze, a następnie zmienił swoje hasło.

Dobrym sposobem na ochronę zasobów wewnętrznych przed środowiskiem zewnętrznym są różne proxy, systemy wydawnicze i inne WAF. Pamiętajmy, że RDGW to nadal http, wtedy aż się prosi o wpięcie specjalistycznego rozwiązania pomiędzy wewnętrzne serwery, a Internet.

Wiem, że są fajne F5, A10, Netscaler(ADC). Jako administrator jednego z tych systemów powiem, że możliwe jest również ustawienie na tych systemach ochrony przed brutalną siłą. I tak, te systemy będą również chronić Cię przed powodzią syn.

Nie każdą jednak firmę stać na zakup takiego rozwiązania (i znalezienie administratora takiego systemu :), ale jednocześnie potrafi zadbać o bezpieczeństwo!

Całkowicie możliwa jest instalacja darmowej wersji HAProxy na darmowym systemie operacyjnym. Testowałem na Debianie 10, haproxy w wersji 1.8.19 w stabilnym repozytorium. Testowałem go także na wersji 2.0.xx z repozytorium testowego.

Konfigurację samego Debiana pozostawimy poza zakresem tego artykułu. W skrócie: na białym interfejsie zamknij wszystko oprócz portu 443, na szarym interfejsie - zgodnie ze swoją polityką np. zamknij także wszystko oprócz portu 22. Otwieraj tylko to co niezbędne do pracy (np. VRRP dla zmiennego ip).

Przede wszystkim skonfigurowałem haproxy w trybie mostu SSL (inaczej trybie http) i włączyłem rejestrowanie, aby zobaczyć, co dzieje się w RDP. Można powiedzieć, że znalazłem się pośrodku. Zatem brakuje ścieżki /RDWeb określonej we „wszystkich” artykułach na temat konfigurowania RDGateway. Wszystko, co tam jest, to /rpc/rpcproxy.dll i /remoteDesktopGateway/. W tym przypadku nie stosuje się standardowych żądań GET/POST, lecz własny typ żądań RDG_IN_DATA, RDG_OUT_DATA.

Niewiele, ale przynajmniej coś.

Przetestujmy.

Uruchamiam mstsc, idę na serwer, widzę w dziennikach cztery błędy 401 (nieautoryzowane), następnie wprowadzam moją nazwę użytkownika/hasło i widzę odpowiedź 200.

Wyłączam, uruchamiam ponownie i w logach widzę te same cztery błędy 401. Wpisuję zły login/hasło i znów widzę cztery błędy 401. Tego mi potrzeba. To właśnie złapiemy.

Ponieważ nie udało się ustalić adresu URL logowania, a poza tym nie wiem, jak wyłapać błąd 401 w haproxy, wyłapię (właściwie nie wyłapię, ale policzę) wszystkie błędy 4xx. Nadaje się również do rozwiązania problemu.

Istota zabezpieczenia będzie polegała na tym, że będziemy zliczać liczbę błędów 4xx (na backendie) w jednostce czasu i jeśli przekroczy ona określony limit, to odrzucać (na frontendzie) wszystkie dalsze połączenia z tego ip na określony czas .

Technicznie rzecz biorąc, nie będzie to ochrona przed brutalną siłą hasła, będzie to ochrona przed błędami 4xx. Na przykład, jeśli często żądasz nieistniejącego adresu URL (404), wówczas ochrona również zadziała.

Najprostszym i najskuteczniejszym sposobem jest skorzystanie z backendu i złożenie raportu, jeśli pojawi się coś dodatkowego:

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
    ...

    #создать таблицу, строковую, 1000 элементов, протухает через 15 сек, записать кол-во ошибок за последние 10 сек
    stick-table type string len 128 size 1k expire 15s store http_err_rate(10s)
    #запомнить ip
    http-request track-sc0 src
    #запретить с http ошибкой 429, если за последние 10 сек больше 4 ошибок
    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

Nie jest to najlepsza opcja, skomplikujmy to. Będziemy liczyć na backend i blokować na frontendzie.

Potraktujemy atakującego niegrzecznie i zerwiemy jego połączenie TCP.

frontend fe_rdp_tsc
    bind *:443 ssl crt /etc/haproxy/cert/ertelecom_ru_2020_06_11.pem
    mode http
    ...
    #создать таблицу ip адресов, 1000 элементов, протухнет через 15 сек, сохрянять из глобального счётчика
    stick-table type ip size 1k expire 15s store gpc0
    #взять источник
    tcp-request connection track-sc0 src
    #отклонить tcp соединение, если глобальный счётчик >0
    tcp-request connection reject if { sc0_get_gpc0 gt 0 }
	
    ...
    default_backend be_rdp_tsc


backend be_rdp_tsc
    ...
    mode http
    ...
	
    #создать таблицу ip адресов, 1000 элементов, протухнет через 15 сек, сохранять кол-во ошибок за 10 сек
    stick-table type ip size 1k expire 15s store http_err_rate(10s)
    #много ошибок, если кол-во ошибок за 10 сек превысило 8
    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
	
    ...
    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

to samo, ale grzecznie, zwrócimy błąd http 429 (zbyt wiele żądań)

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
    ...

Sprawdzam: uruchamiam mstsc i zaczynam losowo wpisywać hasła. Po trzeciej próbie w ciągu 10 sekund wyrzuca mnie z powrotem, a mstsc wyświetla błąd. Jak widać w logach.

Wyjaśnienia. Daleko mi do mistrza haproxy. Nie rozumiem dlaczego np
żądanie http odmowa deny_status 429 if { sc_http_err_rate(0) gt 4 }
pozwala popełnić około 10 błędów, zanim zadziała.

Mam wątpliwości co do numeracji liczników. Mistrzowie haproxy, będzie mi miło, jeśli mnie uzupełnicie, poprawicie, uczynicie mnie lepszym.

W komentarzach możesz zasugerować inne sposoby ochrony RD Gateway, ciekawie będzie je przestudiować.

Jeśli chodzi o Klienta Pulpitu Zdalnego systemu Windows (mstsc), warto zauważyć, że nie obsługuje on protokołu TLS1.2 (przynajmniej w Windows 7), więc musiałem opuścić TLS1; nie obsługuje obecnego szyfru, więc musiałem też zostawić stare.

Dla tych, którzy nic nie rozumieją, dopiero się uczą, a już chcą dobrze sobie radzić, podam całą konfigurację.

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

        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private

        # See: 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

Po co dwa serwery na backendzie? Ponieważ w ten sposób można zapewnić odporność na błędy. Haproxy może również zrobić dwa z pływającym białym adresem IP.

Zasoby obliczeniowe: możesz zacząć od „dwóch gigabajtów, dwóch rdzeni, komputera do gier”. Według Wikipedia to wystarczy na zapas.

Linki:

Konfigurowanie bramy RDP z HAProxy
Jedyny artykuł, jaki znalazłem, w którym zadali sobie trud wymuszenia hasła

Źródło: www.habr.com

Dodaj komentarz