Introduzione
Nella prima parte abbiamo fornito una breve descrizione del meccanismo dell'eSNI (encrypted SNI). Abbiamo mostrato come, sulla base di esso, sia possibile eludere il rilevamento da parte dei moderni sistemi DPI (prendendo come esempio il DPI di Beeline e il tracker RuNet bloccato dal Roskomnadzor), e abbiamo esaminato una nuova variante del domain fronting basata su questo meccanismo.
Nella seconda parte dell'articolo passeremo a questioni più pratiche, che saranno utili agli specialisti della RedTeam nel loro difficile lavoro. Dopotutto, il nostro obiettivo non è ottenere accesso a risorse bloccate (per tali cose banali abbiamo il buon vecchio VPN). Fortunatamente, ci sono un gran numero di provider VPN, come si suol dire, per tutti i gusti, colori e budget.
Cercheremo di applicare il meccanismo del domain fronting agli strumenti moderni della RedTeam, come Cobalt Strike, Empire, ecc., e fornire loro ulteriori possibilità di mimetizzazione ed elusione dai moderni sistemi di filtraggio del contenuto.
L'ultima volta abbiamo integrato il meccanismo dell'eSNI nella libreria OpenSSL e l'abbiamo utilizzato con successo nell'utility di curl conosciuta a tutti. Ma con un semplice curl, come si dice, non ci si nutre. Certamente vogliamo realizzare qualcosa di simile in linguaggi di alto livello. Purtroppo, una rapida ricerca nelle vastità della rete ci delude, poiché il supporto del meccanismo dell'eSNI è implementato pienamente solo in GOLANG. Pertanto, le nostre opzioni non sono poi così molte: o scriviamo in C o C++ puro utilizzando una libreria OpenSSL patchata, oppure utilizziamo un fork separato di GOLANG da CloudFlare e proviamo a portare il nostro toolkit lì. In linea di principio, c'è anche un'altra opzione, più classica, ma allo stesso tempo laboriosa: implementare il supporto per l'eSNI in Python. Dopotutto, anche Python utilizza OpenSSL per lavorare con https. Ma lasceremo questa opzione a qualcun altro per lo sviluppo e noi ci accontenteremo dell'implementazione in Golang, soprattutto perché il nostro amato Cobalt Strike lavora perfettamente con i canali di comunicazione costruiti con mezzi esterni (External C2 channel) – di questo parleremo alla fine dell'articolo.
Try Harder…
Uno degli strumenti realizzati in Go è il nostro sviluppo per il pivoting all'interno della rete: il tunneling , che, tra l'altro, al giorno d'oggi viene rilevato dai prodotti di Microsoft e Symantec come software molto dannoso, volto a compromettere la stabilità globale…

Sarebbe fantastico utilizzare il precedente sviluppo anche in questo caso. Ma qui sorge un piccolo problema. Il fatto è che inizialmente rsockstun implica l'uso di un canale di comunicazione SSL sincrono con il server. Questo significa che la connessione viene stabilita una sola volta e rimane attiva per tutta la durata del funzionamento del tunneler. E, come puoi immaginare, il protocollo https non è proprio concepito per questo tipo di modalità operativa – funziona in modalità request-response, dove ogni nuova richiesta http esiste nel contesto di una nuova connessione tcp.
Il principale svantaggio di questo schema è che il server non può trasferire dati al cliente fino a quando il cliente non invia una nuova richiesta http. Ma, per fortuna, ci sono molte soluzioni a questo problema – il trasferimento dei dati tramite il protocollo http (dopotutto, riusciamo in qualche modo a guardare le nostre serie preferite e ascoltare musica da portali che funzionano su https, e il trasferimento di video e audio non è altro che un trasferimento dati in streaming). Una delle tecnologie per emulare il funzionamento di una connessione tcp completa sopra il protocollo http è la tecnologia dei websocket (WebSockets), il cui principio fondamentale è l'organizzazione di una connessione di rete completa tra cliente e server web.
Per nostra fortuna (evviva!!!), questa tecnologia è attivata per impostazione predefinita in tutti i piani tariffari di CloudFlare e funziona perfettamente in combinazione con eSNI. Proprio questa la utilizzeremo per insegnare al nostro tunneler a usare il domain fronting e a nascondersi dai moderni DPI.
Un po' sui WebSockets
Prima di tutto, spiegheremo brevemente e in termini semplici cosa sono i websocket, affinché tutti abbiano un'idea chiara di cosa stiamo per affrontare.
La tecnologia dei websocket consente di passare temporaneamente da una connessione http a un normale trasferimento dati tramite socket di rete, senza interrompere la connessione tcp stabilita. Quando il cliente desidera passare ai websocket, invia nel suo richiesta http alcuni header http. Due header obbligatori sono Connection: Upgrade e Upgrade: websocket. Può anche specificare forzatamente la versione del protocollo websocket (Sec-Websocket-Version: 13) e qualcosa simile a un identificatore base64 per il websocket (Sec-WebSocket-Key: DAGDJSiREI3+KjDfwxm1FA==). Il server risponde con il codice http 101 Switching Protocols e imposta anche le intestazioni Connection, Upgrade e Sec-WebSocket-Accept. Il processo di switch è ben illustrato nello schermo qui sotto:

Dopo di che si può considerare completata l'installazione della connessione WebSocket. Qualsiasi dato, sia dal client che dal server, ora sarà accompagnato non da intestazioni http, ma da intestazioni WebSocket (iniziano con il byte 0x82). Ora il server non ha bisogno di aspettare una richiesta dal client per trasmettere i dati, poiché la connessione tcp non si interrompe.
In Go ci sono diverse librerie per lavorare con i websocket. Le più popolari sono e la standard . Utilizzeremo quest'ultima, poiché è più semplice, più leggera e funziona, come si dice, un po' più velocemente.
Nel codice cliente rsockstun, dobbiamo sostituire le chiamate net.dial o tls.dial con le corrispondenti chiamate WebSocket:


Vogliamo rendere la parte client del nostro tunneller universale e capace di funzionare sia attraverso una connessione ssl diretta che attraverso il protocollo WebSocket. Per questo creeremo una funzione separata func connectForWsSocks(address string, proxy string) error {…} per analogia con connectForSocks() e useremo questa funzione per lavorare con i websocket nel caso in cui l'indirizzo del server specificato all'avvio del client inizi con ws: o wss: (nel caso di Secure WebSocket).
Per la parte server del tunneller faremo anche una funzione separata per lavorare con i websocket. Qui creeremo un'istanza della classe http e imposteremo un gestore per la connessione http (funzione wsHandler):

E tutta la logica di gestione della connessione (autenticazione del client tramite password, impostazione e chiusura della sessione yamux) la metteremo nel gestore della connessione WebSocket:

Compiliamo il progetto, avviamo la parte server:
.\/rsockstun –listen ws:127.0.0.1:8080 –pass P@ssw0rdE poi la parte client:
.\/rsockstun -connect ws:127.0.0.1:8080 –pass P@ssw0rdE verifichiamo il funzionamento sul host locale:


