301海外跳转原理解析兼谈缓解假墙伪墙攻击勒索的多种技术手段

916次阅读

上一篇文章 中,我们实现了 HTTP 的抢答模式,避免了墙的 TCP Reset。那么这次我们就来聊一聊 HTTPS 如何避免被 TCP Reset。

我们知道,HTTPS 是需要客户端和服务器之间完成 TLS 握手后才能收发 HTTP Request 和 Response。然而,墙是在 TLS 握手时通过 SNI 中的域名信息进行了 TCP Reset,或 通过 ESNI 头进行 TCP 阻断,这时候还没有到能发送 HTTP Request 或 Response 的这个阶段,所以 HTTP Response 抢答模式无法应用于 HTTPS。那么,我们该怎么办呢?

让我们回到 TCP Reset 本身——既然是 TCP Reset,那么它只会对 TCP 协议生效。如果有一个应用层协议,其底层不是 TCP 呢?相信聪明的同学已经想到了,那就是 HTTP 3.0(简称 HTTP/ 3 或 h3)。H3 的底层协议是 QUIC,而 QUIC 是基于 UDP 而非 TCP 的。经过我的测试,发现墙现在无法识别 QUIC 协议,不管 QUIC 协议中出现任何敏感词都能无障碍过墙。这也是为什么有些网站(如 v2ex)在解决了 DNS 污染(比如客户端主动加 hosts)后,并且连接过一次(首次还是需要科学上网,原因之后会讲)后,就能关闭科学上网访问了。也就是说,使用了 H3 后,我们甚至不需要进行 301 跳转,直接就能无障碍访问服务器了(但仍建议使用 301 跳转,否则可能会使封锁升级,如 DNS 污染)。虽然 H3 现在仍旧处于草案阶段,但各大浏览器都已经进行了支持,而其中 Google Chrome 对 H3 的支持是最好的。

那么,如何在服务器上部署并开启 H3 呢?由于 H3 现在仍是草案阶段,所以 Nginx 的正式版并不支持 H3,需要更换为 Nginx-QUIC 来支持 H3。编译使用 Nginx-QUIC 也可以参考 这篇文章。另外,Cloudflare 也已经支持了 H3,可以自行开启(在“网络”设置中打开“HTTP/3(使用 QUIC)”)。在 Cloudflare 中开启 H3 后,Cloudflare 和服务器的通讯仍旧可以使用 HTTP/2(简称 h2)或 HTTP/1.1,并不需要服务器支持 H3,而是由 Cloudflare 进行协议转换。

H3 主要是依赖 Alt-Svc 这个 HTTP 头来进行协议选则的。比如我们可以添加 HTTP 头:Alt-Svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400, h3-28=":443"; ma=86400, h3-27=":443"; ma=86400,指出服务器支持 H3,H3 的 UDP 端口为 443,有效期(过了有效期后浏览器又会重新使用 H2 或 HTTP/1.1 进行访问)为 1 天(86400 秒),支持最新的 H3 草案以及 27、28、29 草案。另外,我们也可以灵活地修改Alt-Svc——比如可以在“:443”之前添加 IP 或域名,做到 HTTP 和 HTTPS 使用不用的 IP 或域名,方便我们在自己的服务器上部署 HTTP 抢答模式,又能使用 Cloudflare 的 H3 协议转换,或者使用不同的域名从而在原域名被 DNS 污染的情况下老用户(没有过有效期 86400 秒的)依然可以通过 H3 访问服务器(因为 H3 是另一个域名,而不是被 DNS 污染的那个域名。或者直接使用 IP,从根本上杜绝了 DNS 污染,只不过之后有可能遭到 IP 封锁)。另外,有效时间也可以进行适当延长(比如从 1 天延长到 1 个月或更长时间),避免客户端尝试 H2 或 HTTP/1.1 并且延长老用户的过期时间。我们可以在还没遇到 HTTPS 的 TCP Reset 时就开启 H3,这样即使之后遭遇了 HTTPS 的 TCP Reset,曾经访问过网站的老用户在 H3 有效期内也能继续访问网站。而且我们也不必担心 UDP 数据包被 ISP 丢弃(俗称 UDP 被 QoS)的问题,因为浏览器在 H3 连接失败的时候会快速回退到 H2 和 HTTP/1.1。

然而,H3 是一个 Alternative 服务。首次访问服务器时,浏览器并不会主动使用 H3,还是会优先使用 H2 或 HTTP/1.1。当获取到 Alt-Svc 头后,浏览器才会在之后的访问中优先使用 H3。这也是为什么有些网站(如 v2ex)需要在科学上网的情况下访问过一次后才能关闭科学上网进行访问(当然,DNS 污染首先还是需要用户自行修改 hosts 解决)。那么,我们如何让用户全程不使用科学上网的情况下访问服务器呢?

