Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 493 – Python 2.7 的 HTTPS 验证迁移工具

作者:
Alyssa Coghlan <ncoghlan at gmail.com>, Robert Kuska <rkuska at redhat.com>, Marc-André Lemburg <mal at lemburg.com>
BDFL 委托
Barry Warsaw
状态:
最终版
类型:
标准跟踪
创建日期:
2015年5月10日
Python 版本:
2.7.12
发布历史:
2015年7月6日,2015年11月11日,2015年11月24日,2016年2月24日
决议:
Python-Dev 消息

目录

摘要

PEP 476 更新了 Python 客户端模块中 HTTPS 证书的默认处理方式,使其与 Web 浏览器中的证书处理方式保持一致,即验证收到的证书是否属于客户端尝试联系的服务器。Python 2.7 长期维护系列被认为是此更改的范围,新行为在 Python 2.7.9 维护版本中引入。

这为受影响的 Python 2.7 维护版本的采用造成了不小的障碍,因此本 PEP 提出了额外的 Python 2.7 特定功能,允许系统管理员和其他用户更容易地将 HTTPS 客户端模块中验证服务器证书的决定与更新到更新的 Python 2.7 维护版本的决定分离。

基本原理

PEP 476 改变了 Python 的默认行为,使其与 Web 浏览器在 HTTPS URL 语义方面建立的预期保持一致:从 Python 2.7.9 和 3.4.3 开始,标准库中的 HTTPS 客户端默认验证服务器证书。

然而,事实是这一改变**确实**给运营依赖自签名证书的私有内网的基础设施管理员带来了问题,或者在新的默认证书验证设置中遇到了其他问题。

为了管理这些情况,Web 浏览器向用户提供“点击通过”警告,允许用户将服务器证书添加到浏览器的证书存储中。网络客户端工具,如 curlwget,提供了完全关闭证书检查的选项(分别通过 curl --insecurewget --no-check-certificate)。

在技术栈的不同层,像 SELinuxAppArmor 这样的 Linux 安全模块,虽然默认由发行版供应商启用,但提供了相对简单的机制来关闭它们。

目前,没有这样方便的机制可以禁用 Python 的整个进程的默认证书检查。

PEP 476 确实试图解决这个问题,通过介绍如何通过猴子补丁 ssl 模块来恢复旧行为,从而将旧设置恢复到整个进程。不幸的是,为允许系统管理员在其标准操作环境定义中默认禁用此功能而提出的基于 sitecustomize.py 的技术在至少某些情况下被证明是不够的。导致本 PEP 初步创建的具体案例是,Linux 发行商旨在为其用户提供比直接使用上游 CPython 2.7 版本提供的标准迁移路径更平滑的迁移路径,但还指出了更新嵌入式 Python 运行时和其他用户级 Python 安装的其他潜在挑战。

本 PEP 不提倡允许大量相互不兼容的迁移技术出现,而是提议向 Python 2.7.12 添加一个附加功能,使其更容易将进程恢复到过去在 HTTPS 客户端模块中跳过证书验证的行为。它还为将这些功能反向移植到 Python 2.7.9 之前的 Python 版本的再分发商提供了额外的建议。

备选方案

在缺乏明确的上游指导和建议的情况下,商业再分发商仍将根据其客户的利益做出自己的设计决策。主要可用的方法有

  • 继续基于新的 Python 2.7.x 版本,除了PEP 476中定义的机制外,不提供任何额外帮助,以实现在标准库 HTTPS 客户端中从未检查主机名到检查主机名的迁移
  • 将 HTTPS 连接的默认处理方式的更改与从 Python 2 升级到 Python 3 挂钩
  • 对于 Linux 发行版供应商,将 HTTPS 连接的默认处理方式的更改与升级到新的操作系统版本挂钩
  • 实施本 PEP 中描述的一个或两个反向移植建议,无论本 PEP 的正式状态如何

范围限制

这些更改纯粹作为在 Python 2.7 背景下帮助管理新默认证书处理行为过渡的工具而提出。它们并未作为 Python 3 的新功能提出,因为预计绝大多数受此问题影响且无法更新应用程序本身的客户端应用程序将是 Python 2 应用程序。

未来版本的 Python 3 可能会允许安全协议的默认证书处理按协议进行配置,但这超出了本 PEP 的范围。