Passiamo al domain-fronting
Con i websocket sembriamo averci capito. Ora passiamo direttamente a eSNI e al domain-fronting. Come già detto prima, per lavorare con DoH e eSNI abbiamo bisogno di prendere un ramo speciale di Go della compagnia . Abbiamo bisogno di un ramo con supporto per eSNI (pwu/esni).
Cloniamolo localmente o scarichiamo e estraiamo il relativo zip:
git clone -b pwu/esni https://github.com/cloudflare/tls-tris.gitDopo, dobbiamo copiare la directory GOROOT, sostituire i file corrispondenti dal branch clonato e impostarlo come principale. Per sollevare il developer da questo grattacapo, i ragazzi di CloudFlare hanno preparato uno script speciale – _dev/go.sh. Basta eseguirlo. Lo script, insieme al makefile, farà tutto da solo. Per curiosità, puoi dare un'occhiata al makefile per ulteriori dettagli.
Dopo l'esecuzione dello script, durante la compilazione del progetto, dovremo specificare come GOROOT la directory locale preparata dallo script. Nel nostro caso appare così:
GOROOT="/opt/tls-tris/_dev/GOROOT/linux_amd64" go build ….Successivamente, dobbiamo implementare nella tunnel la funzionalità per richiedere e analizzare le chiavi eSNI pubbliche per il dominio desiderato. Nel nostro caso, queste sono le chiavi eSNI pubbliche dei server frontend di CloudFlare. A tal fine, creeremo tre funzioni:
func makeDoTQuery(dnsName string) ([]byte, error)
func parseTXTResponse(buf []byte, wantName string) (string, error)
func QueryESNIKeysForHost(hostname string) ([]byte, error)I nomi delle funzioni, in effetti, parlano da soli. Prenderemo il contenuto dal file esni_query.go, che fa parte di tls-tris. La prima funzione crea un pacchetto di rete con una richiesta al server DNS di CloudFlare, utilizzando il protocollo DoH (DNS-over-HTTPS), la seconda analizza i risultati della richiesta e ottiene i valori delle chiavi pubbliche del dominio, mentre la terza funge da contenitore per le prime due.
Successivamente, aggiungiamo nella nostra nuova funzione il collegamento WebSocket connectForWsSocks la funzionalità per richiedere le chiavi eSNI per il dominio. Dove funziona la parte server, impostiamo i parametri TLS e definiamo anche il nome del «dominio di copertura» fittizio:

È importante notare che inizialmente, il branch tls-tris non è progettato per utilizzare il domain fronting. Di conseguenza, non presta attenzione al nome di server fittizio (nel pacchetto client-hello viene inviato un campo serverName vuoto). Per correggere questo, dovremo aggiungere al modello TlsConfig il campo corrispondente FakeServerName. Non possiamo utilizzare il campo ServerName standard della struttura, in quanto è usato dai meccanismi interni di tls e se differisce dall'originale, l handshake tls terminerà con un errore. La descrizione della struttura TlsConfig si trova nel file tls/common.go – e sta a noi modificarlo:


Inoltre, dovremo apportare modifiche al file tls/handshake_client.go, per utilizzare il nostro campo FakeServerName nella generazione del handshake TLS:

