如何部署一台抗封锁的Shadowsocks-libev服务器

https://gfw.report/blog/ss_tutorial/zh/

这篇教程记录了如何安装,配置并维护一台Shadowsocks-libev服务器。 这篇教程的亮点在于, 按照这里的配置建议,你的Shadowsocks-libev服务器可以抵御各种已知的攻击, 包括来自GFW的主动探测和封锁以及partitioning oracle攻击。 我们还在教程的最后加入了常见的有关Shadowsocks-libev部署的常见问题。

我们致力于更新和维护这篇教程。如果今后发现了新的针对Shadowsocks-libev的攻击,我们将在第一时间在这篇教程中加入缓解攻击的办法。 因此请考虑将这个页面加入到你的收藏夹中。 另外,我们希望这篇教程对技术小白同样友好,因此如果你在任何步骤卡住了,请联系我们,或在下方评论区留言。我们会对教程作相应改进。

安装

安装Snap应用商店

通过Snap应用商店安装Shadowsocks-libev是官方推荐的方式。

  • 如果你的服务器运行Ubuntu 16.04 LTS及以上的版本,Snap已经默认安装好了。
  • 如果你的服务器运行了其他的Linux发行版,你只需跟着对应的发行版安装Snap core

现在来检测一下你的服务器已经安装了需要的snapd和Snap core:

sudo snap install core

安装Shadowsocks-libev

现在我们安装最新的Shadowsocks-libev:

sudo snap install shadowsocks-libev --edge

配置

下面是我们推荐的Shadowsocks-libev服务器配置:

{
    "server":["::0","0.0.0.0"],
    "server_port":8388,
    "encryption_method":"chacha20-ietf-poly1305",
    "password":"ExamplePassword",
    "mode":"tcp_only",
    "fast_open":false
}

注意,你需要把里面的ExamplePassword替换成一个更强的密码。 强密码有助缓解最新发现的针对Shadowsocks服务器的Partitioning Oracle攻击。 你可以用以下命令在终端生成一个强密码:openssl rand -base64 16

你还可以考虑将server_port的值从8388改为102465535之间的任意整数。

现在打开通过Snap安装的Shadowsocks-libev默认的配置文件:

sudo nano /var/snap/shadowsocks-libev/common/etc/shadowsocks-libev/config.json

将上方替换过密码的配置信息复制粘贴到配置文件后, 按Ctrl + x退出。 退出时,文本编辑器将问你"Save modified buffer?",请输入y然后按回车键。

可以看到,通过Snap安装的Shadowsocks-libev默认的配置文件路径太长了,不便于记忆。同时默认配置路径又没有在官方文档中标出。 我们因此建议你收藏此页面,以备今后查找。

防火墙

我们使用ufw来管理Shadowsocks服务器的防火墙。

在基于Debian的服务器上,可以通过如下命令安装ufw

sudo apt update && sudo apt install -y ufw

然后开放有关sshShadowsocks-libev的端口。 请注意,以下命令假设你在/var/snap/shadowsocks-libev/common/etc/shadowsocks-libev/config.json中的server_port的值为8388。 如果你的server_port用了其他的值,请对以下命令作相应的修改:

sudo ufw allow ssh
sudo ufw allow 8388/tcp

现在我们启动ufw:

sudo ufw enable

启动时如果弹出Command may disrupt existing ssh connections. Proceed with operation (y|n)?,请输入y并按回车键。

最后,请用sudo ufw status检查一下你的配置是否和下面的一样:

Status: active

To                         Action      From
--                         ------      ----
SSH                        ALLOW       Anywhere
8388/tcp                   ALLOW       Anywhere
SSH (v6)                   ALLOW       Anywhere (v6)
8388/tcp (v6)              ALLOW       Anywhere (v6)

运行Shadowsocks-libev

现在我们启动Shadowsocks-libev:

sudo systemctl start snap.shadowsocks-libev.ss-server-daemon.service

记得设置Shadowsocks-libev开机自启动:

sudo systemctl enable snap.shadowsocks-libev.ss-server-daemon.service

维护

检查运行状态和日志

以下命令可以查看Shadowsocks-libev的运行状态:

sudo systemctl status snap.shadowsocks-libev.ss-server-daemon.service

如果你看到绿色的Active: active (running),那么你的Shadowsocks-libev服务器就在正常的运行; 如果你看到红色的Active: failed,请用跳至如下命令journalctl -u snap.shadowsocks-libev.ss-server-daemon.service的尾部查看问题出在哪里了。

重新加载配置文件

每当你修改过配置文件后,请用如下命令重启Shadowsocks-libev以加载修改后的文件:

sudo systemctl restart snap.shadowsocks-libev.ss-server-daemon.service

常见问题

Q:为什么我用了教程里的配置,服务器还是被封了?

A: 通常来讲,这种事情不会发生,因为通过这篇教程配置的Shadowsocks-libev服务器已经可以抵御已知的所有来自GFW的主动探测。但如果你的服务器确实被封锁了,那么很有可能审查者使用了未知的攻击手段。请将你的封锁情况汇报给我们,我们会认真地调查。

Q: 我应不应该从发行版的仓库下载安装Shadowsocks-libev?

A: 发行版仓库里的Shadowsocks-libev不一定是最新版的。比如,截止2021年1月,Debian buster仓库的Shadowsocks-libev的版本为v3.2.5。而这个版本的Shadowsocks-libev是不够防御来自GFW的主动探测的(详见Figure 10)。

Q: 我应该怎样更新用Snap安装的Shadowsocks-libev?

A: 因为Snap会每天自动更新通过其安装的软件,因此通常情况下你不需要手动更新。如若需要手动更新,请用: sudo snap refresh

Q: 为什么用chacha20-ietf-poly1305作为加密方式?

A: 因为它是其中一种AEAD ciphers。而AEAD ciphers可以抵御来自GFW的主动探测。它同时也是Shadowsocks-libev及OutlineVPN的默认加密方式。

Q: 我应该用Shadowsocks的stream cipher吗?

A: 完全不应该。因为Shadowsocks的stream cipher有着不可接受的安全隐私漏洞,并且可以被准确的主动探测。如Figure 10所示,即使是最新版的Shadowsocks-libev,在使用stream cipher时同样可以被准确识别。更具灾难性的是,在不需要密码的情况下,攻击者可以完全解密被记录下来的Shadowsocks会话

Q: 但为什么我用的机场仍在使用stream cipher?

A: 这清楚地说明你的机场缺乏安全意识和安全措施。请把这篇教程这个演讲,和这篇总结,分享给你的机场主。

Q: 我应该把配置中的server_port改为像443这样的常见端口吗?

A: 不应该。因为不论你使用哪个端口,GFW都会检测并怀疑你的Shadowsocks流量。

Q: 为什么配置文件使用tcp_only模式?

