Skip to main content

So sichern Sie Python-Flask-Anwendungen

Artikel von
Headshot of Gourav Singh Bais

Gourav Singh Bais

blog feature playwright tests

21. Mai 2024

0 Min. Lesezeit

Flask ist ein leistungsstarkes, leichtgewichtiges und vielseitiges Webframework für Python. Es wurde entwickelt, damit Entwickler schnell Webanwendungen mit möglichst wenig Boilerplate-Code erstellen können. Als eigenständiges Microframework benötigt es keine zusätzlichen Bibliotheken oder Tools und verfügt über keine Datenbankabstraktionsschicht.

Flask bietet Funktionen wie Routing, Template-Rendering und Request-Verarbeitung und folgt dem Web Server Gateway Interface (WSGI)-Standard, der für seine Einfachheit und Flexibilität bekannt ist.

Leider ist Flask wie jedes andere Webframework anfällig für Sicherheitslücken, wenn es nicht ordnungsgemäß abgesichert ist. Zu den häufigsten Sicherheitsrisiken bei Flask zählen Cross-Site-Scripting (XSS), Cross-Site-Request-Forgery (CSRF) und SQL-Injection.

In der Snyk Vulnerability Database finden Sie einige der zahlreichen Sicherheitslücken, die für die Python-Bibliothek Flask entdeckt wurden:

Einige der zahlreichen Sicherheitslücken in der Python-Flask-Bibliothek, die in der Snyk Vulnerability Database aufgeführt sind.

In diesem Artikel erfahren Sie mehr über Best Practices zur Absicherung von Python-Anwendungen, die mit dem Flask-Webframework erstellt wurden. Zunächst sehen Sie sich Beispiele für unsichere Konfigurationen an und erfahren anschließend, wie Sie Probleme beheben und ihre Auswirkungen mindern.

Was ist der Web Server Gateway Interface (WSGI)-Standard?

WSGI ermöglicht eine standardisierte Kommunikation zwischen Webservern und Webanwendungen, die in Python geschrieben sind. Als Vermittlungsschicht ermöglicht WSGI es Webservern, Anfragen an eine Webanwendung oder ein Framework weiterzuleiten und die Antworten anschließend an einen Client zurückzusenden.

Warum ist WSGI wichtig?

Vor WSGI waren Python-Webframeworks und -Server oft eng aneinander gekoppelt. Das bedeutete, dass ein bestimmtes Webframework nur auf bestimmten Webservern ausgeführt werden konnte. WSGI hat diese Einschränkung aufgehoben und ermöglicht mehr Flexibilität und Interoperabilität.

Wie unterscheidet sich WSGI von ähnlichen Konzepten in anderen Programmiersprachen?

WSGI erfüllt einen ähnlichen Zweck wie die Java Servlet API in Java oder Rack in Ruby. Diese Technologien abstrahieren die Details von HTTP-Anfragen und -Antworten. So können sich Entwickler auf die Logik ihrer Webanwendungen konzentrieren, statt sich mit der zugrunde liegenden Serverkommunikation zu befassen.

Unsichere Konfigurationen von Python-Flask-Anwendungen

Im Folgenden finden Sie einige der häufigsten Probleme mit unsicheren Konfigurationen, die Python-Flask-Anwendungen betreffen. Entwickler sollten unbedingt darauf achten, diese Fehler nicht in ihren Codebasen zu wiederholen:

Offenlegung des geheimen Schlüssels

Die versehentliche Offenlegung des geheimen Schlüssels ist ein häufiges Sicherheitsversehen. Dabei werden vertrauliche Informationen wie der Flask-Secret-Key, API-Schlüssel und Passwörter im Quellcode gespeichert. Vertrauliche Angaben direkt in den Quellcode einzufügen, stellt ein erhebliches Sicherheitsrisiko dar: Angreifer können diese Schwachstelle ausnutzen, um sich unbefugten Zugriff auf verschiedene Dienste zu verschaffen.

Wenn Sie Ihren Secret Key und API-Schlüssel versehentlich fest im Quellcode hinterlegt haben, könnte das so aussehen:

# Insecure Code
api_key = "my_sensitive_api_key"
api_url = "https://api.example.com"
SEC_KEY = "your_secret_key"

# use of secret key
app.config["SECRET_KEY"] = SEC_KEY
# usage of the API key
response = requests.get(api_url, headers={"Authorization": f"Bearer {api_key}"})

Tipp: Jetzt ist ein guter Zeitpunkt, die Snyk-Erweiterung in Ihrer IDE zu installieren. So erhalten Sie einen visuellen Hinweis auf offengelegte geheime Schlüssel – etwa die blaue Wellenlinie, die Sie in meinem Editor sehen:

Die Snyk-IDE-Erweiterung zeigt Sicherheitsprobleme im Code, etwa offengelegte sensible API-Schlüssel und durchgesickerte Zugangsdaten.

Um in diesem Fall die Offenlegung des geheimen Schlüssels zu verhindern, müssen Sie ihn sicher generieren. Dafür gibt es mehrere Möglichkeiten. Eine der gebräuchlichsten ist das uuid-Modul. Das uuid-Modul wird hauptsächlich dazu verwendet, nach den in RFC 4122 definierten Prinzipien universell eindeutige Bezeichner (UUIDs) zu generieren. Es erzeugt eine zufällige hexadezimale Zeichenfolge mit 32 Byte, die als geheimer Schlüssel verwendet werden kann.

Um einen sicheren Schlüssel zu generieren, importieren Sie das uuid-Modul in Ihren Code und verwenden die Funktionalität uuid4().hex:

import uuid

uuid.uuid4().hex

Den geheimen Schlüssel zu generieren, reicht jedoch nicht aus. Sie müssen ihn auch sicher in Ihrer Flask-App verwenden. Am sichersten weisen Sie den Schlüssel einer environment variable zu oder speichern ihn in einer verschlüsselten Konfigurationsdatei. Wenn Sie den geheimen Schlüssel einer environment variable zuweisen, sieht das so aus:

import os

# Better Code
api_key = os.environ.get("MY_API_KEY")
api_url = "https://api.example.com"
SEC_KEY = os.environ.get("MY_SEC_KEY")

# use of secret key
app.config["SECRET_KEY"] = SEC_KEY

# Usage of the API key
response = requests.get(api_url, headers={"Authorization": f"Bearer {api_key}"})

Entwickler sollten sich an Konfigurationskonventionen halten, etwa an die Empfehlungen der 12-Factor-App, und Umgebungsvariablen zum Speichern von Anmeldedaten verwenden. So vermeiden Sie Datenlecks, unbefugten Zugriff und potenziellen Missbrauch.

Debug-Modus aktiviert

Der Debug-Modus ist in der Regel für Entwicklungsumgebungen vorgesehen und liefert detaillierte Fehlermeldungen sowie umfassende Informationen zum Zustand der Anwendung. Wird dieser Modus jedoch in einer Produktionsumgebung aktiviert, können vertrauliche Informationen und interne Abläufe des Systems offengelegt werden.

In diesem Beispiel ist der Debug-Modus in einer Flask-Webanwendung in der Produktionsumgebung aktiviert:

# Insecure code
from flask import Flask
appFlask = Flask(__name__)

@appFlask.route('/home')
def home():
    result = "Testing Snyk"
    return 'example of debug mode enabled'

if __name__ == "__main__":
    appFlask.run(debug=True)

Hier ist der Debug-Modus auf True gesetzt. Tritt in der Produktionsumgebung ein Fehler auf, können vertrauliche Informationen wie Benutzernamen und Passwörter in der Debug-Konsole offengelegt werden.

In diesem Szenario vergisst man leicht, dass der Debug-Modus auf True gesetzt ist, und ihn vor der Bereitstellung der Anwendung in der Produktionsumgebung wieder zu deaktivieren. Deshalb empfiehlt es sich, eine Plattform wie Snyk zu verwenden, die Sie dabei unterstützt, Schwachstellen in Ihrem Code und Ihren Anwendungen zu finden und zu beheben. Snyk unterstützt zahlreiche Programmiersprachen, darunter Python, Go, PHP, JavaScript und weitere.

Außerdem lässt sich Snyk problemlos in verschiedene IDEs wie Visual Studio Code und PyCharm sowie in CI-Pipelines und Workflows integrieren, etwa in Jenkins, CircleCI und Maven.

Um Snyk in diesem Debugging-Szenario zu verwenden, suchen Sie einfach in Visual Studio Code nach „Snyk“ und installieren Sie die Erweiterung:

Snyk-Sicherheitserweiterung für VS Code.

Nach der Installation wird Snyk im linken Bereich angezeigt. Melden Sie sich an oder erstellen Sie ein Snyk-Konto, falls Sie noch keines haben, indem Sie auf die Schaltfläche Connect klicken. Daraufhin wird ein Browserfenster wie dieses geöffnet:

Verbinden Sie Ihre IDE-Erweiterung mit der Snyk-Anwendung.

Wechseln Sie nach dem Verbinden zurück zu Visual Studio Code und öffnen Sie den Ordner, in dem Ihr Flask-Skript gespeichert ist. Nun sollten Sie die Schwachstellen in Ihrem Code sehen können:

Eine Flask-Python-Anwendung mit Sicherheitslücken und Code-Sicherheitsproblemen in der VS-Code-IDE mit der Snyk-Erweiterung.

Wie Sie sehen, hat Snyk festgestellt, dass der Debug-Modus noch aktiviert war. Um dieses Problem zu beheben, müssen Sie Ihre Flask-Anwendung absichern, indem Sie den Debug-Modus auf False setzen:

# Secure Code
from flask import Flask
appFlask = Flask(__name__)

@appFlask.route('/home')
def home():
    result = "Testing Snyk"
    return 'example of debug mode enabled'

if __name__ == "__main__":
    appFlask.run(debug=False)

Neben dem Deaktivieren des Debug-Modus in der Produktionsumgebung sollten Sie auch den Logging-Mechanismus verwenden, um die Anwendungsprotokolle zu erfassen und auf mögliche Probleme im Code zu prüfen.

Ungeschützte vertrauliche Daten in Konfigurationsdateien

Wichtige Informationen (z. B. Datenbank-Anmeldedaten, API-Schlüssel und Verschlüsselungsschlüssel) in Konfigurationsdateien zu speichern, ist in Flask-Anwendungen üblich. Sind diese Dateien jedoch ungeschützt, können die Daten in falsche Hände geraten.

Sehen wir uns ein Szenario an, in dem Ihre Konfigurationsdatei (üblicherweise config.ini) die Anmeldedaten für die Verbindung zur Datenbank enthält:

# Insecure Config
[database]
username = admin
password = my_secure_password

Hier stehen Benutzername und Passwort im Klartext. Erhält ein Angreifer Zugriff auf diese Datei, kann er die Anmeldedaten verwenden, um auf vertrauliche Informationen zuzugreifen.

Am besten beheben Sie dieses Problem, indem Sie auf Konfigurationsdateien verzichten und Ihre Anmeldedaten in Umgebungsvariablen speichern. Da Umgebungsvariablen alle benötigten Informationen in einer verborgenen Datei wie .bash_profile bündeln, können Sie diese Datei einfach zu gitignore hinzufügen. So verhindern Sie, dass sie versehentlich auf Versionsverwaltungsplattformen wie Git oder Bitbucket hochgeladen wird, auf die Angreifer leicht zugreifen können.

Wenn Sie Konfigurationsdateien in Ihrer Flask-App verwenden müssen, stellen Sie sicher, dass vertrauliche Daten verschlüsselt sind:

# Secure Config
[database]
username = admin
password = $encrypted_password

Hier wird das Passwort durch einen Platzhalter oder einen verschlüsselten Wert dargestellt. In diesem Szenario lassen sich vertrauliche Daten zur Laufzeit über ein externes, sicheres Schlüsselverwaltungssystem oder ein sicheres Entschlüsselungsverfahren abrufen.

Schwachstellen in Flask-Anwendungen

Nachdem Sie nun einige unsichere Konfigurationen von Python-Flask-Anwendungen kennengelernt haben, sehen wir uns einige häufige Web-Sicherheitsschwachstellen an, die ebenfalls Flask-Anwendungen betreffen können.

Cross-Site-Scripting (XSS)

XSS ist eine Web-Sicherheitsschwachstelle, bei der ein Angreifer versucht, beliebigen JavaScript- oder HTML-Code in eine Webseite einzuschleusen. Dadurch können Angreifer schädliche Skripte im Browser eines Nutzers ausführen.

Jinja2, eine von Flask verwendete Template-Engine, bietet eine Funktion zum automatischen Escaping, um XSS-Schwachstellen zu verhindern. Standardmäßig werden alle Variablen automatisch escaped (d. h., sie enthalten keine HTML-Komponenten, sofern dies nicht ausdrücklich festgelegt wird). Das bedeutet, dass von Nutzern eingegebene HTML- oder JavaScript-Inhalte von Jinja2 sicher escaped werden, sodass schädliche Skripte nicht ausgeführt werden.

Ist das automatische Escaping auf False gesetzt, ist die Flask-Anwendung anfällig für XSS-Schwachstellen. In diesem Beispiel benötigt eine Webanwendung eine Nutzereingabe, um eine Seite darzustellen:

from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/')
def index():
    user_input = request.args.get('input')
    return render_template('index.html', input=user_input)

Die zugehörige Seite index.html, in der das automatische Escaping auf false gesetzt ist, würde so aussehen:

<!DOCTYPE html>
{% autoescape false %}
<html>
<head>
    <title>XSS Example</title>
</head>
<body>
    <p>User Input: {{ input }}</p>
</body>
{% endautoescape %}
</html>

Schleust ein Angreifer das schädliche Skript über den Parameter input ein, wird es beim Rendern der Seite ausgeführt. Das kann zu Sicherheitsrisiken wie Datendiebstahl, Kontoübernahme und Phishing-Angriffen führen.

Um XSS zu verhindern, müssen Entwickler ihre Website so konfigurieren, dass Text korrekt escaped wird und keine HTML-Komponenten enthält. Setzen Sie einfach das automatische Escaping auf true, um die Flask-Anwendung vor XSS-Angriffen zu schützen:

<!DOCTYPE html>
{% autoescape true %}
<html>
<head>
    <title>XSS Example</title>
</head>
<body>
    <p>User Input: {{ input }}</p>
</body>
{% endautoescape %}
</html>

Eine weitere Möglichkeit, XSS zu verhindern, ist das explizite Escaping von Markup. Dazu verwenden Sie auf einer Webseite den Filter |safe, wie hier:

<!DOCTYPE html>
<html>
<head>
    <title>XSS Example</title>
</head>
<body>
    <p>User Input: {{ input|safe }}</p>
</body>
</html>

Der Filter |safe sollte jedoch sehr vorsichtig eingesetzt werden, da er voraussetzt, dass der als sicher markierte Inhalt frei von schädlichen Skripten ist.

Weitere Informationen zu Cross-Site-Scripting-Schwachstellen in Python finden Sie in einer kurzen Security-Lektion bei Snyk Learn.

Cross-Site-Request-Forgery (CSRF)

CSRF ist ein schädlicher Angriff, bei dem ein Opfer dazu verleitet wird, eine manipulierte Anfrage zu senden. Bei einer Webanwendung veranlasst ein Angreifer eine nicht autorisierte Anfrage an eine Webseite im Namen eines autorisierten Nutzers. Solche Angriffe sieht man häufig auf Plattformen wie Facebook: Jemand sendet beispielsweise ein Bild an eine Gruppe. Klicken Nutzer dieser Gruppe darauf, wird ihr Konto gelöscht.

Um CSRF besser zu verstehen, sehen wir uns eine einfache Anwendung an, die den Anwendungszustand ändert und keinen CSRF-Schutz bietet:

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/change_password', methods=['POST'])
def change_password():
    new_password = request.form.get('new_password')
    # Change password logic here
    return redirect(url_for('index'))

Dieser Code sendet ein Formular mit einer POST-Anfrage, um das Passwort zu ändern. Da kein CSRF-Schutz vorhanden ist, kann ein Angreifer Nutzer dazu verleiten, ihr Passwort zu ändern, indem er sie auf eine andere Website mit präpariertem HTML- oder Skriptcode leitet. Ein Formular ohne CSRF-Schutz sieht ungefähr so aus:

<!DOCTYPE html>
<html>
<head>
    <title>CSRF Example</title>
</head>
<body>
    <form method="post" action="{{ url_for('change_password') }}">
   	 <label for="new_password">New Password:</label>
   	 <input type="password" name="new_password" required>
   	 <button type="submit">Change Password</button>
    </form>
</body>
</html>

Um CSRF-Schwachstellen zu beheben, verwenden Sie die Flask-WTF-Erweiterung zusammen mit der Middleware CSRFProtect. Flask-WTF ist ein Flask-Plugin, das die WTForms-Bibliothek einbindet. Diese bietet praktische Funktionen zum einfachen Erstellen und Verwalten von Formularen für eine Flask-Webanwendung.

Sie können die Erweiterung wie folgt mit pip installieren:

pip install Flask-WTF

Nach der Installation können Sie Ihre Flask-Anwendung mit Flask-WTF um CSRF-Schutz ergänzen:

from flask import Flask, render_template, request, redirect, url_for
from flask_wtf.csrf import CSRFProtect

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'  # Set a secret key for CSRF protection
csrf = CSRFProtect(app)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/change_password', methods=['POST'])
def change_password():
    new_password = request.form.get('new_password')
    # Change password logic here
    return redirect(url_for('index'))

Hier wird CSRFProtect mit der Flask-App initialisiert und SECRET_KEY festgelegt. Dieser Schlüssel wird speziell zum Erstellen und Validieren von CSRF-Tokens verwendet.

Vergessen Sie nicht, das Template (index.html) zu aktualisieren und das CSRF-Token einzubinden, etwa so:

<!DOCTYPE html>
<html>
<head>
    <title>CSRF Example</title>
</head>
<body>
    <form method="post" action="{{ url_for('change_password') }}">
   	 {{ csrf_token() }}
   	 <label for="new_password">New Password:</label>
   	 <input type="password" name="new_password" required>
   	 <button type="submit">Change Password</button>
    </form>
</body>
</html>

Diese Funktion csrf_token() generiert ein verborgenes Eingabefeld, das das CSRF-Token enthält. Beim Absenden des Formulars validiert Flask-WTF das Token, um sicherzustellen, dass es sich bei der Anfrage nicht um einen CSRF-Angriff handelt.

FSQL-Injection und Datenbanksicherheit

Sehen wir uns nun einige verschiedene Flask-Probleme im Zusammenhang mit der Datenbanksicherheit an:

SQL-Injection

SQL-Injection ist ein schwerwiegendes Problem, bei dem ein Angreifer beliebigen SQL-Code in einer Datenbank ausführt, indem er die in einer SQL-Abfrage verwendeten Parameter manipuliert. Dies kann zu unbefugtem Zugriff, Datenmanipulation und zur Offenlegung vertraulicher Informationen führen.

Wenn Eingaben in Flask-Anwendungen vor ihrer Verwendung in einer SQL-Abfrage nicht ordnungsgemäß bereinigt werden, kann es zu einem SQL-Injection-Angriff kommen.

Um diesen Angriff besser zu verstehen, sehen wir uns Beispielcode für eine Flask-Anwendung an, die Daten aus einer Datenbank abruft:

from flask import Flask, request, render_template
import sqlite3

app = Flask(__name__)

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']

    # Vulnerable SQL query
    query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"

    # Execute the query
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute(query)
    user = cursor.fetchone()
    conn.close()

    if user:
   	 return 'Login successful'
    else:
   	 return 'Login failed'

Diese Abfrage erwartet Benutzername und Passwort in der Abfragezeichenfolge. Schleust ein Angreifer eine Eingabe wie ' OR '1'='1'; -- ein, sieht die Abfrage anschließend so aus:

SELECT * FROM users WHERE username = '' OR '1'='1'; --' AND password = '';

Da diese Abfrage immer True ergibt, wird der Angreifer angemeldet und kann unbefugt auf die Daten zugreifen.

Code zu finden, der anfällig für SQL-Injection ist, kann schwierig sein. Zum Glück kann Snyk Ihnen dabei helfen. Da Sie Snyk bereits in Visual Studio Code eingerichtet haben, müssen Sie nur das Skript öffnen. Snyk erkennt das SQL-Injection-Problem automatisch:

Snyk warnt vor einer SQL-Injection-Schwachstelle, die durch das Verketten von Benutzereingaben mit einer SQL-Abfrage entsteht.

Eine einfache Möglichkeit, diese SQL-Injection zu verhindern, ist die Verwendung parametrisierter Abfragen oder Anweisungen. In 

Flask können Sie außerdem die Methode execute des Cursor-Objekts verwenden, um Parameter sicher zu binden. Eine parametrisierte Abfrage sieht so aus:

from flask import Flask, request, render_template
import sqlite3

app = Flask(__name__)

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']

    # Secure SQL query using parameterized query
    query = "SELECT * FROM users WHERE username = ? AND password = ?"

    # Execute the query with parameters
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute(query, (username, password))
    user = cursor.fetchone()
    conn.close()

    if user:
   	 return 'Login successful'
    else:
   	 return 'Login failed'

In diesem Code werden Platzhalter ? verwendet. Die tatsächlichen Werte werden als Tupel an die execute-Methode übergeben. Dadurch werden Benutzereingaben als Daten und nicht als ausführbarer SQL-Code behandelt.

Validieren und bereinigen Sie Benutzereingaben immer, um diese Art von Angriff zu verhindern. Sie können Eingaben mithilfe von Techniken wie Escape-Zeichen, Bibliotheken von Drittanbietern und regulären Ausdrücken bereinigen.

Einen ausführlichen Artikel zu SQL-Injections finden Sie hier: SQL-Injection-Cheat-Sheet: 8 Best Practices zur Verhinderung von SQL-Injection-Angriffen.

Authentifizierung und Autorisierung

Authentifizierung und Autorisierung sind entscheidende Bestandteile der Websicherheit. Sicherheitsverletzungen bei der Authentifizierung und Autorisierung können zu verschiedenen Sicherheitsproblemen führen, die wir im Folgenden erläutern.

Sicherheitsprobleme bei der Authentifizierung

