PEP 787 – Sicherere Verwendung von Subprozessen mit T-Strings
- Autor:
- Nick Humrich <nick at humrich.us>, Alyssa Coghlan <ncoghlan at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Verschoben
- Typ:
- Standards Track
- Benötigt:
- 750
- Erstellt:
- 13. Apr. 2025
- Python-Version:
- 3.15
- Post-History:
- 14. Apr. 2025
Zusammenfassung
PEP 750 führte Template-Strings (T-Strings) als Verallgemeinerung von f-Strings ein und bot eine Möglichkeit, String-Interpolation in verschiedenen Kontexten sicher zu handhaben. Diese PEP schlägt vor, die Module subprocess und shlex zu erweitern, um T-Strings nativ zu unterstützen, was eine sicherere und ergonomischere Ausführung von Shell-Befehlen mit interpolierten Werten ermöglicht und als Referenzimplementierung für die T-String-Funktion zur Verbesserung der API-Ergonomie dient.
PEP Verschiebung
Während der Diskussionen über den ersten Entwurf der PEP wurde deutlich, dass T-Strings eine potenzielle Gelegenheit bieten, synthetische Bequemlichkeit auf dem Niveau von shell=True für komplexe Subprozessaufrufe zu bieten, ohne all die Sicherheits- und plattformübergreifenden Kompatibilitätsprobleme, die entstehen, wenn benutzergesteuerten Text der Zugriff auf eine vollständige System-Shell gewährt wird.
Zu diesem Zweck planen die PEP-Autoren nun, während der Beta-Phase von Python 3.14 (und darüber hinaus) an einer experimentellen T-String-basierten Bibliothek für Subprozessaufrufe zu arbeiten, bevor ein überarbeiteter Entwurf des Vorschlags für Python 3.15 vorbereitet wird.
Motivation
Trotz der Sicherheitsvorteile und der Flexibilität, die Template-Strings in PEP 750 bieten, fehlt ihnen eine konkrete Konsumentenimplementierung in der Standardbibliothek, die ihre praktische Anwendung demonstriert. Einer der überzeugendsten Anwendungsfälle für T-Strings ist die sicherere Ausführung von Shell-Befehlen, wie im zurückgezogenen PEP 501 vermerkt.
# Unsafe with f-strings:
os.system(f"echo {message_from_user}")
# Also unsafe with f-strings
subprocess.run(f"echo {message_from_user}", shell=True)
# Fails with f-strings
subprocess.run(f"echo {message_from_user}")
# Safe with t-strings and POSIX-compliant shell quoting:
subprocess.run(t"echo {message_from_user}", shell=True)
# Safe on all platforms with t-strings:
subprocess.run(t"echo {message_from_user}")
# Safe on all platforms without t-strings:
subprocess.run(["echo", str(message_from_user)])
Derzeit müssen Entwickler zwischen Bequemlichkeit (Verwendung von f-Strings mit potenziellen Sicherheitsrisiken) und Sicherheit (Verwendung von ausführlicheren, listenbasierten APIs) wählen. Durch die native Unterstützung von T-Strings für das Modul subprocess bieten wir eine Konsumentenreferenzimplementierung, die den Wert von T-Strings demonstriert und gleichzeitig ein häufiges Sicherheitsproblem angeht.
Begründung
Das Subprocess-Modul ist aus mehreren Gründen ein idealer Kandidat für die Unterstützung von T-Strings
- Befehlsinjektionsschwachstellen in Shell-Befehlen sind ein bekanntes Sicherheitsrisiko.
- Das Modul
subprocessunterstützt bereits Befehlspezifikationen sowohl als Zeichenkette als auch als Liste. - Es gibt eine natürliche Entsprechung zwischen T-Strings und korrekter Shell-Escaping, die sowohl Bequemlichkeit als auch Sicherheit bietet.
- Es dient als praktische Demonstration für T-Strings, die Entwickler sofort verstehen und schätzen können.
Durch die Erweiterung von `subprocess` zur nativen Verarbeitung von T-Strings erleichtern wir das Schreiben von sicherem Code, ohne die Bequemlichkeit zu opfern, die viele Entwickler zur Verwendung potenziell unsicherer f-Strings veranlasste.
Spezifikation
Diese PEP schlägt zwei Hauptzusätze zur Standardbibliothek vor
- Eine neue Renderer-Funktion
sh()im Modulshlexzur sicheren Konstruktion von Shell-Befehlen - Hinzufügen der T-String-Unterstützung zu den Kernfunktionen des Moduls
subprocess, - insbesondere
subprocess.Popen,subprocess.run()und andere verwandte Funktionen, die ein Befehlsargument akzeptieren
- Hinzufügen der T-String-Unterstützung zu den Kernfunktionen des Moduls
Renderer für Shell-Escaping hinzugefügt zu shlex
Als Referenzimplementierung wird dem Modul shlex ein Renderer für sicheres POSIX-Shell-Escaping hinzugefügt. Dieser Renderer würde sh heißen und wäre äquivalent zum Aufruf von shlex.quote für jeden Feldwert im Template-Literal.
Somit
os.system(shlex.sh(t"cat {myfile}"))
hätte das gleiche Verhalten wie
os.system("cat " + shlex.quote(myfile)))
Die Hinzufügung von shlex.sh wird die bestehenden Hinweise in der Dokumentation des Moduls subprocess, die besagen, dass die Verwendung von shell=True am besten vermieden werden sollte, sowie den Verweis aus der Dokumentation von os.system() auf die übergeordneten APIs von subprocess NICHT ändern.
Die Implementierung des T-String-Prozessors würde wie folgt aussehen
from string.templatelib import Template, Interpolation
def sh(template: Template) -> str:
parts: list[str] = []
for item in template:
if isinstance(item, Interpolation):
# shlex.sh implementation, so shlex.quote can be used directly
parts.append(quote(str(item.value)))
else:
parts.append(item)
# shlex.sh implementation, so `join` references shlex.join
return join(parts)
Dies ermöglicht die explizite Escaping von T-Strings für die Shell-Nutzung
import shlex
# Safe POSIX-compliant shell command construction
command = shlex.sh(t"cat {filename}")
os.system(command)
Änderungen am subprocess-Modul
Mit dem zusätzlichen Renderer im `shlex`-Modul und der Hinzufügung von Template-Strings kann das Modul subprocess so geändert werden, dass es Template-Strings als zusätzlichen Eingabetyp für Popen akzeptiert, so wie es bereits eine Sequenz oder eine Zeichenkette mit unterschiedlichem Verhalten für jede akzeptiert. Im Gegenzug könnten alle subprocess.Popen-Funktionen der höheren Ebene, wie subprocess.run(), Zeichenketten auf sichere Weise akzeptieren (auf allen Systemen für shell=False und auf POSIX-Systemen für shell=True).
Zum Beispiel:
subprocess.run(t"cat {myfile}", shell=True)
würde automatisch den in dieser PEP bereitgestellten shlex.sh-Renderer verwenden. Daher wäre die Verwendung von shlex innerhalb eines subprocess.run-Aufrufs wie folgt
subprocess.run(shlex.sh(t"cat {myfile}"), shell=True)
redundant, da run automatisch alle Template-Literale über shlex.sh rendern würde
Wenn subprocess.Popen ohne shell=True aufgerufen wird, bietet die T-String-Unterstützung dennoch eine ergonomischere Syntax für `subprocess`. Zum Beispiel
subprocess.run(t"cat {myfile} --flag {value}")
wäre äquivalent zu
subprocess.run(["cat", myfile, "--flag", value])
oder, genauer gesagt
subprocess.run(shlex.split(f"cat {shlex.quote(myfile)} --flag {shlex.quote(value)}"))
Dies würde geschehen, indem zunächst der shlex.sh-Renderer wie oben verwendet und dann shlex.split auf das Ergebnis angewendet wird.
Die Implementierung innerhalb von subprocess.Popen._execute_child würde auf T-Strings prüfen
from string.templatelib import Template
if isinstance(args, Template):
import shlex
if shell:
args = shlex.sh(args)
else:
args = shlex.split(shlex.sh(args))
Abwärtskompatibilität
Diese Änderung ist vollständig abwärtskompatibel, da sie nur neue Funktionalitäten hinzufügt, ohne das bestehende Verhalten zu ändern. Das `subprocess`-Modul wird Zeichenketten und Listen weiterhin auf die gleiche Weise verarbeiten wie bisher.
Sicherheitsimplikationen
Diese PEP ist ausdrücklich darauf ausgelegt, die Sicherheit zu verbessern, indem sie eine sicherere Alternative zur Verwendung von f-Strings mit Shell-Befehlen bietet. Durch die automatische Anwendung einer geeigneten Escaping basierend auf dem Kontext (Shell oder Nicht-Shell) hilft sie, Befehlsinjektionsschwachstellen zu verhindern.
Es ist jedoch erwähnenswert, dass bei Verwendung von shell=True die Sicherheit auf POSIX-konforme Shells beschränkt ist. Auf Windows-Systemen, auf denen cmd.exe oder PowerShell als Shell verwendet werden können, reicht der von shlex.quote() bereitgestellte Escaping-Mechanismus nicht aus, um alle Formen der Befehlsinjektion zu verhindern.
Wie man das lehrt
Diese Funktion kann als natürliche Erweiterung von T-Strings vermittelt werden, die ihren praktischen Wert demonstriert
- Das Problem der Befehlsinjektion einführen und warum f-Strings mit Shell-Befehlen gefährlich sind
- Traditionelle Lösungen zeigen (listenbasierte Befehle, manuelles Escaping)
- Den
shlex.sh-Renderer für explizites Shell-Escaping vorstellen# Unsafe: os.system(f"cat {filename}") # Potential command injection! # Safe using shlex.sh: os.system(shlex.sh(t"cat {filename}")) # Explicitly escaping for shell
- Die native T-String-Unterstützung des `subprocess`-Moduls einführen
# Unsafe: subprocess.run(f"cat {filename}", shell=True) # Potential command injection! # Safe but verbose: subprocess.run(["cat", filename]) # Safe and readable with t-strings: subprocess.run(t"cat {filename}", shell=True) # Automatically escapes filename subprocess.run(t"cat {filename}") # Automatically converts to list form
Die Implementierung sollte sowohl der Dokumentation des `shlex`- als auch des `subprocess`-Moduls mit klaren Beispielen und Sicherheitshinweisen hinzugefügt werden.
Zurückstellen der Unterstützung für Escaping-Rendering für Nicht-POSIX-Shells
shlex.quote() funktioniert, indem die Regex-Zeichenmenge [\w@%+=:,./-] als sicher klassifiziert wird und alle anderen Zeichen als unsicher gelten, was dann ein Quoting des enthaltenden Strings erfordert. Der verwendete Quoting-Mechanismus ist spezifisch für die Art und Weise, wie String-Quoting in POSIX-Shells funktioniert, und kann daher nicht vertraut werden, wenn eine Shell ausgeführt wird, die nicht den Regeln für String-Quoting von POSIX-Shells folgt.
Zum Beispiel ist die Ausführung von subprocess.run(f"echo {shlex.quote(sys.argv[1])}", shell=True) sicher, wenn eine Shell verwendet wird, die den POSIX-Quoting-Regeln folgt
$ cat > run_quoted.py
import sys, shlex, subprocess
subprocess.run(f"echo {shlex.quote(sys.argv[1])}", shell=True)
$ python3 run_quoted.py pwd
pwd
$ python3 run_quoted.py '; pwd'
; pwd
$ python3 run_quoted.py "'pwd'"
'pwd'
bleibt aber unsicher, wenn eine Shell von Python aufgerufen wird, die cmd.exe (oder PowerShell) aufruft
S:\> echo import sys, shlex, subprocess > run_quoted.py
S:\> echo subprocess.run(f"echo {shlex.quote(sys.argv[1])}", shell=True) >> run_quoted.py
S:\> type run_quoted.py
import sys, shlex, subprocess
subprocess.run(f"echo {shlex.quote(sys.argv[1])}", shell=True)
S:\> python3 run_quoted.py "echo OK"
'echo OK'
S:\> python3 run_quoted.py "'& echo Oh no!"
''"'"'
Oh no!'
Die Behebung dieser Einschränkung der Standardbibliothek liegt außerhalb des Rahmens dieser PEP.
Urheberrecht
Dieses Dokument wird in die Public Domain oder unter die CC0-1.0-Universal-Lizenz gestellt, je nachdem, welche Lizenz permissiver ist.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0787.rst
Zuletzt geändert: 2025-04-27 15:17:24 GMT