A: 这是为了缓解最新发现的Partitioning Oracle攻击,文中指出 “Shadowsocks的开发者们已经采取措施,禁用了本默认开启的UDP代理模式(“the Shadowsocks developers took immediate action to disable UDP proxying where it was enabled by default.")。当然,如Vinicius所指出的,如果你使用了长的随机密码,那么partitioning oracle攻击就不能成功。因此也就不需要禁用UDP代理模式。开启UDP代理模式可能会让经过Shadowsocks代理的视频通话质量更佳。

Q: 为什么配置文件禁用了fast_open?

A: 我们推荐你阅读这里的讨论

联系

这篇报告首发于GFW Report。我们鼓励您或公开地或私下地分享您的评论或疑问。我们私下的联系方式可见GFW Report的页脚。

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

https://github.com/lehui99/articles/blob/main/301%E6%B5%B7%E5%A4%96%E8%B7%B3%E8%BD%AC%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90%E5%85%BC%E8%B0%88%E7%BC%93%E8%A7%A3%E5%81%87%E5%A2%99%E4%BC%AA%E5%A2%99%E6%94%BB%E5%87%BB%E5%8B%92%E7%B4%A2%E7%9A%84%E5%A4%9A%E7%A7%8D%E6%8A%80%E6%9C%AF%E6%89%8B%E6%AE%B5%EF%BC%88%E4%B8%80%EF%BC%89.md

我们知道,墙封锁一个网站的手段有DNS污染、IP封锁、TCP Reset(TCP连接重置)等手段。而一个网站一旦被墙,一般情况下是无法直接通过301(或302)跳转到其他网站的。如果只是IP被封还好说,换IP通常能解决问题。但如果是根据域名关键字进行的TCP Reset,这时候不管怎么换IP(除非是国内IP)都无法解除封锁,当然也不可能进行301跳转(浏览器在收到HTTP服务器的301跳转Response之前TCP连接就已经被墙Reset而断开了,浏览器根本收不到HTTP服务器的任何Response)。而DNS污染的话自然更不用多说,只能换域名了,301跳转更不可能做到。然而,现在出现了很多号称可以解决域名被墙的服务,可以在网站被墙后通过301跳转到新的网站上。经过测试,还真能做到绕过墙的TCP Reset封锁,而这些服务的IP却都在海外(并非是使用了国内IP避免被墙的原因),而客户端只需要一个正常的浏览器即可(即客户端并不需要开启科学上网)。那么它们是怎么做到的呢?

要解释清楚其中的技术原理,还得回到2010年的西厢计划。很早就经常科学上网的同学们应该都对西厢计划并不陌生,它是一个只需要运行在客户端就能绕过很多封锁访问目标网站的工具,解决TCP Reset的原理是对本地的TCP/IP协议进行修改,在不伤害客户端和服务器之间的TCP连接的前提下让墙误以为TCP连接已经断开或者无法正确跟踪到TCP连接。之后出现的INTANG项目同样是这个想法的延续。

不过,不管是西厢计划还是INTANG,都是运行在客户端上的工具,理论上只在服务器上运行无法起到效果,经过测试也能看到实际和理论相符。那么有没有一种工具可以在只服务器上运行,修改TCP/IP协议从而绕过封锁的工具呢?这方面同样有团队做了研究,研究的成果就是Geneva项目GFW Report也对其做了详细介绍。在这篇文章中,列举了6种可以绕过TCP Reset的规则,6种规则都可以在只客户端部署生效(这时候服务器并不需要运行Geneva),而前4种可以在只服务器部署生效(这时候客户端并不需要运行Geneva)。不过Geneva的官方Github中只收录了客户端的规则,文章中的服务器规则并没有被收录在Geneva的官方Github中。而且文章中的策略3只给出了客户端的规则,遗漏了服务器端的规则。经过阅读Geneva的规则介绍和策略3的描述,我已经重新还原了策略3的服务器规则,重新收录了4种服务器规则到我自己的Github Fork中。经过本地环境的模拟加上tcpdump抓包观察测试,看到还原的策略3服务器规则和文章中描述的行为一致,可以认为就是策略3本来的服务器规则。但是,在之后的真实环境的测试中发现这4种服务器策略全都失效了,墙依然对TCP进行了Reset。经过抓包看到服务器的行为确实和文章中描述一致,所以可以确认并非是由于Geneva没有正常工作导致的,而是墙已经为了应对这4种策略进行进化了。所以,墙并不是一成不变的,而是会进化的,那我们又该怎么办呢?

讲到这里,就不得不提另一个策略发现工具SymTCP了。虽然现有的4中策略已经失效,但并不代表我们不能发现新的策略。而SymTCP就是新策略发现工具,通过自动学习可以自动发现新的策略绕过墙的TCP Reset。之后我们就能把新的策略转换为Geneva的规则格式进行使用了。不过,这样的话我们就会陷入到和墙的无休止争斗中,不断发现新策略,而墙则不断封锁新策略。而且规则的转换也是一个麻烦事,暂时还没有工具可以自动从SymTCP的规则转换为Geneva的规则,需要人工转换。并且需要修改SymTCP使其不仅可以发现客户端规则同样也能发现服务器端规则。

那么,有没有一种一劳永逸的方法,使墙再怎么进化也无法避免这种策略的影响,而且这种策略只需要运行在服务器上,从而绕过封锁呢?在下结论之前,我们需要来研究一下一个正常的HTTP协议通讯是怎么进行的: 1、浏览器发起TCP连接,经过3步(次)握手建立和服务器的TCP连接。 2、浏览器发送HTTP Request。 3、服务器收到Request,发送HTTP Response。 而墙通常在看到浏览器发送的HTTP Request中包含关键字就会进行TCP Reset。讲到这里,聪明的同学或许已经想到了:如果服务器不等HTTP Request,而在TCP连接建立后立即发送HTTP Response,在墙进行TCP Reset之前就将Response送到浏览器进行抢答,是不是就能绕过TCP Reset了?而且还能无视之后墙的进化(因为浏览器的请求根本还没有经过墙)?说干就干,由于抢答模式不符合HTTP规范,所以常见的HTTP服务器无法实现抢答模式,所以让我们写个Python小程序来测试一下:

import socket
import threading
import time


def main():
    serv_sock = socket.socket()
    serv_sock.bind(('0.0.0.0', 80))
    serv_sock.listen(50)
    while True:
        cli_sock, _ = serv_sock.accept()

        # 关闭Nagle算法,立即发送数据
        cli_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

        cli_sock.sendall(b'''HTTP/1.1 302 Moved Temporarily\r\n'''
            b'''Content-Type: text/html\r\n'''
            b'''Content-Length: 0\r\n'''
            b'''Connection: close\r\n'''
            b'''Location: https://www.microsoft.com/\r\n\r\n''')

        def wait_second():
            time.sleep(1)  # 等待1秒钟,确保数据发送完毕
            cli_sock.close()

        threading.Thread(target=wait_second).start()


if __name__ == '__main__':
    main()

写完了来测试一下,发现依旧被TCP Reset了。那么,问题出在哪里?让我们重新回到上述HTTP协议通讯的3个步骤中的第1步:TCP的3步握手:

TCP三步握手+数据

从TCP的3步握手中,我们可以看到第3步中客户端发送了ACK就已经完成了TCP连接的建立,这时候客户端并不需要再等服务器的回复就能立即发送数据。也就是说,浏览器会在发送ACK后立即发送HTTP Request,ACK和HTTP Request几乎是同时发出的。而服务器在收到浏览器的ACK后基本也就代表着已经收到了HTTP Request了,抢答失败!

那么,有没有办法让浏览器在TCP连接建立后延迟发送HTTP Request,而又不改动客户端行为呢?讲到这里,对TCP协议比较熟悉的同学或许已经想到了,那就是TCP window size。而通过调用setsockopt()就能修改TCP window size。让我们来修改一下Python小程序,把window size改为1再进行测试(TCP连接建立完成后,客户端只能发送1个字节,等待服务器的确认后才能继续发送更多的数据):

import socket
import threading
import time


def main():
    serv_sock = socket.socket()
    serv_sock.bind(('0.0.0.0', 80))
    serv_sock.listen(50)
    while True:
        cli_sock, _ = serv_sock.accept()

        # 设置TCP window size为1
        cli_sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1)

        # 关闭Nagle算法,立即发送数据
        cli_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

        cli_sock.sendall(b'''HTTP/1.1 302 Moved Temporarily\r\n'''
            b'''Content-Type: text/html\r\n'''
            b'''Content-Length: 0\r\n'''
            b'''Connection: close\r\n'''
            b'''Location: https://www.microsoft.com/\r\n\r\n''')

        def wait_second():
            time.sleep(1)  # 等待1秒钟,确保数据发送完毕
            cli_sock.close()

        threading.Thread(target=wait_second).start()


if __name__ == '__main__':
    main()

改完测试,发现仍旧被TCP Reset了。什么原因?通过抓包,我们看到对TCP window size的修改并没有生效,window size依旧很大。在查阅了Linux man page后我们看到关于SO_RCVBUF有这么一段话:

The minimum (doubled) value for this option is 256.

这也就意味着即使我们通过setsockopt()SO_RCVBUF设置为1,Linux内核也会将其作为256处理。而Linux man page中也对SO_RCVBUFFORCE做了说明:只能突破最大值的限制(but the rmem_max limit can be overridden),但不能突破最小值的限制。而256字节基本就可以容纳整个HTTP Request。看来通过setsockopt()行不通(不管SO_RCVBUF还是SO_RCVBUFFORCE都行不通),我们得找别的方法。

讲到这里,我们很自然地又想到了Geneva:上述Geneva的策略2中服务器规则正是利用了TCP window size做到的四字节分割(设置window size为4)。这样,就绕过了setsockopt()的限制,直接对TCP数据包进行修改了。

在我们把四字节分割法部署到服务器运行Geneva后,再结合上述Python小程序,经过测试我们发现已经成功绕过了TCP Reset,浏览器跳转到了微软网站。我们终于成功了!

