Skip to main content

XSS in Django verhindern

Artikel von
blog feature django xss

13. März 2023

0 Min. Lesezeit

Cross-Site-Scripting (XSS) ist eine Art von Schwachstelle, bei der die Interaktion von Nutzern mit einer Webanwendung manipuliert wird, um die Browserumgebung eines Nutzers zu kompromittieren. Diese Schwachstellen können viele Web-Apps betreffen, darunter auch solche, die mit modernen Frameworks wie Django erstellt wurden.

Da XSS-Angriffe so weit verbreitet sind, müssen Sie Ihre Anwendungen unbedingt davor schützen. Dieser Leitfaden erläutert, wie XSS-Schwachstellen in Django-Apps entstehen und was Sie tun können, um sie zu entschärfen. Außerdem erfahren Sie, wie Sie kostenlose Sicherheitstools nutzen, um XSS-Schwachstellen frühzeitig in der Entwicklung zu erkennen und zu beheben.

Was ist XSS?

Der Begriff XSS stammt aus der Anfangszeit dieser Angriffe, als der Diebstahl websiteübergreifender Daten das Hauptziel von Angreifern war. XSS-Angriffe haben sich jedoch weiterentwickelt und gelten heute als alle Angriffe, die es Angreifern ermöglichen, clientseitige Daten, Tokens usw. zu kompromittieren. Erfolgreiche Angriffe können von der Übernahme einer Sitzung bis hin zur vollständigen Übernahme eines Kontos oder Systems führen.

Bei einem XSS-Angriff gelangen unerwünschte Daten in eine Anwendung und werden ohne Validierung an einen Nutzer zurückgegeben. Die als Teil der Antwort übermittelten schädlichen Daten bestehen häufig aus JavaScript oder anderem Code, der im Browser des Nutzers ausgeführt werden kann. Führt der Browser diesen Code aus, umgeht er seine Same-Origin-Policy (SOP) und der Nutzer wird zum Angriffsziel.

XSS-Angriffe wurden traditionell anhand der Persistenz der Daten in zwei Typen eingeteilt: reflektierte und gespeicherte XSS. Außerdem gibt es einen neueren Typ namens DOM-basiertes XSS.

Gespeichertes XSS

Angriffe mit gespeichertem XSS, manchmal auch persistentes XSS genannt, treten auf, wenn schädlicher Code auf dem Server einer anfälligen Anwendung gespeichert und später ohne Bereinigung an Nutzer gesendet wird (und schließlich vom Browser eines Nutzers ausgeführt wird). Die schädliche Payload wird häufig in Datenbanken, Forenbeiträgen, Protokollen oder Kommentarfeldern gespeichert.

Diagramm eines gespeicherten XSS-Angriffs: Ein Angreifer schleust eine Payload in eine Website ein. Ein Opfer besucht die Website, erhält schädlichen Code und seine Informationen werden an

Besucht ein Opfer die betroffene Anwendung, wird die schädliche Payload als Teil der Serverantwort im Browser des Opfers dargestellt. Der Browser führt diese Daten daraufhin aus, wodurch der Nutzer kompromittiert wird.

Reflektiertes XSS

Angriffe mit reflektiertem XSS treten auf, wenn vom Nutzer eingegebene und von der Anwendung zurückgegebene Daten nicht auf dem Server gespeichert werden. In diesem Fall wird der nicht vertrauenswürdige Code als Teil eines Suchergebnisses oder einer Fehlermeldung übermittelt, ohne ordnungsgemäß bereinigt zu werden.

Diagramm eines reflektierten XSS-Angriffs: Ein Angreifer sendet einen schädlichen Link, die Website gibt Code an das Opfer zurück, und Informationen gelangen zurück zum Angreifer.

Der Angreifer tarnt die Payload in der Regel, bevor er sie an das Opfer sendet. Klickt das Opfer auf die Payload, wird der schädliche Code an die betroffene Anwendung gesendet. Die Anwendung antwortet mit dem schädlichen Code, der im Browser des Opfers ausgeführt wird.

DOM-basiertes XSS

Angriffe mit DOM-basiertem XSS treten auf, wenn der Datenfluss der schädlichen Daten im Browser-DOM beginnt und endet. Die Schwachstelle wird ausgelöst, indem das DOM des Opfer-Browsers manipuliert wird. Die resultierende HTTP-Antwort ändert sich dabei nicht, sondern gibt lediglich den clientseitigen Code zurück, der anschließend das Browser-DOM so verändert, dass die Payload ausgeführt wird.

XSS in Django verhindern

Django bietet integrierte Schutzmaßnahmen gegen XSS-Angriffe, darunter einen Auto-Escape-Mechanismus für seine Template-Engine. Dieser kann zwar gängige XSS-Angriffe verhindern, jedoch keine Angriffsvektoren, die durch schlechte Programmierpraktiken entstehen.

Das Risiko von XSS-Angriffen lässt sich minimieren, indem Sie beim Entwickeln Ihrer App bewährte Vorgehensweisen einhalten.

Dynamische Daten in Anführungszeichen setzen

Jeder Teil Ihrer App, der Nutzereingaben ohne Bereinigung akzeptiert, kann XSS-Schwachstellen verursachen. Angreifer können solche Eingabefelder nutzen, um beliebigen JavaScript-Code einzuschleusen, für den keine HTML-Zeichen erforderlich sind.

Das folgende HTML verwendet beispielsweise ein Attribut ohne Anführungszeichen, das so verändert werden kann, dass es JavaScript-Handler enthält, etwa onmouseover=alert(1):

<div class={{ name }}></div>

In der Praxis sind Angreifer natürlich nicht so harmlos. Stattdessen schleusen sie eigens erstellte Payloads ein, um diese Gelegenheit bestmöglich zu nutzen. Ein weiteres Beispiel für XSS-Schwachstellen durch Payloads ohne Anführungszeichen ist die Verwendung von JavaScript-Variablen zum Speichern von Nutzerdaten:

var user = new userClass();&nbsp; 
user.age = {{ age }} ;

Wenn Sie JavaScript-Ganzzahlen auf diese Weise verwenden, um einen übermittelten Wert zu speichern, ist Ihre App XSS-Angriffen ausgesetzt. Djangos Auto-Escaping schützt davor nicht. Ein Angreifer kann etwas so Einfaches wie 23 ; payload() eingeben und so schädlichen Code im Browser eines Nutzers ausführen.

Setzen Sie alle nutzerseitig sichtbaren Attribute in doppelte Anführungszeichen, um diese Schwachstellen zu entschärfen:

<div class="{{ name }}"></div>

Template-Literale vermeiden

Wenn Ihr Code JavaScript-Template-Literale enthält, ist er möglicherweise anfällig für XSS. Template-Literale verwenden Backticks statt Anführungszeichen und maskieren die Zeichen nicht:

<img src="javascript:alert(`xss`)>

Dadurch können Angreifer mit Payloads, die Backticks enthalten, erfolgreich Code ausführen. Am besten vermeiden Sie dies, indem Sie die Verwendung von Template-Literalen untersagen.

Attribut-URLs validieren

Viele HTML-Attribute wie a href und img src erwarten eine URL und können für XSS-Angriffe missbraucht werden. Angreifer können einfach Payloads mit einem javascript:-URI einfügen, um ihren Code auszuführen. Djangos HTML-Escaping schützt Sie in diesem Fall nicht.

<a href="{{ user.website_url }}">Website</a>

Angenommen, Ihre App ermöglicht es Nutzern, die URL ihrer persönlichen Website hinzuzufügen. Böswillige Nutzer können als Link beispielsweise javascript:payload(XSS) verwenden. Klickt jemand auf diesen Link, wird die schädliche Payload ausgeführt.

Erstellen Sie eine Positivliste zulässiger Protokolle für URLs und sperren Sie alle anderen, um dieses Problem zu beheben. Zu den sicheren URIs gehören http:, https:, ftp: und mailto:. Sie können Attribut-URLs auch bereinigen, bevor Sie sie in der Datenbank speichern.

JavaScript-Daten maskieren

Wenn Sie variable Daten direkt in JavaScript einfügen, gelangen sie in den Ausführungskontext. Ermöglicht Ihr Programm Nutzern, Daten zu kontrollieren, die in JavaScript-Blöcke oder Event-Handler eingefügt werden, kann dies zu XSS-Angriffen führen.

<script>var name = {{ username }};</script>

Da Angreifer Code ohne HTML-Zeichen erstellen können, verhindert das Verlassen auf Djangos HTML-Escaping in solchen Fällen keine XSS-Angriffe.

Um das Risiko zu mindern, können Sie Template-Variablen in <script>-Blöcken untersagen oder zum Lesen von JS-Daten das Template-Tag json_script verwenden:

{{ name|json_script:"username" }}
<script>
  var name = JSON.parse(document.getElementById(‘username’).textContent);
</script>

Denken Sie daran, Datenzeichen im Format \xHH zu kodieren und zu überprüfen, dass der Content-Type-Header für JSON den Wert application/json und nicht text/html hat.

