为什么有些第三方客户端可以直连pixiv?


引言

众所周知P站主站是被屏蔽的,需要科学上网才能正常访问。前阵子发现有些P站第三方客户端可以做到直连P站,便心血来潮想要研究一下他们是如何实现的。同时也做一个可以直连P站的图片下载器。

知己知彼

常言道知己知彼才能百战不殆。想要知道P站直连的原理就要先知道P站是如何被拦截的。通过查阅网上资料知道P站是被DNS污染 + SNI阻断的方式被墙。接下来就可以从这两个方面入手。

DNS污染

所谓DNS污染就是污染你的DNS让你得到的DNS解析是一个错误的结果,令你最终的请求发送到一个不正确的IP以达到拦截的目的。当你想要请求某个网站时,通常会经过一下流程:

sequenceDiagram
    your PC->>your PC: Request local DNS cache. Not found, request DNS server.
    your PC->>DNS server: Queries: What is the IP address of www.pixiv.net?
    DNS server-->>your PC: Answers: The IP address of www.pixiv.net is 162.125.32.5!
    your PC ->>pixiv server: Use this IP address to request data.
    pixiv server-->>your PC: Response data

抓包实例:

DNS

DNS污染就是在DNS服务器应答前,经过的中间路由检测到你发送的DNS请求是在GFW黑名单里的网站,中间路由器抢先给你返回一个DNS应答给你错误的IP地址。由于DNS协议设计之初没有身份鉴别机制且底层通常使用UDP协议是无连接的,它采用的策略是优先接收第一个DNS响应报文,后续到达的DNS响应报文将被丢弃,当上面的情况发生时,实际DNS服务器的报文会在后面到达,而它携带正确信息的DNS响应报文将会被你的主机丢弃。

所以真实网络环境下的请求是下面的的流程,而不是上面的简化版。

sequenceDiagram
    your PC ->> your PC: Request local DNS cache. Not found, request DNS server.
    your PC ->> Intermediate Router: Queries: What is the IP address of www.pixiv.net?
    Intermediate Router ->> DNS server: Queries: What is the IP address of www.pixiv.net?
    Intermediate Router -->> your PC: Answers: The IP address of www.pixiv.net is 100.100.100.1!
    DNS server -->> Intermediate Router:  Answers: The IP address of www.pixiv.net is 162.125.32.5!
    Intermediate Router -->> your PC:  Answers: The IP address of www.pixiv.net is 162.125.32.5!
    your PC ->> your PC: accept 100.100.100.1, drop 162.125.32.5 
    your PC ->> Intermediate Router:Use 100.100.100.1 to request data.
    Intermediate Router ->> 100.100.100.1: Use 100.100.100.1 to request data.

明白了上面这个流程,就可以针对性地去解决了。

解决DNS污染问题可以采用下面几个方法:

  • 使用HTTPS。启用HTTPS后,浏览器与网站之间的通信是加密的,中间人无法读取和修改。这可以有效抵御DNS污染。
  • 使用DNS加密协议。如DNS over HTTPS、DNS over TLS等协议,可以加密DNS查询,避免DNS查询结果被劫持或污染。
  • 使用可信任的DNS服务器。选择知名的可信任DNS服务商,如Google DNS、OpenDNS等,而不使用可能被污染的本地ISP DNS。
  • 启用DNSSEC。DNSSEC通过数字签名防止DNS响应被修改,可以验证DNS数据的完整性和真实性。
  • 本地Hosts文件。在本地Hosts文件指定域名与IP地址映射,来绕过DNS查询。
  • 智能选择DNS服务器。智能DNS系统可以检测异常情况,自动切换到其他正常DNS服务器。
  • 使用反向代理服务。通过中间代理服务来进行访问,避免直接暴露在污染的网络环境中。

SNI阻断

什么是SNI

SNI全称是Server Name Indication,中文可以翻译为“服务器名称指示”。它是SSL/TLS协议的一个扩展,允许在建立安全连接时,客户端告诉服务器它正在访问的是哪个 hostname。

以前在一个IP地址上只能部署一个SSL证书,这样就无法在一台服务器上托管多个需要SSL证书的网站。

