天天看點

TLS協定與SNI 阻斷與解決方案

作者:楊工888

名詞釋義:

域名資訊(Server Name Indication 簡稱 SNI)

傳輸層安全性協定(Transport Layer Security 簡稱 TLS)

TLS 傳輸層安全性協定

TLS 是一個偉大的技術,它確定了網絡傳輸的内容不被中間人篡改。

現在越來越多的網站正在使用 HTTPS(即 HTTP over TLS)來保護網頁内容。與此同時,TLS 設計中的一個缺陷,卻使得阻斷 TLS 連接配接變得可控。

在建立新的 TLS 連接配接時,用戶端(如浏覽器)發出的第一個握手包(稱為 Client Hello)中,包含了想要通路的域名資訊(稱為 SNI,Server Name Indication)。某些伺服器(比如 CDN)會同時支援多個域名,在加密傳輸之前,它需要知道用戶端通路的是哪個域名。于是 SNI 必須以明文的方式傳輸。并且由于浏覽器并不知道伺服器是否需要 SNI,浏覽器會對所有的 TLS 握手都加入 SNI。

于是,大家都懂的。根據黑名單,某些防火牆對于 TLS 連接配接可以進行精确地阻斷。

簡圖:

用戶端(如浏覽器)--->第一個握手包(含明文形式的域名資訊SNI,如通路google)---> 伺服器 ---> 防火牆 ---> 阻斷通路

目前 Mozilla 和 CloudFlare 主導了一項對 SNI 的改進方案,稱為 Encrypted SNI (ESNI 加密域名資訊)。這個提案還在早期的讨論狀态中,目測還需要兩年時間才可以定稿和推廣。現階段隻有 Firefox Nightly(用戶端),以及 CloudFlare 和 Wikipedia(網站)支援初代的 ESNI。

在 ESNI 正式推廣之前,我們還需要其它的技術來避開對于 TLS 連接配接的探測。

目前我們還沒有發現通用的解決方案。

有一個較為通用的,但部署起來略麻煩的方案稱為 Domain Fronting。

它的原理簡單來說是這樣的:部分伺服器允許 TLS 連接配接說自己需要域名 A,但之後的 HTTP 協定說自己需要域名 B。或者伺服器壓根就不看 SNI 的資訊。在這種情況下,對于一個黑名單的域名, 我們在建立 TLS 的時候,可以選用一個不在黑名單的域名,繞過對 TLS 連接配接的監測。

當然,它的缺點是,依賴于伺服器行為。也就是說,每個不同的站點,可能都需要不同的政策(域名)。

舉兩個例子:

  1. P 站的伺服器實際上是不看 SNI 的。在建立 TLS 連接配接的時候,即使不攜帶 SNI,也可以正常進行 HTTPS 通路。
  2. zh.wikipedia.org 是一個黑名單域名,但同站點的 www.wikipedia.org 就不是。我們在建立 TLS 連接配接時,使用 SNI = www.wikipedia.org,之後的 HTTP 請求依然可以正常連到 zh.wikipedia.org。

Domain Fronting 域名前置技術

順便說一句,Domain Fronting 實際上不是一個合理的用法,網站完全可以拒絕這類連接配接,比如 Google 和 Amazon 就主動在自己的所有服務中拒絕這項技術。即使這樣,Domain Fronting 依然是在 ESNI 之前最好的繞過技術。

好了,接下來介紹一下 V2Ray 中如何使用 Domain Fronting。對的,作為一個超級複雜,超級難用的工具,如果連 Domain Fronting 都不支援,就愧對它的名聲了。

所需的技能:TLS 配置、dokodemo-door、freedom、路由和對 MITM 的初步了解。

大概的工作流程:

本地的 V2Ray 攔截浏覽器的 TLS 連接配接,讓浏覽器以為自己已經連上了目标伺服器,讀取 TLS 中承載的 HTTP 資料;

然後自己與目标伺服器建立一個新的 TLS 連接配接,把 HTTP 資料發過去。也就是說,你不需要代理伺服器,就可以繞過(部分) SNI 封鎖了。

以下步驟細節可以不用理會,原理上了解一下即可。

第一步,你需要一個自簽的 CA 證書。當然如果你能弄到正規機構簽發的 CA 證書,那就更省力了。簽發的過程就不詳述了,其它各種文章裡都搜得到。簽發完成之後,你需要把證書導入進系統或者浏覽器,進而讓浏覽器信任由此 CA 簽發的網站證書。

第二步,你需要配置一個 dokodemo-door 用于攔截 TLS 連接配接。需要開啟 security: "tls",需要配置簽發證書。樣例如下:

{

"listen": "0.0.0.0",

"port": 443, // 其它端口也可以,但 443 比較友善

"tag": "df-in",

"protocol": "dokodemo-door",

"settings": {

"network": "tcp",

"address": "1.1.1.1", // 不重要,但是要寫

"port": 443,

"followRedirect": true // 一定要寫

},

"streamSettings": {

"security": "tls",

"tlsSettings": {

"alpn": ["h2"],

"certificates": [

{

"usage": "issue", // 重要

"certificateFile": "/path/to/ca.cer", // 剛剛簽發的 CA 證書

"keyFile": "/path/to/ca.key"

}

]

}

}

}

第三步,把浏覽器發出的連接配接轉發到上述的 dokodemo-door。你可以選擇透明代理,或者強行 hosts,都是沒有問題的。

第四步,配置一個 freedom,用于建立指向伺服器的 TLS 連接配接。樣例如下:

{

"protocol": "freedom",

"tag": "no-sni-out",

"settings": {

"domainStrategy": "UseIP"

},

"streamSettings": {

"security": "tls",

"tlsSettings": {

"allowInsecure": true, // 不指定 SNI,就必須打開這一項。

"alpn": ["h2"]

}

}

}

第五步,把 dokodemo-door 和路由連起來。比如:

{

"inboundTag": ["df-in"],

"domain": ["geosite:pixiv"],

"outboundTag": "no-sni-out",

"type": "field"

}

第六步,運作 V2Ray,然後測試一下:

curl --resolve www.pixiv.net:443:127.0.0.1 -I https://www.pixiv.net/

如果配置成功的話,可以看到類似下面的資訊:

HTTP/2 200

server: nginx

好了,簡單介紹到這裡,喜歡折騰的同學可以玩一玩。

寫在後面的話

需要注意的是:被 SNI 阻斷的域名,大多數都被 DNS 污染了,你需要先解決 DNS 污染(也可以用 V2Ray 喲),才可以接着解決 SNI 阻斷。

Domain Fronting 實際上并不要求 MITM,隻要實時修改 TLS 握手資訊即可。但 V2Ray 還不支援這麼做,隻能通過上述的方式變向去支援它。由于 Domain Fronting 的應用場景越來越小,我們也就不打算花很多精力去簡化它的配置了。

繼續閱讀