Eines der häufigsten Sicherheitsprobleme bei der Authentifizierung ist der Brute-Force-Angriff, bei dem ein Angreifer wiederholt versucht, einen Benutzernamen und ein Passwort zu erraten, um Zugriff auf eine Website oder einen Dienst zu erhalten. Ein weiteres häufiges Problem ist Session-Fixation, bei der ein Angreifer eine bekannte Sitzungs-ID festlegt, möglicherweise mithilfe eines Phishing-Angriffs.

Um Brute-Force-Angriffe einzudämmen, müssen Sie eine Sperrfunktion für Sitzungen implementieren. Dabei wird der Benutzer nach einer bestimmten Anzahl fehlgeschlagener Anmeldeversuche gesperrt:

from flask import Flask, request, session

app = Flask(__name__)
app.config['MAX_LOGIN_ATTEMPTS'] = 3

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']

    # Check if the user is locked out
    if 'login_attempts' in session and session['login_attempts'] >= app.config['MAX_LOGIN_ATTEMPTS']:
        return 'Account locked. Please try again later.'

    # Validate the username and password
    # ...

    # Update login attempts
    if 'login_attempts' in session:
        session['login_attempts'] += 1
    else:
        session['login_attempts'] = 1

    # Authenticate the user
    # ...

In diesem Code legen Sie MAX_LOGIN_ATTEMPTS auf 3 fest. Das bedeutet: Wer mehr als dreimal falsche Anmeldedaten eingibt, wird gesperrt.

In der Methode login() rufen Sie username und password ab und prüfen, ob der Benutzer gesperrt ist. Ist das nicht der Fall, überprüfen Sie die Anmeldeversuche. Sind die Anmeldedaten korrekt, ist alles in Ordnung. Andernfalls erhöht sich die Zahl der Anmeldeversuche um eins, und der Vorgang wird wiederholt, bis der Benutzer erfolgreich angemeldet oder gesperrt wird.

Zusätzlich zu einer Sperrfunktion für Sitzungen sollten Sie auch strenge Passwortrichtlinien verwenden und eine Multifaktor-Authentifizierung (MFA) in Betracht ziehen.

Um Probleme mit Session-Fixation zu verhindern, können Sie die Sitzungs-IDs nach der Anmeldung eines Benutzers oder nach einem bestimmten Zeitraum (durch automatischen Ablauf) erneuern – auch wenn der Benutzer weiterhin angemeldet bleibt:

from flask import Flask, request, session

app = Flask(__name__)
app.secret_key = 'your_secret_key'

@app.route('/login', methods=['POST'])
def login():
    # Authenticate the user
    # ...

    # Regenerate session ID after successful login
    session.regenerate()
    # ...

In diesem Code erneuert die Methode login() die Sitzungs-ID mithilfe der Methode regenerate() , sobald der Benutzer erfolgreich angemeldet ist.

Sicherheitsprobleme bei der Autorisierung

Bei der Entwicklung einer Web-App ist nicht jede Funktion für jeden Benutzer vorgesehen ( d. h. ein bestimmter Benutzer sollte nur auf ausgewählte Funktionen zugreifen können). Werden Benutzern Rollen nicht korrekt zugewiesen, kann es zu unbefugtem Zugriff kommen. Deshalb benötigen Sie eine rollenbasierte Zugriffskontrolle (RBAC).

Authentifizierte Benutzer können auch durch Manipulation von Parametern oder URLs auf nicht autorisierte Ressourcen zugreifen. Dies wird häufig als unsichere direkte Objektreferenz (IDOR) bezeichnet. Weitere Informationen finden Sie in diesem Beitrag zum Erkennen und Beheben unsicherer direkter Objektreferenzen in Python.

Um RBAC- und IDOR-Probleme zu beheben, müssen Sie bestimmte Benutzerrollen zuweisen und Zugriffskontrollen auf Grundlage dieser Rollen durchsetzen, etwa so:

from flask import Flask, request, session

app = Flask(__name__)

@app.route('/admin_dashboard')
def admin_dashboard():
    if 'role' in session and session['role'] == 'admin':
   	 # User has admin role and is authorized to access the admin dashboard
   	 # ...
   	 return 'Admin dashboard page'
    else:
   	 # User is not authorized
   	 return 'Unauthorized access'

In diesem Code implementiert die Methode admin_dashboard() die Funktionen, auf die mit der Rolle „admin“ zugegriffen werden kann. Andernfalls wird die Warnung Unauthorized access zurückgegeben.

Sie müssen außerdem die Zugriffskontrolle so verwalten, dass Benutzer nur auf Ressourcen zugreifen können, für die sie eine Berechtigung haben:

from flask import Flask, request, session

app = Flask(__name__)

@app.route('/user_profile/<int:user_id>')
def user_profile(user_id):
    if 'user_id' in session and session['user_id'] == user_id:
   	 # User is authorized to access their own profile
   	 # ...
   	 return 'User profile page'
    else:
   	 # User is not authorized
   	 return 'Unauthorized access'

Speichern Sie Zugangsdaten nicht im Klartext, da Angreifer sie leicht für böswillige Zwecke auslesen können. Verwenden Sie stattdessen Hashing und Salting, um die Zugangsdaten der Benutzer zu speichern.

Beim Hashing wird ein Passwort in eine Zeichenfolge fester Länge umgewandelt. Dadurch ist es praktisch unmöglich, den Vorgang umzukehren und das ursprüngliche Passwort zu ermitteln. Beim Salting wird jedem Passwort vor dem Hashing ein eindeutiger Zufallswert (Salt) hinzugefügt. Das hilft, Angriffe wie Rainbow-Table-Angriffe zu verhindern.

Veraltete Bibliotheken und Sicherheitsupdates

Wenn Sie veraltete Flask-Bibliotheken verwenden, können diese Schwachstellen enthalten, die Angreifer ausnutzen könnten. Bibliotheken erhalten häufig Sicherheitsupdates und Patches, um Schwachstellen zu beheben. Achten Sie daher immer darauf, die aktuellste Paketversion zu verwenden.

Um sicherzustellen, dass Sie die neuesten Sicherheitspatches verwenden, können Sie Tools wie pip oder conda nutzen. Darüber hinaus können Sie Tools wie Snyk verwenden, um Ihren gesamten Code auf Abhängigkeiten und Infrastructure-as-Code-Konfigurationen (IaC) zu überprüfen.

Eine weitere Best Practice ist, die Abhängigkeiten in der Datei requirements.txt festzuschreiben. So verwendet das Projekt bestimmte Versionen. Entwickler behalten dadurch die Kontrolle über Updates und können die Kompatibilität testen.

Sichere Datei-Uploads

Wenn eine Flask-Website beliebigen Personen erlaubt, beliebige Dateitypen hochzuladen, kann dies zu verschiedenen Problemen führen. Lädt ein Angreifer beispielsweise schädliche Dateien auf den Server hoch, kann es zu einem Denial-of-Service-Angriff (DoS) kommen. Dabei besteht auch die Möglichkeit, vorhandene Dateien zu überschreiben. Einige ausführbare Dateien können außerdem verschiedene Arten von Computerviren enthalten.

Das folgende Codebeispiel ermöglicht es jedem Benutzer, beliebige Dateien hochzuladen:

from flask import Flask, request, flash, redirect, url_for
from werkzeug.utils import secure_filename
import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'

@app.route('/upload', methods=['POST'])
def upload_file():
    file = request.files['file']
    filename = secure_filename(file.filename)
    file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
    flash('File successfully uploaded')
    return redirect(url_for('uploaded_file', filename=filename))

In diesem Code liest die Methode upload_file() die vom Benutzer bereitgestellte Datei ein und speichert sie im Ordner uploads.

Eine erste Maßnahme gegen unsichere Dateien besteht darin, eine Liste zulässiger Dateierweiterungen festzulegen. Erlauben Sie in Ihrer Flask-App niemals ausführbare Dateien oder Skriptdateien.

Achten Sie außerdem darauf, die Dateien an einem sicheren Ort zu speichern, um unbefugten Zugriff zu verhindern:

from flask import Flask, request, flash, redirect, url_for
from werkzeug.utils import secure_filename
import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['ALLOWED_EXTENSIONS'] = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
   	 flash('No file part')
   	 return redirect(request.url)

    file = request.files['file']

    if file.filename == '':
   	 flash('No selected file')
   	 return redirect(request.url)

    if file and allowed_file(file.filename):
   	 filename = secure_filename(file.filename)
   	 file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
   	 flash('File successfully uploaded')
   	 return redirect(url_for('uploaded_file', filename=filename))
    else:
   	 flash('Invalid file type')
   	 return redirect(request.url)

Hier werden mehrere Bedingungen angewendet, um die Zulässigkeit der Dateitypen zu überprüfen.

Eine weitere Best Practice ist, Dateinamen zufällig zu generieren. So verhindern Sie, dass Dateien überschrieben werden, und verringern das Risiko vorhersehbarer Namenskonventionen:

from flask import Flask, request, flash, redirect, url_for
import os
import uuid

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'

def generate_random_filename():
    return str(uuid.uuid4())

@app.route('/upload', methods=['POST'])
def upload_file():
    # ... (other code)
    if file and allowed_file(file.filename):
   	 filename = generate_random_filename()
   	 file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
   	 # ... (other code)

Fehlerbehandlung und Offenlegung von Informationen

Die Fehlerbehandlung ist für jede Art von Anwendung wichtig. In der Produktionsumgebung sollten Sie keine print-Anweisungen verwenden und den Debug-Modus nicht aktivieren. Eine ungeeignete Protokollierung kann wichtige Details über Ihre Anwendung preisgeben.

Wenn beispielsweise in der Produktionsumgebung detaillierte Fehlermeldungen angezeigt werden, können vertrauliche Informationen über die internen Abläufe der Anwendung offengelegt werden. Dadurch können Angreifer potenzielle Schwachstellen leichter erkennen:

OperationalError: (1045, "Access denied for user 'root'@'localhost' (using password: YES)")

Dieses Beispiel gibt die wichtigsten Informationen über die mit der Flask-App verknüpfte Datenbank preis.

Auch vollständige Stacktraces in Fehlermeldungen können die Code-Struktur der Anwendung offenlegen und möglicherweise Schwachstellen sichtbar machen:

File "/path/to/app.py", line 42, in some_function
	result = 1 / 0
ZeroDivisionError: division by zero

Um die Offenlegung von Informationen durch die Fehlerbehandlung zu verhindern, können Sie eigene Fehlerseiten für allgemeine Fehlercodes erstellen (z. B. 404 Not Found und 500 Internal Server Error), ohne vertrauliche Details preiszugeben. Verwenden Sie dazu den Decorator errorhandler, um eigene Fehlerbehandlungsroutinen zu definieren:

from flask import Flask, render_template

app = Flask(__name__)

@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_server_error(error):
    return render_template('500.html'), 500

Darüber hinaus sollten Sie eine geeignete Protokollierung für Ausnahmen und Fehler implementieren. Protokollmeldungen sollten detailliert genug für die Fehlersuche sein, aber keine vertraulichen Informationen preisgeben:

import logging
from flask import Flask

app = Flask(__name__)

logging.basicConfig(filename='error.log', level=logging.ERROR)

@app.route('/divide')
def divide_by_zero():
    try:
   	 result = 1 / 0
   	 return str(result)
    except Exception as e:
   	 app.logger.error(f"An error occurred: {str(e)}")
   	 return "Internal Server Error", 500

Mit diesen Best Practices können Sie sicherstellen, dass Ihre Flask-Anwendung robust und sicher ist.

Fazit

Flask ist ein beliebtes Web-Framework für kleine und mittelgroße Webanwendungen. Wie bei jedem anderen Webentwicklungstool oder Framework kann es jedoch auch bei Flask zu Problemen kommen, wenn die Anwendung nicht ordnungsgemäß geschützt wird. Es ist wichtig, dass Sie diese Probleme kennen und ihnen entgegenwirken können, damit Angreifer sie nicht ausnutzen.

Sie können diesem Code-Repository auf GitHub folgen, um vollständige, funktionsfähige Beispiele zu einigen der besprochenen Themen zur Sicherheit von Flask-Anwendungen zu erhalten.

Mit Snyk können Sie Schwachstellen in Ihrem Code ganz einfach finden und beheben. Snyk unterstützt zahlreiche Programmiersprachen und lässt sich nahtlos in Ihre Tools, Pipelines und Workflows integrieren. Starten Sie noch heute kostenlos mit Snyk.

Beachten Sie abschließend Snyks Python Security Best Practices Cheat Sheet und erwerben Sie auf der kostenlosen Website Snyk Learn Kompetenzen in der Anwendungssicherheit mit Python.

Weiterlesen

Blog

Frontier-Modelle fanden die Schwachstellen. Nur der Angreifer fand die Angriffsketten.

Die statische Analyse fand die Schwachstellen, doch erst Live-Angriffstests bewiesen, wie sie sich zu Angriffen verketten lassen. Ein Vergleich von Evo COS, Claude Security und Claude Code Security.

feature insights context
Blog

Autonome Angriffe sind bereits Realität. Die Verteidigung muss Schritt halten.

Autonome Angreifer verkürzen das Zeitfenster für die Verteidigung. Erfahren Sie, wie kontinuierliches Erkennen, Beheben, Validieren und Verhindern Sicherheitsteams helfen kann, Schritt zu halten.

Blog

Warum KI-Coding-Agenten immer wieder fehlerhafte Zugriffskontrollen schreiben

KI-Coding-Agenten können Autorisierungslogik erzeugen, die kompiliert und die Prüfung besteht, dabei aber die Daten eines Mandanten für einen anderen offenlegt. Erfahren Sie, warum fehlerhafte Zugriffskontrollen schwer zu erkennen sind und wie Sie sie verhindern.