

Go 爬虫:三行代码伪造 JA3 等 TLS 指纹,绕过 Cloudflare 五秒盾和各种防火墙!
source link: https://blog.skyju.cc/post/tls-fingerprint-bypass-cloudflare/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go 爬虫:三行代码伪造 JA3 等 TLS 指纹,绕过 Cloudflare 五秒盾和各种防火墙!
探索如何使用 Go 语言伪造 TLS 指纹,绕过 Cloudflare 五秒盾和其他防火墙。本文详细介绍了 JA3、JA4+ 等 TLS 指纹技术,分析了互联网上已有解决方案实现都太过繁琐、而且不支持用于第三方 HTTP 请求库的不足,并提供了一个简单有效的解决方案:实现一个自定义的 http.RoundTripper。相关代码已经开源在 GitHub 上。
阅读时长: 6 分钟
先承认,写这个标题多少有点营销号那味,因为代码里多几个换行就超过三行代码了,而且也不一定能完美绕过所有防火墙及其以后的种种升级版本。但我还是想小小的骄傲一下,本文介绍的方法应该是目前市面上用起来最简单的,并且兼容性最好的(大概)。
解决什么问题
Cloudflare 的五秒盾大家应该都很熟悉,对于网站主来说,使用它可以有效防止 CC 攻击;对于爬虫来说,解决它也算是一个重要的课题。而对于既当网站主又当爬虫开发者的我来说,就只能对其又爱又恨了……
要说到爬虫绕过防火墙的其中一个常见方法,应该得属修改 User-Agent。你用 Python、Go 等写的程序,发起 HTTP 请求时,如果不去额外指定,都有自己的 User-Agent。比如在 Go 里使用resty这个库的默认 UA 是go-resty/2.10.0 (https://github.com/go-resty/resty)
,巴不得把你在用爬虫访问人家网站的事实昭告天下。因此我们一般会修改这个值,将其改成常见浏览器的 UA,例如 Chrome 的:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
。这在以前就足以对付大部分防火墙了。
但是从今年开始 Cloudflare 的防火墙增加了一个识别项,也就是 TLS 的 JA3、JA4 等指纹。如果使用 HTTP/2 访问,还会再多检测一个 Akamai Fingerprint。这些是啥?拿一个典型的 HTTPS Client Hello 的包举例子:

看到里面的 Cipher Suites 和 Extensions 这堆东西了吗?尤其是 Cipher Suites 这个 Array,不同浏览器的数量、种类、顺序都是不一样的。于是网络安全的研究者就根据这个计算了一个哈希,把它作为一个 HTTPS 客户端的 TLS 指纹。
你用 Go 写的爬虫,建立 TLS 握手的时候使用的是 Go 的库,自然也有 Go 自己的 TLS 指纹。那么 Cloudflare 可以直接把 Go 的 TLS 指纹给 ban 掉,只允许真实浏览器访问。甚至来说,实际上 Cloudflare 会把 TLS 指纹和你的 User-Agent 进行比对。人家看到你 User-Agent 宣称是谷歌浏览器的,而 TLS 指纹却是 Go 库的,这不赤裸裸的欺骗吗?绝对要把你 block 之门外了。
关于 JA3、JA4+ 和 Akamai Fingerprint 可以看下面几个文章:
介绍 JA3 的:https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967/
介绍 JA4+ 的:https://blog.foxio.io/ja4-network-fingerprinting-9376fe9ca637
JA4 相当于是 JA3 的升级版。介绍 JA4+ 的这篇文章尤其让我大开眼界。研究者不仅只是算出一个 TLS 指纹而已,还根据不同的因素创造出了多种指纹的变种(因此后面多了个加号)。比如 JA4SSH,就是针对 SSH 协议的。通过对指纹的分析,可以识别加密信道中是否存在恶意流量,本身还是很不错的。
而 Akamai Fingerprint 就是针对 HTTP/2 的,其简单的原理介绍可以看这里:https://lwthiker.com/networks/2022/06/17/http2-fingerprinting.html
可以访问这个网站看到你浏览器的 TLS 指纹:https://tls.peet.ws/

高版本 Chrome 加入了一些随机特性,每次访问 JA3 指纹都不一样。但不管你怎么随机,也就那么几种,都带有 Chrome 的特征。而且经过测试我这边 Chrome 浏览器的 JA4 指纹是不变的。
已有的解决方案
互联网上能找到的解决方案也不算少,都主要围绕uTLS这个库来使用。uTLS 库提供对 Go 原生 tls 库的替代,重写了 Client Hello 的过程,能够自定义上面说的 Cipher Suites 和 Extensions 字段等等。
比如我找到的这篇博文和这篇博文,都是直接裸用 uTLS 库。前者甚至从底层 DialTCP 开始手搓 TLS 加密信道(这篇文章还是很值得一看的,可以帮助你把整个构造的底层原理弄清楚)。这也无可厚非,uTLS 库的缺点就是过于底层了,用起来比较麻烦。
于是我找到了tls-client这个库,他在 uTLS 上面封装了一个 HTTP Client,可以直接使用其提供的方法发起请求,并且封装了常见主流浏览器的 profile:
options := []tls_client.HttpClientOption{
tls_client.WithTimeoutSeconds(30),
tls_client.WithClientProfile(profiles.Chrome_120),// Chrome 120 版本的指纹
}
// ...
// 创建 Request,注意这里创建的 Request 不是 Go 标准库的 http.Request,而是 fhttp.Request,它自己的一个结构
req, err := fhttp.NewRequest(http.MethodGet, "https://tls.peet.ws/api/all", nil)
// ...
resp, err := client.Do(req)// 使用 tls-client 进行请求
// ...
但缺点也是有的,就是提供的接口过于原始了,对于我这样的懒人,早就习惯用resty、req这样封装完善的第三方库了。
自定义 RoundTripper
好在 resty 提供一个 SetTransport 的方法,可以传入一个实现了 http.RoundTripper 的接口。相关的函数签名其实很简单:
type http.RoundTripper interface {
RoundTrip(*http.Request) (*http.Response, error)
}
无非就是接收到一个 http.Request 对象,然后进行网络请求,最后返回一个 http.Response。注意这里的 Request 和 Response 都是 Go 的 http 标准库中的,而 tls-client 使用了另一套 fhttp.Request 和 fhttp.Response,所以只要进行一下桥接,实现一个自定义 RoundTripper 就行了。
完整代码已经开源:https://github.com/juzeon/spoofed-round-tripper
这样和 resty 一起使用起来就变得很容易:
package main
import (
"fmt"
tlsclient "github.com/bogdanfinn/tls-client"
"github.com/bogdanfinn/tls-client/profiles"
"github.com/go-resty/resty/v2"
srt "github.com/juzeon/spoofed-round-tripper"
)
func main() {
// Create a SpoofedRoundTripper that implements the http.RoundTripper interface
tr, err := srt.NewSpoofedRoundTripper(
// Reference for more: https://bogdanfinn.gitbook.io/open-source-oasis/tls-client/client-options
tlsclient.WithRandomTLSExtensionOrder(),// needed for Chrome 107+
tlsclient.WithClientProfile(profiles.Chrome_120),
)
if err != nil {
panic(err)
}
// Set as transport. Don't forget to set the UA!
client := resty.New().SetTransport(tr).
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "+
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
// Use
resp, err := client.R().Get("https://tls.peet.ws/api/all")
if err != nil {
panic(err)
}
fmt.Println(string(resp.Body()))
}
注意有一些用法是不行的,毕竟咱们的 RoundTripper 是自己的实现的,不是 Go 自己的 http.Transport。比如设置代理的时候:
// Don't:
tr, err := srt.NewSpoofedRoundTripper(
tlsclient.WithRandomTLSExtensionOrder(),
tlsclient.WithClientProfile(profiles.Chrome_120),
)
if err != nil {
panic(err)
}
client := resty.New().SetTransport(tr).SetProxy("socks5://127.0.0.1:7890")
// ERROR RESTY current transport is not an *http.Transport instance
// Do:
tr, err := srt.NewSpoofedRoundTripper(
tlsclient.WithRandomTLSExtensionOrder(),
tlsclient.WithClientProfile(profiles.Chrome_120),
tlsclient.WithProxyUrl("socks5://127.0.0.1:7890"),
)
if err != nil {
panic(err)
}
client := resty.New().SetTransport(tr)
上面第一种尝试会报错的原因是 resty 的 SetProxy 方法会在内部尝试把传入的 http.RoundTripper 转成标准库的 http.Transport,显然咱们穿进去的是个假的,所以会报错。好在 tls-client 本身提供了设置代理的方法,可以直接用。
说实话,像 Go 这样能直接撅起底层,自己实现 TLS 握手过程的能力,在其他语言中时很少见的。比如 Python 基本上所有的网络请求库在 TLS 握手的时候都是直接调用底层 C 编写的 openssl 套件,因此完全就不支持自定义。搞了半天,Python 这个被鼓吹为最适合爬虫的语言在写爬虫上居然还不如 Go?
参考掘金上的这篇文章:https://juejin.cn/post/7197740114252447781
嗯…实际上上面介绍的 tls-client 这个库还提供一些其他语言的 binding,不过调用起来相对麻烦,感兴趣的话可以去看看人家的文档。
Licensed under CC BY-NC-SA 4.0
Recommend
-
58
今天CSS大会上腾讯王乐带来的分享:用css实现的一个海浪,我会后觉得有意思,自己实现了一边,效果如下: 代码很简单,只有几行代码。 会上我只理解了思想,代码没记住,于是我写了模拟代码,海浪步骤没有原味的协调,请看原版(目前貌似还没地方看); HTML &...
-
13
生物特征识别之指纹识别,伪造,指纹设备缺陷设计 海南鸡饭...
-
62
本文大约 2000 字,阅读大约需要 6 分钟 我们知道图片除了最普通的彩色图,还有很多类型,比如素描、卡通、黑白等等,今天就介绍如何使用 Python 和 Opencv 来实现图片变素描图。 主要参考这篇文章来实现—
-
57
Python 是机器学习领域内的首选编程语言,它易于使用,也有很多出色的库来帮助你更快处理数据。但当我们面临大量数据时,一些问题就会显现……
-
37
最近有看到一些柯里化的文章,怎么说呢,感觉很奇怪。一篇是阿里云的译文,文章末尾给出了这样一个 "curry": function curry(fn, ...args) { return (..._arg) => { return
-
25
从 PDF 表格中获取数据是一项痛苦的工作。不久前,一位开发者提供了一个名为 Camelot 的工具,使用三行代码就能从 PDF 文件中提取表格数据。 PDF 文件是一种非常常用的文件格式,通常用于正式的电子版文件。它能够很好的将不同的...
-
13
身处数据爆炸增长的信息时代,各种各样的数据都飞速增长,视频数据也不例外。我们可以使用 python 来提取视频中的音频,而这仅仅需要安装一个体量很小的 python 库,然后执行三行代码! 语音数据在数据分析领域极为重要。比如可以分析...
-
7
因为需要分析 tfs 提交日志,并按照一定条件(比如,提交信息或者用户名)分类整理,特意写了这个小工具。在使用过程中发现,某些情况下会花费很长时间才能返回处理结果,当时稍稍做了一些优化,已经能满足当时的应用场景了。但是在处理大文件的时...
-
8
Copy & Paste 三行代码让 TiDB 性能翻倍 Copy _ 2021年7月10日 下午 5.6k...
-
4
我两周就写了三行代码 - ARM Cortex A9 中断与浮点数运算、FPU 问题 发布于:2022-03-08 21:09:38 标签:/ ARM /...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK