Python 3.7+ TLS连接如何分别指定网络域名和证书校验域名
实现方案(Python 3.7及以上版本)
Python 3.7+ 已将证书校验逻辑直接集成到SSL上下文的握手流程中,不需要单独修改ssl.match_hostname方法,只需要拆分TCP连接和TLS握手两个步骤即可实现需求。
原生ssl库实现
- 第一步:建立到代理域名的TCP连接
import socket import ssl # 实际建立网络连接的代理域名 connect_host = "example-proxy.com" connect_port = 443 # 用于证书校验的实际业务域名 verify_host = "example.com" # 先建立普通TCP连接到代理地址 sock = socket.create_connection((connect_host, connect_port), timeout=10)
- 第二步:TLS握手时指定校验域名
# 加载系统默认可信根证书,保持官方默认的证书校验规则 ctx = ssl.create_default_context() # 握手时通过server_hostname参数指定证书校验的主机名 secure_sock = ctx.wrap_socket(sock, server_hostname=verify_host)
此时底层TCP连接走的是example-proxy.com的地址,但证书校验时会自动使用verify_host的值匹配证书的SAN字段,不会触发域名不匹配错误。
常用第三方库实现
如果使用requests等高层网络库,可以通过自定义连接适配器强制指定SNI和校验域名,也可以直接修改hosts解析规则将业务域名指向代理地址,同时保持请求URL的域名为业务域名即可。
安全风险说明
- 证书校验绕过风险:如果配置过程中误关闭了SSL上下文的默认校验规则,或者填错了校验域名,会直接导致TLS身份认证失效,攻击者可以用任意合法证书发起中间人攻击,窃取传输数据。
- SNI信息泄露风险:TLS握手阶段发送的SNI字段为你指定的校验域名(example.com),若网络环境存在SNI审计规则,会直接暴露你实际访问的业务域名,而非你连接的代理域名。
- 代理端风险:TCP连接实际建立在你和代理服务器之间,即使证书校验通过,也仅能保证你和业务服务器的传输内容不会被第三方窃听,但无法防御代理服务器本身的恶意行为,若代理被攻陷,你的所有传输数据都会被窃取或篡改。
- 配置维护风险:连接域名和校验域名拆分配置后,若没有明确的配置标注,后续迭代过程中很容易出现配置错配,要么导致业务不可用,要么引入安全漏洞。
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

