Buffer ausnutzen
5. April 2016
0 Min. LesezeitZwischen den Wundern von Node schlummert eine tickende Zeitbombe namens Buffer. Wird diese riskante Klasse falsch verwendet, kann sie leicht serverseitigen Speicher offenlegen – und damit auch Ihre Geheimnisse und Schlüssel. Ursache ist eine von Feross Aboukhadijeh und Mathias Buss Madsen offengelegte Schwachstelle. Auch beliebte und etablierte Projekte sind in diese Falle getappt und haben dadurch weitreichende Schwachstellen eingeführt, darunter die npm-Pakete mongoose, ws, request und zuletzt sequelize.
In diesem Beitrag erklären wir, wie Buffer funktioniert und warum es sich so verhält. Wir führen einen Exploit gegen eine anfällige Anwendung aus, um die Folgen besser zu veranschaulichen. Zum Schluss besprechen wir, welche Änderungen Sie in Node 6 erwarten können, und listen 5 Schritte auf, mit denen Sie sich vor dem Standardverhalten von Buffer schützen können, und
Wie kam es dazu?
Clientseitiges JavaScript erspart uns den Umgang mit der Speicherzuweisung. Wie bei vielen vergleichbaren Sprachen weist in JS die zugrunde liegende Engine (z. B. V8) den Speicher zu und gibt ihn bei Bedarf durch Garbage Collection wieder frei. Das macht das Programmieren einfacher und sicherer. Im Browser ist es außerdem notwendig, den Zugriff auf den Speicher zu verhindern, um die Sandbox zu schützen, in der JS ausgeführt wird.
Als JS mit Node auf den Server kam, fiel die Browser-Sandbox weg und der Bedarf an einfacher und schneller Verarbeitung binärer Daten stieg. Um diesen Anforderungen gerecht zu werden, führte Node die Klasse Buffer ein, die binäre Daten verarbeitet. Hinweis: ES6 führte später weitere Klassen für binäre Daten ein, insbesondere TypedArray und ArrayBuffer.
Buffer ist ein veränderbares Array binärer Daten, das mit einer Zeichenfolge, einem Array oder einer Zahl initialisiert werden kann. Hier einige Auszüge aus der Node.js-Dokumentation:
Die ersten beiden Varianten erstellen einfach eine binäre Darstellung des jeweils übergebenen Werts. Die letzte Variante reserviert dagegen vorab einen Buffer der angegebenen Größe. Das macht sie zu einem nützlichen – nun ja – Puffer, insbesondere beim Lesen von Daten aus einem Stream.
Die Sicherheitslücke
Bei Verwendung des number-Konstruktors von Buffer wird zwar Speicher zugewiesen, aber nicht mit Nullen gefüllt. Stattdessen enthält der zugewiesene Buffer … was auch immer sich zu diesem Zeitpunkt im Speicher befand. Sie können dieses Verhalten selbst beobachten, indem Sie Node in einem Terminal ausführen und wiederholt einen Buffer einer bestimmten Größe erstellen. Jeder enthält dann andere Werte, da er auf einen anderen Bereich des zuvor verwendeten Speichers verweist.
Dieses Verhalten ist gut dokumentiert, und Sie können den zugewiesenen Speicher ganz einfach mit „buf.fill(0)“ auf null setzen. Wenn Sie das jedoch vergessen, kann Speicher offengelegt werden. Die Offenlegung von Speicher klingt zunächst vielleicht nicht so schlimm – bis Sie bedenken, wie viele Geheimnisse dadurch zugänglich werden könnten: Schlüssel, Quellcode, Systeminformationen. Heartbleed, die massive OpenSSL-Schwachstelle von 2014, ermöglichte die Offenlegung von Speicher.
Das Sicherheitsrisiko veranschaulichen
Um die Buffer-Schwachstelle besser zu verstehen, sehen wir uns eine anfällige Anwendung namens Goof an. Ihr Code ist auf GitHub verfügbar, zusammen mit Installationsanweisungen, falls Sie diese Exploits selbst ausführen möchten.

Dies ist eine einfache (anfällige) TODO-Listen-App. Sie verwendet mongoose, um mit der MongoDB zu kommunizieren, in der die Einträge gespeichert sind. Dazu kommt ein einfaches Schema zum Einsatz:
Wie Sie sehen, ist das Feld content, in dem die eigentliche TODO-Aufgabe gespeichert wird, vom Typ Buffer und unterstützt dadurch binäre Daten. Wie viele Node.js-Apps stellt auch snyk-demo-todo eine JSON-API bereit, über die wir in der Befehlszeile einen Eintrag erstellen:
Der Wert der Variablen content wurde an den Buffer-Konstruktor übergeben. Dadurch wurde eine kurze Zeichenfolge initialisiert und anschließend in die DB geschrieben. Der neue Eintrag ist in der Anwendungsoberfläche sichtbar und wird in der Antwort ebenfalls zurückgegeben (Base64-codiert):