Fatto! Puoi compilare il progetto e verificare il funzionamento. Ma prima di avviare il controllo, è necessario configurare l'account CloudFlare. Come dire, configurare: è sufficiente creare un account su CloudFlare e associare il proprio dominio ad esso. Tutte le funzionalità relative a DoH, WebSocket ed ESNI sono abilitate su CloudFlare di default. Dopo che i record DNS sono stati aggiornati, puoi verificare il funzionamento del dominio effettuando una richiesta per le chiavi eSNI:
dig +short txt _esni.df13tester.info 
Se vedi qualcosa di simile per il tuo dominio, significa che tutto funziona e puoi procedere con il test.
Avviamo un VPS Ubuntu, ad esempio, su DigitalOcean. P.S. Nel nostro caso, l'indirizzo IP appena rilasciato dal provider VPS si è trovato nelle blacklist del RKN. Quindi non sorprenderti se ti succede qualcosa di simile. Ho dovuto utilizzare una VPN per accedere al mio VPS.
Copiamo su VPS il rsockstun già compilato (questo, tra l'altro, è un'altra bellezza del Golang: puoi compilare il progetto sul tuo sistema e avviarlo su qualsiasi Linux, rispettando solo l'architettura di sistema) e avviamo la parte server:

E poi avviamo la parte client:

Come vediamo, il client si è connesso con successo al server attraverso il server frontend di CloudFlare utilizzando un websocket. Per verificare che il tunnel funzioni effettivamente come tale, puoi effettuare una richiesta curl attraverso il socks5 locale aperto sul server:

Ora vediamo cosa vede il DPI nel canale di comunicazione:

Inizialmente, il tunnel, utilizzando il meccanismo DoH, si rivolge al server DNS di Cloudflare per ottenere le chiavi eSNI per il dominio di destinazione (pacchetti n. 1-19), quindi si rivolge al server frontend e stabilisce una connessione TLS, nascondendosi dietro il dominio (questo è il valore predefinito quando, all'avvio del client, non viene specificato un dominio falso). Per specificare il proprio dominio falso è necessario utilizzare il parametro -fronfDomain:
![]()

Ora un altro punto. Di default, nelle impostazioni dell'account su CloudFlare è attivata la modalità Flexible SSL. Ciò significa che le richieste https ai server frontend di CloudFlare da parte dei client verranno reindirizzate in modalità non crittografata (http) al nostro server. È per questo che abbiamo avviato la parte server del tunnel in modalità non-ssl (-listen ws:0.0.0.0), e non (-listen wss:0.0.0.0).

Per passare alla modalità di crittografia completa, è necessario scegliere Completo, o Full (strict) in caso di presenza di un certificato valido sul server. Dopo aver cambiato la modalità, saremo in grado di accettare connessioni da CloudFlare tramite il protocollo https. Non dimenticate di generare un certificato self-signed per la parte server del tunneler.

Il lettore pignolo chiederà: «E per il client su Windows? È certo che l'uso principale del tunneler è sollevare il back-connect da macchine e server aziendali, e lì di solito c'è sempre Windows. Come posso compilare il tunneler per Windows, e anche con uno stack TLS specifico?» Ora presentiamo un'altra funzionalità che dimostra quanto sia comodo Go. Compiliamo per Windows direttamente da Kali, aggiungendo semplicemente il parametro GOOS=windows:
GOARCH=amd64 GOROOT="/opt/tls-tris/_dev/GOROOT/linux_amd64" GOOS=windows go build -ldflags="-s -w"O la versione a 32 bit:
GOARCH=386 GOROOT="/opt/tls-tris/_dev/GOROOT/linux_amd64" GOOS=windows go build -ldflags="-s -w"Tutto qui! E non servono ulteriori complicazioni. Funziona davvero!

Le opzioni del compilatore –w e –s sono necessarie per rimuovere i rifiuti indesiderati dal file eseguibile, rendendolo più leggero di alcuni megabyte. Successivamente, può essere impacchettato utilizzando UPX per ridurre ulteriormente la dimensione.
In conclusione
Nell'articolo, abbiamo dimostrato l'applicazione di una nuova tecnologia di domain fronting utilizzando un tunneler scritto in Go, basandoci su una caratteristica piuttosto interessante del protocollo TLS 1.3. Analogamente, è possibile adattare gli strumenti esistenti scritti in Go per funzionare attraverso i server CloudFlare, ad esempio — un noto C2, o costringere CobaltStrike Beacon a utilizzare domain fronting eSNI quando lavora con Teamserver tramite , realizzato in Go, oppure utilizzare la versione standard in C++ con una versione di OpenSSL patchata, di cui abbiamo parlato nella parte precedente dell'articolo. Insomma, non c'è limite alla fantasia.
L'esempio con il tunneler e CloudFlare è presentato come un concetto, ed è ancora difficile dire quali siano le prospettive future di questo tipo di domain fronting. Al momento, il supporto per eSNI è implementato solo su CloudFlare e, in teoria, niente impedisce loro di disattivare questo tipo di fronting e, ad esempio, interrompere le connessioni tls in caso di non corrispondenza tra SNI ed eSNI. Insomma, il futuro lo dirà. Ma per ora, la prospettiva di operare sotto "copertura di kremlin.ru" appare piuttosto allettante. Non è così?
Il codice aggiornato del tunneller, così come i file exe compilati, sono disponibili in un ramo separato del progetto su . Per eventuali problemi con il tunneller, è meglio riportare un issue sulla pagina del progetto su GitHub.
Fonte: habr.com
