Skip to main content

XSS-Schwachstelle in `marked` beheben

Artikel von

15. Mai 2016

0 Min. Lesezeit

Vor einigen Wochen haben wir eine Cross-Site-Scripting-Schwachstelle (XSS) im beliebten Paket marked in unsere Datenbank aufgenommen. In diesem Beitrag erklären wir die Schwachstelle, zeigen, wie sie sich in einer Beispielanwendung ausnutzen lässt, und erläutern, wie Sie das Problem in Ihrer Anwendung beheben können.

marked parst Markdown und wandelt es in HTML um. So lassen sich Benutzereingaben – etwa Nutzerkommentare, Produktbewertungen oder Supportanfragen – ganz einfach in (mehr oder weniger) formatierten Text mit Links, Fett- und Kursivschrift und vielem mehr umwandeln. Da Markdown JavaScript nicht unterstützt, gilt es oft als immun gegen Cross-Site-Scripting und somit als sicher für die Darstellung von Benutzereingaben.

In Wirklichkeit verringert Markdown jedoch das XSS-Risiko nur, statt es vollständig auszuschließen. Diese leicht ausnutzbare XSS-Schwachstelle in marked verdeutlicht diesen Unterschied eindrücklich.

Wie unser Bericht State of Open Source Security 2019 zeigt, nehmen XSS-Schwachstellen weiter zu. Die meisten gemeldeten Schwachstellen betreffen das PHP-Packagist-Ökosystem, gefolgt von npm und Maven Central.

Die Schwachstelle

Markdown unterstützt zwar keine Skripte, aber marked (wie andere Markdown-Clients auch) unterstützt HTML direkt im Text. Inline-HTML kann Tags enthalten, mit denen Angreifer schädliche Skripte einschleusen können. Da marked häufig verwendet wird, um Benutzereingaben wieder auf der Seite darzustellen, haben die Entwickler eine Sicherheitsoption für diesen Fall hinzugefügt. Das Paket unterstützt die Option sanitize, die HTML und gefährliche Eingaben erkennt und sie kodiert oder entfernt.

Die Option sanitize ist leider standardmäßig deaktiviert, lässt sich aber in Ihrer Anwendung aktivieren. Dieses Beispiel zeigt die Option sanitize in Aktion:

var marked = require('marked');
console.log(marked('<script>alert(1)</script>'));
// Outputs: <script>alert(1)</script>

marked.setOptions({sanitize: true});
console.log(marked('<script>alert(1)</script>'));
// Outputs: <p><script>alert(1)</script></p>

HTML zu erkennen ist wichtig, doch damit ist die Bereinigung nicht abgeschlossen. Markdown unterstützt zwar keine Skripte, aber Links. Dadurch sind JavaScript-Links möglich (z. B. javascript:alert(1)), die Schaden anrichten können, wenn jemand darauf klickt. Die Funktion sanitize berücksichtigt das und entfernt Links, die wie javascript: aussehen. Sie entfernt sogar Links, die den HTML-Entitätencode für den Doppelpunkt verwenden, : (z. B. javascript&58;alert(1)). Doch trotz dieser Vorkehrung bleibt ein Fall unberücksichtigt …

HTML ist ein sehr flexibles Format, und Browser gehen bei der Verarbeitung sehr tolerant vor. Ein Beispiel dafür: Bei der Verarbeitung von HTML-Entitäten verlangen Browser keinen abschließenden Doppelpunkt und akzeptieren sowohl : als auch :. Die Bereinigung in marked verlangt hingegen den Doppelpunkt und behandelt den Text als gewöhnlichen Text, wenn sie ihn nicht findet. Das bedeutet: : wird entfernt, aber &58this; wird unverändert in die Ausgabe übernommen. Ein Angreifer kann diese Technik nutzen, um marked zu umgehen und den Browser trotzdem ein Skript ausführen zu lassen.

Das folgende Codebeispiel zeigt, wann sanitize funktioniert und wann nicht:

var marked = require('marked');
marked.setOptions({sanitize: true});

// Naive attempt - fails.
console.log(marked('[Gotcha](javascript:alert(1))'));
// Outputs: <p>)</p>

// Evasion attempt using '&#58;' instead of ':' - fails.
console.log(marked('[Gotcha](javascript:alert(1))'));
// Outputs: <p></p>

// Evasion attempt using '&#58;' (note the 'this') instead of ':' - SUCCEEDS
console.log(marked('[Gotcha](javascript:alert(1))'));
// Outputs: <p><a href="javascript&#58this;alert(1&#41;)">Gotcha</a></p>
// Same as: <p><a href="javascript:this;alert(1);">Gotcha</a></p>

Der Browser interpretiert : genauso wie : und führt das Skript beim Anklicken aus. Natürlich ist das von uns eingebundene Skript ziemlich harmlos. Ein Angreifer könnte jedoch eine weitaus raffiniertere Payload einschleusen, die die Same-Origin-Policy des Browsers aushebelt und den vollen Schaden anrichtet, den XSS verursachen kann.

