Bypassing SNI filtering : Feat DPITunnel, Zapret and Geneva

Messede DegodMessede Degod
10 min read

This article was originally written and published on 0x00sec.org, it was moved here after 0×00sec became defunct.

I was recently browsing a anti-censorship awesome list and came across few tools with some really cool tricks to bypass SNI based filtering mechanisms, i think these tools and their underlying techniques deserves more attention and are really worth sharing so here we are.

Understanding SNI

Before we dive into how these tools actually works lets understand what SNI is, SNI stands for Server Name Indication it is a header in the TLS protocol which lets the webserver (hosting multiple websites) decide which TLS certificate has to be offered to a client. Lets fire up wireshark and take a quick look at this SNI header, we will use the filter ssl.handshake.extension.type == "server_name" to make our lives easier and try connecting to 0x00sec.org, now wireshark should hide everything except the Client Hello packets which are sent during the TLS handshake, lets take a look...

expanding the server_name TLS extension reveals the SNI header, now some of you must be wondering - isn't TLS supposed encrypt everything ?, actually no, lets see why: say we have a webserver hosting a few sites (on the same IP) via plain HTTP, when a user requests a site the webserver just looks at the HTTP Host header field in the clients HTTP request and serves the appropriate site, everything works fine.

      ========================
      GET /index.html HTTP/1.1    _
      Host: ctf.0x00sec.org        \              ======================
                                    \             WEBSERVER (133.7.133.7)
                                      --------->    --server.conf--
      ========================      / <---------      ctf.0x00sec.org ->  /var/www/html/ctf
      GET /index.html HTTP/1.1    _/                  0x00sec.org     ->  /var/www/html/forum
      Host: 0x00sec.org

Now lets add TLS to the mix, to establish a TLS connection we first need the TLS certificates which are unique to each site1, and to get the appropriate TLS certs we need to tell the server which site we are trying to connect to, this is where SNI comes in, it "indicates" what "server name" we are trying to connect to, so that the server can give us the appropriate certificates, well now why is the SNI field unencrypted ?, thats simply because we haven’t established a secure connection yet and SNI is required to establish that secure connection in the first place! ( its a chicken and egg problem). During the time in which SNI was introduced(around 2003), TLS simply did not want to deal with the overhead of coming up with a way to hide the SNI field and decided to let it be (in other words it was acceptable, a hostname was not really that confidential), the same standard continues to be widely followed/used today.

The Impact Of Unencrypted SNIs

Blocking sites/services has been a favourite past time of many ISPs and governments around the world, in the early days everything was plaintext and intercepting proxies (which checked the host headers) did the job, as networks grew in size and SSL started gaining popularity it became unrealistic to inspect traffic with proxies, everybody switched over to filtering DNS requests although this worked quite well for some time, with the advent of technologies like DoH(DNS over Https) and DoT(DNS over TLS) DNS filtering too became a thing of the past, when all hope seemed lost firewall vendors turned to that one part of TLS that remained unencrypted... SNI !!.

How SNI Inspection Works

Lets say we want to connect to 0x00sec.org from our pc, the TCP connection would look like this:

      Our PC                     [+------+------+]                0x00sec.org
[123.123.1.3 : 62123 ] ==========[F-I-R-E-W-A-L-L]=========> [ 443  : 133.7.133.7 ] 
   Our Ip      Random            [+------+------+]            HTTPS    Servers Ip
             Source Port                                    Standard Port

The blocking process is fairly straight forward, every time a TLS client hello is sent, the firewall just extracts the SNI field(the hostname) from the packet and compares it against a block list, when a match is found the hello packet can simply be dropped preventing the client from establishing the TLS connection. Dropping TLS client hello's as i mentioned before is only practical in smaller networks with limited traffic (i.e in cases of colleges, corporate networks), in case of ISPs inspecting and dropping packets on the gateway would be very costly and impractical due the heavy load ISPs generally are under, thus ISPs usually mirror the traffic2 into a inspection device, the inspecting device matches the extracted SNI field against a block list as well, but to block the traffic it forges a TCP reset packet with the ip and port of the server (in our case 133.7.133.7 and 443 ) and sends it to client (i.e 123.123.1.3 on port 62123), fooling the client into thinking that the server has terminated the TCP connection thus effectively preventing the client from further communicating with the server, this method might not be 100% accurate since the reset packet can get lost during transmission, but that is within a acceptable margin, this method is what makes censorship possible at the level of ISPs.

Bypassing SNI Inspection - A Review of Techniques Used For Circumvention

