JA3、JA4指纹
1. 环境准备
1.1使用openssl生成自签名证书
# centos, roor用户只需以下操作
# 查看服务器是否支持openssl命令
which openssl
# 创建一个文件夹用来存放机构私钥及自签名证书文件
mkdir -p /root/ca
cd /root/ca
# 生成机构的私钥
openssl genrsa -out ca.key 1024
# 生成机构自己的证书申请文件,文件内容会包含一些申请人信息,可以随意填写
openssl req -new -key ca.key -out ca.csr
# 生成自签名证书,ca机构用自己的私钥和证书申请文件生成自己的证书,俗称自签名证书,
# 也可以理解为根证书
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt
# 生成服务器私钥
openssl genrsa -out server.key 1024
# 根据服务器私钥生成证书请求文件,文件内包含申请人信息,随意填写
openssl req -new -key server.key -out server.csr
# 根据CA机构生成的ca.crt根证书、CA机构私钥ca.key、服务器证书申请文件
# server.csr生成服务端证书
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt
# 最后生成服务端证书文件
# server.crt 服务端证书
# server.key 服务端私钥
1.2使用nginx配置自签名证书
# 把服务端证书文件拷贝到nginx conf目录下
cp /root/ca/server.crt /opt/openresty/nginx/conf
cp /root/ca/server.key /opt/openresty/nginx/conf
# 进入到nginx工作路径
cd /opt/openresty/nginx
# 配置一个https服务
vim conf/nginx.conf
server {
listen 443 ssl;
server_name ja-fingerprint.test.cn;
ssl_certificate server.crt;
ssl_certificate_key server.key;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("hello, ja3/ja4 fingerprint!")
}
}
}
# 测试、重启nginx服务
./sbin/nginx -t
./sbin/nginx -s reload
1.3配置客户端服务器,访问自签名https服务(192.168.187.153)
1.4wireshark抓取自签名https服务报文
1.5 wireshark将此抓包文件另存为 ja-tcpdump.pcap文件,文件名自拟
1.6 使用 pyja3 python module从pcap文件中生成ja指纹
pip install pyja3
ja3 --json .\ja-tcpdump.pcap
我们可以看到ja3的哈希值是一样的
2. 那什么是JA3
2.1 简介:JA3获取 SSL\TLS 部分报文数据,让这些数据以固定格式排列组合,最终计算出哈希值。此哈希值是客户端应用程序的指纹。无论恶意软件如何改变报文、目的IP、命令参数、或者证书如何,最终该恶意软件的指纹始终唯一。
我们在两台centos7虚拟机上,安装nmap的相同版本,对我们的域名 ja-fingerprint.test.cn 进行渗透,可以在wireshark里面发现这两台服务器的JA3指纹都是一样的。
首先在服务器B进行渗透尝试
我们在wireshark抓包软件中根据服务器IP以及tls过滤出Client Hello的报文,可以看到这次的渗透过程中,所有的JA3指纹都是一样的。这里笔者选择第一个、中间一个、最后一个报文的截图以供参考。
然后我们回到服务器C中,对我们的域名进行渗透尝试。
我们可以看到两台服务器对我们ja-fingerprint.test.cn的渗透,它们的指纹都是一样的
至此,我们大胆做一个的推论。如果发现TLS报文JA3和上述一致,那我们就可以得出此人使用的操作系统是Centos7,工具是nmap这么一个结论
2.2 那么JA3为何如此可靠呢
JA3使用的是SSL Client Hello数据包中的信息来识别的客户端,此概念在2009年就已有人提出。后续2015年 Lee Brotherston 发布了该主题的研究,并且发布了FingerprintTLS,这是一款独立工具。在后续就是JA3的作者,在前人的基础上进行改进,最终得出JA3。
下图是Client Hello协议内的信息,JA3使用红色箭头标记处的内容
将红色箭头的信息提取出来后,按照一定的顺序组合,最后计算得出哈希
SSLVersion、Ciphers、Extensions、EllipticCurves(现在称为supported_groups)、EllipticCurvePointFormats(现在称为 ec_point_formats)
这里我们使用自己抓包获取的信息来做计算,如下图所示
2.2.1 首先是SSLVersion版本信息,这里是 0x0303,十进制 771,我们记作A。
2.2.2 再然后是CipherSuites信息,这里我们可以看到客户端总共支持109种加密套件方法,我们全部列以十六进制的形式先列出来。
0000 00 05 00 04 00 02 00 01 00 16 00 33 00 39 00 3a
0010 00 18 00 35 00 0a 00 1b 00 2f 00 34 c0 10 c0 06
0020 c0 15 c0 0b c0 01 00 3b c0 30 c0 2c c0 28 c0 24
0030 c0 14 c0 0a 00 a5 00 a3 00 a1 00 9f 00 6b 00 6a
0040 00 69 00 68 00 38 00 37 00 36 00 88 00 87 00 86
0050 00 85 c0 19 00 a7 00 6d 00 89 c0 32 c0 2e c0 2a
0060 c0 26 c0 0f c0 05 00 9d 00 3d 00 84 c0 2f c0 2b
0070 c0 27 c0 23 c0 13 c0 09 00 a4 00 a2 00 a0 00 9e
0080 00 67 00 40 00 3f 00 3e 00 32 00 31 00 30 00 9a
0090 00 99 00 98 00 97 00 45 00 44 00 43 00 42 c0 18
00a0 00 a6 00 6c 00 9b 00 46 c0 31 c0 2d c0 29 c0 25
00b0 c0 0e c0 04 00 9c 00 3c 00 96 00 41 c0 12 c0 08
00c0 00 13 00 10 00 0d c0 17 c0 0d c0 03 00 07 c0 11
00d0 c0 07 c0 16 c0 0c c0 02 00 ff
将这些值以短横线、十进制的方式连接起来,我们记作B
5-4-2-1-22-51-57-58-24-53-10-27-47-52-49168-49158-49173-49163-49153-59-49200-49196-49192-49188-49172-49162-165-163-161-159-107-106-105-104-56-55-54-136-135-134-133-49177-167-109-137-49202-49198-49194-49190-49167-49157-157-61-132-49199-49195-49191-49187-49171-49161-164-162-160-158-103-64-63-62-50-49-48-154-153-152-151-69-68-67-66-49176-166-108-155-70-49201-49197-49193-49189-49166-49156-156-60-150-65-49170-49160-19-16-13-49175-49165-49155-7-49169-49159-49174-49164-49154-255
2.2.3 Extensions里面的所有类型的Type值
将所有值以十进制短横线的方式连接起来 0-11-10-35-13-15-21
我们记作C
2.2.4 supported_groups,指定客户端支持的椭圆曲线或有限域DH组
将值以十进制短横线连接起来 23-25-24-22
我们记作D
2.2.5 ec_point_formats,指定客户端支持的椭圆曲线点格式
将值以十进制短横线连接起来 0-1-2
我们记作E
最后,我们分别将A、B、C、D、E使用英文逗号 , 连接,形如
ja3_full_string = A,B,C,D,E
我们使用python计算出 ja3_full_string的MD5,可以看出这个值和Wireshark计算得出的值是一样的。完整代码代码如下。
import hashlib
def main():
ja3_full_string = "771,5-4-2-1-22-51-57-58-24-53-10-27-47-52-49168-49158-49173-49163-49153-59-49200-49196-49192-49188-49172-49162-165-163-161-159-107-106-105-104-56-55-54-136-135-134-133-49177-167-109-137-49202-49198-49194-49190-49167-49157-157-61-132-49199-49195-49191-49187-49171-49161-164-162-160-158-103-64-63-62-50-49-48-154-153-152-151-69-68-67-66-49176-166-108-155-70-49201-49197-49193-49189-49166-49156-156-60-150-65-49170-49160-19-16-13-49175-49165-49155-7-49169-49159-49174-49164-49154-255,0-11-10-35-13-15-21,23-25-24-22,0-1-2"
md5_hash = hashlib.md5()
md5_hash.update(ja3_full_string.encode("utf-8"))
ja3 = md5_hash.hexdigest()
print("JA3 MD5:", ja3)
if __name__ == '__main__':
main()
运行上述代码可以得到centos7下nmap工具扫描我们域名 ja-fingerprint.test.cn 的指纹
标准输出:JA3 MD5: 90083a70e09fb136b7db4d10a115d045
3. JA4
- JA4就是JA3的升级改良版,是用来取代JA3的。JA4包含的内容更加全面,扩展性更强大,适配度更科学,算法层面也更复杂。JA3只能对HTTPS的客户端增加指纹,总共也只有JA3(客户端指纹)、JA3S(服务端指纹)。JA4就全面多了,具体内容可以看下图了解。
我们把JA4的指纹统一称为JA4+,因为后续还不断有新的协议会支持。
- 所有JA4+的指纹格式都是固定的,采用a_b_c这种格式。采用这样格式的好处就可以方便的进行查询,例如只需要对ab\ab\c这几个部分进行搜索即可。举个搜索的例子,GeryNoise是一款互联网监听器,它能进行互联网扫描,它有一个不断变化的单一的TLS密码扫描互联网,如果仍然是JA3,那么会有大量完全不同的JA3指纹。而对于JA4,只有JA4_b这部分的指纹会发生变化,a和c保持不变,因此,GeryNoise可以通过查看JA4_ac指纹来追踪客户端。
举几个例子来更深入的了解JA4。
JA4:客户端指纹
JA4S:TLS服务端指纹
JA4H:HTTP客户端指纹
JA4L:光距离指纹,测量服务端与客户端之间的距离
JA4X:X509 TLS证书指纹
3.JA4+可以用于扫描威胁行为者、恶意软件检测、会话劫持预防、合规性自动化、位置追踪、DDoS检测、方向shell检测等等。
4.实际用例中,博客举出了几个例子。
4.1 A组织通过SoftEther生成的证书,大量进行扫描操作,然后很难将正常HTTPS的流量与这些扫描流量进行区分,但是通过SoftEther生成的证书方式是编程式的,因此 JA4X是SoftEther所独有的,如果将JA4X实施到防火墙中,组织这些流量将变得轻而易举。
4.2 由于大多数证书机构使用相同的底层程序来生成和签署其所有证书,因此大部分证书(通过互联网收集的数据)的 JA4X是一样的,但数据收集者发现了几个看起来完全不一样的数据,经过分析发现都是 Cobalt Strike!
更多的技术细节,以及实现文档,完全可以参考官方仓库里的文档。