首先想到的是通过 上一篇文章 中的 HTTP 抢答模式提供 Alt-Svc 头,不过可惜的是现在的主流浏览器会忽略 HTTP 中的 Alt-Svc 头,只接受 HTTPS 中的 Alt-Svc 头。而如果 HTTPS 本来就已经被 TCP Reset 的话,浏览器就无法获取 Alt-Svc 头了。那么,那些提供 HTTPS 的 301 海外跳转服务是怎么做的呢?

在调查和尝试了几个支持 HTTPS 的 301 海外跳转服务后,我们发现,它们根本就没有解决 HTTPS 的 TCP Reset 问题,HTTPS 依然被 TCP Reset 了,而它们宣称支持 HTTPS 中 301 跳转的做法就是在 HTTP 抢答模式下加上普通的 TLS 服务。因为大部分网站遭遇的只是 HTTP 中的 TCP Reset 而 HTTPS 并不会被 TCP Reset,所以只需要解决 HTTP 的 301 跳转,再加上普通的 HTTPS,表面上就能同时做到 HTTP 和 HTTPS 的 301 跳转。而且在调查过程中我们还发现有个跳转服务主页的 HTTPS 也被 TCP Reset 了,而他们自己却对此毫无办法。那么,对于新用户访问 HTTPS 的 TCP Reset,我们也只能止步于此,束手无策了吗?那也未必。

其实,从本系列一开始,我们就假设 301 跳转服务器是在国外。如果使用的是国内服务器,那么就能避免墙的识别了(因为不过墙)。但使用国内服务器(似乎)有个绕不过去的坎:备案系统——使用 HTTP 会提示域名没有备案,使用默认端口 443 的 HTTPS 同样会有 TCP Reset。我们又该怎么办?其实,备案系统其实就是一个简化版的墙,没有 TCP 流量重组的功能,使用 上一篇文章 的抢答模式同样也能绕过备案系统。而且正是因为备案系统没有 TCP 流量重组的功能,我们甚至可以在 TCP 模式的 HTTP 和 HTTPS 中设置 TCP window size(比如 Linux 上可以使用 Geneva;Windows 上可以自行编写一个反向代理,在其中设置SO_RCVBUF 为 1,两者的用法在 上一篇文章 中都已进行说明,在此不再重复),从而可以直接通过 HTTP 和 HTTPS 绕过备案系统而无需使用 301 跳转(因为备案系统没有 TCP 流量重组功能,所以只需要在连接初始时设置个较小的 TCP window size,之后恢复正常即可。这也是很多国内免备案服务器的原理和使用的方案)。不过,绕过备案系统展示网页有一定的风险,301 跳转风险会小一些,希望大家还是要权衡好利弊。

在讨论好 HTTPS 中防 TCP Reset 的方案后,最后让我们来聊一聊 DNS 污染。

其实,在撰写本文之前,我曾去尝试过几个声称可以解决域名污染的 301 海外跳转的服务,但无一例外都失败了,都无法解决 DNS 污染。然后我也去咨询了提供了这些服务的人,他们的说法大致分为两种:
1、需要将被 DNS 污染的域名的 NS 记录指向国内的 DNS 服务(如 DNSPod、阿里云等),然后需要等一段时间,运气好的话过一段时间就会解封了(对于这种说法,我也曾经亲自验证过,将一个被 DNS 污染的域名的 NS 记录转移回国内,等了几个月,依然被污染)。
2、域名污染指的是域名被关键字 Reset,而不是 DNS 污染(这个说法和大多数人理解的不同,将域名污染解释为了 TCP Reset,和 DNS 污染分为了两个概念),他的服务只能解决域名污染,不能解决 DNS 污染。

那么,对于 DNS 污染,我们只能束手无策,或只能碰运气转移回国内了吗?那也未必。不过,由于解决 DNS 污染所需要的成本较高,所以这也是为什么之前 H3 虽然能让用户在原有域名下继续访问,我仍旧建议使用 301 跳转的原因。否则封锁升级为 DNS 污染后连 301 跳转都会变得比较困难了。