Before we begin lets consider a simple representation of a TLS client hello packet for the sake of conversation.

        +---------------------------+-----------------------------+
        | SRC IP: 123.123.1.3       |     DST ISP: 133.7.133.7    |     // IP
        +---------------------------+-----------------------------+
        | SRC PORT: 45637           |     DST PORT: 443           |     // TCP
        +---------------------------+-----------------------------+
        | TLS Ver |..TLS.Hdrs.....  | SNI:Server-Name: 0x00sec.org|     // TLS  Client Hello  
        +---------------------------+-----------------------------+
  1. Split the Client hello at SNI field : As i have mentioned before the filtering process is interested in the Server Name indicated by the SNI, this process expects the entire TLS Client Hello to be present in a single packet3, to evade this filtering process we segment the TLS data in half such that SNI server name is not completely in one portion, we then transmit those portions as separate TCP segments. When the first TCP segment is seen by the filter, it would immediate recognise it as TLS Client hello since the first portion has the appropriate TLS application header, but when it extracts the SNI server name it only gets the partial hostname (ex: 0x00 instead of 0x00sec.org), since the partial hostname doesn't match against any of the block lists the packet is let through, as far as the second segment is concerned it has no meaningful application header and hence is not processed by the filter and let through.

    
      SEGMENT 1: 
     +---------------------------+-----------------------------+
     | SRC IP: 123.123.1.3       |     DST ISP: 133.7.133.7    |     // IP
     +---------------------------+-----------------------------+
     | SRC PORT: 45637           |     DST PORT: 443           |     // TCP
     +---------------------------+-----------------------------+
     | TLS Ver |..TLS.Stuff....  |   SNI:Server-Name: 0x00     |     // Partial Client Hello  
     +---------------------------+-----------------------------+
    
     SEGMENT 2: 
     +---------------------------+-----------------------------+
     | SRC IP: 123.123.1.3       |     DST ISP: 133.7.133.7    |     // IP
     +---------------------------+-----------------------------+
     | SRC PORT: 45637           |     DST PORT: 443           |     // TCP
     +---------------------------+-----------------------------+
     | sec.org................................................ |     // Partial Client Hello  
     +---------------------------+-----------------------------+
    
  2. Split and send the client hello in random order : This is variation of the first method except we send the segments in reverse or random order4, this is done in order to bypass certain filters which can reassemble segments but expects the segments to be sent in the right order.

  3. Use TCP window size to fragment the server hello : Most filtering processes only analyze outbound packets (i.e packets originating from clients, TLS client hello in our case), but there are a few which analyze inbound packets as well, in such a case if the server hello is analyzed, it would contain a "certificate common name" (which again is nothing but hostname for which the TLS certificate was issued), which might match against the block list. In this situation we need to achive something similar to the first technique but from the servers end, we thus advertise a small TCP Window size which would force the server to split the server hello across multiple TCP segments hence achieving evasion.

  4. Send Fake client hello with an allowed SNI, but with a TTL not long enough to reach server : In some scenarios, once a TLS client hello has been analysed by the filter and allowed to pass, that particular TCP stream is ignored, any further inbound or outbound packets from that stream will not be analysed, this is done in order to reduce the amount of processing that a filter has to perform since a TLS client hello is sent only once per TLS session. We now can send a TLS client hello with a fake SNI-Server Name of a random allowed site (say wikipedia.org) but with a TTL(time to live) value just enough to reach the filter but not the actual server, thus effectively preventing our TCP stream from being further analysed, now we can send the actual TLS client hello and go on with our business.

            ____________                     ____\ /____                     __^____^__
          <[Your Machine]>                  |Your Router|                   [|Firewall|]          
      +-----------------------+       +-----------------------+       +-----------------------+
      | DST: 133.7.133.7      |       | DST: 133.7.133.7      |       | DST: 133.7.133.7      |
      | SNI: wiki.org | TTL: 3|  ===> | SNI: wiki.org | TTL: 2|  ===> | SNI: wiki.org | TTL: 1| 
      +-----------------------+       +-----------------------+       +-----------------------+
                                                                                   ||
      |<===========================================================================<|
      ||   
      ||    [[Some interm Router]]               [[ The Webserver - 0x00sec.org @ 133.7.133.7]]
      ||  +-----------------------+             
      ||  | DST: 133.7.133.7      |   \/                  < packet doesnt arrive >
      |==>| SNI: wiki.org | TTL: 0|   /\
          +-----------------------+
    
          TTL Becomes Zero Packet 
                is dropped
    
  5. Corrupting the checksum : This is variation of the 4th technique, but here instead of using TTL to prevent the fake TLS client hello from reaching the server, we allow the client hello to reach the server, but we corrupt its checksum value, filters usually don't validate the checksum of packets to avoid overhead, but the client and server are fully compliant TCP implementations and will drop any packet with a bad checksum, we can then send the real client hello and go on with our business. (This method has few caveats check note 5)

           ____________                      __^____^__                      
         <[Your Machine]>                   [|Firewall|]              [[ Webserver - 0x00sec.org]] 
     +-----------------------+        +-----------------------+        +-----------------------+
     | DST: 133.7.133.7      |        | DST: 133.7.133.7      |        | DST: 133.7.133.7      |
     | SNI: wiki.us| BADCHKSM|  ====> | SNI: wiki.us| BADCHKSM|  ====> | SNI: wiki.us| BADCHKSM|
     +-----------------------+        +-----------------------+        +-----------------------+
    
                                      . wiki.us is not blocked          \/   Packet has bad         
                                        allow packet to pass            /\   checksum drop it
    
                                      . Further packets from the
                                        TCP stream is no longer
                                        processed
    

    These are the most common techniques used by circumvention tools, but there are many others, one of tools Geneva goes the extra mile by developing tailored bypass mechanisms depending on the censor, Geneva is really cool project and you can read more about it here