Nun zum Exploit der Schwachstelle. Wir senden dieselbe Anfrage, ersetzen jedoch die Zeichenfolge "Buy Milk" durch die nicht in Anführungszeichen gesetzte Zahl 800:
Wie zuvor wird der Wert von content an den Buffer-Konstruktor übergeben. Diesmal ist der Wert jedoch vom Typ number und löst damit den anderen Buffer-Konstruktor aus … Dadurch wird der TODO-Eintrag mit 800 Byte nicht initialisiertem Speicher angelegt und in der DB gespeichert. Diese Daten sind wiederum in der HTML-Antwort sichtbar (allerdings binär und daher schwer lesbar) und werden nach der Base64-Codierung an unser curl zurückgegeben:

Nun können wir diesen Aufruf immer wiederholen, um weitere Speicherblöcke abzurufen und Base64 zu dekodieren. Wahrscheinlich löschen wir die erstellten Notizen nach und nach, um keinen Verdacht zu erregen. Anschließend können wir die Daten prüfen oder nach Mustern wie secret, ssh-key, Quellcode und mehr durchsuchen.
Wie lässt sich das beheben?
In unserer Beispielanwendung lag die Schwachstelle in der Abhängigkeit mongoose. Die Lösung bestand darin, Zahlen in Arrays umzuwandeln, sodass sie als einzelne Zahl statt als Länge behandelt werden. Wenn Sie eine anfällige Version von mongoose verwenden, sollten Sie auf eine neuere Version aktualisieren. Falls Sie nicht sicher sind, ob Sie die Bibliothek verwenden, hilft Ihnen Snyk, diese und weitere Schwachstellen zu finden und zu beheben.
Wenn die Schwachstelle in Ihrem eigenen Code liegt, können Sie entweder die Verwendung des number-Konstruktors unterbinden (und den Wert optional in ein Array oder eine Zeichenfolge umwandeln) oder nach jedem Aufruf die Funktion fill(0) verwenden. Beachten Sie, dass das Zurücksetzen des Speichers auf null zwar effizient ist, bei größeren Buffern aber Zeit kostet. Berücksichtigen Sie daher die möglichen Auswirkungen auf die Leistung Ihrer Anwendung.
Geplante Änderungen in Node 6
Dieses Standardverhalten von Buffer ist problematisch und Ursache für mehrere bekannte Schwachstellen. In aktuellen Node-Versionen lässt es sich jedoch nur schwer ändern, da dadurch Anwendungen beeinträchtigt werden könnten. Ab Node 6 wird jedoch empfohlen, die expliziten Methoden alloc und allocUnsafe zu verwenden. Sie weisen Speicher mit beziehungsweise ohne vorheriges Zurücksetzen auf null zu. Diese explizite API kann Entwicklerinnen und Entwicklern helfen, ähnliche Fehler zu vermeiden.
Der Standardkonstruktor wird weiterhin unterstützt und funktioniert unverändert, gilt jedoch als veraltet und wird nicht empfohlen. Sie können Node (ebenfalls erst ab Node 6) das Flag --zero-fill-buffers übergeben. Dadurch wird der zugewiesene Buffer beim standardmäßigen Buffer-Zahlenkonstruktor mit Nullen gefüllt.
5 Schritte für mehr Sicherheit
Buffer ist eine nützliche, aber gefährliche Klasse. Wenn Sie ihren Einsatz erwägen, sollten Sie Folgendes berücksichtigen:
Können Sie stattdessen
TypedArrayoderArrayBufferverwenden? Auch diese Klassen eignen sich für binäre Daten und füllen den Speicher mit Nullen.Können Sie den
number-Konstruktor unterbinden? Wenn ja, tun Sie es wiemongoose.Können Sie die zugewiesenen Daten mit
buf.fill(0)mit Nullen füllen? Das wirkt sich geringfügig auf die Leistung aus, ist aber sicherer.Falls Sie nichts davon umsetzen können, verfolgen Sie genau, wohin der Inhalt von
Buffergelangt. Sehr genau.Wenn Sie npm-Pakete als Abhängigkeiten verwenden, führen Sie snyk wizard aus, um alle mit
Bufferzusammenhängenden Schwachstellen in Ihren Abhängigkeiten 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.