Sicheren Code zu schreiben ist eine kritische Aufgabe für jeden Python-Entwickler. Diese Anleitung erkundet Best Practices und Techniken, die helfen, Code zu schreiben, der gegen häufige Schwachstellen und Angriffe widerstandsfähig ist. Die hier beschriebenen Prinzipien orientieren sich an den Empfehlungen des OWASP-Projekts zur sicheren Softwareentwicklung.
Verwendung virtueller Umgebungen
Virtuelle Umgebungen in Python ermöglichen es, Projektabhängigkeiten zu isolieren und das Risiko von Konflikten und Schwachstellen zu verringern. Sie bieten:
- Abhängigkeitsisolierung: Jede virtuelle Umgebung hat ihren eigenen Satz von Abhängigkeiten, unabhängig von anderen Projekten oder der globalen Umgebung.
- Versionskontrolle: Sie können die Versionen aller verwendeten Pakete kontrollieren und so die Kompatibilität und Sicherheit des Codes gewährleisten.
- Verringertes Risiko: Die versehentliche Installation eines bösartigen Pakets betrifft nur die virtuelle Umgebung, nicht das gesamte System.
Beispiel für die Erstellung einer virtuellen Umgebung:
python3 -m venv env source env/bin/activate
Diese Befehle erstellen und aktivieren eine virtuelle Umgebung mit dem Namen env. Alle Python-Pakete werden isoliert in env installiert, ohne die globale Umgebung oder andere Projekte zu beeinflussen.
Einschränkung des Gültigkeitsbereichs von Variablen und Funktionen
Der Gültigkeitsbereich von Variablen und Funktionen sollte so eng wie möglich gehalten werden:
- Vermeiden Sie globale Variablen, da sie das Risiko einer versehentlichen Änderung oder eines unbefugten Zugriffs auf Daten erhöhen.
- Bevorzugen Sie lokale Variablen, die durch den Funktionsbereich geschützt und für den Rest des Codes unzugänglich sind.
Betrachten Sie dieses Beispiel mit einer globalen Variable:
secret = "mein supergeheimes Passwort"
def print_secret():
# Zugriff auf globale Variable
print(secret)
print_secret()
In diesem Fall ist die Variable secret für alle Funktionen im Code zugänglich, was sie potenziell anfällig macht. Eine lokale Variable schützt den Wert durch den Funktionsbereich:
def print_secret():
secret = "mein supergeheimes Passwort"
print(secret)
print_secret()
Modularisierung des Codes
Modularität ist der Schlüssel zum Schreiben von sicherem und wartbarem Python-Code. Indem Sie Code in separate, unabhängige Module aufteilen, erreichen Sie:
- Verbesserung der Codeorganisation und Wiederverwendbarkeit.
- Vereinfachung von Tests und Debugging sowie bessere Fehlerisolierung.
- Verringerung des Risikos, Schwachstellen einzuführen, da jedes Modul unabhängig überprüft werden kann.
Gutes Beispiel mit aufgeteilten Modulen:
# module_a.py
def do_part_one():
print("Teil eins")
# module_b.py
def do_part_two():
print("Teil zwei")
# main.py
from module_a import do_part_one
from module_b import do_part_two
def main():
do_part_one()
do_part_two()
Schutz vor Code-Injektion und SQL-Injection
Code-Injektion ist eine der schwerwiegendsten Bedrohungen für jede Anwendung. Schützen Sie Ihren Python-Code durch:
- Parametrisierte Abfragen anstelle von String-Formatierung, um die Ausführung von bösartigem Code zu verhindern.
- Gründliche Validierung und Bereinigung aller Eingabedaten vor ihrer Verwendung.
Anfälliger Code mit direkter String-Formatierung:
def get_user(user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
return execute_query(query)
Sichere Version mit parametrisierter Abfrage:
def get_user(user_id):
query = "SELECT * FROM users WHERE id = ?"
return execute_query(query, (user_id,))
Die Verwendung parametrisierter Abfragen verhindert, dass Eingabedaten als Teil des SQL-Befehls interpretiert werden. Weitere Details zu Injection-Angriffsvektoren finden Sie im MITRE ATT&CK-Framework.
Sichere Serialisierung und Deserialisierung
Serialisierung und Deserialisierung können eine Quelle von Schwachstellen sein, wenn ein Angreifer die Daten manipulieren kann:
- Vermeiden Sie das Modul
pickle, das während der Deserialisierung beliebigen Code ausführen kann. - Bevorzugen Sie sichere Alternativen wie das
json-Modul, das nur mit einfachen Datentypen arbeitet.
Anfälliger Code mit dem pickle-Modul:
import pickle
def unsafe_deserialization(data):
return pickle.loads(data)
Sicheres Beispiel mit dem json-Modul:
import json
def safe_deserialization(data):
return json.loads(data)
Prinzip des geringsten Privilegs
Die Begrenzung der Berechtigungen von Programmen und Prozessen auf das absolute Minimum reduziert mögliche Schäden durch Schwachstellen erheblich.
def process_user_data(user_data):
# Code verarbeitet hier Benutzerdaten ohne unnötige Berechtigungen
pass
In diesem Beispiel arbeitet die Funktion process_user_data nur mit Benutzerdaten und benötigt keine erhöhten Berechtigungen, wodurch die Risiken im Falle einer Kompromittierung reduziert werden.
Sicherheit von Authentifizierung und Autorisierung
Schwache Authentifizierungs- und Autorisierungsmechanismen können zu unbefugtem Zugriff führen. Schützen Sie Passwörter immer durch sicheres Hashing:
import bcrypt password = b"super geheimes Passwort" hashed = bcrypt.hashpw(password, bcrypt.gensalt())
Die bcrypt-Bibliothek stellt sicher, dass ein Angreifer selbst im Falle einer Datenschutzverletzung das ursprüngliche Passwort nicht leicht wiederherstellen kann.
Ordnungsgemäße Sitzungsverwaltung
Eine sichere Sitzungsverwaltung in Webanwendungen schützt Benutzerdaten und -interaktionen. Wichtige Maßnahmen:
- Setzen Sie die Flags
SecureundHttpOnlyfür Sitzungscookies, um sie vor Abfangen und XSS-Angriffen zu schützen. - Generieren Sie die Sitzungs-ID nach der Authentifizierung neu, um Session-Fixation-Angriffe zu verhindern.
- Legen Sie eine angemessene Sitzungsdauer fest, um Sitzungsübernahme-Risiken zu verringern.
Beispielcode mit Flask:
from flask import Flask, session
from datetime import timedelta
app = Flask(__name__)
app.config.update(
SESSION_COOKIE_SECURE=True, # nur HTTPS
SESSION_COOKIE_HTTPONLY=True, # Kein JavaScript-Zugriff auf Cookies
SESSION_COOKIE_SAMESITE='Lax' # Schutz vor CSRF
)
app.permanent_session_lifetime = timedelta(minutes=15)
@app.route('/login', methods=['POST'])
def login():
# Anmeldeinformationen prüfen ...
# Neue Session-ID nach erfolgreicher Anmeldung erzeugen:
session.clear()
session['user_id'] = authenticated_user_id
return "Erfolgreich angemeldet!"
Redaktioneller Hinweis (2026): Der ursprüngliche Code verwendete session.regenerate(), das in Flask nicht existiert. Die korrekte Methode zum Invalidieren der alten Session-ID ist session.clear() gefolgt vom Neusetzen der Session-Variablen, wie im aktualisierten Beispiel gezeigt.
Vorsicht bei eval() und exec()
Die Funktionen eval() und exec() ermöglichen die Ausführung von beliebigem Code, was gefährlich sein kann. Verwenden Sie diese Funktionen ausschließlich nach sorgfältiger Bereinigung und Validierung aller Eingabedaten — und in den meisten Fällen gar nicht.
Beispiel für eine potenziell gefährliche Verwendung von eval():
eval('os.system("rm -rf /")')
Solcher Code erlaubt die Ausführung beliebiger Betriebssystembefehle. Alternativen wie ast.literal_eval() sind auf einfache Python-Literale beschränkt und deutlich sicherer für die Verarbeitung von Benutzereingaben.
Kontinuierliche Sicherheitsüberprüfung
Sicherheit ist kein einmaliger Zustand, sondern ein fortlaufender Prozess. Setzen Sie in Ihrem Entwicklungsworkflow auf statische Analyse-Tools wie bandit (speziell für Python-Sicherheitsprobleme) und integrieren Sie regelmäßige Dependency-Audits mit pip audit, um bekannte Schwachstellen in verwendeten Paketen frühzeitig zu erkennen. Die aktuellen Sicherheitsempfehlungen für Python-Pakete werden im National Vulnerability Database (NVD) veröffentlicht.