能力检测要求

由于本 PEP 中的提议旨在促进对早期 Python 版本的向后移植,因此 Python 版本号不能用作检测它们的可靠方法。相反,它们旨在允许使用以下技术确定功能的存在或不存在

python -c "import ssl; ssl.<_relevant_attribute>"

如果相关功能不可用,这将导致 AttributeError(因此返回非零代码)。

本 PEP 定义的功能检测属性有

  • ssl._https_verify_certificates: 运行时配置 API
  • ssl._https_verify_envvar: 基于环境的配置
  • ssl._cert_verification_config: 基于文件的配置 (PEP 476 选择启用)

标记属性前缀带有下划线,以表示这些功能是依赖于实现的且对安全性敏感。

特性:配置 API

此更改提议包含在 CPython 2.7.12 及更高版本的 CPython 2.7.x 版本中。它包含一个新的 ssl._https_verify_certificates() 用于指定标准库客户端库中 HTTPS 证书的默认处理方式。

不建议将此更改向前移植到 Python 3,因此需要支持跳过证书验证的 Python 3 应用程序仍需要定义自己的适当安全上下文。

特性检测

ssl 模块上与此功能相关的标记属性是 ssl._https_verify_certificates 函数本身。

规范

ssl._https_verify_certificates 函数将按如下方式工作

def _https_verify_certificates(enable=True):
    """Verify server HTTPS certificates by default?"""
    global _create_default_https_context
    if enable:
        _create_default_https_context = create_default_context
    else:
        _create_default_https_context = _create_unverified_context

如果调用时不带参数,或者将 enable 设置为真值,则标准库客户端模块将默认验证 HTTPS 证书,否则将跳过验证。

如果调用时将 enable 设置为假值,则标准库客户端模块将默认跳过验证 HTTPS 证书。

安全注意事项

包含此功能将允许安全敏感的应用程序在其代码中包含以下前向兼容的代码片段

if hasattr(ssl, "_https_verify_certificates"):
    ssl._https_verify_certificates()

一些开发者也可能选择使用 ssl._https_verify_certificates(enable=False) 来选择不进行证书检查。这不会引入任何主要的新的安全问题,因为对受影响的内部 API 进行猴子补丁已经是可能的。

特性:基于环境的配置

此更改提议包含在 CPython 2.7.12 及更高版本的 CPython 2.7.x 版本中。它包含一个新的 PYTHONHTTPSVERIFY 环境变量,可以将其设置为 '0' 以禁用默认验证,而无需修改应用程序源代码(在仅包含字节码的应用程序分发情况下可能甚至不可用)

不建议将此更改向前移植到 Python 3,因此需要支持跳过证书验证的 Python 3 应用程序仍需要定义自己的适当安全上下文。

特性检测

ssl 模块上与此功能相关的标记属性是

  • ssl._https_verify_envvar 属性,表示影响默认行为的环境变量的名称

这不仅使得检测功能的存在(或不存在)变得简单,还使得以编程方式确定相关环境变量名称成为可能。

规范

不再总是默认使用 ssl.create_default_contextssl 模块将被修改为

  • 当模块首次导入到 Python 进程时读取 PYTHONHTTPSVERIFY 环境变量
  • 如果此环境变量存在且设置为 '0',则将 ssl._create_default_https_context 函数设置为 ssl._create_unverified_context 的别名
  • 否则,将 ssl._create_default_https_context 函数照常设置为 ssl.create_default_context 的别名

示例实现

_https_verify_envvar = 'PYTHONHTTPSVERIFY'

def _get_https_context_factory():
    if not sys.flags.ignore_environment:
        config_setting = os.environ.get(_https_verify_envvar)
        if config_setting == '0':
            return _create_unverified_context
    return create_default_context

_create_default_https_context = _get_https_context_factory()

安全注意事项

相对于 Python 3.4.3+ 和 Python 2.7.9->2.7.11 中的行为,这种方法确实引入了一种新的针对默认安全设置的降级攻击,可能允许足够坚定的攻击者将 Python 恢复到 CPython 2.7.8 及更早版本中使用的默认行为。

攻击面的轻微增加是以下关键原因

  • 安全敏感的应用程序仍应定义自己的 SSL 上下文
  • 本 PEP 中描述的迁移功能未添加到 Python 3

