Python绕过DNS获取指定域名SSL证书遇TLS握手失败问题
问题解决:绕过DNS获取指定站点的SSL证书
核心错误分析
你的代码里连接目标和SNI参数完全搞反了:你需要连接到承载siteB.fqdn证书的服务器serverA.fqdn(或其IP),同时通过server_hostname传递siteB.fqdn触发SNI扩展,让Apache返回对应站点的证书。原代码连接siteB.fqdn却指定SNI为hostA.fqdn,服务器无法匹配到对应站点,直接抛出握手失败错误(提示里的SSLV3是通用错误标识,并非真的用SSLv3握手,Python3.12默认已禁用SSLv3)。
修正后的代码
#!/usr/bin/env python3 import pprint import socket import ssl # 使用默认安全上下文,手动锁定TLS版本范围(匹配目标服务器支持的TLSv1.2/1.3) context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) context.minimum_version = ssl.TLSVersion.TLSv1_2 context.maximum_version = ssl.TLSVersion.TLSv1_3 # 创建TCP socket,连接到实际承载证书的服务器serverA.fqdn(可替换为直接IP,彻底绕过DNS) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(("serverA.fqdn", 443)) # 包装socket时,通过server_hostname指定要获取证书的站点域名(SNI) conn = context.wrap_socket(sock, server_hostname="siteB.fqdn") try: # 获取证书元数据,无需发起HTTP请求 cert = conn.getpeercert() pprint.pprint(cert) finally: # 确保连接关闭 conn.close()
关键配置说明
- SNI参数的正确使用:
server_hostname必须设为你要获取证书的域名siteB.fqdn,这是Apache多站点识别证书的核心依据。 - 连接目标指向实际服务器:
connect的参数是serverA.fqdn(或其IP),直接绕过DNS解析到目标服务器。 - TLS版本锁定:手动指定版本范围,避免系统默认配置出现兼容性问题,完全匹配目标服务器的支持版本。
验证逻辑参考
你提到ssl.get_server_certificate能正常工作,本质是该函数正确处理了连接目标和SNI的对应关系,等价调用如下:
cert_pem = ssl.get_server_certificate(("serverA.fqdn", 443), server_hostname="siteB.fqdn")
内容的提问来源于stack exchange,提问作者Henrik Hudson
相关产品推荐
相关产品推荐

