深蓝实验室@lz520520

介绍

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

例子
769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0
然后使用MD5 hash生成指纹
ada70206e40642a3e4461f35503241d5

具体分析

这里以go开发的frp为例
Client Hello数据包如下,ja3就是由以下圈出来的部分生成的
TLS指纹模拟 - 图1
SSLVersion
这里显示是 TLS1.2,对应实际值是0x0303
TLS指纹模拟 - 图2
转换成十进制为
771
Cipher-Suites
如下加密算法套件有19个,根据ja3规则,每个之间用”-“连接
TLS指纹模拟 - 图3
转换成果如下
49199-49200-49195-49196-52392-52393-49171-49161-49172-49162-156-157-47-53-49170-10-4865-4867-4866
SSLExtension
这个扩展列表是由所有扩展类型值组成,如下扩展类型值有8个
TLS指纹模拟 - 图4
生成结果为
5-10-11-13-65281-18-43-51
EllipticCurve
椭圆曲线密码
即扩展type值为10对应的数据,这里写的是supported_group
生成结果为
29-23-24-25
EllipticCurvePointFormat
椭圆曲线格式
即扩展type只为11对应的数据,这里数据值为0
TLS指纹模拟 - 图5
生成结果为
0
最后将上面所有部分用”,”拼接组成最终需要hash的字符串
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计算如下
TLS指纹模拟 - 图6
通过ja3工具解析数据包结果,可以看到和我们计算结果一致

ja3s

JA3S方法会收集Server Hello数据包中以下各个字段的十进制字节值:版本、可接受的加密算法和扩展列表。然后,它将这些值串联在一起,使用“,”来分隔各个字段,并通过“-”来分隔每个字段中的各个值。

  1. SSLVersion,Cipher,SSLExtension
  2. 769,47,6528101135516

PS: 因为Server Hello会因为Client Hello不同和不同,所以只能做一个参考。

计算

FRP的Server Hello数据包如下,计算需要的三部分如下
TLS指纹模拟 - 图7
SSLVersion
版本
TLS指纹模拟 - 图8
生成结果
771
Cipher
可接受的加密算法,只有一个
生成结果
4865
SSLExtension
扩展列表,扩展列表只有两个
TLS指纹模拟 - 图9
根据类型值,生成结果
43-51
将上述拼接起来
771,4865,43-51
但ja3s工具生成,加密套件这块额外添加了参数实际算法名称
如下,但实际表示意义也是唯一的
TLS指纹模拟 - 图10
771,CipherSuite(0x1301, TLS_AES_128_GCM_SHA256),43-51
TLS指纹模拟 - 图11

绕过

文章
https://mp.weixin.qq.com/s/og2IKo8lcydh8PROUPD7jQhttps://segmentfault.com/a/1190000041699815

https://github.com/refraction-networking/utls
这个库是基于crypto/tls进行开发的,可以模拟绝大部分情况下的ja3指纹。
和官方库差不多的用法,如下在封装时除了conn连接和config配置以外,需要传入ClientHelloID,这个ID是有一些内置的指纹可以直接调用,或者也可以自定义。

  1. conn, _ := net.DialTimeout("tcp", "121.14.77.201:443", 10*time.Second)
  2. uConn := tls.UClient(conn, &tls.Config{ServerName: "www.qq.com", InsecureSkipVerify: true}, tls.HelloChrome_102)
  3. uConn.Write([]byte("aaa"))

这是官方库
u_common.go,如下有非常全的现成指纹信息可以用。
TLS指纹模拟 - 图12
这些ID最终对应到这个函数utlsIdToSpec里
TLS指纹模拟 - 图13
后续可参考这里编写实现自己的。
TLS指纹模拟 - 图14
并且该库还支持解析数据包中的client hello信息,来自动化构建一个自定义参数,实现模拟各种ja3指纹
浏览器访问,然后抓包找到client hello包,选中tls层的数据,然后复制成hex stream即可。
TLS指纹模拟 - 图15
将tls的hex数据粘贴到以下位置,通过fingerprinter.FingerprintClientHello即可解析生成一个自定义spec,封装到tlsConn里直接使用。
要注意的是ClientHelloID还设置成HelloCustom即自定义

  1. conn, _ := net.DialTimeout("tcp", "121.14.77.201:443", 10*time.Second)
  2. rawCapturedClientHelloBytes, _ := hex.DecodeString("1603010200010001fc03037741eebedfb7afbcfcd0f49f9b59d7a9c13eb3ccd8f207b8c692ffb1b9b9f5c22017bd40c3ec96ca8c21df97de564ce5e4e88bc945ca902d7d4260f77fb980631400221a1a1301130213021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035010001918a8a000000000012001000000d7370312e62616964752e636f6d00170000ff01000100000a000a0008dada001d00170018000b00020100002300a00efd73f80bf561e25ea122de025cb65678ebfcd201e8c49325fbabe586918cdb8cfdeaac64d4798b351295c62d94aa3c48a8f4181bee25d4202025cbf7eaf074233d576018c8adfe0d4527daa496e1b05162c0490a00fb108522a31e0bf369482a97a77d62f147f1657e927b45223545e7ad54f99239d820ed81b41c172a15dc3762f5d8fd1d333e082f55daca4e38ae11456fa4caf6be4419b56e5ed36a08580010000e000c02683208687474702f312e31000500050100000000000d0012001004030804040105030805050108060601001200000033002b0029dada000100001d0020ecb49f62fef0dc89ff3e6084d99e39b27820e68d9c4d8bf24b6d367e286ce05a002d00020101002b000706baba03040303001b0003020002446900050003026832caca0001000015002800000000000000000000000000000000000000000000000000000000000000000000000000000000")
  3. uConn := tls.UClient(conn, &tls.Config{ServerName: "www.qq.com", InsecureSkipVerify: true}, tls.HelloCustom)
  4. fingerprinter := &tls.Fingerprinter{}
  5. generatedSpec, err := fingerprinter.FingerprintClientHello(rawCapturedClientHelloBytes)
  6. if err != nil {
  7. t.Fatalf("fingerprinting failed: %v", err)
  8. }
  9. if err := uConn.ApplyPreset(generatedSpec); err != nil {
  10. t.Fatalf("applying generated spec failed: %v", err)
  11. }
  12. uConn.Write([]byte("aaa"))

可以看到完全一致(wireshark好像3.6以上就支持ja3的指纹生成了)
PS: 要注意一点,SNI也会参与计算,如果ServerName为空会不插入Extension里,导致ja3指纹计算结果不一样
TLS指纹模拟 - 图16
这个库还有些其他玩法,可以自行参考文档或者他的example.go
存在小bug,如果自动握手,可能会出现握手失败的问题,建议手动握手

  1. uConn := tls.UClient(c, tlsConfig, tls.HelloChrome_Auto)
  2. // 错误姿势,如此可能会报错
  3. uConn.Write([]byte("a"))
  4. // 正确姿势
  5. err := uConn.Handshake()