编程语言

InsecureRequestWarning: Unverified HTTPS request 怎么消除

作者: · 最后核实

报错信息InsecureRequestWarning: Unverified HTTPS request is being made to host

InsecureRequestWarning: Unverified HTTPS request is being made to host 'example.com' 的意思是:程序发出了一个没有校验服务器证书的 HTTPS 请求。 请求本身成功了。之所以警告,是因为某段代码(通常是为了绕过证书报错而加的 verify=False)关掉了“确认对方是真服务器”这道检查。正确的处理方式是找到这个开关,解决它掩盖的证书问题,然后把它删掉。

这条警告是什么意思

这条警告表示 urllib3 建立了一条没有校验证书的 HTTPS 连接。urllib3 是 requests、pip、botocore 和大量 SDK 底层的 HTTP 库。它在发送每个请求之前都会检查连接:如果连接是用 cert_reqs='CERT_NONE' 建立的(requests 里的 verify=False 最终就变成这个),又没有固定证书指纹,就会调用 warnings.warn() 输出:

InsecureRequestWarning: Unverified HTTPS request is being made to host 'example.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings

Python 会在前面加上触发警告的文件和行号,比如 .../urllib3/connectionpool.py:<行号>:。这个位置在 urllib3 内部而不是你的代码里,所以很多人找不到源头。流量仍然是加密的,但没有校验时,你是在和“任何应答的人”加密通信。代理、被劫持的 DNS、恶意 Wi-Fi 都可以出示任意证书并读取数据。

还要分清它和报错:CERTIFICATE_VERIFY_FAILED 是校验失败、请求被拦下;InsecureRequestWarning 是没有校验、请求被放行。前者说明保护在起作用,后者说明保护已经被拆掉了。所以出现警告时,不要把“消除提示”当成目标,目标应该是“让校验重新开启并且通过”。

常见原因(按出现频率排序)

这条警告有 4 种来源,最常见的是代码里为绕过证书报错写了 verify=False。

1. 代码里写了 verify=False

典型经过:请求报 CERTIFICATE_VERIFY_FAILED,有人从网上抄来 verify=False,报错变成了这条警告。

  • 确认: 在代码库里搜索:
grep -rnE "verify\s*=\s*False|CERT_NONE|_create_unverified_context" --include=*.py .
  • 修复: 删掉它,再按下文修复原本的证书错误。

2. Session、客户端或 SDK 的选项关闭了校验

session.verify = False、某个客户端的 ssl_verify=False 参数、AWS CLI 的 --no-verify-ssl 这类命令行开关,最终都会变成一条未校验的 urllib3 连接。它们经常藏在配置文件或“开发环境”配置里。

  • 确认: 把警告变成异常,拿到完整调用栈:
python -W error::urllib3.exceptions.InsecureRequestWarning app.py
  • 修复: 把选项改回开启,并通过这个库自己的参数指定 CA 证书包。

3. 第三方库自己关闭了校验

一些老旧 SDK、爬虫框架会把 verify=False 写死在代码里,上一步的调用栈会停在这个包里面。

  • 修复: 升级这个包,或者给它传入开启校验的 session、参数。都不支持的话就向维护者报告,这属于库的安全漏洞。

4. 直接用 urllib3 并设置了 CERT_NONE

urllib3.PoolManager(cert_reqs="CERT_NONE") 会让每个请求都发出警告。删掉这个参数即可,urllib3 2.x 默认就会校验。

分平台修复命令

每种做法都是让 Python 能信任服务器,之后就可以删掉 verify=False。先用 SSL 检测工具检查服务器,如果证书链不完整,应该修服务器而不是客户端。

Debian / Ubuntu / 容器:

cp company-root.crt /usr/local/share/ca-certificates/company-root.crt
update-ca-certificates
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt

CentOS / RHEL / 阿里云 Alibaba Cloud Linux:

cp company-root.crt /etc/pki/ca-trust/source/anchors/ && update-ca-trust
export REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt

Windows 和 macOS: 用 truststore(Python 3.10+)读取系统证书库,公司 IT 下发的根证书也能识别:

import truststore
truststore.inject_into_ssl()

只访问某一个内网服务:

import requests
requests.get("https://intranet.example.com", verify="/etc/ssl/certs/company-root.pem")

