JA3指纹技术及对抗
聊一聊JA3指纹的前世今生,是怎么出现的?、原理是什么?、以及如果我们要对抗该怎么做?
[TOC]
聊一聊JA3指纹的前世今生
聊一聊JA3指纹的前世今生,JA3指纹出现的时间并不长,数年而已;
记得我还在相关岗位从业的时候,听说出现了一个JA3指纹的东西,用于检测爬虫流量 起到反爬虫的目的。短短数年时间之后已经有很多站点使用了该技术,比如海外的Akamai、AWS,国内的美团、淘宝……。
相信很多站点都采用了这个技术,用于区分正常流量和爬虫流量,即使没有直接用此特征去打击爬虫,也会利用这个点去区分爬虫流量发现更隐蔽的特征点;
那么什么是JA3指纹呢?说到这个问题就得聊一聊TLS握手的过程。
早期的网络通信比如HTTP,传输的内容都是明文,非常不安全,只要流量被截获,获取流量包的人就能够知道你发的什么内容。
所以出现了HTTPS,也就是HTTP + SSL。传输的内容是全部通过加密的,通常是非对称加密(RSA);而TLS是SSL更先进的版本,取代了SSL协议。
进行TLS传输之前需要先进行握手,交换并协商 Session ID、版本、密钥、加密套件等信息。一个传统的TLS握手的过程包含两次往返,而最新的TLS1.3优化了建立连接的握手效率,整个握手交互信息的过程仅需要一次网络通信往返即可。
下面是用Wireshark抓的TLS1.3握手相关的包:
客户端向服务端发现一条 Client Hello数据包。(图1)
包含下面几个比较重要的信息:
- TLS版本
- 随机数
- Session ID
- Cipher Suites
其中每次建立连接都会变的是随机数和Session ID。跟客户端相关且固定不变的有:TLS版本、支持的Cipher Suites等信息。
服务端收到握手请求(Client Hello)之后,会回复一条 Server Hello数据包。(图2)
包含Server选择的加密套件以及后续数据传输时用到的数据包加密密钥
服务端利用**不同Client TLS版本、Cipher Suites 不同且固定不变**等特性,可以区分不同的Request Client。
由此原理诞生出JA3指纹。
JA3指纹说的简单一点,就是把客户端握手时传递的TLS版本、Cipher Suites等固定不变的信息,用固定的方式排列组合,再做md5加密生成的一个跟客户端相关固定不变的JA3指纹,可以用来区分不同的Request Client。
服务端也可以利用这一点,先记录爬虫常用Client的JA3指纹,例如requests、httpx等客户端。当发现请求来自于这些客户端,那么该流量必然就是爬虫流量。
有一些网站可以查看你所使用客户端的JA3指纹等特征:
怎么反制呢?
反制的根本手段就是“伪装”,把你所使用的Client伪装成正常用户所使用的Client。
比如如下代码所示,修改httpx的Cipher Suites信息,能发现返回的JA3指纹确实是变了。
1
2
3
4
5
6
7
8
9
import ssl
import httpx
ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS)
CIPHERS = 'ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+CHACHA20:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:RSA+AESGCM:RSA+AES:RSA+HIGH'
ssl_context.set_ciphers(CIPHERS)
r = httpx.get('https://tls.browserleaks.com/json', verify=ssl_context)
print(r.json())
但有一些信息并不好伪装,比如requests编译时用的是OpenSSL,而Chrome编译时用的却是BoringSSL。想要完美伪装除非在编译requests时把OpenSSL也编译进去,这样一来难度就大了不少。
国外有个佬开源了一个curl-impersonate,但并不支持直接在python中使用。
国内有另外一个佬借鉴curl-impersonate写了一个curl_cffi。能相对完美的伪造正常浏览器or其他Client的TLS特征。
先就写到这吧……

