Sicherheitslücken in NodeJS-C/C++-Add-on-Erweiterungen
Alessio Della Libera
14. August 2024
0 Min. LesezeitEines der Hauptziele dieser Untersuchung war es, C/C++-Sicherheitslücken im Kontext von NodeJS-npm-Paketen zu untersuchen. Im Mittelpunkt stehen die Untersuchung und Identifizierung klassischer Sicherheitslücken wie Buffer Overflows, Denial-of-Service-Angriffe (Abstürze von Prozessen, ungeprüfte Typen) und Speicherlecks im Kontext von NodeJS-C/C++-Add-ons sowie die Modellierung relevanter Quellen, Senken und Bereinigungsfunktionen mit Snyk Code (siehe Snyk bringt einen entwicklerorientierten AppSec-Ansatz für C/C++).
Ziel dieser Untersuchung sind NPM-Pakete, die im Rahmen ihrer Implementierung C/C++-Schnittstellen verwenden. Projekte, die nicht bei NPM gelistet sind, wurden nicht berücksichtigt.
Dieser Blogbeitrag gibt einen Überblick über häufige Sicherheitslücken und anfällige Muster, die beim Schreiben von C/C++-Add-ons in NodeJS auftreten können. Außerdem stellen wir Beispiele für Abhilfemaßnahmen und Empfehlungen für Open-Source-Maintainer vor.
Inspiriert wurde dieser Blogbeitrag von der Studie „Bilingual Problems: Studying the Security Risks Incurred by Native Extensions in Scripting Languages“ von Cristian-Alexandru Staicu, Sazzadur Rahaman, Àgnes Kiss und Michael Backes.[1] In ihrer ursprünglichen Arbeit analysierten die Autoren die Sicherheitsrisiken nativer Erweiterungen in verbreiteten Programmiersprachen, darunter JavaScript.
Hintergrund zu NodeJS-C/C++-Add-ons
NodeJS bietet verschiedene APIs zum Aufrufen nativen C/C++-Codes. Im Rahmen dieser Untersuchung wurden Sicherheitslücken betrachtet, die bei der Verwendung eines der folgenden Mechanismen auftreten können:
node_api.h: Node-APInapi.h: C++-Wrapper für die Node-API
Ein gutes Beispiel für die Verwendung der oben genannten Bibliotheken finden Sie auf GitHub.
Eine vollständige Einführung in Add-ons und ihre Erstellung finden Sie in der offiziellen NodeJS-Dokumentation.
Die folgenden Sicherheitslücken wurden untersucht und in mindestens einem Paket gefunden:
Speicherlecks
Ungeprüfter Typ (DoS)
Erreichbare Assertion (DoS)
Nicht behandelte Ausnahmen (DoS)
Buffer Overflow
Integer Overflow
In den folgenden Abschnitten werden Beispiele für anfällige Muster vorgestellt und die Bedingungen erläutert, die erfüllt sein müssen, damit sich die Sicherheitslücke ausnutzen lässt.
Beispiele für anfällige Muster
In diesem Abschnitt untersuchen wir, wie Add-on-spezifische APIs zu Sicherheitsproblemen führen können, wenn sie nicht ordnungsgemäß behandelt werden, und stellen einige im Rahmen dieser Studie identifizierte anfällige Muster vor.
HINWEIS: Die folgenden Beispiele stellen keine vollständige Liste dar. Es kann weitere Szenarien geben
die zu Sicherheitsproblemen führen und in diesem Blogbeitrag nicht behandelt werden.
Einrichtung
Installieren Sie node-gyp (https://github.com/nodejs/node-gyp).
Die folgenden Dateien werden verwendet, um die Beispiele im nächsten Abschnitt auszuführen:
package.json
binding.gyp
Führen Sie die folgenden Befehle aus, um die C/C++-Erweiterungen zu erstellen:
node-gyp configurenode-gyp build
Ein bestimmtes Beispiel ausführen:
main.js
Nicht behandelte Ausnahmen
Auswirkung: Denial of Service (DoS)
napi
Die napi-API bietet verschiedene Funktionen zum Behandeln von Ausnahmen und Auslösen von Fehlern. Je nach verwendetem Flag in der Datei binding.gyp ist jedoch besondere Vorsicht geboten, um unerwartete Abstürze zu vermeiden.
Ist beispielsweise das Flag NAPI_DISABLE_CPP_EXCEPTIONS in der Datei binding.gyp gesetzt, können die folgenden Szenarien zum Absturz eines Prozesses führen (DoS):
Napi::TypeError::New(env, "").ThrowAsJavaScriptException();sowie andere Funktionen, die einen Fehler auslösen können (zum Beispiel ein Argument mit falschem Typ)throw Napi::Error::Newohne umgebendestry/catchMehrere Aufrufe von
Napi::TypeError::New(env, "").ThrowAsJavaScriptException();ohnereturn, die innerhalb derselben Funktion erreichbar sind
Wie in der Dokumentation erläutert: „Nach dem Auslösen einer JavaScript-Ausnahme sollte der Code normalerweise sofort aus dem nativen Callback zurückkehren, nachdem alle erforderlichen Bereinigungen durchgeführt wurden.“ .
test_napi_exceptions.cpp
Führen Sie diese Beispiele aus:
Erreichbare Assertion
Auswirkung: Denial of Service (DoS)
node_api
Betrachtet man die bereitgestellten Beispiele, sieht man, dass in einigen Beispielen assert verwendet wird, um den Rückgabewert bestimmter Funktionen zu prüfen. Wird eine assert-Anweisung während der Programmausführung jedoch durch nicht vertrauenswürdige Werte (aus dem JavaScript-Code) erreicht, kann dies zu einem Absturz führen (DoS). Bei der Überprüfung einiger Projekte fanden wir mehrere erreichbare Assertions in der Programmlogik. Daher hielt ich es für wichtig, sie in die oben stehende Liste aufzunehmen.
Eine mögliche Lösung für dieses Szenario wäre, den Rückgabewert in einer if-Anweisung zu prüfen und anschließend den entsprechenden Wert zurückzugeben (abhängig von der Programmlogik), statt assert zu verwenden.
test_node_api_assert.c
Führen Sie dieses Beispiel aus:
Ungeprüfter Datentyp
Auswirkung: Denial of Service (DoS)
napi
napi bietet mehrere APIs zum Umwandeln von JavaScript-Typen. Zum Beispiel:
Napi::Value::ToString() „gibt den in einen JavaScript-String umgewandelten Napi::Value zurück“. Ebenso „Napi::Value::ToNumber() gibt den in eine JavaScript-Zahl umgewandelten Napi::Value zurück“.
Die napi-API Napi::Value::ToString() ruft intern napi_coerce_to_string aus der Node-API auf:
Ebenso ruft die napi-API Napi::Value::ToNumber() intern napi_coerce_to_number aus der Node-API auf:
In der offiziellen Dokumentation zu napi_coerce_to_string heißt es: „Diese API implementiert die abstrakte Operation ToString() gemäß Abschnitt 7.1.13 der ECMAScript-Sprachspezifikation. Diese Funktion führt möglicherweise JavaScript-Code aus, wenn der übergebene Wert ein Objekt ist.“ Das bedeutet: Wenn die Benutzereingabe eine toString-Eigenschaft definiert, wird der Wert dieser Eigenschaft zurückgegeben (anstatt toString() aufzurufen), was zu unerwarteten Ergebnissen führen kann.
Rufen wir andere Methoden für die von Napi::Value::ToString() zurückgegebenen Werte auf und definiert die Eingabe eine toString-Eigenschaft, kann eine Ausnahme auftreten, die meist zum Absturz des Prozesses führt. Dasselbe gilt für napi_coerce_to_number.
Anfälliges Muster:
Aufrufe wie
Napi::String::Utf8Value()für einenNapi::Value, der ausToString()oderToNumberstammt, ohne vorherige ordnungsgemäße Typprüfung
Um diese Szenarien zu vermeiden, können Sie prüfen, ob der von Napi::Value::ToString() oder Napi::Value::ToNumber() zurückgegebene Wert ein String beziehungsweise eine Zahl ist, bevor Sie andere Methoden darauf aufrufen.
HINWEIS: Wie bei den zuvor erwähnten Fällen nicht behandelter Ausnahmen treten diese Probleme auf, wenn das Flag NAPI_DISABLE_CPP_EXCEPTIONS in der Datei binding.gyp gesetzt ist.
test_napi_unchecked_type.cpp
Führen Sie diese Beispiele aus:
Speicherlecks
Auswirkung: Offenlegung von Informationen
napi
Die napi-API bietet mehrere Methoden, um aus einem UTF8-, UTF16-LE- oder ISO-8859-1-codierten C-String einen JavaScript-String zu erstellen. Diese APIs sind:
Alle diese Methoden haben dieselbe Signatur:
Der Wert, der sorgfältig geprüft werden muss, ist [in] length, also die Länge des Strings in Bytes. Wird dieser Wert von einem Angreifer kontrolliert oder fest codiert und ist der Eingabewert nicht vertrauenswürdig, können unerwartete Speicherwerte im Wert result gespeichert werden.
Um solche Probleme zu vermeiden, verwenden Sie für den Wert size_t length NAPI_AUTO_LENGTH.
Anfälliges Muster:
napi_create_string_*mit einem Wert fürsize_t length, der größer ist als die Länge vonconst char* str
test_napi_memory_leak.c
Führen Sie dieses Beispiel aus:
Methodik
Um möglichst viele Probleme automatisch zu testen und zu finden, habe ich den folgenden Ansatz verwendet, um die Leistungsfähigkeit von Snyk Code zu nutzen:
Erstellen eines Datensatzes mit npm-Paketen, die über NodeJS-Add-on-APIs C/C++ aufrufen
Schreiben von Sicherheitsregeln in Snyk Code zur Modellierung von:
Quellen: In diesem Kontext sind Quellen Werte aus JavaScript-Code, etwa Daten aus
Napi::CallbackInfo::Env()im Kontext vonnapioder ausnapi_get_value_*im Kontext vonnode_apiSenken: Je nach Sicherheitsproblem modellierte ich das Vorhandensein mehrerer Aufrufe von
ThrowAsJavaScriptExceptioninnerhalb derselben Funktion, dieassert-Prüfung sowie verschiedene Methoden zur Erstellung von String-Werten (um nur einige zu nennen). Ich berücksichtigte auch Fälle, in denen der Code aufgrund bestimmter Argumente nicht anfällig ist, etwa durchNAPI_AUTO_LENGTHbei Speicherlecks.
Schreiben von Regeln, die die definierten Senken und Quellen für eine Taint-Analyse verwenden, um den Datenfluss von den Quellen zu den Senken nachzuverfolgen
Verwenden der Quellen aus den bestehenden unterstützten Regeln (zum Beispiel Buffer Overflow oder Integer Overflow), damit ich noch mehr C/C++-Sicherheitslücken abdecken kann, nicht nur solche, die speziell NodeJS-Add-on-APIs verwenden
Ausführen dieser Regeln für den zuvor erstellten Datensatz
Manuelles Überprüfen der Ergebnisse und gegebenenfalls Erstellen eines PoC
Mit diesem Ansatz konnte ich mehrere Probleme in npm-Paketen finden, indem ich die relevanten APIs für NodeJS-Add-ons mit Snyk Code modellierte.
Bei einigen der gefundenen Probleme wählte ich jedoch eine Stichprobe von Projekten aus dem erstellten Datensatz aus und überprüfte sie manuell.
Ergebnisse
Im Rahmen dieser Untersuchung wurden mehrere Sicherheitslücken in Paketen gefunden. Sie sind unten aufgeführt.
Fazit
Persönlich war diese Untersuchung aus mehreren Gründen eine unglaublich lehrreiche Erfahrung. Ich hatte die Gelegenheit, tief in die Welt der NodeJS-Add-ons einzutauchen, vorhandene Literatur zu bestehenden Problemen zu lesen und mithilfe von Snyk Code einige Szenarien zu modellieren, um Probleme in einer großen Zahl von Repositories zu finden.
Obwohl ich mit JavaScript und vielen anderen Programmiersprachen recht vertraut bin, habe ich erst vor Kurzem begonnen, C/C++ zu lernen – aufgrund unserer Arbeit an der Unterstützung mehrerer Sicherheitsregeln, die Snyk Code-Kunden jetzt zur Verfügung stehen. Die Verbindung dieser beiden Aspekte – das Lernen und die Möglichkeit, mit Snyk Code verschiedene Sicherheitsprobleme zu modellieren – hat mir an dieser Untersuchung besonders viel Freude bereitet. Dafür möchte ich Snyk für diese Gelegenheit danken.
Quellen
Node-API – https://nodejs.org/api/n-api.html
node-addon-api – https://github.com/nodejs/node-addon-api
C++-Add-ons – https://nodejs.org/api/addons.html