无法更换证书的自签名设备(路由器、NAS、摄像头管理页等): 用 openssl s_client -connect 203.0.113.10:443 -showcerts 导出证书,在证书解析工具里核对内容,然后作为 verify= 传入,或者用 urllib3 固定指纹:

import urllib3
http = urllib3.PoolManager(
    cert_reqs="CERT_NONE",
    assert_fingerprint="<设备证书的 SHA-256 指纹>",
)
http.request("GET", "https://203.0.113.10/")

设置了 assert_fingerprint 后,urllib3 会把连接视为已校验,不再输出警告;对方一旦换了证书就会被拒绝。

国内装包: 安装 truststore 时可以用清华镜像 pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple truststore。镜像地址用 https://,不要加 --trusted-host。

不推荐:把警告屏蔽掉

urllib3.disable_warnings()、requests.packages.urllib3.disable_warnings()、warnings.filterwarnings("ignore")、PYTHONWARNINGS=ignore,都只是让提示消失,风险一点没少。urllib3 文档明确写着强烈不建议发出未校验的 HTTPS 请求。没有校验时,处在你和服务器之间的任何人都能读取或篡改账号密码、会话 Cookie 和接口数据;屏蔽警告还会让这个漏洞在日志和代码审查里看不出来。

正当场景都有替代办法:内网和代理根证书用 CA 证书包,系统里已有的根证书用 truststore,缺中间证书就补全服务器证书链,确实无法更换证书的单台设备用证书固定。证书链为什么重要,见证书链原理。

团队里怎么防止它再出现

防止复发要靠流程:在代码审查清单里列出 verify=False、CERT_NONE、disable_warnings 这几个关键词,提交时自动搜索;测试和预发环境也使用由内部测试 CA 签发的证书,而不是自签名证书加关闭校验,这样测试走的就是和生产一样的校验路径。需要访问公司内网服务的脚本,统一在基础镜像里装好内网根证书并设置 REQUESTS_CA_BUNDLE,大家就没有理由再去关闭校验。

怎么确认已经修好

修好的标准是:把警告当成错误运行,脚本也不再失败。

python -W error::urllib3.exceptions.InsecureRequestWarning -c "import requests; print(requests.get('https://example.com', timeout=10).status_code)"

把警告当成错误运行,只要这条代码路径上还有未校验的请求,命令就会失败。最好在 CI 里也这样运行测试,以后有人再加 verify=False,构建会直接失败,而不是悄悄上线。最后再搜一遍代码里的 verify=False 和 CERT_NONE,确认已经清理干净。

常见问题

这只是个警告,程序能跑,可以不管吗?

不能不管。正因为是警告,程序才会在“没确认对方身份”的情况下照常运行。中间人完全可以冒充真服务器应答,你的脚本察觉不到。应该把它当成安全缺陷处理,而不是日志噪音。

怎么找出是哪个库发出的未校验请求?

把警告变成异常,就能拿到完整调用栈:python -W error::urllib3.exceptions.InsecureRequestWarning app.py,或者在程序启动时调用 warnings.simplefilter('error', urllib3.exceptions.InsecureRequestWarning)。调用栈会指出具体位置,包括第三方 SDK 内部的调用。

pip 用国内镜像时加 --trusted-host,也算这类问题吗?

--trusted-host 的作用就是让 pip 对该主机放弃有效 HTTPS 校验,属于同一类风险。清华、阿里云等镜像都使用正规证书,地址写成 https:// 就能正常校验,不需要 --trusted-host。如果去掉它就报证书错误,说明是本机证书包或代理的问题,应按 pip 证书验证失败 去修。

设备是自签名证书,没法换,最稳妥的办法是什么?

只信任这一张证书,而不是关闭校验:把设备证书存成 PEM,传 verify='/path/device.pem'。如果证书里的名字无法匹配访问地址,可以用 urllib3 的 assert_fingerprint 固定证书的 SHA-256 指纹,此时 urllib3 视连接为已校验,不会再发警告,换了证书则会直接拒绝。

资料来源

  1. urllib3: Advanced Usage — TLS Warnings
  2. urllib3 source: connectionpool.py
  3. Requests: Advanced Usage (SSL Cert Verification)
  4. Python docs: warnings — Warning control
  5. truststore documentation