Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 493 – Migrationstools für HTTPS-Verifizierung in Python 2.7

Autor:
Alyssa Coghlan <ncoghlan at gmail.com>, Robert Kuska <rkuska at redhat.com>, Marc-André Lemburg <mal at lemburg.com>
BDFL-Delegate:
Barry Warsaw
Status:
Final
Typ:
Standards Track
Erstellt:
10. Mai 2015
Python-Version:
2.7.12
Post-History:
06. Jul 2015, 11. Nov 2015, 24. Nov 2015, 24. Feb 2016
Resolution:
Python-Dev Nachricht

Inhaltsverzeichnis

Zusammenfassung

PEP 476 hat die Standardhandhabung von HTTPS-Zertifikaten in Client-Modulen von Python aktualisiert, um sie mit der Zertifikatsbehandlung in Webbrowsern in Einklang zu bringen, indem überprüft wurde, ob die empfangenen Zertifikate zu dem Server gehörten, den der Client zu kontaktieren versuchte. Die Python 2.7 Langzeitwartungsserie wurde als im Geltungsbereich dieser Änderung betrachtet, wobei das neue Verhalten in der Python 2.7.9 Wartungsversion eingeführt wurde.

Dies hat eine nicht triviale Hürde für die Übernahme von betroffenen Python 2.7 Wartungsversionen geschaffen. Daher schlägt dieser PEP zusätzliche, spezifisch für Python 2.7 geltende Features vor, die es Systemadministratoren und anderen Benutzern ermöglichen, die Entscheidung zur Überprüfung von Serverzertifikaten in HTTPS-Client-Modulen leichter von der Entscheidung zur Aktualisierung auf neuere Python 2.7 Wartungsversionen zu entkoppeln.

Begründung

PEP 476 hat das Standardverhalten von Python an die von Webbrowsern etablierten Erwartungen bezüglich der Semantik von HTTPS-URLs angepasst: Ab Python 2.7.9 und 3.4.3 validieren HTTPS-Clients in der Standardbibliothek standardmäßig Serverzertifikate.

Es trifft jedoch auch zu, dass diese Änderung Probleme für Infrastrukturadministratoren verursacht, die private Intranets betreiben, die auf selbstsignierte Zertifikate angewiesen sind, oder anderweitig Probleme mit den neuen Standardeinstellungen für die Zertifikatüberprüfung haben.

Um solche Situationen zu bewältigen, stellen Webbrowser Benutzern „Durchklickwarnungen“ zur Verfügung, die es dem Benutzer erlauben, das Zertifikat des Servers zum Zertifikatsspeicher des Browsers hinzuzufügen. Netzwerkkclient-Tools wie curl und wget bieten Optionen, um die Zertifikatprüfung vollständig auszuschalten (mittels curl --insecure und wget --no-check-certificate, jeweils).

Auf einer anderen Ebene des Technologiestacks bieten Linux-Sicherheitsmodule wie SELinux und AppArmor, obwohl standardmäßig von den Distributionsanbietern aktiviert, relativ einfache Mechanismen, um sie auszuschalten.

Derzeit existieren keine derart bequemen Mechanismen, um Pythons Standard-Zertifikatprüfung für einen ganzen Prozess zu deaktivieren.

PEP 476 hat versucht, diese Frage zu behandeln, indem er beschrieb, wie man die alten Einstellungen prozessweit zurücksetzen kann, indem das ssl-Modul per Monkeypatching verändert wird, um das alte Verhalten wiederherzustellen. Leider hat sich die vorgeschlagene Technik auf Basis von sitecustomize.py, die es Systemadministratoren ermöglicht, das Feature standardmäßig in ihrer Standardbetriebsumgebungsdefinition zu deaktivieren, in mindestens einigen Fällen als unzureichend erwiesen. Der spezifische Fall, der zur anfänglichen Erstellung dieses PEP führte, ist der, bei dem ein Linux-Distributor seinen Benutzern einen glatteren Migrationspfad als den von der direkten Nutzung von Upstream CPython 2.7-Releases angebotenen bieten möchte, aber auch andere potenzielle Herausforderungen beim Aktualisieren von eingebetteten Python-Laufzeiten und anderen Benutzerinstallationen von Python wurden aufgezeigt.

Anstatt einer Vielzahl von sich gegenseitig ausschließenden Migrationstechniken zu erlauben, sich zu entwickeln, schlägt dieser PEP ein zusätzliches Feature für Python 2.7.12 vor, das es einfacher macht, einen Prozess auf das frühere Verhalten zurückzusetzen, bei dem die Zertifikatsvalidierung in HTTPS-Client-Modulen übersprungen wird. Er bietet auch zusätzliche Empfehlungen für Redistributoren, die diese Features auf Versionen von Python vor Python 2.7.9 backporten.

Alternativen

Mangels klarer Upstream-Anleitung und Empfehlungen werden kommerzielle Redistributoren weiterhin ihre eigenen Designentscheidungen im Interesse ihrer Kunden treffen. Die wichtigsten verfügbaren Ansätze sind

  • Weiterhin auf neuen Python 2.7.x Releases basieren und keine zusätzliche Unterstützung über die in PEP 476 definierten Mechanismen hinaus für die Migration von nicht überprüften zu überprüften Hostnamen in Standardbibliotheks-HTTPS-Clients bieten
  • Die Verfügbarkeit der Änderungen bei der Standardhandhabung von HTTPS-Verbindungen an die Aktualisierung von Python 2 auf Python 3 koppeln
  • Für Linux-Distributionsanbieter die Verfügbarkeit der Änderungen bei der Standardhandhabung von HTTPS-Verbindungen an die Aktualisierung auf eine neue Betriebssystemversion koppeln
  • Implementieren einer oder beider in diesem PEP beschriebenen Backport-Vorschläge, unabhängig vom formalen Status des PEP

Einschränkungen des Geltungsbereichs

Diese Änderungen werden rein als Werkzeuge zur Unterstützung der Transition zum neuen Standardverhalten der Zertifikatsbehandlung im Kontext von Python 2.7 vorgeschlagen. Sie werden nicht als neue Features für Python 3 vorgeschlagen, da erwartet wird, dass die überwiegende Mehrheit der von diesem Problem betroffenen Client-Anwendungen, die nicht die Anwendung selbst aktualisieren können, Python 2-Anwendungen sein werden.

Es wäre wünschenswert, wenn eine zukünftige Version von Python 3 die Standard-Zertifikatbehandlung für sichere Protokolle pro Protokoll konfigurierbar machen würde, aber diese Frage liegt außerhalb des Geltungsbereichs dieses PEP.

Anforderungen für die Fähigkeitserkennung

Da die Vorschläge in diesem PEP darauf abzielen, Backports auf frühere Python-Versionen zu erleichtern, kann die Python-Versionsnummer nicht als zuverlässiges Mittel zur Erkennung dieser verwendet werden. Stattdessen sind sie so konzipiert, dass das Vorhandensein oder Fehlen des Features mit der folgenden Technik bestimmt werden kann

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

Dies schlägt mit AttributeError fehl (und somit mit einem Rückgabecode ungleich Null), wenn die relevante Fähigkeit nicht verfügbar ist.

Die von diesem PEP definierten Feature-Erkennungsattribute sind

  • ssl._https_verify_certificates: Laufzeitkonfigurations-API
  • ssl._https_verify_envvar: Umgebungsbasierte Konfiguration
  • ssl._cert_verification_config: Datei-basierte Konfiguration (PEP 476 Opt-in)

Die Marker-Attribute sind mit einem Unterstrich präfixiert, um die Implementierungsabhängigkeit und sicherheitskritische Natur dieser Fähigkeiten anzuzeigen.

Feature: Konfigurations-API

Diese Änderung wird zur Aufnahme in CPython 2.7.12 und spätere CPython 2.7.x Releases vorgeschlagen. Sie besteht aus einer neuen Funktion ssl._https_verify_certificates() zur Festlegung der Standardhandhabung von HTTPS-Zertifikaten in Client-Bibliotheken der Standardbibliothek.

Es ist nicht geplant, diese Änderung nach Python 3 zu portieren. Daher müssen Python 3-Anwendungen, die das Überspringen der Zertifikatüberprüfung unterstützen müssen, weiterhin ihren eigenen geeigneten Sicherheitskontext definieren.

Feature-Erkennung

Das Marker-Attribut im ssl-Modul im Zusammenhang mit diesem Feature ist die Funktion ssl._https_verify_certificates selbst.

Spezifikation

Die Funktion ssl._https_verify_certificates funktioniert wie folgt:

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

Wenn sie ohne Argumente aufgerufen wird oder wenn enable auf einen wahren Wert gesetzt ist, überprüfen die Client-Module der Standardbibliothek anschließend standardmäßig HTTPS-Zertifikate, andernfalls überspringen sie die Überprüfung.

Wenn sie mit enable auf einen falschen Wert gesetzt aufgerufen wird, überspringen die Client-Module der Standardbibliothek anschließend standardmäßig die Überprüfung von HTTPS-Zertifikaten.

Sicherheitsüberlegungen

Die Einbeziehung dieses Features ermöglicht es sicherheitskritischen Anwendungen, den folgenden vorwärtskompatiblen Ausschnitt in ihren Code aufzunehmen:

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

Einige Entwickler können sich auch entscheiden, die Zertifikatprüfung mittels ssl._https_verify_certificates(enable=False) zu deaktivieren. Dies führt keine größeren neuen Sicherheitsprobleme ein, da das Monkeypatching der betroffenen internen APIs bereits möglich war.

Feature: Umgebungsbasierte Konfiguration

Diese Änderung wird zur Aufnahme in CPython 2.7.12 und spätere CPython 2.7.x Releases vorgeschlagen. Sie besteht aus einer neuen Umgebungsvariable PYTHONHTTPSVERIFY, die auf '0' gesetzt werden kann, um die Standardüberprüfung zu deaktivieren, ohne den Anwendungscode zu ändern (der in Fällen von reiner Bytecode-Distribution möglicherweise nicht einmal verfügbar ist).

Es ist nicht geplant, diese Änderung nach Python 3 zu portieren. Daher müssen Python 3-Anwendungen, die das Überspringen der Zertifikatüberprüfung unterstützen müssen, weiterhin ihren eigenen geeigneten Sicherheitskontext definieren.

Feature-Erkennung

Das Marker-Attribut im ssl-Modul im Zusammenhang mit diesem Feature ist

  • das Attribut ssl._https_verify_envvar, das den Namen der Umgebungsvariable angibt, die das Standardverhalten beeinflusst.

Dies macht nicht nur die Erkennung der Anwesenheit (oder Abwesenheit) der Fähigkeit einfach, sondern ermöglicht auch die programmatische Ermittlung des relevanten Umgebungsnominalnamens.

Spezifikation

Anstatt immer standardmäßig ssl.create_default_context zu verwenden, wird das ssl-Modul wie folgt geändert:

  • liest die Umgebungsvariable PYTHONHTTPSVERIFY, wenn das Modul zum ersten Mal in einen Python-Prozess importiert wird.
  • setzt die Funktion ssl._create_default_https_context auf einen Alias für ssl._create_unverified_context, wenn diese Umgebungsvariable vorhanden und auf '0' gesetzt ist.
  • andernfalls setzt die Funktion ssl._create_default_https_context auf einen Alias für ssl.create_default_context wie üblich.

Beispielhafte Implementierung

_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()

Sicherheitsüberlegungen

Im Vergleich zum Verhalten in Python 3.4.3+ und Python 2.7.9->2.7.11 führt dieser Ansatz einen neuen Downgrade-Angriff auf die standardmäßigen Sicherheitseinstellungen ein, der potenziell einem hinreichend entschlossenen Angreifer ermöglicht, Python auf das Standardverhalten von CPython 2.7.8 und früheren Versionen zurückzusetzen.

Diese leichte Erhöhung der verfügbaren Angriffsfläche ist ein Hauptgrund dafür, dass

  • sicherheitskritische Anwendungen weiterhin ihren eigenen SSL-Kontext definieren sollten.
  • die in diesem PEP beschriebenen Migrationstools nicht in Python 3 integriert werden.

Es ist jedoch auch erwähnenswert, dass die Durchführung eines solchen Angriffs die Fähigkeit erfordert, die Ausführungsumgebung eines Python-Prozesses vor dem Import des ssl-Moduls zu modifizieren. In Kombination mit der Möglichkeit, auf beliebige Teile des Dateisystems zu schreiben (z. B. auf /tmp), könnte ein Angreifer mit solchem Zugriff bereits das Verhalten der zugrunde liegenden OpenSSL-Implementierung, des dynamischen Linkers und anderer potenziell sicherheitskritischer Komponenten modifizieren.

Interaktion mit Python-virtuellen Umgebungen

Die Standardeinstellung wird direkt aus der Prozessumgebung gelesen und funktioniert daher unabhängig davon, ob der Interpreter in einer aktivierten Python-virtuellen Umgebung ausgeführt wird oder nicht.

Referenzimplementierung

Ein Patch für Python 2.7, der die beiden oben genannten Features implementiert, ist an die relevante Tracker-Ausgabe angehängt.

Backporting dieses PEP auf frühere Python-Versionen

Wenn dieser PEP akzeptiert wird, können kommerzielle Python-Redistributoren die in diesem PEP definierten prozessweiten Konfigurationsmechanismen auf Basisversionen älter als Python 2.7.9 backporten, *ohne* die Änderung von PEP 476 zum Standardverhalten der gesamten Python-Installation ebenfalls zu backporten.

Ein solcher Backport würde sich vom in diesem PEP vorgeschlagenen Mechanismus nur im Standardverhalten unterscheiden, wenn PYTHONHTTPSVERIFY überhaupt nicht gesetzt war: Er würde weiterhin standardmäßig das Überspringen der Zertifikatsvalidierung verwenden.

In diesem Fall sollte, wenn die Umgebungsvariable PYTHONHTTPSVERIFY definiert und auf etwas anderes als '0' gesetzt ist, die HTTPS-Zertifikatüberprüfung aktiviert werden.

Feature-Erkennung

Es gibt kein spezifisches Attribut, das anzeigt, dass diese Situation zutrifft. Stattdessen wird sie durch das Vorhandensein der Attribute ssl._https_verify_certificates und ssl._https_verify_envvar in einer Python-Version angezeigt, die nominell älter als Python 2.7.12 ist.

Spezifikation

Die Implementierung dieses Backports beinhaltet das Backporting der Änderungen in PEP 466, 476 und diesem PEP, mit der folgenden Änderung an der Handhabung der Umgebungsvariable PYTHONHTTPSVERIFY im ssl-Modul:

  • liest die Umgebungsvariable PYTHONHTTPSVERIFY, wenn das Modul zum ersten Mal in einen Python-Prozess importiert wird.
  • setzt die Funktion ssl._create_default_https_context auf einen Alias für ssl.create_default_context, wenn diese Umgebungsvariable vorhanden und auf einen Wert ungleich '0' gesetzt ist.
  • andernfalls setzt die Funktion ssl._create_default_https_context auf einen Alias für ssl._create_unverified_context.

Beispielhafte Implementierung

_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

Sicherheitsüberlegungen

Diese Änderung wäre ein striktes Sicherheitsupgrade für jede Python-Version, die standardmäßig das Überspringen der Zertifikatsvalidierung in Standardbibliotheks-HTTPS-Clients verwendet. Die zu berücksichtigenden technischen Kompromisse beziehen sich hauptsächlich auf das Ausmaß des ebenfalls erforderlichen Backports von PEP 466 und nicht auf sicherheitsrelevante Aspekte.

Interaktion mit Python-virtuellen Umgebungen

Die Standardeinstellung wird direkt aus der Prozessumgebung gelesen und funktioniert daher unabhängig davon, ob der Interpreter in einer aktivierten Python-virtuellen Umgebung ausgeführt wird oder nicht.

Backporting von PEP 476 auf frühere Python-Versionen

Der oben beschriebene Backporting-Ansatz belässt das Standardverhalten der HTTPS-Zertifikatsvalidierung einer Python 2.7-Installation unverändert: Die Zertifikatsvalidierung muss weiterhin pro Verbindung oder pro Prozess aktiviert werden.

Um das Standardverhalten der gesamten Installation ändern zu können, ohne die Abwärtskompatibilität zu beeinträchtigen, hat Red Hat einen Konfigurationsmechanismus für die System-Python-2.7-Installation in Red Hat Enterprise Linux 7.2+ entwickelt, der Folgendes bietet:

  • ein Opt-in-Modell, das die Entscheidung zur Aktivierung der HTTPS-Zertifikatsvalidierung unabhängig von der Entscheidung zur Aktualisierung auf die Betriebssystemversion, in der das Feature erstmals backportet wurde, ermöglicht.
  • die Möglichkeit für Systemadministratoren, das Standardverhalten von Python-Anwendungen und -Skripten festzulegen, die direkt in der System-Python-Installation ausgeführt werden.
  • die Möglichkeit für den Redistributor, das Standardverhalten von *neuen* Installationen zu einem späteren Zeitpunkt zu ändern, ohne bestehende Installationen zu beeinträchtigen, die explizit so konfiguriert wurden, dass die Überprüfung von HTTPS-Zertifikaten standardmäßig übersprungen wird.

Da dies nur Backports auf frühere Releases von Python 2.7 betrifft, wird diese Änderung nicht zur Aufnahme in Upstream CPython vorgeschlagen, sondern als Empfehlung an andere Redistributoren, die ihren Benutzern eine ähnliche Funktionalität anbieten möchten.

Dieser PEP positioniert sich nicht dazu, ob diese spezielle Änderung eine gute Idee ist – vielmehr schlägt er vor, dass *wenn* ein Redistributor den Weg wählt, das Standardverhalten in einer Python-Version älter als Python 2.7.9 konfigurierbar zu machen, die Aufrechterhaltung eines konsistenten Ansatzes zwischen Redistributoren für die Benutzer von Vorteil wäre.

Dieser Ansatz sollte jedoch NICHT für eine Python-Installation verwendet werden, die sich als Python 2.7.9 oder neuer ausgibt, da die meisten Python-Benutzer die berechtigte Erwartung haben werden, dass alle solchen Umgebungen HTTPS-Zertifikate standardmäßig überprüfen.

Feature-Erkennung

Das Marker-Attribut im ssl-Modul im Zusammenhang mit diesem Feature ist

_cert_verification_config = '<path to configuration file>'

Dies macht nicht nur die Erkennung der Anwesenheit (oder Abwesenheit) der Fähigkeit einfach, sondern ermöglicht auch die programmatische Ermittlung des relevanten Konfigurationsdateinamens.

Beispielhafte Implementierung

_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()

Sicherheitsüberlegungen

Die spezifischen Empfehlungen für diesen Backporting-Fall sind so konzipiert, dass sie auch für privilegierte, sicherheitskritische Prozesse funktionieren, selbst wenn diese in der folgenden gesperrten Konfiguration ausgeführt werden:

  • Ausführung von einem gesperrten, administratorgesteuerten Verzeichnis anstelle eines normalen Benutzerverzeichnisses (verhindert Angriffe zur Privilegieneskalation basierend auf sys.path[0]).
  • Ausführung mit dem Schalter -E (verhindert Angriffe zur Privilegieneskalation basierend auf Umgebungsvariablen PYTHON*).
  • Ausführung mit dem Schalter -s (verhindert Angriffe zur Privilegieneskalation basierend auf benutzerdefinierten Verzeichnissen).
  • Ausführung mit dem Schalter -S (verhindert Angriffe zur Privilegieneskalation basierend auf sitecustomize).

Die Absicht ist, dass der *einzige* Grund, warum die HTTPS-Überprüfung bei Verwendung dieses Ansatzes installationsweit ausgeschaltet werden sollte, darin besteht, dass

  • ein Endbenutzer eine vom Redistributor bereitgestellte Version von CPython ausführt, anstatt Upstream CPython direkt auszuführen.
  • dieser Redistributor entschieden hat, einen glatteren Migrationspfad zur standardmäßigen HTTPS-Zertifikatsüberprüfung bereitzustellen als das, was vom Upstream-Projekt angeboten wird.
  • entweder der Redistributor oder der lokale Infrastrukturadministrator festgestellt hat, dass es angebracht ist, das Standardverhalten vor 2.7.9 beizubehalten (zumindest vorerst).

Die Verwendung einer administratorkontrollierten Konfigurationsdatei anstelle einer Umgebungsvariablen hat den wesentlichen Vorteil, einen glatteren Migrationspfad zu bieten, selbst für Anwendungen, die mit dem Schalter -E ausgeführt werden.

Interaktion mit Python-virtuellen Umgebungen

Diese Einstellung ist nach der Interpreterinstallation abgegrenzt und betrifft alle Python-Prozesse, die diesen Interpreter verwenden, unabhängig davon, ob der Interpreter in einer aktivierten Python-virtuellen Umgebung ausgeführt wird oder nicht.

Ursprünge dieser Empfehlung

Diese Empfehlung basiert auf dem Backporting-Ansatz, der für Red Hat Enterprise Linux 7.2 übernommen wurde, wie in der ursprünglichen Julidraft dieses PEP veröffentlicht und detailliert in diesem KnowledgeBase-Artikel beschrieben. Red Hats Patches zur Implementierung dieses Backports für Python 2.7.5 sind im CentOS Git-Repository zu finden.

Empfehlung für kombinierte Feature-Backports

Wenn ein Redistributor die Umgebungsvariablen-basierte Konfigurationseinstellung dieses PEP in eine modifizierte Python-Version backporten möchte, die auch den Konfigurationsdateien-basierten PEP 476-Backport implementiert, dann sollte die Umgebungsvariable Vorrang vor der systemweiten Konfigurationseinstellung haben. Dies ermöglicht die Änderung der Einstellung für einen bestimmten Benutzer oder eine Anwendung, unabhängig vom installationsweiten Standardverhalten.

Beispielhafte Implementierung

_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()

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

Zuletzt geändert: 2025-02-01 08:59:27 GMT