Live-Exploit in Goof

Wie schon bei der Besprechung der Buffer-Schwachstelle in mongoose haben wir diese Schwachstelle in unsere anfällige Anwendung Goof eingebaut. Wir sind überzeugt, dass das Ausnutzen einer Schwachstelle und das Betrachten des anfälligen Codes dabei helfen, das Problem besser zu verstehen. Sie können Goof klonen und anhand der Anleitung auf GitHub ausführen.

Goof ist eine To-do-Anwendung und verwendet marked, um Markdown in Notizen zu unterstützen. Goof ist eine erstklassige To-do-App – und eine solche App MUSS einfach Links, Fett- und Kursivschrift unterstützen!

Wenn Sie beispielsweise die To-do-Einträge Buy **beer** und [snyk](https://snyk.io/) eingeben, werden sie wie erwartet fett bzw. als Hyperlink dargestellt:

Goof-Aufgabenliste mit Aufgaben neben den Browser-Entwicklertools, die HTML-Links untersuchen, darunter ein sichtbarer „snyk“-Link.

Versuchen wir nun, eine schädliche Payload einzugeben. Der nächste Screenshot zeigt den visuellen Zustand und den DOM-Zustand, nachdem jede der drei obigen Angriffs-Payloads eingegeben wurde. Da es sich um eine To-do-Liste handelt, steht der zuerst eingegebene Eintrag ganz unten (an dritter Stelle) auf der Liste.

Wie Sie sehen, hat der Bereiniger die beiden unteren Einträge, die ersten beiden Angriffsversuche, zu <p></p> und <p>)</p> reduziert. Die oberste Payload hat jedoch erfolgreich einen Hyperlink erzeugt, der javascript:this;alert(1) aufruft. Die Ausführung von this bewirkt nichts (sie verweist lediglich auf eine vorhandene Variable), während der Aufruf von alert ein Popup anzeigt.

Nachdem wir die Meldung unseres Exploits etwas aussagekräftiger gestaltet und auf den Link geklickt haben, sehen wir Folgendes:

Browserwarnung auf der Goof-TODO-Seite mit der Meldung „marked exploit successful“ und einer OK-Schaltfläche.

Sie können diesen Angriffsablauf selbst nachvollziehen, indem Sie Goof lokal installieren und die Exploit-Payloads im Verzeichnis exploits ausprobieren.

So beheben Sie die Schwachstelle

Ungewöhnlicherweise gibt es keine offizielle Version von marked, in der das Problem behoben ist. Das marked-Repository war seit dem vergangenen Sommer inaktiv, und die Schwachstelle wurde erst später offengelegt.

Sie können das Problem jedoch ganz einfach beheben, indem Sie mithilfe des Snyk Wizard einen Patch anwenden. Unser Security-Research-Team hat diesen Patch erstellt. Er basiert auf dem ursprünglichen Pull Request von Matt Austin an das Repository.

Wie alle anderen Snyk-Patches können Sie die detaillierten Patch-Dateien in unserer Open-Source-Schwachstellendatenbank einsehen. Für verschiedene Versionen von marked gibt es insgesamt drei unterschiedliche Patches. Der einfachste davon umfasst lediglich Folgendes:

Code-Diff mit einem überarbeiteten regulären Ausdruck zum Erkennen dezimaler, hexadezimaler und benannter HTML-Entities.
  • Update: Die Maintainer von marked haben schließlich am 30. Juli 2016 eine neue Version von marked (v0.3.6) veröffentlicht, in der dieses Problem behoben ist.

Alternativ können Sie ein anderes Markdown-Paket verwenden, zum Beispiel markdown-it oder remarkable. Es gibt keine Garantie, dass diese Pakete frei von Schwachstellen sind, aber derzeit sind keine bekannten, unbehandelten Schwachstellen vorhanden.

Neue Veröffentlichung, alte Schwachstelle

Ein weiterer interessanter Aspekt dieser Schwachstelle ist, dass sie tatsächlich schon recht alt ist. Das Problem wurde im Mai 2015 gemeldet, aber erst im vergangenen Monat in Schwachstellendatenbanken erfasst. Solche Verzögerungen sind nicht ungewöhnlich: Auf GitHub gibt es unglaublich viele Probleme, und es ist schwierig, mit ihrer Bearbeitung Schritt zu halten.

Wenn Sie auf ein Sicherheitsproblem in einem npm-Paket stoßen, unabhängig davon, ob es bereits behoben wurde, informieren Sie uns bitte unter security@snyk.io. Wir prüfen den Hinweis und nehmen ihn in unsere Datenbank auf. Das npm-Ökosystem ist so groß, dass wir alle zusammenarbeiten müssen, um über solche Probleme informiert zu bleiben und unsere Sicherheit zu gewährleisten.

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.