MS Remote Desktop Gateway, HAProxy e attacco brute force

Ciao amici!

Ci sono molti modi per connettersi da casa al luogo di lavoro in ufficio. Uno di questi è utilizzare Microsoft Remote Desktop Gateway. Questo è RDP sopra HTTP. Non voglio trattare qui la configurazione del RDGW, né discutere se sia buono o cattivo; consideriamolo come uno degli strumenti di accesso remoto. Voglio parlare della protezione del tuo server RDGW contro il male dell'internet. Quando ho configurato i server RDGW, mi sono preoccupato subito della sicurezza, in particolare della protezione contro gli attacchi brute force. Sono rimasto sorpreso di non aver trovato articoli su internet su come fare questo. Beh, dovrò farlo da solo.

Il RDGW di per sé non ha alcuna protezione. Sì, può essere esposto con un'interfaccia nuda nella rete pubblica e funzionerà perfettamente. Ma un buon amministratore o un esperto di sicurezza informatica non si sentirà a proprio agio con questo. Inoltre, aiuterà a evitare la situazione di blocco dell'account, quando un dipendente distratto ha memorizzato la password dell'account aziendale sul computer di casa e poi l'ha cambiata.

Un buon modo per proteggere le risorse interne dagli ambienti esterni è rappresentato da vari proxy, sistemi di pubblicazione e altri WAF. Ricordiamo che RDGW è comunque un http, quindi si suggerisce di inserire una soluzione specializzata tra i server interni e Internet.

So che ci sono ottimi F5, A10, Netscaler (ADC). In qualità di amministratore di uno di questi sistemi, posso dire che è possibile configurare la protezione contro gli attacchi di forza bruta su questi sistemi. E sì, queste soluzioni vi proteggeranno anche da vari attacchi di tipo syn flood.

Ma non ogni azienda può permettersi di acquistare una soluzione del genere (e trovare un amministratore per un simile sistema :), ma ci si può comunque occupare della sicurezza!

È possibile installare una versione gratuita di HAProxy su un sistema operativo gratuito. Ho testato su Debian 10, nel repository stabile la versione di haproxy è 1.8.19. Ho anche verificato le versioni 2.0.xx del repository testing.

Lasciamo la configurazione di Debian al di fuori di questo articolo. In breve: chiudere tutto sull'interfaccia bianca, tranne la porta 443, sull'interfaccia grigia — secondo la vostra politica, ad esempio, chiudere tutto tranne la porta 22. Aprire solo ciò che è necessario per il funzionamento (ad esempio VRRP, per un IP flottante).

Per prima cosa ho configurato haproxy in modalità SSL bridging (che è la modalità http) e ho abilitato il logging per vedere quali informazioni circolano nell'RDP. Insomma, sono intervenuto nel mezzo. Tuttavia, il percorso indicato in "tutti" gli articoli sulla configurazione del RDGateway, /RDWeb, è assente. Tutto ciò che è presente è /rpc/rpcproxy.dll e /remoteDesktopGateway/. Non vengono utilizzati normali richieste GET/POST, ma un tipo di richiesta personalizzato RDG_IN_DATA, RDG_OUT_DATA.

Non molto, ma è pur sempre qualcosa.

Iniziamo a testare.

Avvio mstsc, vado sul server, nei log vedo quattro errori 401 (non autorizzato), poi inserisco login/password e vedo la risposta 200.

Disconnetto, riavvio, nei log vedo gli stessi quattro errori 401. Inserisco login/password errati e vedo di nuovo quattro errori 401. Questo è ciò che ci serve. Questo è ciò che registreremo.

Poiché non sono riuscito a determinare l'URL di accesso e non so come catturare specificamente l'errore 401 in haproxy, considererò (in realtà non catturerò, ma conterò) tutti gli errori 4xx. Questo sarà ugualmente accettabile per risolvere il problema.

La protezione consisterà nel contare il numero di errori 4xx (sul backend) in un'unità di tempo e, se supera il limite specificato, rifiutare (sul frontend) tutte le ulteriori connessioni da questo IP per un tempo specificato.

