MS Remote Desktop Gateway, HAProxy și parola brute force

Prieteni, salutare!

Există multe modalități de a vă conecta de acasă la spațiul de lucru de la birou. Una dintre ele este utilizarea Microsoft Remote Desktop Gateway. Acesta este RDP peste HTTP. Nu vreau să ating aici configurarea RDGW în sine, nu vreau să discut de ce este bine sau rău, să-l tratăm ca pe unul dintre instrumentele de acces la distanță. Vreau să vorbesc despre protejarea serverului tău RDGW de Internetul rău. Când am configurat serverul RDGW, am devenit imediat îngrijorat de securitate, în special de protecția împotriva forței brute a parolei. Am fost surprins că nu am găsit niciun articol pe Internet despre cum să fac asta. Ei bine, va trebui să o faci singur.

RDGW în sine nu are nicio protecție. Da, poate fi expus cu o interfață goală la o rețea albă și va funcționa grozav. Dar acest lucru va face să fie neliniștit administratorul potrivit sau specialistul în securitatea informațiilor. În plus, vă va permite să evitați situația de blocare a contului, când un angajat neatent și-a amintit parola pentru un cont corporativ de pe computerul său de acasă, apoi și-a schimbat parola.

O modalitate bună de a proteja resursele interne de mediul extern este prin diferite proxy-uri, sisteme de publicare și alte WAF-uri. Să ne amintim că RDGW este încă http, atunci doar cere să conectați o soluție specializată între serverele interne și Internet.

Știu că există F5, A10, Netscaler (ADC). În calitate de administrator al unuia dintre aceste sisteme, voi spune că este posibil să se configureze și protecție împotriva forței brute pe aceste sisteme. Și da, aceste sisteme vă vor proteja și de orice inundație syn.

Dar nu orice companie își poate permite să achiziționeze o astfel de soluție (și să găsească un administrator pentru un astfel de sistem :), dar în același timp se pot ocupa de securitate!

Este complet posibil să instalați o versiune gratuită de HAProxy pe un sistem de operare gratuit. Am testat pe Debian 10, versiunea haproxy 1.8.19 în depozitul stabil. L-am testat și pe versiunea 2.0.xx din depozitul de testare.

Vom lăsa configurarea debian în sine în afara domeniului de aplicare al acestui articol. Pe scurt: pe interfața albă, închideți totul, cu excepția portului 443, pe interfața gri - conform politicii dvs., de exemplu, închideți totul, cu excepția portului 22. Deschideți doar ceea ce este necesar pentru lucru (VRRP de exemplu, pentru ip flotant).

În primul rând, am configurat haproxy în modul SSL bridging (alias modul http) și am activat înregistrarea pentru a vedea ce se întâmplă în interiorul RDP. Ca să spun așa, am ajuns la mijloc. Deci, calea /RDWeb specificată în „toate” articolele despre configurarea RDGateway lipsește. Tot ce este acolo este /rpc/rpcproxy.dll și /remoteDesktopGateway/. În acest caz, cererile standard GET/POST nu sunt utilizate; este folosit propriul lor tip de solicitare RDG_IN_DATA, RDG_OUT_DATA.

Nu mult, dar măcar ceva.

Să testăm.

Lansez mstsc, merg la server, văd patru erori 401 (neautorizate) în jurnale, apoi introdu numele meu de utilizator/parola și văd răspunsul 200.

Îl opresc, îl pornesc din nou și în jurnale văd aceleași patru erori 401. Intru autentificare/parolă greșită și văd din nou patru erori 401. De asta am nevoie. Asta vom prinde.

Deoarece nu a fost posibilă determinarea adresei URL de conectare și, în plus, nu știu cum să prind eroarea 401 în haproxy, voi prinde (nu prind de fapt, ci număr) toate erorile 4xx. Potrivit și pentru rezolvarea problemei.

Esența protecției va fi că vom număra numărul de erori 4xx (pe backend) pe unitatea de timp și dacă depășește limita specificată, atunci respingem (pe front-end) toate conexiunile ulterioare de la acest ip pentru timpul specificat .

Din punct de vedere tehnic, aceasta nu va fi protecție împotriva parolei forței brute, ci va fi protecție împotriva erorilor 4xx. De exemplu, dacă solicitați adesea o adresă URL inexistentă (404), atunci și protecția va funcționa.

Cea mai simplă și eficientă modalitate este de a conta pe backend și de a raporta dacă apare ceva în plus:

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

Nu este cea mai bună opțiune, hai să o complicăm. Vom conta pe backend și vom bloca pe front.

Vom trata atacatorul nepoliticos și îi vom renunța la conexiunea 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

același lucru, dar politicos, vom returna eroarea http 429 (Too Many Requests)

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

Verific: lansez mstsc și încep să introduc la întâmplare parole. După a treia încercare, în 10 secunde mă dă înapoi și mstsc dă o eroare. După cum se vede în jurnalele.

Explicații. Sunt departe de a fi un maestru haproxy. Nu înțeleg de ce, de exemplu
http-request deny deny_status 429 if { sc_http_err_rate(0) gt 4 }
vă permite să faceți aproximativ 10 greșeli înainte de a funcționa.

Sunt confuz cu privire la numerotarea contoarelor. Maeștri ai haproxy, mă voi bucura dacă mă completați, mă corectați, mă faceți mai bun.

În comentarii puteți sugera și alte modalități de a proteja RD Gateway, va fi interesant de studiat.

În ceea ce privește Windows Remote Desktop Client (mstsc), este de remarcat faptul că nu suportă TLS1.2 (cel puțin în Windows 7), așa că a trebuit să părăsesc TLS1; nu suportă cifra actuală, așa că a trebuit să le las și pe cele vechi.

Pentru cei care nu înțeleg nimic, doar învață și doresc deja să facă bine, vă voi oferi întreaga configurație.

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

De ce două servere pe backend? Pentru că așa poți face toleranță la greșeală. Haproxy poate face și două cu un ip alb plutitor.

Resurse de calcul: puteți începe cu „două gig, două nuclee, PC de jocuri”. Conform wikipedia asta va fi suficient pentru a economisi.

referințe:

Configurarea rdp-gateway de la HAProxy
Singurul articol pe care l-am găsit în care s-au deranjat să forțeze parola

Sursa: www.habr.com

Adauga un comentariu