CSS-Daten maskieren

In CSS-Style-Tags oder -Attributen eingefügte Daten können XSS-Schwachstellen in Ihrer Django-App verursachen. Angreifer können diese CSS-Daten nutzen, um in den Ausführungskontext zu gelangen. Dies geschieht in der Regel, indem JavaScript-Code in CSS-Kontexte eingeschleust wird:

<style> body{ margin-left:expression('alert(‘XSS’)') } </style>

Mit der Direktive expression() lassen sich beliebige JavaScript-Anweisungen zur Auswertung des Werts einer CSS-Eigenschaft verwenden. Dadurch wird Ihre Django-App anfällig für XSS-Angriffe.

Verwenden Sie zum Entschärfen dieses Risikos die Direktive expression() nicht, um Werte für CSS-Parameter festzulegen. Wenn Sie JavaScript-Anweisungen zum Festlegen oder Ändern von CSS-Eigenschaften verwenden, versuchen Sie es mit etwas wie style.property = x.

safe sparsam verwenden

Djangos Auto-Escaping ist in den meisten Fällen hilfreich, bietet aber keinen Schutz, wenn Sie das HTML-Escaping in einem Template mit dem Filter safe deaktivieren. Wenn Sie etwas als safe markieren, maskiert Django es nicht, sondern gibt die Daten unverändert aus.

<span id="query" >Searches similar to {{ query | safe }}</span>

Wenn Ihre App diese Abfrage ohne Datenvalidierung ausgibt, könnten Angreifer sie zum Einschleusen von Payloads nutzen. Verwenden Sie den safe-Filter nur, wenn Sie sicher sind, dass die Daten tatsächlich sicher sind. Von Nutzern bereitgestellte Daten dürfen Sie niemals als safe markieren.

Verwendung des safeseq-Filters einschränken

Der Filter safeseq gibt an, dass der auszugebende Inhalt vollkommen sicher ist. Genau wie safe kann er XSS-Payloads ermöglichen.

{{ ids | safeseq | join:", " }}

Vermeiden Sie diesen Filter für Sequenzdaten. Wenn Sie ihn dennoch verwenden müssen, greifen Sie auf mark_safe() zurück. So können Sie bei Code-Reviews gezielt alle Stellen prüfen, an denen Sie etwas ausdrücklich als sicher markiert haben.

html_safe() nur eingeschränkt verwenden

Die Methode html_safe() fügt der bereitgestellten Klasse die magische Methode __html__ hinzu, die eine exakte Zeichenfolgendarstellung dieser Klasse zurückgibt:

@html_safe
class UserData(str):
  pass

Django maskiert diese Daten nicht über seine Template-Engine. Sie stellen daher ein potenzielles XSS-Risiko dar. Verwenden Sie html_safe() nur, wenn es notwendig ist. Außerdem sollten Sie die magische Methode __html__ nicht in Klassen verwenden.

Filter mit is_safe=True vermeiden

Wenn Sie einen benutzerdefinierten Filter mit is_safe=True registrieren, maskiert Django das HTML nicht und markiert den vom Filter zurückgegebenen Wert als sicher.

@register.filter(is_safe=True)
def randomfilter(value):
  return value

Da die vom Filter zurückgegebenen Daten Djangos Auto-Escaping nicht durchlaufen, kann dies zu XSS-Angriffen führen. Registrieren Sie Filter auf diese Weise nur, wenn Sie sicher sind, dass die zurückgegebenen Daten bedenkenlos verwendet werden können.

mark_safe() und SafeString nur eingeschränkt verwenden

Die Methode mark_safe() kennzeichnet die ausgegebenen Daten als sicher und umgeht Djangos integrierten XSS-Schutz. Wenn Sie diese Methode häufig verwenden, kann das XSS-Schwachstellen begünstigen.

mark_safe(some_content)

Außerdem gibt mark_safe() die Daten als SafeString zurück. Django verwendet die Klasse SafeString, um zu bestimmen, welche Daten sicher ausgegeben werden können und welche nicht. Was passiert also, wenn Sie SafeString direkt verwenden?

SafeString(f"<div>{request.POST.get('id')}</div>")

Wie Sie vielleicht vermutet haben, umgeht die direkte Verwendung der Klasse SafeString auf diese Weise Djangos HTML-Escaping und kann zu XSS führen. Verwenden Sie sowohl mark_safe() als auch SafeString möglichst sparsam.

Antworten nicht mit HttpResponse schreiben

