SNI 阻断与解决方案

in #cn6 years ago (edited)

TLS 是一个伟大的技术,它确保了网络传输的内容不被中间人篡改。现在越来越多的网站正在使用 HTTPS(即 HTTP over TLS)来保护网页内容。与此同时,TLS 设计中的一个缺陷,却使得阻断 TLS 连接变得可控。

在建立新的 TLS 连接时,客户端(如浏览器)发出的第一个握手包(称为 Client Hello)中,包含了想要访问的域名信息(称为 SNI,Server Name Indication)。某些服务器(比如 CDN)会同时支持多个域名,在加密传输之前,它需要知道客户端访问的是哪个域名。于是 SNI 必须以明文的方式传输。并且由于浏览器并不知道服务器是否需要 SNI,浏览器会对所有的 TLS 握手都加入 SNI。

于是,大家都懂的。根据黑名单,某些防火墙对于 TLS 连接可以进行精确地阻断。

目前 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 实际上不是一个合理的用法,网站完全可以拒绝这类连接,比如 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 的应用场景越来越小,我们也就不打算花很多精力去简化它的配置了。

Sort:  

Congratulations @v2ray! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!