Attempts To Solve The Unencrypted SNI Problem

  • ESNI/ECH (Encrypted SNI/ Encrypted Client Hello): There are RFC drafts discussing these possibilities but there are also concerns regarding their implementation, ESNI/ECH would completely blind firewall and other filtering devices in educational institutions, SNI also serves as a possible IOC for SEIMs, but it is possible to block ESNI/ECH and force the client to fall back to regular methods, The Great Firewall has been observed doing this since mid 2020

Alternative Use Cases Of These Tools

  • In pentests and red teaming scenarios :

    • Can enable access to blocked services (ex: telegram), which can used for ex-filtration

    • Provide better OpSec for your beacon/agent by hiding the C2 host names from the firewall

Scenarios In Which These Tools Likely Will Not Help

  • The fragmentation attack performed by these tools will not work against proxies which perform SNI filtering (ex: Squid), since proxies generally reassemble TCP fragments before inspection.

  • In cases of IP filtering (ex: blackholing 133.7.133.7)

  • In cases where UDP is being filtered (DPITunnel only works for TCP traffic)

Notes :

  1. There are exceptions to certificates being unique to a site; a single wildcard certificate (e.g., certificate for *.0x00sec.org) can be used by all the subdomains of the site.

  2. Port Mirroring or a similar feature is usually available in switches and other routing devices, where a (physical) port can be configured for monitoring purposes. A copy of all packets flowing through the device is sent to this port. A machine can be connected to this monitoring port with its network interface in promiscuous mode, allowing all passing traffic to be inspected by listening on this interface without any processing overhead on the gateway.

  3. Most filtering implementations expect the entire client hello to be part of a single TCP packet because that generally is the case. However, it is possible to segment the client hello across multiple TCP packets. In such a scenario, the filtering process would be required to reassemble the TCP segments before analyzing them, which is computationally costly and thus not implemented in mainstream filtering products. One exception in this case is "The Great Firewall," which is said to have reassembly capability.

  4. Out-of-order delivery is a feature of TCP. The receiver buffers segments received in random order until all segments of a sequence arrive, then reassembles and delivers them to the application layer.

  5. Many home routers may drop packets with a bad checksum. The kernel parameter net.netfilter.nf_conntrack_checksum can be set to 0 to prevent this.

Tools :

  1. https://github.com/Kkevsterrr/geneva/ (Linux) (recommended)

  2. https://github.com/zhenyolka/DPITunnel-cli (Linux) (recommended, personal choice)

  3. https://github.com/bol-van/zapret (Linux,BSD) (recommended)

  4. https://github.com/ValdikSS/GoodbyeDPI

  5. https://github.com/SadeghHayeri/GreenTunnel (windows)

  6. https://github.com/macronut/ghostcp (windows)

  7. https://github.com/zhenyolka/DPITunnel-android (android)

References :

  1. https://github.com/bol-van/zapret/blob/master/docs/readme.eng.md

  2. https://geneva.cs.umd.edu/papers/geneva_ccs19.pdf

  3. https://hal.inria.fr/hal-01202712/document

  4. https://blog.torproject.org/learning-more-about-gfws-active-probing-system

  5. https://habr.com/ru/post/335436/

Further Reading :

  1. https://geneva.cs.umd.edu/posts/india-sni-filtering/

  2. https://geneva.cs.umd.edu/posts/iran-whitelister/

  3. https://geneva.cs.umd.edu/posts/china-censors-esni/esni/

  4. https://citizenlab.ca/2018/04/planet-netsweeper/

  5. How the Great Firewall Discovers Hidden Circumvention Servers

  6. https://geneva.cs.umd.edu/posts/

0
Subscribe to my newsletter

Read articles from Messede Degod directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Messede Degod
Messede Degod