讲到这里,细心的同学应该已经发现,其实在刚才 H3 的使用方法中,已经介绍了如何使老用户在 DNS 污染的情况下继续进行访问的方法了。这是解决 DNS 污染部分问题的方法之一。那么,还有没有别的方法也能解决部分问题呢?其实,在 H3 的方案中,我们主要利用了浏览器的 Cache 中记录了 H3 的服务信息,来让老用户通过不同的域名或 IP 进行访问的。那么,浏览器的 Cache 中除了能保存 H3 的信息外,也是可以保存其他内容的。讲到这里,聪明的同学应该已经想到了。没错,就是 Cache-Control(或使用Expires 也有相同的效果)。通过这个 HTTP 头,我们可以将一个页面的过期时间设置成很长,在过期之前,浏览器并不会发起 HTTP 请求,甚至没有网络的离线情况下都能访问(使用 F5 刷新除外,这时候浏览器会忽略过期时间从而发起 HTTP 请求;Ctrl+F5 更是会发起请求)。在这个页面中,我们可以引用别的域名下的 JavaScript 脚本文件,在 JavaScript 而非 HTML 中渲染整个网页。这样,老用户同样可以在 DNS 污染的情况下继续访问我们的服务器。不过,这种做法对 SEO 不是很友好,但我们可以使用 HTML 和 JavaScript 同时渲染的方法让搜索引擎可以进行索引——HTML 中仍旧是正常内容给搜索引擎进行索引,而浏览器会加载 JavaScript,使用 JavaScript 重新渲染一遍网页,避免 Cache 没有过期而呈现老页面的问题。老用户的问题可以解决,但新用户怎么办呢?或者我们有没有办法从根本上来解决 DNS 污染呢?而且听说现在有一些价格昂贵的污染清洗服务,它们真的能从根本上解决 DNS 污染吗?它们是怎么做的呢?

如果我们想从根本上解决问题,首先我们还需要了解整个 DNS 系统是怎么工作的:
1、DNS 服务器分为递归查询服务器、DNS 代理和权威服务器(称为 ADNS)。我们把递归查询服务器和 DNS 代理统称为 LDNS。
2、普通用户上网所使用的一般是 ISP 提供的 LDNS,它会负责向 ADNS 查询真实的 A(和 AAAA 以及其它)记录。
3、ADNS 即是域名的 NS 记录所指向的服务器。

我们知道,DNS 污染是墙在海外 ADNS 返回正确的结果之前进行了抢答,返回了错误的结果。这样,在国内 LDNS 向海外 ADNS 查询的时候,同样会受到 DNS 污染,从而返回给普通用户错误的结果。那么,我们有没有办法劫持 ISP 的 LDNS,从而让其返回我们想要的 IP 而不是墙返回的错误 IP 呢?这样,虽然 DNS 污染仍旧存在,但普通用户却得到了正确的 IP,从而可以正常访问我们的服务器了。

讲到这里,就不得不提到 2008 年曾经轰动全球的 DNS 投毒攻击案 了。在 这篇文章 中,Kaminsky 可以修改任意 LDNS 中缓存的 A(或 AAAA 以及其它)记录,虽然在经过了那次事件后这个漏洞更难被利用了,但终究无法完全修复,我们仍旧可以利用其中的原理劫持 ISP 的 LDNS(能猜中源端口和 QID 就能进行劫持),将被污染域名的 IP 换成自己想要的 IP。而且由于墙污染的 TTL 较小,我们也能更快地利用这个漏洞而不需要每次等待 1 天的时间。所以短则几天,慢则几个星期就能劫持成功。这也是现在有些价格不菲的污染清洗服务所采用的方案之一。当然,某些攻击团队也同样在利用这个漏洞就行 DNS 劫持,虽然导致的结果是 LDNS 被劫持而非墙的 DNS 污染,但对于普通用户所造成的结果是一致的——网站无法访问。不过,要实施这种 DNS 劫持需要源 IP 欺骗,现在能进行源 IP 欺骗的服务器已经越来越少了。那么我们还有没有别的方法无需源 IP 欺骗来劫持 LDNS 呢?

既然大家已经看了上面的 这篇文章,那么让我们来重新细致地梳理一下整个 DNS 查询过程。以浏览器访问 https://www.youtube.com/ 为例:
1、操作系统向 ISP 的 LDNS 发起请求 www.youtube.com 的 A(以及 AAAA)记录。
2、LDNS 查询缓存中有没有 www.youtube.com 的 A(或 AAAA)记录,如有则返回给客户端,如没有则执行第 3 条。
3、LDNS 查询(可从缓存中查询)DNS 根服务器(当前为 13 个)的 A(或 AAAA)记录。
4、LDNS 向根服务器(或从缓存中)查询 .com 的 ADNS。
5、LDNS 向 .com 的 ADNS 发起查询 youtube.com 的 ADNS(即 youtube.com 的 NS 记录)。
6、LDNS 向 youtube.com 的 ADNS 发起查询 www.youtube.com 的 A(或 AAAA)记录。之后返回给客户端。

