You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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规则。

实现步骤

  1. 确认已安装基础兼容依赖:
    执行安装命令:pip install requests pyOpenSSL ndg-httpsclient pyasn1
  2. 编写自定义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)
    
  3. 初始化带自定义适配器的请求会话,替换原直接调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.01 15:54:05