在 Linux 上抓 HTTPs/TLS 流量包
介绍
- 参考 SSL/TLS 概览
- NSS 可以写入
Key logs(密钥日志)
,以便外部程序解密 TLS 连接。Wireshark 1.6.0 及以上版本可以使用这些日志文件来解密数据包。通过 Wireshark
-> Edit
-> Preferences
-> Protocols
-> TLS
-> (Pre)-Master-Secret log filename
告诉 Wireshark 密钥文件的位置。
- 通过将环境变量
SSLKEYLOGFILE
设置为指向一个文件,即可启用密钥日志记录。
- 注意:从
mozilla_projects_nss_nss_3_24_release_notes
开始(仅用于 Firefox 48 和 49 仅在 Firefox 48 和 49 中使用),对于使用 Makefile 的优化编译会默认禁用 SSLKEYLOGFILE
方法(通过 build.sh
使用 gyp 的编译不受影响)。发行商可以在编译时重新启用它(使用 NSS_ALLOW_SSLKEYLOGFILE=1
make 变量)。Firefox 官方二进制文件也是这样做的。(参见 bug 1188657)。值得注意的是,Debian 没有启用 选项,请参见 Debian bug 842292。
key log
文件由一系列的行组成,注释行以尖字符(’#’)开头,会被忽略。密文格式为 <Label> <space> <ClientRandom> <space> <Secret>
:
<Label>
描述了以下 secret.
<ClientRandom>
is 32 bytes Random value from the Client Hello message, encoded as 64 hexadecimal characters.
<Secret>
depends on the Label (see below).
The following labels are defined, followed by a description of the secret:
RSA
: 48 bytes for the premaster secret, encoded as 96 hexadecimal characters (removed in NSS 3.34)
CLIENT_RANDOM
: 48 bytes for the master secret, encoded as 96 hexadecimal characters (for SSL 3.0, TLS 1.0, 1.1 and 1.2)
CLIENT_EARLY_TRAFFIC_SECRET
: the hex-encoded early traffic secret for the client side (for TLS 1.3)
CLIENT_HANDSHAKE_TRAFFIC_SECRET
: the hex-encoded handshake traffic secret for the client side (for TLS 1.3)
SERVER_HANDSHAKE_TRAFFIC_SECRET
: the hex-encoded handshake traffic secret for the server side (for TLS 1.3)
CLIENT_TRAFFIC_SECRET_0
: the first hex-encoded application traffic secret for the client side (for TLS 1.3)
SERVER_TRAFFIC_SECRET_0
: the first hex-encoded application traffic secret for the server side (for TLS 1.3)
EARLY_EXPORTER_SECRET
: the hex-encoded early exporter secret (for TLS 1.3).
EXPORTER_SECRET
: the hex-encoded exporter secret (for TLS 1.3)
RSA
形式允许记录使用 RSA 密钥协议的密码套件,是 Wireshark 1.6.0 支持的第一种形式。它已被 CLIENT_RANDOM
取代,后者也适用于其他密钥协议算法(如基于 Diffie-Hellman 的算法),并从 Wireshark 1.8.0 开始支持。
The TLS 1.3 lines are supported since NSS 3.34 (bug 1287711) and Wireshark 2.4 (EARLY_EXPORTER_SECRET
exists since NSS 3.35, bug 1417331). The size of the hex-encoded secret depends on the selected cipher suite. It is 64, 96 or 128 characters for SHA256, SHA384 or SHA512 respectively.
- 有两种方法抓包:
- 客户端使用浏览器获取对称加密的密钥
- 使用 SSL 私钥证书,需满足
- 加密算法不能使用
DHE
、ECDHE
- 报文须完整,包括 TCP 握手信息
浏览器获取对称加密的密钥方法
安装 wireshark
sudo add-apt-repository universe
# sudo add-apt-repository ppa:wireshark-dev/stable
sudo apt install wireshark
# 配置
sudo dpkg-reconfigure wireshark-common
配置环境变量
# /etc/profile
export SSLKEYLOGFILE=/tmp/sslkeylog.txt
需要重启 Linux 机器
tcpdump 抓包
- tcpdump 抓包
- 抓包
tcpdump -i eth0 tcp and port 443 -s0 -nn -w first.cap
- 使用
浏览器
打开网页,浏览 https 的页面,即可抓取解密的包
- 打开 wireshark,配置:
编辑
> 首选项/Preferences
> Protocols
> SSL/TLS
> (Pre)-Master-Secret log filename
为 SSLKEYLOGFILE
的路径
wireshark 直接抓包
- 为 wireshark 配置 SSL/TLS 证书
编辑
> 首选项/Preferences
> Protocols
> SSL/TLS
> 导入RSA Key
和密码(若存在) > 抓包
- 为 wireshark 配置
SSLKEYLOGFILE
编辑
> 首选项/Preferences
> Protocols
> SSL/TLS
> (Pre)-Master-Secret log filename
为 SSLKEYLOGFILE
的路径
Python 获取对称加密的密钥方法
Python urllib3 通过配置 SSLKEYLOGFILE
实现抓取 SSL 对称加密密钥,参考
$ export SSLKEYLOGFILE=/tmp/keylogfile.txt
$ pip install requests
$ cat test.py
import requests
resp = requests.get("https://baidu.com")
print(resp.status_code)
print(resp.headers)
print(resp.text)
# 执行 Python 脚本
$ python3 test.py
200
...
# 查看 Key logs
$ cat $SSLKEYLOGFILE
# TLS secrets log file, generated by OpenSSL / Python
CLIENT_RANDOM f96ff44084a6be28bdcf26a96ad673cf36c6f1df3ac43e0cb0c9017dd7e93229 2843cad38b5a3f96c789adc45f07ada5195f29d059779301d5c1db8441e0dfcc536664ba99f97bdf1810466a9e9d6336
- 采用 tcpdump 抓包、wireshark 解包即可
Golang 获取对称加密的密钥方法
通过 tls 包输出 Key log 到 os.Stdout
// Typically the log would go to an open file:
// w, err := os.OpenFile("tls-secrets.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
w := os.Stdout
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
KeyLogWriter: w,
InsecureSkipVerify: true, // test server certificate is not trusted.
},
},
}
// https://pkg.go.dev/crypto/tls#example-X509KeyPair-HttpServer
package main
import (
"crypto/tls"
"log"
"net/http"
"os"
"time"
)
func main() {
certPem := []byte(`-----BEGIN CERTIFICATE-----
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
6MF9+Yw1Yy0t
-----END CERTIFICATE-----`)
keyPem := []byte(`-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49
AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q
EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
-----END EC PRIVATE KEY-----`)
cert, err := tls.X509KeyPair(certPem, keyPem)
if err != nil {
log.Fatal(err)
}
cfg := &tls.Config{
Certificates: []tls.Certificate{cert},
KeyLogWriter: os.Stdout,
}
srv := &http.Server{
Addr: ":8000",
TLSConfig: cfg,
ReadTimeout: time.Minute,
WriteTimeout: time.Minute,
}
log.Fatal(srv.ListenAndServeTLS("", ""))
}
# 获取 ClientHello demo
cfg := &tls.Config{
Certificates: []tls.Certificate{*cert},
GetConfigForClient: func(clientHello *tls.ClientHelloInfo) (*tls.Config, error) {
collectInfos.collectClientHello(clientHello)
currentClientHello = clientHello
return nil, nil
},
}
// https://pkg.go.dev/crypto/tls#example-Config-KeyLogWriter
package main
import (
"crypto/tls"
"fmt"
"log"
"net/http"
"os"
)
func main() {
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
KeyLogWriter: os.Stdout,
InsecureSkipVerify: true, // test server certificate is not trusted.
},
},
}
resp, err := client.Get("https://127.0.0.1:8000")
if err != nil {
log.Fatalf("Failed to get URL: %v", err)
}
resp.Body.Close()
fmt.Println(resp.StatusCode, resp.Header, resp.Body)
}
# server
$ go run ./server.go
CLIENT_HANDSHAKE_TRAFFIC_SECRET 53d756ab252a819bd0713fe6b8437e770eaf888a5a79ba6064262fdec2a3ebaa cbcdb4060e505fea35131fef28cab62ef92dd6e628c4beb6c3dd5fac3e4e3dbf
SERVER_HANDSHAKE_TRAFFIC_SECRET 53d756ab252a819bd0713fe6b8437e770eaf888a5a79ba6064262fdec2a3ebaa 825c15a820e077438866a8885105d49b3dedbfbbb473d52a04b11df6671b1f76
CLIENT_TRAFFIC_SECRET_0 53d756ab252a819bd0713fe6b8437e770eaf888a5a79ba6064262fdec2a3ebaa 18e8d1eea03f650649e15fd67a024ef82cf593dd404532d95bbee07901130bd4
SERVER_TRAFFIC_SECRET_0 53d756ab252a819bd0713fe6b8437e770eaf888a5a79ba6064262fdec2a3ebaa 05d694d6f3c9e87c6fa70a0c912616a5180014295b1ae17234452ec67f727edc
# client
$ go run ./client.go
CLIENT_HANDSHAKE_TRAFFIC_SECRET 53d756ab252a819bd0713fe6b8437e770eaf888a5a79ba6064262fdec2a3ebaa cbcdb4060e505fea35131fef28cab62ef92dd6e628c4beb6c3dd5fac3e4e3dbf
SERVER_HANDSHAKE_TRAFFIC_SECRET 53d756ab252a819bd0713fe6b8437e770eaf888a5a79ba6064262fdec2a3ebaa 825c15a820e077438866a8885105d49b3dedbfbbb473d52a04b11df6671b1f76
CLIENT_TRAFFIC_SECRET_0 53d756ab252a819bd0713fe6b8437e770eaf888a5a79ba6064262fdec2a3ebaa 18e8d1eea03f650649e15fd67a024ef82cf593dd404532d95bbee07901130bd4
SERVER_TRAFFIC_SECRET_0 53d756ab252a819bd0713fe6b8437e770eaf888a5a79ba6064262fdec2a3ebaa 05d694d6f3c9e87c6fa70a0c912616a5180014295b1ae17234452ec67f727edc
404 map[Content-Length:[19] Content-Type:[text/plain; charset=utf-8] X-Content-Type-Options:[nosniff]] &{0xc000184040 {0 0} true <nil> 0x120dfe0 0x120e0c0}
Nginx 获取 TLS key logs
$ nginx -V
nginx version: nginx/1.18.0 (Ubuntu)
built with OpenSSL 3.0.2 15 Mar 2022
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -ffile-prefix-map=/build/nginx-zctdR4/nginx-1.18.0=. -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -flto=auto -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-compat --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --add-dynamic-module=/build/nginx-zctdR4/nginx-1.18.0/debian/modules/http-geoip2 --with-http_addition_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_sub_module
# 安装依赖
sudo apt install -y libssl-dev
# 编译
cc sslkeylog.c -shared -o libsslkeylog.so -fPIC -ldl
# 复制动态连接库到 /usr/local/lib/
cp libsslkeylog.so /usr/local/lib/
cd /etc/nginx
# 生成私钥
openssl genrsa -out server.key 2048
# 生成公钥(证书)
openssl req -new -x509 -key server.key -out server.pem -days 3650
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
# 新增
env SSLKEYLOGFILE;
env LD_PRELOAD;
#
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
# 新增
ssl_certificate /etc/nginx/server.pem;
ssl_certificate_key /etc/nginx/server.key;
ssl_session_cache off;
ssl_session_tickets off;
#
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
server {
listen 443 ssl;
root /var/www/html;
}
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
SSLKEYLOGFILE=/tmp/keylogfile.txt LD_PRELOAD=/usr/local/lib/libsslkeylog.so nginx
$ curl \
-o /dev/null \
-v \
--tlsv1.2 \
--resolve "t.xiexianbin.cn:443:127.0.0.1" \
--cacert server.pem \
https://t.xiexianbin.cn/
$ tail -f /tmp/keylogfile.txt
tail -f /tmp/keylogfile.txt
SERVER_HANDSHAKE_TRAFFIC_SECRET c3a77115f84538b339cdfd7f83adc2887dedcb948bd119e425858aefbc3aa2f5 2e529237805d1c1f68b8b2ccb55542e037f810a55d80e17f4791587b6019d142b74a6156b1aa7a4659d005e907ff53b5
EXPORTER_SECRET c3a77115f84538b339cdfd7f83adc2887dedcb948bd119e425858aefbc3aa2f5 6aed6f55bc908679c2f6c7c58a46d1d908d79dad24a788153823a884a5dd6c38333c6bbf2d2eb8afbf709ded0c2a1af2
SERVER_TRAFFIC_SECRET_0 c3a77115f84538b339cdfd7f83adc2887dedcb948bd119e425858aefbc3aa2f5 14546a11ec065a28c6e279e27cce15a69e23e8eb85724a767541f2f2d01bd94fc93d5bf06a4fedf517e990a1da693a75
CLIENT_HANDSHAKE_TRAFFIC_SECRET c3a77115f84538b339cdfd7f83adc2887dedcb948bd119e425858aefbc3aa2f5 fc988b90492ce0de264086c9eddd320b7b9ec411a3ed6adf887ae070ad1a90ac58dfe48a973a6c3ca92f4e28bb5b07b6
CLIENT_TRAFFIC_SECRET_0 c3a77115f84538b339cdfd7f83adc2887dedcb948bd119e425858aefbc3aa2f5 eb5f57f4301df44f97e5c08fbb499dd98c48ee0346527cfeb9e33422f2a5bc1b6d7794150c72fcd2514af241f840a983