我们知道,youtube.com 是个被 DNS 污染的域名,所以第 5 和第 6 步会受到墙的 DNS 污染,而前 4 步不会。第 6 步我们也很熟悉了,国内 IP 向国外 IP 发起查询请求时墙就会抢答 www.youtube.com 的错误 A(或 AAAA)记录。而第 5 步中,由于 LDNS 在国内,查询到 youtube.com 的 NS 记录同样会受到污染。我们同样知道,墙的 DNS 污染虽然成功概率接近 100%,但仍有很小的概率会污染失败。那么我们能不能不停地向 LDNS 请求 www.youtube.com 的 A(或 AAAA)记录,在墙污染失败的时候,LDNS 就能刷新到正确的 IP 地址了呢?可惜的是,LDNS 是有缓存的,在缓存有效期内,不会再次向 ADNS 发起请求。即使在缓存失效后偶尔会由于污染失败得到了正确的 IP 地址,但在缓存再次失效后由于污染再次回到了错误的 IP 地址。所以被污染的概率仍旧接近 100%。墙看上去似乎无懈可击,我们该怎么办呢?

让我们重新回到第 5 条,使用国内 IP 向 .com 的 ADNS 请求 youtube.com 的 NS 记录。以 Linux 为例:
dig ns youtube.com @e.gtld-servers.nete.gtld-servers.net为 .com 的其中一个 ADNS)
我们看到墙返回了污染的结果,youtube.com 被污染的 NS 是……咦?不对!墙竟然返回的是 A(或 AAAA)记录,而不是我们查询的 NS 记录!而且墙的污染是有很小的概率会失败的!相信聪明的同学已经想到了——由于墙返回的是不是 NS 记录,所以 LDNS 没有获取到 youtube.com 的 NS 记录,自然无法将 youtube.com 的 NS 记录存入缓存中。所以,在下次客户端请求 youtube.com 的 NS 记录时,LDNS 会再次向 .com 的 ADNS 请求 youtube.com 的 NS 记录而不是从缓存中获取。既然不存在缓存,我们就能一直向 LDNS 发起请求,而 LDNS 就会一直向 ADNS 发起请求,直到墙的污染失败出现,LDNS 终于获得了正确的 NS 记录。而由于 NS 记录本身是带有 TTL 的,所以会被存入 LDNS 的缓存之中,在缓存过期之前不会再受到墙的污染。而我们可以将 NS 记录的 TTL 设置得非常长,从而可以在很长得时间内让墙得污染无法生效。而在 TTL 过期之后,我们可以利用同样的方法再次让 LDNS 获得正确的 NS 记录。

在解决了第 5 条中的污染后,我们还需要解决第 6 条中的污染。而第 6 条中墙返回的确实是查询的 A(或 AAAA)记录,会被存入 LDNS 缓存,也就无法利用上述方法了。我们该怎么办呢?相信聪明的同学也已经想到了。没错,就是将 NS 记录转移回国内,这样 DNS 请求就不会过墙,自然就不会受到污染了。可是,不对呀?刚才不是讲过我也曾经亲自验证过,将一个被 DNS 污染的域名的 NS 记录转移回国内,等了几个月,依然被污染么?那是因为之前的测试只是将 NS 记录指向了国内服务器,我们并没有大量地发送 NS 查询请求到 LDNS,所以 LDNS 并没有获得正确的 NS 记录,所以污染仍旧存在。而且,ISP 的 LDNS 是分运营商并且分区域的。只将一个 LDNS 中的 NS 刷新到正确结果只能解决一个运营商的一小片区域中的污染,如果想要在全国范围内解决污染,需要使用大量的 IP 地址(因为很多 ISP 的 LDNS 限制了查询请求的发起 IP 只能是本地宽带用户),不停地对大量的 LDNS 查询 NS 记录,直到全国大部分地区的 LDNS 都获取到了正确的 NS 记录,才能在大范围内解决 DNS 污染。而且即使 LDNS 获取到了正确的 NS 记录,查询仍然要继续,因为缓存是有过期时间的。而这,也是现在很多昂贵的污染清洗服务所采用的方案之一。

关于 DNS 污染,我所了解到的现阶段有这些方案。如果你有别的方法,或者对本系列话题感兴趣的,都欢迎和我联系。我的联系方式为:
1、Email: lehui99#gmail.com
2、Twitter: @davidsky2012
3、TG: 技术交流群 技术交流频道
4、本系列 Github: lehui99/articles

下一篇我会开始讲解如何缓解假墙伪墙攻击方面的话题,敬请期待。

最后,本文欢迎转载,不过转载时还请保留 本文链接,因为之后我还会对本文进行完善(如错误修正、内容补充等)。

正文完
 0