Wenn Sie HttpResponse oder ähnliche Klassen direkt in Ihrem Code verwenden, umgehen Sie Djangos Template-System und deaktivieren dadurch das HTML-Escaping.

return HttpResponse("My name is, " + name)

Dadurch kann Ihr Programm XSS-Angriffen ausgesetzt werden. Vermeiden Sie es daher, Antworten auf diese Weise zu schreiben. Verwenden Sie stattdessen die Methode render() mit einem Template.

Django-XSS-Schwachstellen mit Snyk erkennen und beheben

XSS-Angriffe zählen zu den aktivsten Bedrohungen für moderne Web-Apps. Da sie auf vielfältige Weise auftreten können, ist es schwierig, bei Code-Reviews alle XSS-Schwachstellen zu erkennen. Daher ist es entscheidend, diese Schwachstellen frühzeitig im Software Development Lifecycle (SDLC) aufzuspüren.

Integrieren Sie dazu spezielle Testtools in Ihre Entwicklungsumgebung. Wir veranschaulichen dies anhand von Snyk.

Snyk bietet mehrere Möglichkeiten, Django-Apps zu testen, darunter eine Web-UI, eine Befehlszeilenschnittstelle, IDE-Plug-ins und APIs. In diesem Tutorial verwenden Sie das Snyk-Plug-in für PyCharm. Folgen Sie der Anleitung mit einer installierten Version von PyCharm (Snyk unterstützt aber auch andere IDEs wie Eclipse und Visual Studio).

Um Snyk zu verwenden, erstellen Sie ein kostenloses Konto und installieren Sie anschließend das Plug-in gemäß der Installationsanleitung.

Das Einstellungsfenster von PyCharm zeigt den Plugin-Marktplatz mit der Suche nach „snyk“ und das Snyk Security-Plugin mit einer Schaltfläche „Installieren“.

Nach der Installation des Plug-ins richten Sie das Snyk-Plug-in für Ihre IDE ein. Sie müssen das Plug-in mit der Snyk-Plattform verbinden. Dafür ist die Snyk CLI erforderlich, die Sie jedoch nicht separat installieren müssen. Beim ersten Scan lädt Snyk sie automatisch herunter.

PyCharm zeigt eine Django-requirements.txt-Datei an, während das Snyk-Panel die Snyk CLI herunterlädt.

Sie werden aufgefordert, sich bei Snyk zu authentifizieren. Klicken Sie auf Code jetzt testen, um zur Snyk-Web-UI zu gelangen.

JetBrains-IDE mit geöffneter Django-base.html-Vorlage und geöffnetem Snyk-Panel zum Testen der Codesicherheit.

Klicken Sie auf Authentifizieren, um die Integration Ihres Plug-ins zu bestätigen.

Snyk-Webseite mit der Meldung „Authenticate for CLI“ und einer Schaltfläche „Authenticate“

Nach der Authentifizierung analysiert Snyk Ihre Django-App automatisch und zeigt Probleme im Tab Snyk Ihrer IDE an. Alternativ können Sie die Analyse starten, indem Sie auf Scan ausführen klicken.

Django-Projekt in einem Code-Editor geöffnet. Das Snyk-Panel fordert dazu auf, nach Sicherheitslücken und Code-Problemen zu suchen.

Snyk zeigt hilfreiche Informationen zu den erkannten Schwachstellen an, darunter deren Schweregrad und empfohlene Korrekturen.

IDE mit einer Django-Datei „settings.py“, die einen fest codierten SECRET_KEY enthält, und Snyk, das eine schwerwiegende Schwachstelle durch ein fest codiertes Geheimnis meldet.

Sie können die Schwachstellen beheben, indem Sie die von Snyk empfohlenen Upgrades übernehmen. Das Plug-in empfiehlt stets die geringstmöglichen Änderungen zur Korrektur Ihres Codes.

Fazit

XSS-Angriffe zählen zu den häufigsten Sicherheitsproblemen in Django-Anwendungen. Da sie viele Ursachen haben und in unterschiedlichen Formen auftreten können, lassen sie sich nur schwer vollständig verhindern. Mit den folgenden Best Practices können Sie das Risiko von XSS-Angriffen jedoch verringern.

Auch die richtigen Tools während der Entwicklung sind entscheidend. Eine dedizierte Security-Plattform wie Snyk hilft Ihnen, XSS-Schwachstellen frühzeitig in der Entwicklung Ihrer App zu finden und zu beheben.

Starten Sie mit Capture the Flag

Erfahren Sie in unserem virtuellen On-Demand-Workshop für Einsteiger, wie Sie Capture-the-Flag-Herausforderungen lösen.