Tecnicamente, non sarà una protezione contro il tentativo di accesso non autorizzato, ma una protezione dagli errori 4xx. Ad esempio, se si richiede frequentemente un URL inesistente (404), la protezione si attiverà comunque.

Il modo più semplice e funzionante è contare e bloccare sul backend, se appare qualcosa di eccessivo:

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

    #creare una tabella, tipo stringa, 1000 elementi, scade dopo 15 secondi, registrare il numero di errori negli ultimi 10 secondi
    stick-table type string len 128 size 1k expire 15s store http_err_rate(10s)
    #memorizzare l'ip
    http-request track-sc0 src
    #negare con errore http 429, se ci sono più di 4 errori negli ultimi 10 secondi
    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

Non è l'opzione migliore, ma complicheremo le cose. Calcoleremo sul backend, mentre bloccheremo sul frontend.

Affronteremo l'attaccante in modo deciso, chiuderemo la sua connessione tcp.

frontend fe_rdp_tsc
    bind *:443 ssl crt /etc/haproxy/cert/ertelecom_ru_2020_06_11.pem
    mode http
    ...
    #crea una tabella di indirizzi IP, 1000 elementi, scade dopo 15 secondi, conserva dal contatore globale
    stick-table type ip size 1k expire 15s store gpc0
    #prendi la sorgente
    tcp-request connection track-sc0 src
    #rifiuta la connessione tcp se il contatore globale >0
    tcp-request connection reject if { sc0_get_gpc0 gt 0 }
	
    ...
    default_backend be_rdp_tsc


backend be_rdp_tsc
    ...
    mode http
    ...
	
    #crea una tabella di indirizzi IP, 1000 elementi, scade dopo 15 secondi, conserva il numero di errori per 10 secondi
    stick-table type ip size 1k expire 15s store http_err_rate(10s)
    #troppi errori se il numero di errori in 10 secondi supera 8
    acl errors_too_fast sc1_http_err_rate gt 8
    #segnala attacco nel contatore globale (incrementa il contatore)
    acl mark_as_abuser sc0_inc_gpc0(fe_rdp_tsc) gt 0
    #azzerare il contatore globale
    acl clear_as_abuser sc0_clr_gpc0(fe_rdp_tsc) ge 0
    #prendi la sorgente
    tcp-request content track-sc1 src
    #rifiuta, segnala attacco
    tcp-request content reject if errors_too_fast mark_as_abuser
    #consenti, reimposta il flag dell'attacco
    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 stesso, ma in modo cortese, restituiremo l'errore http 429 (Troppe richieste)

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

Controllo: avvio mstsc e inizio a digitare password a caso. Dopo il terzo tentativo in 10 secondi vengo espulso e mstsc restituisce un errore. Come si può vedere nei log.

Chiarimenti. Non sono un esperto di haproxy. Non capisco, per esempio, perché
http-request deny deny_status 429 if { sc_http_err_rate(0) gt 4 }
permette di fare circa 10 errori prima che scatti.

Mi confondo con la numerazione dei contatori. Esperti di haproxy, sarei felice se mi integraste, correggeste, o miglioraste.

Nei commenti potete suggerire altri metodi di protezione per RD Gateway, sarà interessante studiarli.

Per quanto riguarda il client di desktop remoto Windows (mstsc), va notato che non supporta TLS1.2 (almeno in Windows 7), quindi ho dovuto mantenere TLS1; non supporta i cipher attuali, quindi ho dovuto lasciare anche i più vecchi.

Per chi non ha ancora capito e sta iniziando a imparare, voglio presentare l'intera configurazione.

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

        # Posizioni predefinite dei materiali SSL
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private

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

Perché due server sul backend? Perché in questo modo si può garantire resilienza. Puoi anche configurare HAProxy con due indirizzi IP pubblici flottanti.

Risorse di calcolo: puoi iniziare con "due gigabyte, due core, un PC da gioco". Secondo Wikipedia questo sarà più che sufficiente.

Link:

Configurazione di rdp-gateway da HAProxy
L'unico articolo che ho trovato in cui si affronta il brute forcing

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster