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

Python Enhancement Proposals

PEP 706 – Filter für tarfile.extractall

Autor:
Petr Viktorin <encukou at gmail.com>
Discussions-To:
Discourse thread
Status:
Final
Typ:
Standards Track
Erstellt:
09. Feb. 2023
Python-Version:
3.12
Post-History:
25. Jan. 2023, 15. Feb. 2023
Resolution:
Discourse-Nachricht

Inhaltsverzeichnis

Wichtig

Dieses PEP ist ein historisches Dokument. Die aktuelle, kanonische Dokumentation finden Sie jetzt unter tarfile-Dokumentation.

×

Siehe PEP 1, um Änderungen vorzuschlagen.

Zusammenfassung

Die Extraktionsmethoden in tarfile erhalten ein filter-Argument, mit dem Dateien abgelehnt oder Metadaten während der Archivextraktion geändert werden können. Drei vordefinierte benannte Filter werden bereitgestellt, die darauf abzielen, unerwartete oder gefährliche Funktionen einzuschränken. Diese können direkt verwendet oder als Grundlage für benutzerdefinierte Filter dienen.

Nach einer Deprekationsperiode wird ein strikter (aber sichererer) Filter zum Standard.

Motivation

Das tar-Format wird für verschiedene Anwendungsfälle verwendet, von denen viele unterschiedliche Anforderungen haben. Zum Beispiel:

  • Ein Backup einer UNIX-Workstation sollte alle Details wie Dateiberechtigungen, Symlinks zu Systemkonfigurationen und verschiedene Arten von Sonderdateien originalgetreu erhalten.
  • Beim Entpacken eines Datenpakets ist es viel wichtiger, dass das Entpacken keine unbeabsichtigten Folgen hat – wie z. B. das Offenlegen einer Passwortdatei, indem sie mit einem Symlink an einen öffentlichen Ort verknüpft wird.

Um all seine Anwendungsfälle zu unterstützen, hat das tar-Format viele Funktionen. In vielen Fällen ist es am besten, einige davon bei der Extraktion eines Archivs zu ignorieren oder zu verbieten.

Python ermöglicht die Extraktion von tar-Archiven mit tarfile.TarFile.extractall(), dessen Dokumentation warnt, niemals Archive aus nicht vertrauenswürdigen Quellen ohne vorherige Prüfung zu extrahieren. Es ist jedoch nicht klar, welche Art von Prüfung durchgeführt werden sollte. Tatsächlich ist es ziemlich schwierig, eine solche Prüfung korrekt durchzuführen. Infolgedessen machen es viele Leute nicht, oder sie führen die Prüfung falsch durch, was zu Sicherheitsproblemen wie CVE-2007-4559 führt.

Seit tarfile erstmals geschrieben wurde, ist es akzeptierter, dass Warnungen in der Dokumentation nicht ausreichen. Wann immer möglich, sollte eine unsichere Operation explizit angefordert werden; potenziell gefährliche Operationen sollten gefährlich aussehen. TarFile.extractall sieht jedoch bei einer Code-Überprüfung harmlos aus.

Tarfile-Extraktion wird auch über shutil.unpack_archive() zugänglich gemacht, was es dem Benutzer ermöglicht, sich nicht um die Art des zu verarbeitenden Archivs zu kümmern. Die API ist sehr einladend für die Extraktion von Archiven ohne vorherige Prüfung, obwohl die Dokumentation erneut davor warnt.

Es wurde argumentiert, dass Python nicht falsch liegt – es verhält sich genau wie dokumentiert –, aber das ist nebensächlich. Lassen Sie uns die Situation verbessern, anstatt Schuld zuzuweisen/zu vermeiden. Python und seine Dokumentation sind der beste Ort, um Dinge zu verbessern.

Begründung

Wie verbessern wir die Dinge? Leider müssen wir die Standardwerte ändern, was eine Bruch der Abwärtskompatibilität bedeutet. TarFile.extractall ist das, worauf die Leute zurückgreifen, wenn sie einen Tarball extrahieren müssen. Sein Standardverhalten muss sich ändern.

Was wäre das beste Verhalten? Das hängt vom Anwendungsfall ab. Wir werden also mehrere allgemeine „Richtlinien“ hinzufügen, um die Extraktion zu steuern. Sie basieren auf Anwendungsfällen und sollten idealerweise klare Sicherheitsimplikationen haben.

  • Aktuelles Verhalten: Vertrauen auf das Archiv. Geeignet z. B. als Baustein für Bibliotheken, die die Prüfung selbst durchführen, oder zum Extrahieren eines gerade erstellten Archivs.
  • Entpacken eines UNIX-Archivs: Folgt ungefähr dem GNU tar, z. B. Entfernen führender / aus Dateinamen.
  • Entpacken eines allgemeinen Datenarchivs: Der Anwendungsfall von shutil.unpack_archive(), bei dem es nicht wichtig ist, detailspezifische für tar oder Unix-ähnliche Dateisysteme zu erhalten.

Nach einer Deprekationsperiode wird die letzte Option – die eingeschränkteste, aber sicherste – zum Standard.

Selbst mit besseren allgemeinen Standardeinstellungen sollten Benutzer die Archive, die sie extrahieren, weiterhin überprüfen und möglicherweise einige der Metadaten ändern. Oberflächlich betrachtet sieht die folgende Methode heute vernünftig aus:

  • Aufruf von TarFile.getmembers
  • Überprüfung oder Änderung der TarInfo jedes Members
  • Übergabe des Ergebnisses an extractalls members

Es gibt jedoch einige Probleme mit diesem Ansatz:

  • Es ist möglich, TarInfo-Objekte zu ändern, aber die Änderungen daran wirken sich auf alle nachfolgenden Operationen mit demselben TarFile-Objekt aus. Dieses Verhalten ist für die meisten Verwendungen in Ordnung, aber trotzdem wäre es sehr überraschend, wenn TarFile.extractall dies standardmäßig tun würde.
  • Der Aufruf von getmembers kann teuer sein und erfordert ein seekbares Archiv.
  • Bei der Vorabprüfung von Members kann es notwendig sein, zu verfolgen, wie jedes Member das Dateisystem verändert hätte, z. B. wie Symlinks eingerichtet werden. Dies ist schwierig. Wir können von Benutzern nicht erwarten, dass sie das tun.

Um diese Probleme zu lösen, werden wir

  • eine unterstützte Methode zum „Klonen“ und Ändern von TarInfo-Objekten bereitstellen. Eine replace-Methode, ähnlich wie dataclasses.replace() oder namedtuple._replace, sollte den Trick machen.
  • einen „Filter“-Hook in der Schleife von extractall bereitstellen, der Members ändern oder verwerfen kann, bevor sie verarbeitet werden.
  • verlangen, dass dieser Hook direkt vor der Extraktion jedes Members aufgerufen wird, damit er den aktuellen Zustand der Festplatte scannen kann. Dies vereinfacht die Implementierung von Richtlinien (sowohl in stdlib als auch im Benutzercode) erheblich, auf Kosten einer präzisen „Trockenlauf“-Fähigkeit.

Die Hook-API wird der vorhandenen filter-Argument für TarFile.add sehr ähnlich sein. Wir werden sie auch filter nennen. (In einigen Fällen wäre „policy“ ein passenderer Name, aber die API kann für mehr als nur Sicherheitsrichtlinien verwendet werden.)

Die integrierten Richtlinien/Filter, die oben beschrieben wurden, werden mithilfe der öffentlichen Filter-API implementiert, sodass sie als Bausteine oder Beispiele verwendet werden können.

Setzt einen Präzedenzfall

Wenn und wenn andere Bibliotheken für die Archivextraktion, wie z. B. zipfile, ähnliche Funktionalität erhalten, sollten sie diese API, soweit sinnvoll, nachahmen.

Um dies für einfache Fälle zu ermöglichen, erhalten die integrierten Filter Zeichenkettennamen; z. B. können Benutzer filter='data' anstelle einer spezifischen Funktion übergeben, die mit TarInfo-Objekten arbeitet.

Die Funktion shutil.unpack_archive() erhält ein filter-Argument, das sie an extractall weiterleitet.

Das Hinzufügen einer funktionsbasierten API, die über Archivformate hinweg funktioniert, liegt außerhalb des Rahmens dieses PEP.

Volle Offenlegung & Informationen für Weiterverteiler

Der PEP-Autor arbeitet für Red Hat, einen Weiterverteiler von Python mit anderen Sicherheitsanforderungen und Support-Zeiträumen als CPython im Allgemeinen. Solche Weiterverteiler möchten möglicherweise Vendor-Patches mitführen, um

  • die Standardwerte systemweit konfigurieren zu können und
  • den Standard so schnell wie möglich zu ändern, auch in älteren Python-Versionen.

Der Vorschlag macht dies einfach und ermöglicht es Benutzern, die Einstellungen abzufragen.

Spezifikation

Ändern und Vergessen von Member-Metadaten

Die Klasse TarInfo erhält eine neue Methode, replace(), die ähnlich wie dataclasses.replace funktioniert. Sie gibt eine Kopie des TarInfo-Objekts zurück, mit Attributen, die als schlüsselwortbasierte Argumente ersetzt werden.

  • name
  • mtime
  • Modus
  • linkname
  • uid
  • gid
  • uname
  • gname

Jeder dieser Werte, außer name und linkname, darf auf None gesetzt werden. Wenn extract oder extractall auf ein solches None trifft, wird dieser Metadatenwert nicht gesetzt. (Wenn uname oder gname None ist, wird auf uid bzw. gid zurückgegriffen, als ob der Name nicht gefunden worden wäre.) Wenn addfile oder tobuf auf ein solches None trifft, löst es einen ValueError aus. Wenn list auf ein solches None trifft, wird ein Platzhalterstring gedruckt.

Die Dokumentation wird den Grund für die Existenz der Methode erwähnen: TarInfo-Objekte, die von TarFile.getmembers abgerufen werden, sind „live“; ihre direkte Änderung wirkt sich auf nachfolgende, nicht zusammenhängende Operationen aus.

Filter

Die Methoden TarFile.extract und TarFile.extractall erhalten einen schlüsselwortbasierten Parameter filter, der einen aufrufbaren Wert annimmt, der aufgerufen werden kann als

filter(/, member: TarInfo, path: str) -> TarInfo|None

wobei member das zu extrahierende Mitglied ist und path der Pfad ist, zu dem das Archiv extrahiert wird (d. h. er ist für jedes Mitglied gleich).

Wenn es verwendet wird, wird es bei jedem zu extrahierenden Mitglied aufgerufen, und die Extraktion funktioniert mit dem Ergebnis. Wenn es None zurückgibt, wird das Mitglied übersprungen.

Die Funktion kann auch eine Ausnahme auslösen. Dies kann, abhängig von TarFile.errorlevel, die Extraktion abbrechen oder dazu führen, dass das Mitglied übersprungen wird.

Hinweis

Wenn die Extraktion abgebrochen wird, kann das Archiv teilweise extrahiert bleiben. Es liegt in der Verantwortung des Benutzers, aufzuräumen.

Wir werden auch eine Reihe von Standardwerten für gängige Anwendungsfälle bereitstellen. Zusätzlich zu einer Funktion kann das filter-Argument eine der folgenden Zeichenketten sein:

  • 'fully_trusted': Aktuelles Verhalten: Metadaten unverändert übernehmen. Sollte verwendet werden, wenn der Benutzer dem Archiv vollständig vertraut oder seine eigene komplexe Verifizierung implementiert.
  • 'tar': Verhält sich ungefähr wie die Standardeinstellungen des GNU tar-Befehls (wenn er als normaler Benutzer ausgeführt wird)
    • Entfernt führende '/' und os.sep aus Dateinamen
    • Verweigert die Extraktion von Dateien mit absoluten Pfaden (nach dem obigen Entfernen von /, z. B. C:/foo unter Windows).
    • Verweigert die Extraktion von Dateien, deren absoluter Pfad (nach dem Auflösen von Symlinks) außerhalb des Ziels liegt. (Beachten Sie, dass GNU tar stattdessen die Erstellung einiger Links verzögert.)
    • Löscht hohe Modusbits (setuid, setgid, sticky) und Gruppen-/andere Schreibbits (S_IWGRP|S_IWOTH). (Dies ist eine Annäherung an die Standardeinstellung von GNU tar, die den Modus anhand der aktuellen umask-Einstellung begrenzt.)
  • 'data': Extrahiert ein „Daten“-Archiv, verbietet gängige Angriffsvektoren, schränkt aber die Funktionalität ein. Insbesondere werden viele Funktionen, die für UNIX-ähnliche Dateisysteme (oder gleichwertig, für das tar-Archivformat) spezifisch sind, ignoriert, was dies zu einem guten Filter für plattformübergreifende Archive macht. Zusätzlich zu tar:
    • Verweigert die Extraktion von Links (hart oder symbolisch), die auf absolute Pfade verweisen.
    • Verweigert die Extraktion von Links (hart oder symbolisch), die auf einen Pfad außerhalb des Ziels verweisen. (Auf Systemen, die keine Links unterstützen, fällt tarfile in den meisten Fällen auf die Erstellung regulärer Dateien zurück. Dieser Vorschlag ändert dieses Verhalten nicht.)
    • Verweigert die Extraktion von Gerätedateien (einschließlich Pipes).
    • Für reguläre Dateien und Hardlinks
      • Setzt die Lese- und Schreibberechtigungen des Besitzers (S_IRUSR|S_IWUSR).
      • Entfernt die Gruppen- & anderen ausführbaren Berechtigungen (S_IXGRP|S_IXOTH), wenn der Besitzer diese nicht hat (S_IXUSR).
    • Für andere Dateien (Verzeichnisse) wird der Modus vollständig ignoriert (auf None gesetzt).
    • Ignoriert Benutzer- und Gruppeninformationen (setzt uid, gid, uname, gname auf None).

Jede andere Zeichenkette verursacht einen ValueError.

Die entsprechenden Filterfunktionen werden als tarfile.fully_trusted_filter(), tarfile.tar_filter() usw. verfügbar sein, sodass sie leicht in benutzerdefinierten Richtlinien verwendet werden können.

Beachten Sie, dass diese Filter niemals None zurückgeben. Das Überspringen von Members auf diese Weise ist eine Funktion für benutzerdefinierte Filter.

Standardwerte und deren Konfiguration

TarFile erhält ein neues Attribut, extraction_filter, um die Konfiguration des Standardfilters zu ermöglichen. Standardmäßig ist es None, aber Benutzer können es auf einen aufrufbaren Wert setzen, der verwendet wird, wenn das filter-Argument fehlt oder None ist.

Hinweis

Zeichenkettennamen werden hier nicht akzeptiert. Dies würde Code wie my_tarfile.extraction_filter = 'data' fördern. In Python-Versionen ohne diese Funktion würde dies nichts tun und eine sicherheitsbezogene Anfrage stillschweigend ignorieren.

Wenn sowohl das Argument als auch das Attribut None sind

  • In Python 3.12-3.13 wird eine DeprecationWarning ausgegeben und die Extraktion verwendet den 'fully_trusted'-Filter.
  • Ab Python 3.14+ verwendet er den 'data'-Filter.

Anwendungen und Systemintegratoren möchten möglicherweise extraction_filter der Klasse TarFile selbst ändern, um einen globalen Standard festzulegen. Bei Verwendung einer Funktion werden sie diese im Allgemeinen in staticmethod() verpacken, um die Injektion eines self-Arguments zu verhindern.

Unterklassen von TarFile können auch extraction_filter überschreiben.

FilterError

Eine neue Ausnahme, FilterError, wird dem Modul tarfile hinzugefügt. Sie wird mehrere neue Unterklassen haben, eine für jeden der oben genannten Ablehnungsgründe. Das member-Attribut von FilterError enthält die relevante TarInfo.

In den obigen Listen bedeutet „Verweigern“ der Extraktion einer Datei, dass ein FilterError ausgelöst wird. Wie bei anderen Extraktionsfehlern bricht dies die Extraktion ab, wenn TarFile.errorlevel 1 oder höher ist; bei errorlevel=0 wird der Fehler protokolliert und das Mitglied ignoriert, aber die Extraktion wird fortgesetzt. Beachten Sie, dass extractall() das Archiv möglicherweise teilweise extrahiert zurücklässt; es liegt in der Verantwortung des Benutzers, aufzuräumen.

Fehlerstufe und fatale/nicht-fatale Fehler

Derzeit hat TarFile ein errorlevel-Argument/Attribut, das angibt, wie Fehler behandelt werden.

  • Bei errorlevel=0 besagt die Dokumentation, dass „alle Fehler bei der Verwendung von extract() und extractall() ignoriert werden“. Der Code ignoriert nur nicht-fatale und fatale Fehler (siehe unten), sodass Sie beispielsweise immer noch einen TypeError erhalten, wenn Sie None als Zielpfad übergeben.
  • Bei errorlevel=1 (dem Standard) werden alle nicht-fatalen Fehler ignoriert. (Sie können nach sys.stderr protokolliert werden, indem das debug-Argument/-Attribut gesetzt wird.) Welche Fehler nicht-fatal sind, ist nicht in der Dokumentation definiert, aber der Code behandelt ExtractionError als solche. Insbesondere handelt es sich um diese Probleme:
    • „Link kann nicht innerhalb des Archivs aufgelöst werden“ (ausgelöst auf Systemen, die keine Symlinks unterstützen)
    • „Fifo/Sondergeräte werden vom System nicht unterstützt“ (wird nicht für Fehler verwendet, wenn das System diese unterstützt, z. B. für einen PermissionError)
    • „Besitzer/Modus/Änderungszeit konnte nicht geändert werden“

    Beachten Sie, dass z. B. Dateiname zu lang oder Festplattenspeicher voll nicht dazu gehören. Die nicht-fatalen Fehler treten auf Unix-ähnlichen Systemen eher selten auf.

  • Bei errorlevel=2 werden alle Fehler ausgelöst, einschließlich fataler. Welche Fehler fatal sind, ist wieder nicht definiert; praktisch ist es OSError.

Ein Filter, der die Extraktion eines Members verweigert, passt nicht sauber in die Kategorien fatal/nicht-fatal.

  • Dieses PEP ändert das bestehende Verhalten nicht. (Ideen für Verbesserungen sind in Discourse topic 25970 willkommen.)
  • Wenn ein Filter die Extraktion eines Members verweigert, sollte der Fehler standardmäßig nicht lautlos vorübergehen.

Um dies zu gewährleisten, wird FilterError als fataler Fehler betrachtet, d. h. er wird nur mit errorlevel=0 ignoriert.

Benutzer, die FilterError, aber nicht andere fatale Fehler ignorieren möchten, sollten eine benutzerdefinierte Filterfunktion erstellen und in einem try-Block einen anderen Filter aufrufen.

Hinweise für weitere Überprüfung

Selbst mit den vorgeschlagenen Änderungen ist tarfile nicht für die Extraktion nicht vertrauenswürdiger Dateien ohne vorherige Prüfung geeignet. Unter anderem verhindern die vorgeschlagenen Richtlinien keine Denial-of-Service-Angriffe. Benutzer sollten zusätzliche Prüfungen durchführen.

Neue Dokumente werden den Benutzern empfehlen, Folgendes zu beachten:

  • Extrahieren in ein neues leeres Verzeichnis,
  • Verwendung von externen (z. B. OS-Level) Grenzen für Festplatten-, Speicher- und CPU-Auslastung,
  • Überprüfung von Dateinamen gegen eine Whitelist von Zeichen (um Steuerzeichen, Verwechslungsgefahr usw. zu filtern),
  • Überprüfung, ob Dateinamen erwartete Erweiterungen haben (um Dateien abzuschrecken, die beim „Anklicken“ ausgeführt werden, oder dateilose Dateien wie spezielle Windows-Gerätenamen),
  • Begrenzung der Anzahl extrahierter Dateien, der Gesamtgröße der extrahierten Daten und der Größe einzelner Dateien,
  • Prüfung auf Dateien, die auf fallunempfindlichen Dateisystemen überschattet würden.

Außerdem wird die Dokumentation darauf hinweisen, dass

  • tar-Dateien häufig mehrere Versionen derselben Datei enthalten: spätere sollen frühere beim Extrahieren überschreiben,
  • tarfile schützt nicht vor Problemen mit „lebenden“ Daten, z. B. wenn ein Angreifer das Zielverzeichnis während der Extraktion (oder Hinzufügung) manipuliert (siehe das GNU tar-Handbuch für weitere Informationen).

Diese Liste ist nicht erschöpfend, aber die Dokumentation ist ein guter Ort, um solche allgemeinen Tipps zu sammeln. Sie kann in ein separates Dokument verschoben werden, wenn sie zu lang wird oder wenn sie mit zipfile oder shutil konsolidiert werden muss (was außerhalb des Rahmens dieses Vorschlags liegt).

TarInfo-Identität und offset

Bei Filtern, die replace() verwenden, sind die von der Extraktionsmaschinerie verarbeiteten TarInfo-Objekte nicht notwendigerweise dieselben Objekte wie die in members vorhandenen. Dies kann TarInfo-Unterklassen beeinträchtigen, die Methoden wie makelink überschreiben und von der Objektidentität abhängen.

Solcher Code kann auf den Vergleich von offset, der Position des Member-Headers innerhalb der Datei, umstellen.

Beachten Sie, dass sowohl die überschreibbaren Methoden als auch offset nur in Quellcode-Kommentaren dokumentiert sind.

Tarfile CLI

Die CLI (python -m tarfile) erhält eine Option --filter, die den Namen eines der bereitgestellten Standardfilter annimmt. Es wird nicht möglich sein, eine benutzerdefinierte Filterfunktion anzugeben.

Wenn --filter nicht angegeben wird, verwendet die CLI den Standardfilter ('fully_trusted' mit einer Deprecation-Warnung jetzt und 'data' ab Python 3.14).

Es wird keine Kurzoption geben. (Die Option -f wäre verwirrend ähnlich zur Dateinamenoption von GNU tar.)

Andere Archivbibliotheken

Wenn und wenn andere Archivbibliotheken, wie z. B. zipfile, ähnliche Funktionalität erhalten, sollten ihre Extraktionsfunktionen ein filter-Argument verwenden, das zumindest die Zeichenketten 'fully_trusted' (was alle Sicherheitsvorkehrungen deaktivieren sollte) und 'data' (was Funktionen vermeiden sollte, die Benutzer überraschen könnten) annimmt.

Die Standardisierung einer funktionsbasierten Filter-API liegt außerhalb des Rahmens dieses PEP.

Shutil

shutil.unpack_archive() erhält ein filter-Argument. Wenn es angegeben wird, wird es an die zugrunde liegende Extraktionsfunktion übergeben. Die Übergabe für ein zip-Archiv schlägt vorerst fehl (bis zipfile ein filter-Argument erhält, falls jemals).

Wenn filter nicht angegeben ist (oder als None belassen wird), wird es nicht weitergegeben, sodass die Extraktion eines Tarballs den Standardfilter verwendet ('fully_trusted' mit einer Deprecation-Warnung jetzt und 'data' ab Python 3.14).

Komplexe Filter

Beachten Sie, dass einige benutzerdefinierte Filter beispielsweise die Anzahl der extrahierten Members zählen oder Nachbearbeitungen durchführen müssen. Dies erfordert eine komplexere API als ein filter-Callable. Diese komplexe API muss jedoch nicht für tarfile zugänglich gemacht werden. Mit einem hypothetischen StatefulFilter würden Benutzer zum Beispiel schreiben:

with StatefulFilter() as filter_func:
    my_tar.extract(path, filter=filter_func)

Ein einfaches StatefulFilter-Beispiel wird der Dokumentation hinzugefügt.

Hinweis

Die Notwendigkeit von zustandsbehafteten Filtern ist ein Grund gegen die Zulassung der Registrierung benutzerdefinierter Filter-Namen zusätzlich zu 'fully_trusted', 'tar' und 'data'. Mit einem solchen Mechanismus müsste die API für (zumindest) Setup und Teardown in Stein gemeißelt werden.

Abwärtskompatibilität

Das Standardverhalten von TarFile.extract und TarFile.extractall ändert sich, nachdem für 2 Releases DeprecationWarnings ausgegeben wurden (kürzeste in Pythons Abwärtskompatibilitätsrichtlinie zulässige Deprekationsperiode).

Zusätzlich kann Code, der von der Objektidentität von tarfile.TarInfo abhängt, unterbrochen werden. Siehe TarInfo-Identität und offset.

Backporting & Vorwärtskompatibilität

Dieses Feature kann auf ältere Python-Versionen zurückportiert werden.

In CPython fügen wir keine Warnungen zu Patch-Releases hinzu, daher sollte der Standardfilter in Backports auf 'fully_trusted' geändert werden.

Abgesehen davon sollten alle Änderungen an tarfile zurückportiert werden, sodass hasattr(tarfile, 'data_filter') ein zuverlässiger Check für alle neuen Funktionalitäten wird.

Beachten Sie, dass die übliche Politik von CPython darin besteht, neue APIs in Sicherheits-Backports zu vermeiden. Dieses Feature ergibt ohne eine neue API (TarFile.extraction_filter und das filter-Argument) keinen Sinn, daher machen wir eine Ausnahme. (Siehe Discourse-Kommentar 23149/16 für Details.)

Hier sind Beispiele für Code, der berücksichtigt, dass tarfile das vorgeschlagene Feature möglicherweise hat oder nicht.

Beachten Sie beim Kopieren dieser Snippets, dass das Setzen von extraction_filter nachfolgende Operationen beeinflusst.

  • Vollständig vertrauenswürdiges Archiv
    my_tarfile.extraction_filter = (lambda member, path: member)
    my_tarfile.extractall()
    
  • Verwenden Sie den 'data'-Filter, falls verfügbar, aber greifen Sie auf das Verhalten von Python 3.11 zurück ('fully_trusted'), falls dieses Feature nicht verfügbar ist.
    my_tarfile.extraction_filter = getattr(tarfile, 'data_filter',
                                           (lambda member, path: member))
    my_tarfile.extractall()
    

    (Dies ist eine unsichere Operation und sollte daher explizit aufgeführt werden, idealerweise mit einem Kommentar.)

  • Verwenden Sie den 'data'-Filter; geben Sie eine Fehlermeldung aus, falls er nicht verfügbar ist.
    my_tarfile.extractall(filter=tarfile.data_filter)
    

    oder

    my_tarfile.extraction_filter = tarfile.data_filter
    my_tarfile.extractall()
    
  • Verwenden Sie den 'data'-Filter; geben Sie eine Warnung aus, falls er nicht verfügbar ist.
    if hasattr(tarfile, 'data_filter'):
        my_tarfile.extractall(filter='data')
    else:
        # remove this when no longer needed
        warn_the_user('Extracting may be unsafe; consider updating Python')
        my_tarfile.extractall()
    

Sicherheitsimplikationen

Dieser Vorschlag verbessert die Sicherheit auf Kosten der Abwärtskompatibilität. Insbesondere hilft er Benutzern, CVE-2007-4559 zu vermeiden.

Wie man das lehrt

Die API, Nutzungshinweise und Tipps zur weiteren Überprüfung werden der Dokumentation hinzugefügt. Diese sollten für Benutzer verständlich sein, die mit Archiven im Allgemeinen vertraut sind, aber nicht mit den Besonderheiten von UNIX-Dateisystemen oder den damit verbundenen Sicherheitsproblemen.

Referenzimplementierung

Siehe Pull Request #102953 auf GitHub.

Abgelehnte Ideen

SafeTarFile

Eine erste Idee von Lars Gustäbel war, eine separate Klasse bereitzustellen, die Sicherheitsprüfungen implementiert (siehe gh-65308). Dieser Ansatz hat zwei Hauptprobleme:

  • Der Name ist irreführend. Allgemeine Archivoperationen können niemals "sicher" gegen alle Arten von unerwünschtem Verhalten gemacht werden, ohne legitime Anwendungsfälle zu beeinträchtigen.
  • Das Problem unsicherer Standardeinstellungen wird nicht gelöst.

Viele der Ideen hinter SafeTarFile wurden jedoch in diesem PEP wiederverwendet.

Option absolute_path zu tarfile hinzufügen

Das Issue gh-73974 fordert die Hinzufügung einer Option absolute_path zu Extraktionsmethoden. Dies wäre eine minimale Änderung, um CVE-2007-4559 formell zu beheben. Es geht nicht weit genug, um die Unwissenden zu schützen, noch um die Fleißigen und Neugierigen zu befähigen.

Andere Namen für den 'tar'-Filter

Der 'tar'-Filter gibt Features preis, die für UNIX-ähnliche Dateisysteme spezifisch sind, daher könnte er 'unix' genannt werden. Oder 'unix-like', 'nix', '*nix', 'posix'?

Funktional gesehen sind *Tar-Format* und *UNIX-ähnliches Dateisystem* im Wesentlichen äquivalent, daher ist tar ein guter Name.

Mögliche weitere Arbeit

Hinzufügen von Filtern zu zipfile und shutil.unpack_archive

Zur Konsistenz könnten zipfile und shutil.unpack_archive() die Unterstützung für ein filter-Argument erhalten. Dies würde jedoch Forschung erfordern, die der Autor dieses PEPs für Python 3.12 nicht versprechen kann.

Filter für zipfile würden wahrscheinlich nicht zur Sicherheit beitragen. Zip wird hauptsächlich für plattformübergreifende Datenpakete verwendet, und entsprechend sind die Standardeinstellungen von ZipFile.extract bereits ähnlich dem, was ein 'data'-Filter tun würde. Ein 'fully_trusted'-Filter, der absolute Pfade und ..-Pfadkomponenten neu zulassen würde, wäre möglicherweise nicht für viel anderes nützlich als eine einheitliche unpack_archive-API.

Filter sollten für andere Anwendungsfälle als die Sicherheit nützlich sein, aber diese würden normalerweise benutzerdefinierte Filterfunktionen benötigen, und diese würden eine API benötigen, die sowohl mit TarInfo als auch mit ZipInfo funktioniert. Das liegt definitiv außerhalb des Rahmens dieses PEPs.

Wenn nur dieses PEP implementiert wird und sich für zipfile nichts ändert, ist die Auswirkung für Aufrufer von unpack_archive, dass der Standard für Tar-Dateien von 'fully_trusted' auf das passendere 'data' geändert wird. In der Übergangszeit werden Python 3.12-3.13 eine DeprecationWarning ausgeben. Das ist ärgerlich, aber es gibt mehrere Möglichkeiten, damit umzugehen: z. B. ein filter-Argument bedingt hinzufügen, TarFile.extraction_filter global setzen oder die Warnung bis Python 3.14 ignorieren/unterdrücken.

Da viele Aufrufe von unpack_archive wahrscheinlich unsicher sind, besteht die Hoffnung, dass die DeprecationWarning oft ein hilfreicher Hinweis sein wird, den betroffenen Code zu überprüfen.

Danksagungen

Dieser Vorschlag basiert auf früheren Arbeiten und Diskussionen vieler Personen, insbesondere von Lars Gustäbel, Gregory P. Smith, Larry Hastings, Joachim Wagner, Jan Matejek, Jakub Wilk, Daniel Garcia, Lumír Balhar, Miro Hrončok und vielen anderen.


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

Zuletzt geändert: 2025-02-01 08:55:40 GMT