Postman正常时Python requests请求TLS1.0站点报bad handshake错误
Python requests访问老旧TLS站点SSLError排障方案
问题现象
Postman可正常向目标站点发起请求,但拷贝Postman生成的Python requests代码本地执行时,抛出如下TLS握手报错:
ssl.SSLError: ("bad handshake: SysCallError(-1, 'Unexpected EOF')",) urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='...', port=443): Max retries exceeded with url: /dkmh/login.asp (Caused by SSLError(SSLError("bad handshake: SysCallError(-1, 'Unexpected EOF')"))) requests.exceptions.SSLError: HTTPSConnectionPool(host='...', port=443): Max retries exceeded with url: /dkmh/login.asp (Caused by SSLError(SSLError("bad handshake: SysCallError(-1, 'Unexpected EOF')")))
已完成的排查操作:
- 确认目标站点TLS版本为1.0
- 先后在Python 3.6/3.7/3.8环境运行测试脚本
- 安装
requests[security]依赖包 - 代码中已设置
verify=False跳过证书校验
以上操作均未解决报错。
根本原因定位
- 目标站点仅支持TLS 1.0协议,且配套使用RC4、3DES等老旧加密套件,现代Python版本的SSL默认上下文已硬性禁用这类低版本协议与不安全套件
verify=False参数仅跳过服务端证书的有效性校验,不会放宽TLS版本、加密套件的匹配规则,无法触发老协议的兼容逻辑- 安装
requests[security]仅能提供pyOpenSSL相关依赖,不会主动调整默认SSL上下文的协议禁用规则,因此无法匹配目标站点的老TLS配置 - Postman可正常请求,是因为其内置网络栈保留了对老旧TLS版本的兼容,未做协议层面的硬性拦截
可落地解决方法
通过自定义HTTPS适配器挂载会话,单独为目标站点放开老旧TLS兼容配置,不修改全局Python环境的SSL规则。
实现步骤
- 确认已安装基础兼容依赖:
执行安装命令:pip install requests pyOpenSSL ndg-httpsclient pyasn1 - 编写自定义Legacy TLS适配器,放宽SSL上下文的协议与套件限制:
import requests from requests.adapters import HTTPAdapter from urllib3.util.ssl_ import create_urllib3_context class LegacyTLSAdapter(HTTPAdapter): def init_poolmanager(self, *args, **kwargs): ctx = create_urllib3_context() # 允许连接老旧TLS站点,绕过Python的硬拦截 ctx.options |= 0x4 # 将加密套件安全等级降至0,匹配目标站点仅支持的老旧加密套件 ctx.set_ciphers('DEFAULT:@SECLEVEL=0') kwargs['ssl_context'] = ctx return super().init_poolmanager(*args, **kwargs) - 初始化带自定义适配器的请求会话,替换原直接调用
requests.request的逻辑:# 初始化会话并挂载适配器 session = requests.Session() session.mount('https://', LegacyTLSAdapter()) url = "https://xxx/login.asp" payload = 'chkSubmit=ok&txtLoginId=19020055&txtPassword=qj28r8&txtSel=1' headers = { 'Connection': 'keep-alive', 'Cache-Control': 'max-age=0', 'Upgrade-Insecure-Requests': '1', 'Origin': 'https://xxx', 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-User': '?1', 'Sec-Fetch-Dest': 'document', 'Referer': 'https://xxx/login.asp', 'Accept-Language': 'vi-VN,vi;q=0.9,en-US;q=0.8,en;q=0.7', 'Cookie': '_ga=GA1.3.1781562536.1609987797; ASPSESSIONIDAQDQQQSC=MDJHCACBKMNBFFLOGABMCGEA; UnivId=; intUserLogedin=92573; selab=intUserRef=92573; first=PortalModule_376; intUserLogedin=92573; selab=intUserRef=92573; UnivId=; first=PortalModule_376' } # 通过会话发起请求,无需额外设置verify=False response = session.post(url, headers=headers, data=payload) print(response.text.encode('utf8'))
注意事项
- 自定义适配器中的
0x4对应Python的OP_LEGACY_SERVER_CONNECT常量,作用是绕过Python对无SNI支持、低版本TLS站点的连接拦截,所有Python3版本通用 - 设置
SECLEVEL=0会临时放开老旧加密套件的兼容限制,该配置仅可用于可信的内部老旧站点,不得用于公网不可信的第三方站点 - 适配器仅挂载在当前请求会话上,不会污染全局Python环境的SSL配置,其他正常HTTPS请求仍会沿用默认的严格安全规则
内容的提问来源于stack exchange,提问作者Hoàng Tùng
相关产品推荐
相关产品推荐

