tls指纹
介绍
https等协议通过tls加密传输,大家可能觉得数据被全加密了应该可以保证无恶意流量特征,流量侧通信不会被发现,但在加密前,会有一个tls协商过程,client和server协商交互才会生成最后加密用的密钥,而这个协商过程就可以切切实实进行指纹提取。这就是tls指纹。
JA3是一种创建SSL/TLS客户端指纹的方法,它易于在任何平台上生成,并且可以轻松共享以获取威胁情报。JA3 由三位 Salesforce 成员(John Althouse、Jeff Atkinson 和 Josh Atkins)开发,是一种用于根据 ClientHello 数据包生成 SSL 指纹以识别建立加密连接的客户端的技术。相同的客户端工具所产生的JA3指纹总是一致的。 JA3 指纹从一开始就是为了证明客户端应用程序是否存在恶意。
我们在使用burpsuite等工具时,经常会遇到网站无法打开,去掉代理又变正常的情况,很可能是网站的WAF使用了JA3指纹识别到了代理工具。常见的如cloudfare waf就使用了该指纹技术。
go默认ja3指纹89be98bbd4f065fe510fca4893cf8d9b
chromeja3指纹cd08e31494f9531f560d64c695473da9
分析
ja3
tls指纹生成过程
JA3方法用于收集Client Hello数据包中以下字段的十进制字节值:版本、可接受的密码、扩展列表、椭圆曲线密码和椭圆曲线密码格式。然后,它将这些值串联在一起,使用“,”来分隔各个字段,同时,使用“-”来分隔各个字段中的各个值。
1 2 | 版本、可接受的密码、扩展列表、椭圆曲线密码和椭圆曲线密码格式 SSLVersion,Cipher-Suites,SSLExtension,EllipticCurve,EllipticCurvePointFormat |
例子
1 | 769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0 |
然后使用MD5 hash生成指纹
1 | ada70206e40642a3e4461f35503241d5 |
具体分析
这里以go开发的frp为例
Client Hello数据包如下,ja3就是由以下圈出来的部分生成的
SSLVersion
这里显示是 TLS1.2,对应实际值是0x0303
转换成十进制为
1 | 771 |
Cipher-Suites
如下加密算法套件有19个,根据ja3规则,每个之间用"-"连接
转换成果如下
1 | 49199-49200-49195-49196-52392-52393-49171-49161-49172-49162-156-157-47-53-49170-10-4865-4867-4866 |
SSLExtension
这个扩展列表是由所有扩展类型值组成,如下扩展类型值有8个
生成结果为
1 | 5-10-11-13-65281-18-43-51 |
EllipticCurve
椭圆曲线密码
即扩展type值为10对应的数据,这里写的是supported_group
生成结果为
1 | 29-23-24-25 |
EllipticCurvePointFormat
椭圆曲线格式
即扩展type只为11对应的数据,这里数据值为0
生成结果为
1 |
最后将上面所有部分用","拼接组成最终需要hash的字符串
1 | 771,49199-49200-49195-49196-52392-52393-49171-49161-49172-49162-156-157-47-53-49170-10-4865-4867-4866,5-10-11-13-65281-18-43-51,29-23-24-25,0 |
进行hash计算如下
通过ja3工具解析数据包结果,可以看到和我们计算结果一致
ja3s
JA3S方法会收集Server Hello数据包中以下各个字段的十进制字节值:版本、可接受的加密算法和扩展列表。然后,它将这些值串联在一起,使用“,”来分隔各个字段,并通过“-”来分隔每个字段中的各个值。
1 2 | SSLVersion,Cipher,SSLExtension 769,47,65281–0–11–35–5–16 |
PS: 因为Server Hello会因为Client Hello不同和不同,所以只能做一个参考。
计算
FRP的Server Hello数据包如下,计算需要的三部分如下
SSLVersion
版本
生成结果
1 | 771 |
Cipher
可接受的加密算法,只有一个
生成结果
1 | 4865 |
SSLExtension
扩展列表,扩展列表只有两个
根据类性值,生成结果
1 | 43-51 |
将上述拼接起来
1 | 771,4865,43-51 |
但ja3s工具生成,加密套件这块额外添加了参数实际算法名称
如下,但实际表示意义也是唯一的
1 | 771,CipherSuite(0x1301, TLS_AES_128_GCM_SHA256),43-51 |
绕过
文章
https://mp.weixin.qq.com/s/og2IKo8lcydh8PROUPD7jQ
https://segmentfault.com/a/1190000041699815
库
https://github.com/refraction-networking/utls
这个库是基于crypto/tls进行开发的,可以模拟绝大部分情况下的ja3指纹。
和官方库差不多的用法,如下在封装时除了conn连接和config配置以外,需要传入ClientHelloID
,这个ID是有一些内置的指纹可以直接调用,或者也可以自定义。
1 2 3 | conn, _ := net.DialTimeout("tcp", "121.14.77.201:443", 10*time.Second) uConn := tls.UClient(conn, &tls.Config{ServerName: "www.qq.com", InsecureSkipVerify: true}, tls.HelloChrome_102) uConn.Write([]byte("aaa")) |
这是官方库
u_common.go,如下有非常全的现成指纹信息可以用。
这些ID最终对应到这个函数utlsIdToSpec
里
后续可参考这边编写实现自己的。
并且该库还支持解析数据包中的client hello信息,来自动化构建一个自定义参数,实现模拟各种ja3指纹
浏览器访问,然后抓包找到client hello包,选中tls层的数据,然后复制成hex stream即可。
将tls的hex数据粘贴到以下位置,通过fingerprinter.FingerprintClientHello
即可解析生成一个自定义spec,封装到tlsConn里直接使用。
要注意的是ClientHelloID
还设置成HelloCustom
即自定义
1 2 3 4 5 6 7 8 9 10 11 12 | conn, _ := net.DialTimeout("tcp", "121.14.77.201:443", 10*time.Second) rawCapturedClientHelloBytes, _ := hex.DecodeString("1603010200010001fc03037741eebedfb7afbcfcd0f49f9b59d7a9c13eb3ccd8f207b8c692ffb1b9b9f5c22017bd40c3ec96ca8c21df97de564ce5e4e88bc945ca902d7d4260f77fb980631400221a1a1301130213021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035010001918a8a000000000012001000000d7370312e62616964752e636f6d00170000ff01000100000a000a0008dada001d00170018000b00020100002300a00efd73f80bf561e25ea122de025cb65678ebfcd201e8c49325fbabe586918cdb8cfdeaac64d4798b351295c62d94aa3c48a8f4181bee25d4202025cbf7eaf074233d576018c8adfe0d4527daa496e1b05162c0490a00fb108522a31e0bf369482a97a77d62f147f1657e927b45223545e7ad54f99239d820ed81b41c172a15dc3762f5d8fd1d333e082f55daca4e38ae11456fa4caf6be4419b56e5ed36a08580010000e000c02683208687474702f312e31000500050100000000000d0012001004030804040105030805050108060601001200000033002b0029dada000100001d0020ecb49f62fef0dc89ff3e6084d99e39b27820e68d9c4d8bf24b6d367e286ce05a002d00020101002b000706baba03040303001b0003020002446900050003026832caca0001000015002800000000000000000000000000000000000000000000000000000000000000000000000000000000") uConn := tls.UClient(conn, &tls.Config{ServerName: "www.qq.com", InsecureSkipVerify: true}, tls.HelloCustom) fingerprinter := &tls.Fingerprinter{} generatedSpec, err := fingerprinter.FingerprintClientHello(rawCapturedClientHelloBytes) if err != nil { t.Fatalf("fingerprinting failed: %v", err) } if err := uConn.ApplyPreset(generatedSpec); err != nil { t.Fatalf("applying generated spec failed: %v", err) } uConn.Write([]byte("aaa")) |
可以看到完全一致(wireshark好像3.6以上就支持ja3的指纹生成了)
PS: 要注意一点,SNI也会参与计算,如果ServerName为空会不插入Extension里,导致ja3指纹计算结果不一样
这个库还有些其他玩法,可以自行参考文档或者他的example.go
存在小bug,如果自动握手,可能会出现握手失败的问题,建议手动握手
1 2 3 4 5 | uConn := tls.UClient(c, tlsConfig, tls.HelloChrome_Auto) // 错误姿势,如此可能会报错 uConn.Write([]byte("a")) // 正确姿势 err := uConn.Handshake() |