然而,在浏览器第二次访问服务器时发现依然被TCP Reset了。不过,这已经影响不到301跳转(上述Python小程序还是302跳转,需要301的同学自行修改)了,301跳转的话浏览器已经被重定向到新的网站了,不会再次访问这个服务器(需要保证新旧网站不能使用相同IP),但这并不妨碍我们继续探究一下为什么第二次访问会被TCP Reset:通过抓包我们看到,第一次访问时浏览器虽然在第一个附带用户数据的数据包中只发送了4个字节,但后续会将剩余的整个HTTP Request通过一个数据包发送到服务器导致TCP Reset。而墙是有审查残留的,一段时间(几分钟)内不管是否出现关键字,对源IP和目标IP之间的TCP连接会进行无差别的Reset。所以在之后的这段审查残留时间内,只要TCP连接建立就会被Reset,抢答模式无法起到作用。

知道了原因我们就能采取对策了,我们知道客户端是因为收到了服务器确认数据包中的TCP window size很大,所以才能一次性把剩余的Request发送完毕,所以需要对后续的TCP window size做同样的修改,保证客户端看到的window size一直处于比较小的水平:通过对TCP协议的了解,我们知道连接建立时的window size是通过SYN+ACK包确定的,而后续的window size是通过ACK包确定的。所以,我们对规则2做少许的修改就能做到对后续window size的修改:

[TCP:flags:A]-tamper{TCP:window:replace:4}-|

在服务器上我们同时运行规则2和上述修改后的规则(需要开2个Geneva进程,注意第2个进程需要在命令行中指定--in-queue-num和--out-queue-num避免和第1个冲突),我们终于能稳定地运行上述抢答模式,再也不会被TCP Reset了。

实际上我们可以将2条规则中的window size都设置得更小一些,甚至设置为0,避免客户端发送任何数据(实际上由于window size探测机制的原因,客户端仍旧会以极慢的速度一个字节一个字节地发送数据,不过不影响我们的抢答模式):

[TCP:flags:SA]-tamper{TCP:window:replace:0}-|
[TCP:flags:A]-tamper{TCP:window:replace:0}-|

至此,HTTP的抢答模式就基本完成了。不过,由于Geneva和上述小程序都是用Python编写的(甚至都没有使用asyncio),性能会比较差一些。Geneva会自己添加iptables的NFQUEUE规则,不过规则太过于宽松,导致不需要处理的数据包也会经过Geneva。所以大家可以在启动Geneva后手动删除这些规则,自行添加更精确的规则(只处理SYN+ACK和ACK)。不过经过我的测试,即使使用精确的iptables规则也差不多只能利用20Mbps左右的带宽。如果希望有更高的性能,可以使用C/C++(结合libev,或asio,或直接epoll;或者使用golang、rust等)结合libnetfilter_queue,利用iptables的NFQUEUE来完成,可以跑满千兆带宽。其实Geneva的底层用的也是libnetfilter_queue和NFQUEUE。由于本系列只做概念验证,而且因为篇幅的限制(本文已经很长了),在此就不展开C/C++的实现了,感兴趣的同学可以和我联系,如果感兴趣的同学比较多的话我就再新开一个系列讲一下这方面的内容。另外需要注意的是,Geneva的编译环境为Python 3.6,使用Debian 10自带的Python 3.8/3.9会出现各种莫名其妙的问题,建议大家还是从Python官网下载Python 3.6的源码进行编译使用Geneva

在解决了HTTP的TCP Reset问题后,我们还需要解决HTTPS的TCP Reset。而HTTPS由于需要完成TLS握手才能发送HTTP Response,所以抢答模式似乎无法应用于HTTPS。在下一篇中,我会介绍几个绕过HTTPS的TCP Reset方法。敬请期待。

如果对本系列话题感兴趣的同学也可以联系我。我的联系方式为:
1、Email: lehui99#gmail.com
2、Twitter: @davidsky2012
3、TG: @davidsky2000
4、本系列Github: lehui99/articles

BTW,本来想将此文首发于hostloc的(因为hostloc上站长朋友比较多,而本系列是帮助站长解决TCP Reset问题的),不过由于hostloc需要邀请码,一直没有注册成功,而淘宝购买hostloc邀请码曾经有过不愉快的经历,遂作罢。

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

全面学习GFW

https://thinkgust.blogspot.com/2017/
GFW会是一个长期的存在。要学会与之共存,必须先了解GFW是什么。做为局外人,学习GFW有六个角度。渐进的来看分别是:
首先我们学习到的是WHAT和WHEN。比如说,你经常听到人的议论是“昨天”,“github”被封了。其中的昨天就是WHEN,github就是WHAT。这是学习GFW的最天然,最朴素的角度。在这个方面做得非常极致的是一个叫做greatfire的网站。这个网站长期监控成千上万个网站和关键词。通过长期监控,不但可以掌握WHAT被封锁了,还可以知道WHEN被封的,WHEN被解封的。
接下来的角度是WHO。比如说,“方校长”这个人名就经常和GFW同时出现。但是如果仅仅是掌握一个两个人名,然后像某位同志那样天天在twitter上骂一遍那样,除了把这个人名骂成名人之外,没有什么特别的积极意义。我更看好这篇文章“通过分析论文挖掘防火长城(GFW)的技术人员”的思路。通过网络上的公开信息,掌握GFW的哪些方面与哪些人有关系,这些合作者之间又有什么联系。除了大家猜测的将来可以鞭尸之外,对现在也是有积极的意义的。比如关注这些人的研究动态和思想发展,可以猜测GFW的下一步发展方向。比如阅读过去发表的论文,可以了解GFW的技术演进历史,可以从历史中找到一些技术或者管理体制上的缺陷。
再接下来就是WHY了。github被封之后就常听人说,github这样的技术网站你封它干啥?是什么原因促成了一个网站的被封与解封的?我们做为局外人,真正的原因当然是无从得知的。但是我们可以猜测。基于猜测,可以把不同网站被封,与网络上的舆情时间做关联和分类。我们知道,方校长对于网路舆情监控是有很深入研究的。有一篇论文(Whiskey, Weed, and Wukan on the World Wide Web: On Measuring Censors’ Resources and Motivations)专门讨论监管者的动机的。观测触发被封的事件与实际被封之间的时间关系,也可以推测出一些有趣的现象。比如有人报告,OpenVPN触发的封端口和封IP这样的事情一般都发生在中国的白天。也就是说,GFW背后不光是机器,有一些组件是血肉构成的。
剩下的两个角度就是对如何翻墙穿墙最有价值的两个角度了:HOW和WHERE。HOW是非常好理解的,就是在服务器和客户端两边抓包,看看一个正常的网络通信,GFW做为中间人,分别给两端在什么时候发了什么包或者过滤掉了什么包。而这些GFW做的动作,无论是过滤还是发伪包又是如何干扰客户端与服务器之间的正常通信的。WHERE是在知道了HOW之后的进一步发展,不但要了解客户端与服务器这两端的情况,更要了解GFW是挂在两端中间的哪一级路由器上做干扰的。在了解到GFW的关联路由器的IP的基础上,可以根据不同的干扰行为,不同的运营商归属做分组,进一步了解GFW的整体部署情况。
整体上来说,对GFW的研究都是从WHAT和WHEN开始,让偏人文的就去研究WHO和WHY,像我们这样偏工程的就会去研究HOW和WHERE。以上就是全面了解GFW的主体脉络。接下来,我们就要以HOW和WHERE这两个角度去看一看GFW的原理。

GFW的原理

要与GFW对抗不能仅仅停留在什么不能访问了,什么可以访问之类的表面现象上。知道youtube不能访问了,对于翻墙来说并无帮助。但是知道GFW是如何让我们不能访问youtube的,则对下一步的翻墙方案的选择和实施具有重大意义。所以在讨论如何翻之前,先要深入原理了解GFW是如何封的。
总的来说,GFW是一个分布式的入侵检测系统,并不是一个严格意义上的防火墙。不是说每个出入国境的IP包都需要先经过GFW的首可。做为一个入侵检测系统,GFW把你每一次访问facebook都看做一次入侵,然后在检测到入侵之后采取应对措施,也就是常见的连接重置。整个过程一般话来说就是:
检测有两种方式。一种是人工检测,一种是机器检测。你去国新办网站举报,就是参与了人工检测。在人工检测到不和谐的网站之后,就会采取一些应对方式来防止国内的网民访问该网站。对于这类的封锁,规避检测就不是技术问题了,只能从GFW采取的应对方式上采取反制措施。另外一类检测是机器检测,其检测过程又可以再进一步细分:

重建


