查看: 143|回复: 0

Python requests SSLError证书校验失败排查与修复方

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在Python开发中,通过requests、urllib、aiohttp发起HTTPS请求时,经常遇到SSLError异常。这个错误本质上是SSL/TLS握手阶段,Python客户端无法验证服务端SSL证书的合法性,导致连接被OpenSSL底层直接断开。常见的报错片段包括:
  1. requests.exceptions.SSLError: HTTPSConnectionPool(...): Max retries exceeded with url: ...
  2. Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)'))
复制代码

除了证书校验失败,证书过期、域名不匹配、SSL版本不兼容、CA证书缺失、代理抓包工具干扰,都可能触发SSLError。下面按照实际排查顺序,给出几种可落地的处理方案。

一、临时关闭证书校验(仅限开发调试)

当目标环境使用自签名证书或内网测试证书时,可以在开发调试阶段临时关闭校验。requests库通过verify=False实现:
  1. import requests
  2. resp = requests.get("https://example.com", verify=False)
  3. print(resp.text)
复制代码

关闭校验后,requests会输出大量InsecureRequestWarning警告,可以通过urllib3的disable_warnings屏蔽:
  1. import requests
  2. from requests.packages.urllib3.exceptions import InsecureRequestWarning
  3. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  4. resp = requests.get("https://example.com", verify=False)
复制代码

如果使用urllib.request,需要手动创建不校验证书的SSL上下文:
  1. import urllib.request
  2. import ssl
  3. ctx = ssl.create_default_context()
  4. ctx.check_hostname = False
  5. ctx.verify_mode = ssl.CERT_NONE
  6. req = urllib.request.Request("https://example.com")
  7. with urllib.request.urlopen(req, context=ctx) as r:
  8.     print(r.read())
复制代码

aiohttp的TCPConnector支持直接关闭SSL校验:
  1. import aiohttp
  2. import asyncio
  3. async def demo():
  4.     connector = aiohttp.TCPConnector(ssl=False)
  5.     async with aiohttp.ClientSession(connector=connector) as session:
  6.         async with session.get("https://example.com") as resp:
  7.             print(await resp.text())
  8. asyncio.run(demo())
复制代码

需要强调,verify=False只是让报错不再出现,并没有真正修复证书信任链。生产环境使用会引入中间人劫持风险,绝对禁止。

二、指定CA证书文件(推荐正式方案)

对于自签名服务,可以导出服务端CA根证书(.crt或.pem),然后在请求时指定该证书路径,让Python验证这个自定义CA:
  1. import requests
  2. resp = requests.get("https://example.com", verify="./ca-root.pem")
复制代码

更推荐在Session上全局设置,后续同一Session的所有请求都自动携带该CA:
  1. import requests
  2. session = requests.Session()
  3. session.verify = "./ca-root.pem"
  4. resp = session.get("https://example.com")
复制代码

这种方式既保留了证书校验能力,又能兼容自签名内网环境,是线上服务对接内部系统时的首选。

三、修复Python本地CA证书库

很多SSLError并不是网站证书有问题,而是Python运行环境找不到可信任的CA证书。requests依赖certifi包维护内置CA根证书集合,可以查看当前路径:
  1. import certifi
  2. print(certifi.where())
复制代码

输出的是certifi维护的ca.pem文件路径。如果这个文件缺失、损坏或过期,公网HTTPS请求就会报CERTIFICATE_VERIFY_FAILED。最简单的处理是升级certifi,更新根证书列表:
  1. pip install -U certifi
复制代码

macOS上通过Python官网安装包安装Python后,常出现找不到系统CA证书的情况。需要手动执行证书安装脚本,注意按实际Python版本替换路径:
  1. /Applications/Python\ 3.12/Install\ Certificates.command
复制代码

Windows下如果使用精简版Python、绿色版Python,容易缺失CA链或OpenSSL动态库,建议直接安装官方Python,不要使用精简版。

四、SSL/TLS版本兼容问题

当报错信息中包含SSL_ERROR_UNSUPPORTED_VERSION时,说明服务端SSL/TLS版本过旧,与当前Python使用的OpenSSL版本不兼容。OpenSSL 3.0之后默认拒绝很多老旧的不安全TLS版本,内网老旧设备接口很容易触发该错误。