SNI的出现改变了这个限制。客户端在连接服务器时发送想要访问的hostname,这样服务器就可以根据不同的hostname选择正确的SSL证书来建立安全连接。

SNI扩展于2003年被提出,2005年正式成为SSL标准的一部分。SNI是在TLS 1.0版本中提出的扩展,但直到TLS 1.2版本才被正式采纳为标准。主流浏览器对SNI的支持也是从2010年开始。

TLS 1.3版本对SNI做了优化,不再允许客户端Anonymous模式,必须发送SNI来建立连接。所以综上,SNI主要是在TLS 1.2/1.3版本中使用,一般来说如果不发送SNI,服务器会根据配置返回默认证书。

在TLS 1.3版本中,客户端必须发送SNI主要有以下两个原因:

  1. 提高安全性

早期的TLS版本允许客户端匿名连接服务器,这带来了一些安全隐患。不发送SNI,服务器无法验证客户端请求的域名,存在被攻击的风险。TLS 1.3去掉了这种客户端匿名模式,要求必须发送SNI,以提高安全性。服务器可以验证连接请求是否对应合法域名。

  1. 优化性能

TLS 1.3改进了握手流程,服务器在收到SNI后,可以立即准备对应的证书,从而省去了额外的往返延迟。不发送SNI,服务器只能使用默认证书,需要重新协商一遍才能切换到正确的证书,降低了性能。

综合考虑安全性和性能,TLS 1.3做出了需要强制SNI的设计。这尽量减少了匿名连接可能带来的风险,也优化了连接流程的效率。这项变化让SNI成为建立安全TLS连接的必备组件之一。

具体细节

SNI作为TLS扩展出现在ClientHello消息中。ClientHello是TLS握手过程的第一步,客户端首先发送这条消息给服务器。

  1. ClientHello消息中,客户端可以发送一个Server Name Indication extension(类型是0号扩展)。这个扩展包含了客户端请求访问的主机名。 Server Name是可选的,如果客户端请求的是IP直接访问,就不需要带主机名。
  2. 当服务器收到ClientHello时,会检查是否有SNI扩展,如果有就取出Server Name。服务器会根据这个Server Name选择合适的证书去回复客户端。如果没有接收到SNI扩展,通常会返回默认证书。
  3. 之后就是标准的TLS握手流程,协商加密算法、交换证书等,来建立安全连接。如果握手成功,客户端就可以通过这个安全通道与服务器通信了。

其中第二步时,服务器会做出何种反应完全是由服务器配置规则决定的,通常都会采用以下几种策略:

  • 返回一个通配符证书,适用于服务器上的所有域名。
  • 返回一个不存在的域名的证书,使客户端出现证书错误,强制要求使用SNI。
  • 返回一个第三方通用证书,如Let’s Encrypt的DST Root CA X3证书。
  • 甚至可以返回一个自签名证书作为默认证书。
sequenceDiagram
    Client ->> Server: Client Hello
    Server -->> Client: Server Hello
    Server -->> Client: Certificate
    Server -->> Client: Server Key Exchange
    Client ->> Server: Client Key Exchange
    Server -->> Client: New Session Ticket

我们可以抓包看看实际p站点的TLS流程,因为我这里没有清除电脑上原有的证书所以没有证书返回和密钥交换的过程。

SNI发送

绕过思路

基于以上抓包信息,我们可以得到一个直接思路就是删除SNI扩展即直接不发送SNI,让服务器返回默认的证书供我们使用。恰巧P站返回的默认证书是*.pixiv.net证书,一个泛解析的证书,这样我们就可以直接利用啦!

第三方客户端是怎么做的

PixEz Flutter这个客户端为例,它是支持直连P站的。而且他们的代码是开源的,我们可以随意查看。通过翻他们的代码和问群里的大佬得知。

针对DNS污染问题,他们是用修改本地host的思路,用一些技术手段获取到了p站一个服务器的真实IP,然后直接向这个地址发请求从而绕过了DNS污染。而对于SNI阻断他们采用的方案是直接在请求过程中删除SNI,刚刚好P站采用的是TLS 1.2版本,这样就实现了直连P站。