重建是指GFW从网络上监听过往的IP包,然后分析其中的TCP协议,最后重建出一个完整的字节流。分析是在这个重建的字节流上分析具体的应用协议,比如HTTP协议。然后在应用协议中查找是不是有不和谐的内容,然后决定采用何种应对方式。
所以,GFW机器检测的第一步就是重建出一个字节流。那么GFW是如何拿到原始的IP包的呢?真正的GFW部署方式,外人根本无从得知。据猜测,GFW是部署在国家的出口路由器的旁路上,用“分光”的方式把IP包复制一份到另外一根光纤上,从而拿到所有进出国境的IP包。下图引在gfwrev.blogspot.com:
但是Google在北京有自己的机房。所以聪明的网友就使用Google的北京机房提供的GAE服务,用Goagent软件达到高速翻墙的目的。但是有网友证实(https://twitter.com/chengr28/status/260970749190365184),即便是北京的机房也会被骨干网丢包。事实上Google在北京的谷翔机房有一个独立的AS(BGP的概念)。这个AS与谷歌总部有一条IPV6的直连线路,所以通过这个机房可以用IPV6不受墙的限制出去。但是这个AS无论是连接国内还是国外都是要经过GFW的。所以机房在北京也不能保证国内访问不被墙。GFW通过配置骨干网的BGP路由规则,是可以让国内的机房也经过它的。另外一个例子是当我们访问被封的网站触发连接重置的时候,往往收到两个RST包,但是TTL不同。还有一个例子是对于被封的IP,访问的IP包还没有到达国际出口就已经被丢弃。所以GFW应该在其他地方也部署有设备,据推测是在省级骨干路由的位置。
对于GFW到底在哪这个话题,最近又有国外友人表达了兴趣(https://github.com/mothran/mongol)。笔者在前人的基础上写了一个更完备的探测工具https://github.com/fqrouter/qiang。其原理是基于一个IP协议的特性叫TTL。TTL是Time to Live的简写。IP包在没经过一次路由的时候,路由器都会把IP包的TTL减去1。如果TTL到零了,路由器就不会再把IP包发给下一级路由。然后我们知道GFW会在监听到不和谐的IP包之后发回RST包来重置TCP连接。那么通过设置不同的TTL就可以知道从你的电脑,到GFW之间经过了几个路由器。比如说TTL设置成9不触发RST,但是10就触发RST,那么到GFW就是经过了10个路由器。另外一个IP协议的特性是当TTL耗尽的时候,路由器应该发回一个TTL EXCEEDED的ICMP包,并把自己的IP地址设置成SRC(来源)。结合这两点,就可以探测出IP包是到了IP地址为什么的路由器之后才被GFW检测到。有了IP地址之后,再结合IP地址地理位置的数据库就可以知道其地理位置。据说,得出的位置大概是这样的:
但是这里检测出来的IP到底是GFW的还是骨干路由器的?更有可能的是骨干路由器的IP。GFW做为一个设备用“分光”的方式挂在主干路由器旁边做入侵检测。无论如何,GFW通过某种神奇的方式,可以拿到你和国外服务器之间来往的所有的IP包,这点是肯定的。更严谨的理论研究有:Internet Censorship in China: Where Does the Filtering Occur?
GFW在拥有了这些IP包之后,要做一个艰难的决定,那就是到底要不要让你和服务器之间的通信继续下去。GFW不能太过于激进,毕竟全国性的不能访问国外的网站是违反GFW自身存在价值的。GFW就需要在理解了IP包背后代表的含义之后,再来决定是不是可以安全的阻断你和国外服务器之间的连接。这种理解就要建立了前面说的“重建”这一步的基础上。大概用图表达一下重建是在怎么一回事:
重建需要做的事情就是把IP包1中的GET /inde和IP包2中的x.html H和IP包3中的TTP/1.1拼到一起变成GET /index.html HTTP/1.1。拼出来的数据可能是纯文本的,也可能是二进制加密的协议内容。具体是什么是你和服务器之间约定好的。GFW做为窃听者需要猜测才知道你们俩之间的交谈内容。对于HTTP协议就非常容易猜测了,因为HTTP的协议是标准化的,而且是未加密的。所以GFW可以在重建之后很容易的知道,你使用了HTTP协议,访问的是什么网站。
重建这样的字节流有一个难点是如何处理巨大的流量?这个问题在这篇博客(http://gfwrev.blogspot.tw/2010/02/gfw.html)中已经讲得很明白了。其原理与网站的负载均衡器一样。对于给定的来源和目标,使用一个HASH算法取得一个节点值,然后把所有符合这个来源和目标的流量都往这个节点发。所以在一个节点上就可以重建一个TCP会话的单向字节流。
最后为了讨论完整,再提两点。虽然GFW的重建发生在旁路上是基于分光来实现的,但并不代表整个GFW的所有设备都在旁路。后面会提到有一些GFW应对形式必须是把一些GFW的设备部署在了主干路由上,比如对Google的HTTPS的间歇性丢包,也就是GFW是要参与部分IP的路由工作的。另外一点是,重建是单向的TCP流,也就是GFW根本不在乎双向的对话内容,它只根据监听到的一个方向的内容然后做判断。但是监听本身是双向的,也就是无论是从国内发到国外,还是从国外发到国内,都会被重建然后加以分析。所以一个TCP连接对于GFW来说会被重建成两个字节流。具体的证据会在后面谈如何直穿GFW中详细讲解。

分析

分析是GFW在重建出字节流之后要做的第二步。对于重建来说,GFW主要处理IP协议,以及上一层的TCP和UDP协议就可以了。但是对于分析来说,GFW就需要理解各种各样的应用层的稀奇古怪的协议了。甚至,我们也可以自己发明新的协议。
总的来说,GFW做协议分析有两个相似,但是不同的目的。第一个目的是防止不和谐内容的传播,比如说使用Google搜索了“不该”搜索的关键字。第二个目的是防止使用翻墙工具绕过GFW的审查。下面列举一些已知的GFW能够处理的协议。
对于GFW具体是怎么达到目的一,也就是防止不和谐内容传播的就牵涉到对HTTP协议和DNS协议等几个协议的明文审查。大体的做法是这样的。
像HTTP这样的协议会有非常明显的特征供检测,所以第一步就没什么好说的了。当GFW发现了包是HTTP的包之后就会按照HTTP的协议规则拆包。这个拆包过程是GFW按照它对于协议的理解来做的。比如说,从HTTP的GET请求中取得请求的URL。然后GFW拿到这个请求的URL去与关键字做匹配,比如查找Twitter是否在请求的URL中。为什么有拆包这个过程?首先,拆包之后可以更精确的打击,防止误杀。另外可能预先做拆包,比全文匹配更节省资源。其次,xiaoxia和liruqi同学的jjproxy的核心就是基于GFW的一个HTTP拆包的漏洞,当然这个bug已经被修复了。其原理就是GFW在拆解HTTP包的时候没有处理有多出来的rn这样的情况,但是你访问的google.com却可以正确处理额外的rn的情况。从这个例子中可以证明,GFW还是先去理解协议,然后才做关键字匹配的。关键字匹配应该就是使用了一些高效的正则表达式算法,没有什么可以讨论的。
HTTP代理和SOCKS代理,这两种明文的代理都可以被GFW识别。之前笔者认为GFW可以在识别到HTTP代理和SOCKS代理之后,再拆解其内部的HTTP协议的正文。也就是做两次拆包。但是分析发现,HTTP代理的关键字列表和HTTP的关键字列表是不一样的,所以笔者现在认为HTTP代理协议和SOCKS代理协议是当作单独的协议来处理的,并不是拆出载荷的HTTP请求再进行分析的。
目前已知的GFW会做的协议分析如下:

DNS 查询

GFW可以分析53端口的UDP协议的DNS查询。如果查询的域名匹配关键字则会被DNS劫持。可以肯定的是,这个匹配过程使用的是类似正则的机制,而不仅仅是一个黑名单,因为子域名实在太多了。证据是:2012年11月9日下午3点半开始,防火长城对Google的泛域名 .google.com 进行了大面积的污染,所有以 .google.com 结尾的域名均遭到污染而解析错误不能正常访问,其中甚至包括不存在的域名(来源http://zh.wikipedia.org/wiki/%E5%9F%9F%E5%90%8D%E5%8A%AB%E6%8C%81
目前为止53端口之外的查询也没有被劫持。但是TCP的DNS查询已经可以被TCP RST切断了,表明了GFW具有这样的能力,只是不屑于大规模部署。而且TCP查询的关键字比UDP劫持的域名要少的多。目前只有dl.dropbox.com会触发TCP RST。相关的研究论文有:

HTTP 请求

GFW可以识别出HTTP协议,并且检查GET的URL与HOST。如果匹配了关键字则会触发TCP RST阻断。前面提到了jjproxy使用的构造特殊的HTTP GET请求欺骗GFW的做法已经失效,现在GFW只要看到rn就直接TCP RST阻断了(来源u/0/108661470402896863593/posts/6U6Q492M3yY)。相关的研究论文有:

HTTP 响应

GFW除了会分析上行的HTTP GET请求,对于HTTP返回的内容也会做全文关键字检查。这种检查与对请求的关键字检查不是由同一设备完成的,而且对GFW的资源消耗也更大。相关的研究论文有:

HTTP代理协议

TODO

SOCKS4/5代理协议

TODO

SMTP 协议

因为有很多翻墙软件都是以邮件索取下载地址的方式发布的,所以GFW有针对性的封锁了SMTP协议,阻止这样的邮件往来。
封锁有三种表现方式(http://fqrouter.tumblr.com/post/43400982633/gfw-smtp),简单概要的说就是看邮件是不是发往上了黑名单的邮件地址的(比如xiazai@upup.info就是一个上了黑名单的邮件地址),如果发现了就立马用TCP RST包切断连接。

电驴(ed2k)协议

GFW还会过滤电驴(ed2k)协议中的查询内容。因为ed2k还有一个混淆模式,会加密往来的数据包,GFW会切断所有使用混淆模式的ed2k连接,迫使客户端使用明文与服务器通讯(http://fqrouter.tumblr.com/post/43490772120/gfw-ed2k)。然后如果客户端发起了搜索请求,查找的关键字中包含敏感词的话就会被用TCP RST包切断连接。

对翻墙流量的分析识别

GFW的第二个目的是封杀翻墙软件。为了达到这个目的GFW采取的手段更加暴力。原因简单,对于HTTP协议的封杀如果做不好会影响互联网的正常运作,GFW与互联网是共生的关系,它不会做威胁自己存在的事情。但是对于TOR这样的几乎纯粹是为翻墙而存在的协议,只要检测出来就是格杀勿论的了。GFW具体是如何封杀各种翻墙协议的,我也不是很清楚,事态仍然在不断更新中。但是举两个例子来证明GFW的高超技术。
第一个例子是GFW对TOR的自动封杀,体现了GFW尽最大努力去理解协议本身。根据这篇博客(https://blog.torproject.org/blog/knock-knock-knockin-bridges-doors)。使用中国的IP去连接一个美国的TOR网桥,会被GFW发现。然后GFW回头(15分钟之后)会亲自假装成客户端,用TOR的协议去连接那个网桥。如果确认是TOR的网桥,则会封当时的那个端口。换了端口之后,可以用一段时间,然后又会被封。这表现出了GFW对于协议的高超检测能力,可以从国际出口的流量中敏锐地发现你连接的TOR网桥。据TOR的同志说是因为TOR协议中的握手过程具有太明显的特征了。另外一点就表现了GFW的不辞辛劳,居然会自己伪装成客户端过去连连看。
第二个例子表现了GFW根本不在乎加密的流量中的具体内容是不是有敏感词。只要疑似翻墙,特别是提供商业服务给多个翻墙,就会被封杀。根据这个帖子(http://www.v2ex.com/t/55531),使用的ShadowSocks协议。预先部署密钥,没有明显的握手过程仍然被封。据说是GFW已经升级为能够机器识别出哪些加密的流量是疑似翻墙服务的。
关于GFW是如何识别与封锁翻墙服务器的,最近写了一篇文章提出我的猜想,大家可以去看看:http://fqrouter.tumblr.com/post/45969604783/gfw。
最近发现GFW对OpenVPN和SSL证书已经可以做到准实时的封IP(端口)。原理应该是离线做的深包分析,然后提取出可疑的IP列表,经过人工确认之后封IP。因为OpenVPN有显著的协议的特征,而且基本不用于商业场景所以很容易确认是翻墙服务。但是SSL也就是HTTPS用的加密协议也能基于“证书”做过滤不得不令人感到敬畏了。Shadowsocks的作者Clowwindy为此专门撰文“为什么不应该用SSL翻墙“:https://gist.github.com/clowwindy/5947691
总结起来就是,GFW已经基本上完成了目的一的所有工作。明文的协议从HTTP到SMTP都可以分析然后关键字检测,甚至电驴这样不是那么大众的协议GFW都去搞了。从原理上来说也没有什么好研究的,就是明文,拆包,关键字。GFW显然近期的工作重心在分析网络流量上,从中识别出哪些是翻墙的流量。这方面的研究还比较少,而且一个显著的特征是自己用没关系,大规模部署就容易出问题。我目前没有在GFW是如何封翻墙工具上有太多研究,只能是道听途说了。

应对

GFW的应对措施是三步中最明显的,因为它最直接。GFW的重建过程和协议分析的过程需要耐心的试探才能大概推测出GFW是怎么实现的。但是GFW的应对手段我们每天都可以见到,比如连接重置。GFW的应对目前可以感受到的只有一个目的就是阻断。但是从广义上来说,应对方式应该不限于阻断。比如说记录下日志,然后做统计分析,秋后算账什么的也可以算是一种应对。就阻断方式而言,其实并不多,那么我们一个个来列举吧。

封IP

一般常见于人工检测之后的应对。还没有听说有什么方式可以直接使得GFW的机器检测直接封IP。一般常见的现象是GFW机器检测,然后用TCP RST重置来应对。过了一段时间才会被封IP,而且没有明显的时间规律。所以我的推测是,全局性的封IP应该是一种需要人工介入的。注意我强调了全局性的封IP,与之相对的是部分封IP,比如只对你访问那个IP封个3分钟,但是别人还是可以访问这样的。这是一种完全不同的封锁方式,虽然现象差不多,都是ping也ping不通。要观摩的话ping twitter.com就可以了,都封了好久了。
其实现方式是把无效的路由黑洞加入到主干路由器的路由表中,然后让这些主干网上的路由器去帮GFW把到指定IP的包给丢弃掉。路由器的路由表是动态更新的,使用的协议是BGP协议。GFW只需要维护一个被封的IP列表,然后用BGP协议广播出去就好了。然后国内主干网上的路由器都好像变成了GFW的一份子那样,成为了帮凶。
如果我们使用traceroute去检查这种被全局封锁的IP就可以发现,IP包还没有到GFW所在的国际出口就已经被电信或者联通的路由器给丢弃了。这就是BGP广播的作用了。

DNS劫持

这也是一种常见的人工检测之后的应对。人工发现一个不和谐网站,然后就把这个网站的域名给加到劫持列表中。其原理是基于DNS与IP协议的弱点,DNS与IP这两个协议都不验证服务器的权威性,而且DNS客户端会盲目地相信第一个收到的答案。所以你去查询facebook.com的话,GFW只要在正确的答案被返回之前抢答了,然后伪装成你查询的DNS服务器向你发错误的答案就可以了。

TCP RST阻断

TCP协议规定,只要看到RST包,连接立马被中断。从浏览器里来看就是连接已经被重置。我想对于这个错误大家都不陌生。据我个人观感,这种封锁方式是GFW目前的主要应对手段。大部分的RST是条件触发的,比如URL中包含某些关键字。目前享受这种待遇的网站就多得去了,著名的有facebook。还有一些网站,会被无条件RST。也就是针对特定的IP和端口,无论包的内容就会触发RST。比较著名的例子是https的wikipedia。GFW在TCP层的应对是利用了IPv4协议的弱点,也就是只要你在网络上,就假装成任何人发包。所以GFW可以很轻易地让你相信RST确实是Google发的,而让Google相信RST是你发的。

封端口

GFW除了自身主体是挂在骨干路由器旁路上的入侵检测设备,利用分光技术从这个骨干路由器抓包下来做入侵检测 (所谓 IDS),除此之外这个路由器还会被用来封端口 (所谓 IPS)。GFW在检测到入侵之后可以不仅仅可以用TCP RST阻断当前这个连接,而且利用骨干路由器还可以对指定的IP或者端口进行从封端口到封IP,设置选择性丢包的各种封禁措施。可以理解为骨干路由器上具有了类似“iptables”的能力(网络层和传输层的实时拆包,匹配规则的能力)。这个iptables的能力在CISCO路由器上叫做ACL Based Forwarding (ABF)。而且规则的部署是全国同步的,一台路由器封了你的端口,全国的挂了GFW的骨干路由器都会封。一般这种封端口都是针对翻墙服务器的,如果检测到服务器是用SSH或者VPN等方式提供翻墙服务。GFW会在全国的出口骨干路由上部署这样的一条ACL规则,来封你这个服务器+端口的下行数据包。也就是如果包是从国外发向国内的,而且src(源ip)是被封的服务器ip,sport(源端口)是被封的端口,那么这个包就会被过滤掉。这样部署的规则的特点是,上行的数据包是可以被服务器收到的,而下行的数据包会被过滤掉。
如果被封端口之后服务器采取更换端口的应对措施,很快会再次被封。而且多次尝试之后会被封IP。初步推断是,封端口不是GFW的自动应对行为,而是采取黑名单加人工过滤地方式实现的。一个推断的理由就是网友报道,封端口都是发生在白天工作时间。
在进入了封端口阶段之后,还会有继发性的临时性封其他端口的现象,但是这些继发性的封锁具有明显的超时时间,触发了之后(触发条件不是非常明确)会立即被封锁,然后过了一段时间就自动解封。目前对于这一波封SSH/OPENVPN采用的以封端口为明显特征的封锁方式研究尚不深入。可以参考我最近写的一篇文章:http://fqrouter.tumblr.com/post/45969604783/gfw

HTTPS间歇性丢包

对于Google的HTTPS服务,GFW不愿意让其完全不能访问。所以采取的办法是对于Google的某些IP的443端口采取间歇性丢包的措施。其原理应该类似于封端口,是在骨干路由器上做的丢包动作。但是触发条件并不只是看IP和端口,加上了时间间隔这样一个条件。

网站SSL证书出现错误和解决过程

https://www.williamlong.info/archives/6574.html?utm_source=dlvr.it&utm_medium=twitter

 9月30日,我的网站遇到一个很奇怪的问题,当天我在网站发了一篇新文章,之后就有不少用户向我反馈说,文章无法访问,访问时候提示证书错误,我感觉很奇怪,莫非网站SSL证书没有自动更新?我于是用电脑访问了一下网站,发现用电脑浏览器访问网站并没有出现异常。

  根据用户的截图,用户是用手机端Chrome浏览器访问的,于是我就用自己iPhone的Chrome访问了一下我的网站,访问的结果让我不禁大吃一惊,我的网站访问后竟然提示“ERR_CERT_DATE_INVALID”错误,证书的确出了问题。

  我的第一反应是网站的时间是不是有问题,登陆上去看了一下,时间没问题,那么是证书没有自动更新吗?我的网站使用的是Let's Encrypt的证书,我设置为自动更新,我查看了一下,证书刚刚在9月23日自动更新过一次,有效期没有到,SSL证书肯定没有过期。

  我自己测试,在PC端用Chrome访问Let's Encrypt的证书没问题,但用iPhone的Chrome访问就会报SSL证书错误“ERR_CERT_DATE_INVALID”。难道是这个证书更新的时候出了什么问题,但为什么23日更新到现在都没异常,就是今天出现异常?

  我陷入了迷茫之中,我的网站使用Let's Encrypt的免费证书,已经自动更新了好几年,几年来一直都没有问题,今天是第一次出现这种奇怪的问题。

  好在我早先为网站申请过多个品牌的SSL证书,我于是先紧急手动安装了网站的DigiCert免费版SSL证书,证书是一年有效期的,安装部署SSL证书完成之后,使用手机端Chrome浏览网站恢复了正常,PC端浏览网站也正常。这说明问题的根源是Let's Encrypt的证书存在问题。

  考虑到Let's Encrypt的免费SSL证书今天出现的这种重大问题,同时考虑到这个证书3个月更换一次,可能会出现不可预知的更新错误,我决定,网站干脆就换DigiCert的免费版SSL证书算了,大不了就是一年手动更新一次SSL证书。

  我后来查询了一下相关新闻,这次的网站事故可能和这条新闻有关:《Let's Encrypt根证书过期预警 请在9月30日前及时更新》,时间正好对的上,正好是9月30日证书出错。下面是该新闻的全文:

  Let's Encrypt根证书过期预警 请在9月30日前及时更新

  安全研究员 Scott Helme 警告称:作为全球最大 HTTPs 证书提供商之一的 Let's Encrypt,即将于下周停用旧版根证书(Root CA)。这意味着数百万计依赖它的网站必须在 9 月 30 日前及时更新,不然将面临不受计算机、设备或 Web 浏览器信任的困扰。

Let's Encrypt的证书

  据悉,作为一家非盈利组织,Let's Encrypt 致力于通过颁发证书来推动设备和互联网数据通信的加密,确保不被第三方拦截并窃取信息。

  然而 Let's Encrypt 当前使用的 IdentTrust DST Root CA X3 根证书,也即将于下周过期。对于大部分网站的访客来说,9 月 30 日可能是个波澜不惊的一天。

  但是对于较旧的设备来说,可能还是会遇到一些问题 --正如 AddTrust External CA Root 在今年 5 月遭遇的根证书过期中断那样,造成Stripe、Red Hat 和 Roku 都受到了影响。

  Scott Helme 在一篇博文中写道:“考虑到 Let's Encrypt 和 AddTrust 之间的体量差异,我有预感 IdenTrust 根证书到期时又会让历史重演,甚至可能引发更多的问题”。

  当然,潜在易受影响的,主要是那些不怎么定期更新的设备 -- 比如嵌入式系统、或运行多年前软件版本的智能手机。

  举个例子,运行 macOS 2016 和 Windows XP SP3 的设备用户可能在月底之后遇到麻烦。依赖 OpenSSL 1.0.2 或更早版本的客户端平台也可能受到影响,此外还有尚未升级到新版固件的 PlayStation 老游戏机。

  鉴于 Android 生态有着长期存在且众所周知的问题,为了防止大多数智能机受到本次事件的影响,Let's Encrypt 已于今年早些时候未雨绸缪地过渡到了自家的 ISRG Root X1 证书(到期时间为 2035 年)。

  虽然包括 Android 7.1.1(Nougat)及更早版本的设备并不信任它,但 Let's Encrypt 还是能够通过对自颁证书进行交叉签名的方式,让大多数 Android 设备可在未来三年内避免受到波及。

  但若你还想在 Android 5.0(Lollipop)上安装 Firefox,最好还是尽早为迁移至新平台做打算。

  最后,自 2014 年成立以来,截止 2021 年 9 月初,Let's Encrypt 总计已经颁发了超过 20 亿份证书。

互联网的刻耳柏洛斯:GFW的DNS审查系统


https://yangxiamao.github.io/2021/09/30/%E4%BA%92%E8%81%94%E7%BD%91%E7%9A%84%E5%88%BB%E8%80%B3%E6%9F%8F%E6%B4%9B%E6%96%AF-GFW%E7%9A%84DNS%E5%AE%A1%E6%9F%A5%E7%B3%BB%E7%BB%9F/

刻耳柏洛斯是希腊神话中看守冥界入口的恶犬,它允许每一个死者的灵魂进入冥界,但不让任何人出去,同时也不允许活人进入。

纽约大学石溪分校的 Nguyen Phong Hoang 和多伦多大学的 Arian Akhavan Niaki 等人,建立了一个名为 GFWatch 的网络平台,对中国网络长城(俗称 GFW)的 DNS 审查系统进行了探测和实验,最后写出了一篇论文发表在历史悠久的 USENIX(高等计算系统协会)上。文章名为《How Great is the Great Firewall ? Measuring China’s DNS Censorship》,您可通过链接 https://www.usenix.org/system/files/sec21-hoang.pdf 获得论文原文。

在 GFWatch 工作的九个月时间里,它测试了5.34 亿个域名。论文展示了一组触目惊心的数据:至少有 31.1 万个域名被 GFW 的 DNS 过滤系统干扰。并且 GFW 还主动出击,在世界范围内污染了公共 DNS 解析服务(pub- lic DNS resolvers)中至少 7.7 万个域名的数据,其中包括谷歌和 Cloudflare 的 DNS resolvers。

他们在论文中说:

“These techniques will not only help public DNS resolvers and other DNS-related services to sanitize tainted records , but can also assist future development of circumvention tools tobypass the GFW’s DNS censorship .”

他们的研究不仅可以帮助清除 DNS resolver 和其他 DNS 相关服务中污染了的 DNS 数据,还可以帮助今后的开发人员去开发绕过 GFW 的 DNS 审查系统的工具。

How GFW fuck DNS

DNS (Domain Name System)的作用是根据域名查出IP地址。它是一个将域名和IP地址相互映射的分布式数据库,你可以把它想象成一本巨大的电话本。

举一个例子,如果我们要访问域名 www.baidu.com ,首先要通过 DNS 查出它的IP地址 183.232.231.172。下图是 DNS 查询的一个简单示意图。

论文中提到,由于 GFW 是一个通路的(on-path)/人在侧面(man-on- the-side)的系统 ,所以它没办法通过修改或者简单丢弃互联网上传输的那些被封锁的域名的 DNS 查询响应。但是由于 DNS 使用无状态、未加密的 UDP 协议进行传输,所以 GFW 可以通过可以实时监测互联网上的流量,当在用户的 DNS 查询中检测到受审查的内容时,注入错误的响应。

由于 GFW 的相关设备通常离客户端更近(就物理/网络距离而言),所以被检测的响应通常会比合法的响应更早到达,从而达到让用户无法获得正确的域名的 DNS 的目的。

利器:观测 GFW 的平台 GFWatch

当你凝视深渊时,深渊也在凝视着你

GFWatch 的设计要求中,有一点就是要可以探测到尽可能多的被 GFW 阻断的网站。GFWatch 从 超过 1500 个TLD zone file 处获得实时更新的域名列表,平均每天会对 4.11 亿个网站进行监测,截至 2020 年总共探测了5.34 亿个网站。发现至少有 31.1 万个域名被 GFW 的 DNS 过滤系统干扰。

GFWatch 同时被设计以可以实行长期探测。一旦它探测到某个网站被封锁,GFWatch就会持续对这个网站进行观测,观察这个网站是否会在某个时间点被 GFW 解封。

同时,GFWatch 还被设计来收集统计 GFW 返回的虚假 IP 地址。

接下来我们来看看 GFWatch 的实验和探测手段。

GFWatch 的主要探测器位于没有 DNS 审查制度的美国,从这台机器发送 DNS 查询消息前往位于中国的两台主机。但是位于中国的那两台主机并没有 DNS 解析能力,因此,主探测器的任何 DNS 响应都应该是来自 GFW。

由于 GFWatch 被设计为使用 UDP 进行探测,而UDP是一个无状态和不可靠的协议,数据包可能会由于不受控制的因素(例如,网络拥堵)而丢失。为了尽量减少这些因素对数据收集的影响,GFWatch 每天至少对每个域名进行三次测试。

很妙的一个实验方法!一个优秀的猎手往往以猎物的姿态存在。虽然你耗费了电费和带宽,但是你可是钓上了 GFW 这条大鱼啊!

中国的两台主机位于两个不同的自治系统(AS)中。但是从探测结果来看,发往这两台中国主机的 DNS 查询所接受的封锁政策是相同的,故研究人员猜测 GFW 应该是采用中心化政策(centralized blocking policy)的一个系统。

在位探测仪完成每个探测批次后,被检测到的受审查域名被转移到中国主机上,

接着,研究人员又控制位于中国境内的主机向位于美国的主机发送网站的 DNS 查询信息。研究人员观察到从美国发往中国的 DNS 查询时会被审查的域名在中国发往美国时同样会被审查。

通过两个探测路径探测到了相同的被审查的域名名单。

截至论文发表时,GFWatch 仍在运行,每天都在收集数据。

从 GFW 封锁网络清单中反推规则

无论多么天衣无缝的犯罪,只要是人作的,就没有解不开的道理。

如果 subdomain.example.com 和 example.com 的所有子域都被封锁,研究人员就将 example.com 视为一个被封锁的域名(blocked domain)。最短的审查域名便称为 “基础域名”(base domain)。通过对 GFWatch 发现的 31.1 万个被审查的域名进行分析,研究人员发现了13.87 万个基础域名。截至 2020 年 12 月 31 日,仍存在 12.6 万个被封禁的基础域名。

但是研究人员同时注意到,当一个子域名被封锁时,基础域名可能不会被封锁。例如,cs.colorado.edu 被封锁了,而 colorado.edu 没有被封锁,这说明 GFW 没有简单地采用一刀切的封锁措施。于是研究人员进一步的进行了分类,对于一个给定的域,研究人员测试了每个审查的域和随机字符串的以下排列组合。

  • Rule 0 censored_domain
  • Rule 1 censored_domain{.rnd_str}
  • Rule 2 censored_domain{rnd_str}
  • Rule 3 {rnd_str.}censored_domain
  • Rule 4 {rnd_str}censored_domain
  • Rule 5 {rnd_str.}censored_domain{.rnd_str}
  • Rule 6 {rnd_str.}censored_domain{rnd_str}
  • Rule 7 {rnd_str}censored_domain{.rnd_str}
  • Rule 8 {rnd_str}censored_domain{rnd_str}

在 138.7 万个基础域名中,有 11.8 万个域名根据规则 0 进行是独立审查的。换句话说,这些域名是被审查的,但在与随机字符串连接时不会触发GFW的DNS审查。

在这些规则中,只有规则 1 和 3 是基础域名的子域名的正确存在形式。研究人员把规则 1、3 以外的规则与较短的域名字符串组合在一起的被审查的域名称为被过度封锁(overblocked)的域名。

按照封锁严重程度的升序,研究人员发现在规则2、3、4、6和8下,分别有4、11.38 万、1.09 万、1400 个和 696 个不同的基础域名被封锁。

对 GFW 过度封锁的研究

有超过 1.3 万个基础域名被过度封锁。在发现的 33.1 万个被审查的域名中,有 41000 个域名是过度封锁的。

论文中举了一个例子:GFW 将 torproject.org 进行了严格审查,对其进行了过度封锁(overblocked)。包括 mentorproject.org 在内,任何包含了 torproject.org 字段的网站都被 GFW 封锁,令人啼笑皆非。(Tor 是一个旨在实现匿名通信的自由软件(free software),Tor 用户的互联网活动相对较难追踪。)

919.com、jetos.com 和 33a.com 这三个域名共造成15000个不相关的域名被过度封锁,如果有朋友打算购买域名,请注意避开包含有相关子字符串的域名。以避免被 GFW 不明不白的封锁了。

对被封域名种类的研究

研究人员使用了 FortiGuard 提供的服务,进行域名分类。

统计发现, “商业”(business)、”色情 “(pornography)和 “信息技术 “(imformation technology)这三种网站是 GFW 封锁的主要类型(除了未分类的网站外)。

另外一项没有没有统计子域名的研究则发现,”代理 “(proxy avoidance)和 “个人网站和博客 “(personal websites and blogs)是被封锁最多的网站类型。

虽然 “教育 “不是被审查的首要类别,但研究人员同时发现了许多与教育有关的域名被封锁,包括 mit.edu、umich.edu、gwu.edu、armstrong.edu、brookings.edu、citizenlab.ca、feitian.edu、languagelog.ldc.upenn.edu、pori.hk、soas.ac.uk、 scratch.mit.edu、cs.colorado.edu……

这是 GFW 滥封网站的又一个证据。

covid-19、自动化工具与雇员

GFWatch 检测到大量与 COVID- 19 有关的域名被 GFW 通过 DNS 篡改进行审查,包括 covid19classaction.it、covid19song.info、covidcon.org、ccpcoronavirus.com、covidhaber.net以及covid-19truth.info 等网站。

虽然大多数 COVID-19 相关的网站在出现后很快被 GFW 发现并封锁,但研究人员发现 GFW 无法做到实时封禁相关网站。

ccpcoronavirus.com 和 covidhaber.net 于 2020 年 4 月首次出现在 GFWatch 的测试列表上,但分别直到 7 月和 9 月才被 GFW 封杀。同样的,covid-19truth.info 在 2020 年 9 月出现在研究人员的数据集中,但直到 10 月才被审查。

GFW 审查不同域名所需时间的巨大差异表明,封锁网站名单很可能是由自动工具和人工共同完成的。

伪造的 IP 与被蒙蔽的人们

真理是永远蒙蔽不了的。

了解 GFW 伪造的 IP 和它们被注入的模式(如果有的话)是至关重要的。

研究人员分析了 GFWatch 收集的 IP,以研究是否存在任何特定的注入模式,在此基础上,我们可以制定策略来有效地检测和绕过 GFW 的 DNS 审查制度。

伪造的 IP 的数量随时间流逝增加

研究人员从 GFWatch 捕获的所有中毒的 DNS 响应中发现了 1781 和 1799 个伪造的 IPv4 和 IPv6 地址。

研究人员发现所有被 GFW 注入的 IPv6 地址都是假的,因此,研究人员把分析的重点放在伪造的IPv4地址上。

从 2020 年 5 月份开始,GFWatch 监测到 GFW 使用的伪造 IP 数量开始增多,在2020年的最后四个月,伪造的 IP 数量增长到 1700 个左右。

有迹可循:伪造 IP 的注入模式

通过分析每个伪造 IP 地址的注入频率,我们发现并不是所有的伪造 IP 都有同样的机会被注入到被审查的回应中,也就是说,他们的注入模式并不是完全随机的。

尽管 GFW 伪造的 IP 数量迅速增加,但最初的 200 个伪造的 IP 仍然对 99% 的 DNS 注入负责。从 5 月到 8 月发现的新的的 1300 个伪造 IP 位于长尾部分,研究人员只在 1% 的 GFW 伪造的 DNS 响应中发现了它们。

根据这一试验结果,我们或许获得了些许反制 GFW DNS 污染的灵感。

围城:双向拦截

城外的人想进去,城里的人想出来。

因为有时 DNS 查询的信息不可避免的会经过中国网络,触发 GFW 的双向 DNS 过滤行为,故先前研究人员曾认为 这是中国境外的公共 DNS resolvers 缓存被污染的原因。

经过进一步的研究,研究人员发现许多域名的权威名称服务器(authoritative name servers)位于中国境内是另一个主要原因,这些中毒的 DNS 缓存借此玷污了世界各地的许多公共 DNS resolvers。

GFWatch 发现的被审查的域名和 GFW 伪造的 IP 的数据集有助于检测和净化公共 DNS resolvers 缓存中的中毒资源记录。

GFW 对中国域名的地理封锁

2020 年 8 月 8 日,GFWatch 检测到 GFW 一个奇怪的封锁行为:GFW 对中国政府网站进行了审查阻断。

www.beian.gov.cn 名为“全国互联网安全管理服务平台”,由中国工业和信息化部管理。这个域名有两个权威的名称服务器,dns7.hichina.com 和 dns8.hichina.com,它们被托管在 16 个不同的中国境内的 IP 地址上。一旦在中国境外发出向针对该网站的 DNS 查询,GFW 便会对查询进行污染与注入。但位于中国境内的主机仍然可以正常访问这个网站。

因此,这是一个明显的地理封锁案例。GFW 不仅仅封堵了主机从中国向境外被审核网站的访问,也污染了境外主机向中国境内部分网站访问时的 DNS 查询。

从中国境外访问 www.beian.gov.cn 会间歇性地成功,因为 GFW 的 DNS 注入有时会比正确的响应更晚到达使用终端。

GFW 大炮

研究人员提到了 GFW 发动资源耗尽攻击(resource exhaustion attacks)的可能性。 一旦 GFW 将 DNS 查询的结果大量导向某个 IP 地址,受影响的组织将在服务器上付出不可忽视的开销。

GFW 甚至可以针对一个 DNS 查询发出多达三个伪造响应。从GFW的角度来看,注入多个虚假响应不仅增加了成功污染查询结果的可能性,也使检测和规避 DNS 污染的成本增高,难度增大。

如烛者,思至则见,不思不见

你就是这道黑暗中强烈的光束,从属于你的夜晚中,照亮了他们曾经看不见的白天。

一旦 GFWatch 检测到有域名被 GFW 封锁,研究人员就向公共的 DNS resovlers 查询它们。最终,研究人员发现了公共 DNS resolvers 的缓存中,有 7.7 万个被 GFW 审查的域名被污染。下图显示了数据被污染得最多的前十个公共 DNS resolvers。

这一发现显示了 GFW 在世界范围内的广泛影响,使得公共的 DNS resolvers 的操作者必须有一个有效的机制来防止这些中毒的资源记录污染他们的缓存,以保证他们的DNS服务质量。

现在,研究人员将展示如何根据前文展示的 GFW 的特点和 GFWatch 获得的浩如烟海的数据制定策略,以有效和高效地规避GFW的DNS审查制度。

当收到一个以上的 IPv6 回应时,客户端可以根据 GFW 伪造的 IPV6 的显著特点排除掉被污染的 IP 地址。对于 IPv4 答案,客户端可以根据前文中发现的 GFW 伪造 IP 的注入模式和伪造的 IPv4 特点来检查它们。

从下图中,我们可以看到 99% 的被 GFW 污染的 DNS 响应比正确的响应提前 364ms 到达我们的机器(这个延迟时间根据终端和 GFW 之间的相对距离的不同而可能会有所不同)。换句话说,在查询一个受审查的域名,收到 DNS 响应时,客户端最多应该多等 364ms,以等待合法域名的到来。

愿我们能够在没有黑暗的地方相遇

人类收到火的礼物之后,国王会用它征服世界,厨师会用它喂养世界,工程师会用它移动世界。小丑只会用它玩杂耍。

研究团队开发了 GFWatch,监测并统计了 GFW 基于 DNS 审查的封锁行为。然而,DNS 审查并不是 GFW 使用的唯一的封锁技术,还有其他许许多多的技术被采用来防止信息在互联网间自由的流通。例如,基于SNI的封锁、基于关键字的过滤、针对特定 IP 的封锁、使用深度包监测识别异常流量……

今后我也会继续关注这方面的内容,但是在顺利前去留学之前(现大三在读),我会尽可能的专注于学习。同时,由于撰写这篇文章时时间仓促,也因为我本人学术水平有限,如有错讹,欢迎您和我联系。

您可以通过以下方式找到我:

同时感谢群友、推友们在我撰写文章时的鼓励与交流,没有你们,就没有这篇文章。

本篇文章以 MIT 协议开源,欢迎任何人转载、引用。祝您生活愉快、学业、事业顺利。

有效避免 GFW 的 DNS poisoning


https://weidi.medium.com/%E6%9C%89%E6%95%88%E9%81%BF%E5%85%8D-gfw-%E7%9A%84-dns-poisoning-351f3d0b3102 

在這篇 Tweet 看到有趣的 Paper How Great is the Great Firewall? Measuring China’s DNS Censorship ,透過在美國及中國的實驗找出 censored domains,進而實作出可以有效避免 GFW DNS poisoning 的方式。

GFW 如何運作?

GFW 是 on-path injector,並且不會丟棄或修改由 resolver 或是 authoritative name server 的回覆。因此只有在路徑上會經過 GFW 時才會觀察到此行為,例如日本客戶端透過中國 resolver 查詢 www.google.com 

// 中國聯通的 DNS resolver 
// 解析 Google 網站回傳的 IP 地址卻指向 Microsoft 的網站
$ dig www.google.com @202.102.224.68 +short 173.244.217.42

如何知道一個域名可能被污染?

比較 client — resolver — authoritative name server 路徑經過與不經過 GFW 的回覆,例如:

// suspicious$ dig mentorproject.org @202.102.224.68 +short
203.161.230.171
// looks fine$ dig mentorproject.org @1.1.1.1 +short
23.236.62.147
$ dig mentorproject.org @8.8.8.8 +short
23.236.62.147
$ dig mentorproject.org @168.95.1.1 +short
23.236.62.147

所以,如何預防?

因為 GFW 不會丟棄或修改由 resolver 或是 authoritative name server 的回覆,我們只要 讓客戶端等待足夠長的時間 ,確保所有的回覆都收到再採用即可。

實驗時間

中國聯通跟 Cloudflare resolver 給了不同的答案:

$ dig mentorproject.org @202.102.224.68 +short
64.33.88.161
$ dig mentorproject.org @1.1.1.1 +short
23.236.62.147
$ sudo tshark -i any -f 'port 53' -n// 中國聯通  1 0.000000000 172.31.37.101 -> 202.102.224.68 DNS 90 Standard query 0x89c6  A mentorproject.org
2 0.154465529 202.102.224.68 -> 172.31.37.101 DNS 95 Standard query response 0x89c6 A 64.33.88.161
3 0.154511590 202.102.224.68 -> 172.31.37.101 DNS 95 Standard query response 0x89c6 A 203.161.230.171
// Cloudflare 4 12.710885026 172.31.37.101 -> 1.1.1.1 DNS 90 Standard query 0x9e3d A mentorproject.org
5 12.886210394 1.1.1.1 -> 172.31.37.101 DNS 106 Standard query response 0x9e3d A 23.236.62.147