然而,值得记住的是,进行此类攻击需要能够在导入 ssl 模块之前修改 Python 进程的执行环境。结合写入文件系统任何部分(如 /tmp)的能力,任何具有此类访问权限的攻击者已经能够修改底层 OpenSSL 实现、动态库加载器以及其他可能对安全性敏感的组件的行为。

与 Python 虚拟环境的交互

默认设置直接从进程环境中读取,因此无论解释器是否在已激活的 Python 虚拟环境中运行,其工作方式都相同。

参考实现

一个实现上述两个功能的 Python 2.7 补丁已附加到相关追踪问题中。

将此 PEP 反向移植到更早的 Python 版本

如果此 PEP 被接受,则商业 Python 再分发商可以选择将此 PEP 中定义的按进程配置机制向后移植到早于 Python 2.7.9 的基本版本,**而不**同时向后移植PEP 476对整个 Python 安装默认行为的更改。

这种反向移植与本 PEP 中提出的机制的不同之处仅在于当 PYTHONHTTPSVERIFY 完全未设置时的默认行为:它将继续默认跳过证书验证。

在这种情况下,如果定义了 PYTHONHTTPSVERIFY 环境变量,并将其设置为 '0' **以外的**任何值,则应启用 HTTPS 证书验证。

特性检测

没有特定的属性指示这种情况适用。相反,它由 ssl._https_verify_certificatesssl._https_verify_envvar 属性存在于名义上早于 Python 2.7.12 的 Python 版本中来指示。

规范

实现此反向移植涉及反向移植 PEP 466、476 和此 PEP 中的更改,并对 ssl 模块中 PYTHONHTTPSVERIFY 环境变量的处理进行以下更改

  • 当模块首次导入到 Python 进程时读取 PYTHONHTTPSVERIFY 环境变量
  • 如果此环境变量存在且设置为除 '0' 以外的任何值,则将 ssl._create_default_https_context 函数设置为 ssl.create_default_context 的别名
  • 否则,将 ssl._create_default_https_context 函数设置为 ssl._create_unverified_context 的别名

示例实现

_https_verify_envvar = 'PYTHONHTTPSVERIFY'

def _get_https_context_factory():
    if not sys.flags.ignore_environment:
        config_setting = os.environ.get(_https_verify_envvar)
        if config_setting != '0':
            return create_default_context
    return _create_unverified_context

_create_default_https_context = _get_https_context_factory()

def _disable_https_default_verification():
    """Skip verification of HTTPS certificates by default"""
    global _create_default_https_context
    _create_default_https_context = _create_unverified_context

安全注意事项

此更改对于任何当前默认跳过标准库 HTTPS 客户端中证书验证的 Python 版本来说都将是一个严格的安全升级。需要考虑的技术权衡主要与所需的PEP 466反向移植的规模有关,而不是与任何安全相关的问题有关。

与 Python 虚拟环境的交互

默认设置直接从进程环境中读取,因此无论解释器是否在已激活的 Python 虚拟环境中运行,其工作方式都相同。

将 PEP 476 反向移植到更早的 Python 版本

上述的反向移植方法保持 Python 2.7 安装的默认 HTTPS 证书验证行为不变:验证证书仍需按连接或按进程选择启用。

为了允许在不破坏向后兼容性的情况下修改整个安装的默认行为,Red Hat 为 Red Hat Enterprise Linux 7.2+ 中的系统 Python 2.7 安装设计了一种配置机制,该机制提供

  • 一种选择启用模型,允许启用 HTTPS 证书验证的决定独立于升级到首次向后移植该功能的操作系统版本的决定
  • 系统管理员能够设置直接在系统 Python 安装中运行的 Python 应用程序和脚本的默认行为
  • 再分发商能够在将来某个时候考虑更改**新**安装的默认行为,而不会影响已明确配置为默认跳过 HTTPS 证书验证的现有安装

由于它仅影响向后移植到更早的 Python 2.7 版本,因此此更改未提议包含在上游 CPython 中,而是作为对选择向用户提供类似功能的其他再分发商的建议。

此 PEP 不对这一特定更改是否是一个好主意发表立场——相反,它建议,**如果**再分发商选择在早于 Python 2.7.9 的 Python 版本中使默认行为可配置,那么在不同再分发商之间保持一致的方法将对用户有利。

但是,此方法不应用于任何宣称提供 Python 2.7.9 或更高版本的 Python 安装,因为大多数 Python 用户会合理地期望所有此类环境默认验证 HTTPS 证书。

特性检测

ssl 模块上与此功能相关的标记属性是

_cert_verification_config = '<path to configuration file>'

这不仅使得检测功能的存在(或不存在)变得简单,还使得以编程方式确定相关配置文件名称成为可能。

示例实现

_cert_verification_config = '/etc/python/cert-verification.cfg'

def _get_https_context_factory():
    # Check for a system-wide override of the default behaviour
    context_factories = {
        'enable': create_default_context,
        'disable': _create_unverified_context,
        'platform_default': _create_unverified_context, # For now :)
    }
    import ConfigParser
    config = ConfigParser.RawConfigParser()
    config.read(_cert_verification_config)
    try:
        verify_mode = config.get('https', 'verify')
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        verify_mode = 'platform_default'
    default_factory = context_factories.get('platform_default')
    return context_factories.get(verify_mode, default_factory)

_create_default_https_context = _get_https_context_factory()

安全注意事项

此反向移植案例的具体建议旨在适用于特权、安全敏感进程,即使是那些在以下锁定配置中运行的进程

  • 从锁定的管理员控制目录而不是普通用户目录运行(防止基于 sys.path[0] 的权限提升攻击)
  • 使用 -E 开关运行(防止基于 PYTHON* 环境变量的权限提升攻击)
  • 使用 -s 开关运行(防止基于用户站点目录的权限提升攻击)
  • 使用 -S 开关运行(防止基于 sitecustomize 的权限提升攻击)

意图是,在使用此方法时,安装范围内的 HTTPS 验证被关闭的**唯一**原因是

  • 终端用户正在运行再分发商提供的 CPython 版本,而不是直接运行上游 CPython
  • 该再分发商已决定提供比上游项目更平滑的迁移路径,以默认验证 HTTPS 证书
  • 再分发商或本地基础设施管理员已确定保留默认的 2.7.9 之前的行为是适当的(至少暂时是)

使用管理员控制的配置文件而不是环境变量具有提供更平滑迁移路径的基本功能,即使对于使用 -E 开关运行的应用程序也是如此。

与 Python 虚拟环境的交互

此设置受解释器安装的范围限制,并影响所有使用该解释器的 Python 进程,无论解释器是否在已激活的 Python 虚拟环境中运行。

此推荐的来源

此建议基于 Red Hat Enterprise Linux 7.2 所采用的向后移植方法,该方法发布于本 PEP 的 2015 年 7 月原始草案中,并在此知识库文章中详细描述。Red Hat 为 Python 2.7.5 实现此向后移植的补丁可在 CentOS git 仓库中找到。

组合特性反向移植的推荐

如果再分发商选择将本 PEP 中基于环境变量的配置设置反向移植到也实现基于配置文件的 PEP 476 反向移植的修改版 Python 版本,则环境变量应优先于系统范围的配置设置。这允许为给定用户或应用程序更改设置,无论安装范围的默认行为如何。

示例实现

_https_verify_envvar = 'PYTHONHTTPSVERIFY'
_cert_verification_config = '/etc/python/cert-verification.cfg'

def _get_https_context_factory():
    # Check for an environmental override of the default behaviour
    if not sys.flags.ignore_environment:
        config_setting = os.environ.get(_https_verify_envvar)
        if config_setting is not None:
            if config_setting == '0':
                return _create_unverified_context
            return create_default_context

    # Check for a system-wide override of the default behaviour
    context_factories = {
        'enable': create_default_context,
        'disable': _create_unverified_context,
        'platform_default': _create_unverified_context, # For now :)
    }
    import ConfigParser
    config = ConfigParser.RawConfigParser()
    config.read(_cert_verification_config)
    try:
        verify_mode = config.get('https', 'verify')
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        verify_mode = 'platform_default'
    default_factory = context_factories.get('platform_default')
    return context_factories.get(verify_mode, default_factory)

_create_default_https_context = _get_https_context_factory()

来源:https://github.com/python/peps/blob/main/peps/pep-0493.rst

最后修改时间:2025-02-01 08:59:27 GMT