可以自定义SSL上下文,通过设置OP_LEGACY_SERVER_CONNECT选项来兼容老旧服务端。注意这个操作会降低安全性,需仔细评估风险:
  1. import ssl
  2. import requests
  3. from requests.adapters import HTTPAdapter
  4. from urllib3.poolmanager import PoolManager
  5. class SslAdapter(HTTPAdapter):
  6.     def init_poolmanager(self, connections, maxsize, block=False):
  7.         ctx = ssl.create_default_context()
  8.         ctx.options |= 0x4  # OP_LEGACY_SERVER_CONNECT
  9.         self.poolmanager = PoolManager(
  10.             num_pools=connections,
  11.             maxsize=maxsize,
  12.             block=block,
  13.             ssl_context=ctx
  14.         )
  15. session = requests.Session()
  16. session.mount("https://", SslAdapter())
  17. resp = session.get("https://old-server.com")
复制代码

这个方案专门用于对接老旧服务端,不建议对公网请求使用。

五、代理和抓包工具导致的SSLError

本地开启Fiddler、Charles等抓包工具抓取HTTPS流量时,这些工具会替换服务端证书,Python请求自然无法通过证书校验。

处理方式有两种:一是把抓包工具生成的根证书导入到certifi的ca.pem中,让Python信任该证书;二是调试阶段临时设置verify=False绕过。另外,检查环境变量中的HTTP_PROXY、HTTPS_PROXY,如果误设置了不可用的代理,同样会导致SSL握手失败:
  1. import os
  2. os.environ["HTTP_PROXY"] = ""
  3. os.environ["HTTPS_PROXY"] = ""
复制代码

清除代理后,再重新发起请求往往就能恢复正常。

六、线上排查步骤模板

遇到SSLError时,按以下顺序排查更高效:

1. 检查系统时间是否正确。时间偏差过大,证书会直接被判定过期。
2. 用浏览器访问目标URL,确认浏览器是否也报证书错误。如果浏览器正常,则问题出在Python客户端本地CA库;如果浏览器也报错,则是服务端证书本身有问题。
3. 升级certifi、requests、urllib3到最新版本,排除旧版本已知BUG。
4. 用verify=False做一次测试请求,判断网络链路是否畅通。
5. 如果verify=False正常,说明是证书校验问题。正式环境导入自签名CA证书并指定verify参数,调试环境可临时关闭校验。
6. 如果verify=False仍然报错,大概率是TLS版本不兼容、网络代理或防火墙拦截。打印完整堆栈,区分CERTIFICATE_VERIFY_FAILED和SSL_ERROR_UNSUPPORTED_VERSION两类错误,再针对性处理。

七、开发规范与避坑建议

生产代码中禁止全局设置verify=False。自签名内网服务优先使用指定CA证书文件的方式。

不要使用精简版Python,容易缺失OpenSSL与CA证书。容器化部署Python服务时,镜像务必安装ca-certificates包,例如Alpine系统:
  1. RUN apk add ca-certificates
复制代码

长连接场景下,建议复用同一个requests.Session和自定义SSL上下文,避免每次请求重新创建上下文,既提升性能,也减少握手开销。

出现SSLError时,不要无脑关闭校验,先定位根因。多数情况下,系统中维护一份正确的CA证书文件,就能解决大部分问题。

常见错误关键字速查:

- CERTIFICATE_VERIFY_FAILED:CA缺失或自签名证书。处理思路是升级certifi或指定CA pem文件。
- SSL_ERROR_UNSUPPORTED_VERSION:TLS版本不匹配。处理思路是自定义SSL上下文并开启兼容选项。
- SSL: CERTIFICATE_EXPIRED:证书过期。需要服务端更新证书,客户端无法单方面修复。
- hostname mismatch:证书域名与访问域名不一致。检查URL和证书的Common Name/SAN字段。

最后总结:SSLError绝大多数是由SSL证书校验失败引起,底层由OpenSSL抛出。verify=False只是绕过校验的调试手段,生产环境禁用。自签名证书优先传入CA根证书文件。certifi包维护Python内置CA根证书,升级certifi可以解决大部分公网证书链问题。内网老旧设备则考虑自定义SSL上下文做TLS版本兼容。代理抓包、系统时间错误、容器缺少ca-certificates也都是高频诱因,排查时按顺序检查即可。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-8-31 15:24 , Processed in 0.023882 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部