5 Methoden zur Vermeidung von Code-Injection in JavaScript und Node.js
6. April 2021
0 Min. LesezeitDas Schreiben von sicherem JavaScript-Code auf eine Weise, die Code-Injection verhindert, mag wie eine einfache Aufgabe erscheinen, aber auf dem Weg dorthin gibt es viele Fallstricke. Die Tatsache, dass Sie (als Entwicklungsteam) die Best Practices für Sicherheit befolgen, bedeutet zum Beispiel nicht, dass andere dies auch tun. Sie verwenden wahrscheinlich Open-Source-Pakete in Ihrer Anwendung. Woher wissen Sie, ob diese sicher entwickelt wurden? Was ist, wenn es dort unsicheren Code wie eval()
gibt? Finden wir es heraus.
Was ist Code-Injection?
Code-Injection ist eine spezielle Form von breit angelegten Injection-Angriffen, bei denen ein Angreifer JavaScript- oder Node.js-Code senden kann, der vom Browser oder der Node.js-Laufzeitumgebung interpretiert wird. Die Sicherheitslücke tritt auf, wenn der Interpreter nicht in der Lage ist, zwischen dem vertrauenswürdigen Code, den das Entwicklungsteam beabsichtigt hat, und dem injizierten Code, den Angreifende als Input bereitgestellt hat, zu unterscheiden.
Wie man Code-Injection verhindert
Eine wichtige Regel für sicheren Code ist, dass in der Anwendung kein dynamischer Code ausgeführt werden darf. Das bedeutet, dass Sie Sprachkonstrukte wie eval
und Code-Strings, die an setTimeout()
oder den Konstruktor Function
übergeben werden, vermeiden sollten. Zweitens sollte ebenfalls eine Serialisierung vermieden werden, die anfällig für Injektionsangriffe sein könnte, bei denen Code während des Serialisierungsprozesses ausgeführt wird. Führen Sie schließlich eine Überprüfung der Abhängigkeiten durch, um sicherzustellen, dass Ihre Anwendung nicht aufgrund von Open-Source-Komponenten von Drittanbietern für diesen Angriff anfällig ist. Wenn Sie außerdem ein Tool zur Static Code Analysis wie Snyk Code verwenden, können Sie diese potenziellen Code-Injection-Sicherheitslücken in Ihrem Code oder dem Ihres Teams finden.
In diesem Beitrag stellen wir fünf Möglichkeiten zur Verhinderung von Code-injection vor:
Vermeiden Sie
eval()
,setTimeout()
undsetInterval()
Vermeiden Sie
new Function()
Vermeiden Sie die Serialisierung von Code in JavaScript
Verwenden Sie einen Node.js-Sicherheits-Linter
Verwenden Sie Tools zur Static Code Analysis (SCA) zum Auffinden und Beheben von Code-Injection-Problemen
1\. Vermeiden Sie eval()
, setTimeout()
und setInterval()
Ich weiß, was Sie jetzt denken – noch ein Guide, der empfiehlt, eval
zu vermeiden. Ja, das stimmt, aber ich möchte Ihnen auch Beispiele aus der echten Welt anderer beliebter Bibliotheken nennen, die eval
(oder andere Formen der Codekonstruktion) verwendet haben, was sich als problematisch erwies und zu einer schwerwiegenden Sicherheitslücke führte.
Doch bevor wir uns mit den anfälligen Verweisen auf Drittanbieter-Pakete befassen, sollen zunächst eval
und die dazugehörigen Funktionen erklärt werden. JavaScript-Laufzeitumgebungen wie der Browser und die serverseitige Node.js-Plattform ermöglichen die Auswertung und Ausführung von Code während der Anwendungsphase. Ein praktisches Beispiel hierfür:
const getElementForm = getElementType == “id” ? “getElementById” : “getElementByName”;
const priceTagValue = eval(“document.”+getElementForm+”(“+elementId+”).value”);
Mit dieser Methode versuchen Programmierer/innen, einen dynamischen Weg zum Zugriff auf Daten im DOM zu schaffen. In diesem Beispiel wird davon ausgegangen, dass getElementform
ebenso wie die Variable elementId
potenziell den Nutzenden kontrollierbar ist. Es gibt bessere Möglichkeiten, diese Aufgabe ohne eval
zu lösen, daher sollten Sie dynamischen Code wie diesen unbedingt vermeiden.
Im Node.js-Bereich möchte man vielleicht den Zugriff auf bestimmte Datenpunkte in der Anwendung auf der Grundlage einer dynamischen Auswertung ermöglichen. Hier ein Beispiel:
const db = "./db.json"
const dataPoints = eval("require('"+db+"')");
In diesem Beispiel wird allgemein davon ausgegangen, dass die genaue Datei, die wir anfordern wollen, dynamisch und potenziell von den Nutzenden kontrollierbar ist, was wiederum zu möglichen Sicherheitslücken durch Code-Injection führt.
Die Dustjs-Code-Injection ist ein Beispiel für die unsichere Verwendung von eval in der Praxis
Das npm-Paket dustjsvon LinkedIn – ein Projekt für asynchrone Vorlagen für den Browser und serverseitiges Node.js – zeigt, wie schwerwiegend eine Code-Injection-Schwachstelle sein kann.
Dieses Paket wird zwar nicht mehr optimal weiterentwickelt, verzeichnet aber immer noch etwa 72.000 monatliche Downloads und wurde mit einer Code-Injection-Sicherheitslücke konfrontiert.
Die Entwicklerteams von dustjs haben ihr Bestes gegeben, um potenziell gefährliche User-Inputs zu vermeiden, die in unsichere Codekonstrukte wie die Funktion eval()
einfließen könnten. Die Funktion escapeHtml
selbst hatte jedoch einen Sicherheitsmangel, da sie nur auf String-Typen prüfte und dann die Eingaben überging, obwohl sie auch auf andere Typen, wie z. B. Arrays, hätte prüfen müssen. Dieser Pull-Request behebt die Code-Injection-Sicherheitslücke:
Sie fragen sich, welche Rolle eval()
dabei spielt?
Wenn Sie dustjs verwenden, können Sie auch das npm-Paket dustjs-helpers einsetzen, um zusätzliche Template-Hilfen wie mathematische und logische Operationen zu erhalten. Eines dieser zusätzlichen Hilfsmittel ist eine if
-Bedingung, die Sie möglicherweise in Ihren eigenen dust-Templates verwenden werden:
Ergibt doch Sinn, oder?
Das Problem ist, dass unkontrollierte User-Inputs in diesem Abfrageparameter device
direkt in die if-Bedingungshilfe fließen, die, wie in Zeile 227 zu sehen ist, eval verwendet, um die Bedingung dynamisch auszuwerten:
Jetzt wird deutlich, wie mehrere Sicherheitsprobleme auf unvorhergesehene Weise zusammenkommen:
dustjs-linkedin, ein Open-Source-Paket, weist eine Sicherheitslücke auf, bei der Eingabestrings in einer Funktion
escapeHtml
nicht korrekt bereinigt werden.dustjs-helpers, ein Open-Source-Paket, verwendet unsicheren Code wie die Funktion
eval()
, um diesen während der Ausführung dynamisch auszuwerten.
Möchten Sie sehen, wie ich diese Sicherheitslücke ausgenutzt und eine echte, funktionierende Anwendung gehackt habe, die genau diese Schwachstelle aufweist? Hier kommt meine Lösung:
Vermeiden Sie auch setTimeout()
und setInterval()
Um die Best Practices zur Vermeidung von eval()
abzuschließen, möchte ich auch andere Funktionen nennen, die Sie als JavaScript-Entwicklerteams mit Sicherheit schon einmal gehört oder verwendet haben: und zwar setTimeout()
und setInterval()
.
Eine weniger bekannte Tatsache über diese Funktionen ist, dass sie auch Code-Strings empfangen. Sie kann zum Beispiel wie folgt verwendet werden:
setTimeout(“console.log(1+1)”, 1000);
Glücklicherweise sind String-Literale in einer Node.js-Umgebung nicht erlaubt!
2\. Vermeiden Sie new Function()
Ein weiteres Sprachkonstrukt, das den obigen eval()
, setTimeout()
und setInterval()
ähnelt, ist der Konstruktor Function
, der die dynamische Definition einer Funktion auf der Grundlage von String-Literalen ermöglicht.
Betrachten Sie das folgende, ganz banale Beispiel:
const addition = new Function(‘a’, ‘b’, ‘return a+b’);
addition(1, 1)
Wenn Sie bis hierhin mitgelesen haben, kennen Sie bereits die potenziellen Sicherheitsprobleme, die sich aus den User-Inputs ergeben können, die in eine solche Funktion fließen...
3\. Vermeiden Sie die Serialisierung von Code in JavaScript
Serialisierung ist im Bereich Java ein wichtiges Thema. Mein Kollege Brian Vermeer hat einen Blogbeitrag darüber geschrieben, wie sich Sicherheitslücken aufgrund unsicherer Serialisierungsvorgänge auf Java-Anwendungen auswirken. Ich kann Ihnen die Lektüre wärmstens empfehlen: Serialisierung und Deserialisierung in Java: Erläuterung der Java Deserialize-Schwachstelle.
Zurück zu JavaScript, bei dem Serialisierung scheinbar auch ein Thema ist.
Wahrscheinlich werden Sie Ihre eigene Serialisierungs- und Deserialisierungslogik nicht selbst programmieren, aber warum sollten Sie diese in der wunderbaren Welt von npm und mehr als 1.500.000 Open-Source-Paketen, die Ihnen zur Verfügung stehen, nicht nutzen?
js-yaml gewinnt mit über 28.000.000 Downloads pro Woche an Popularität und wird auf der Grundlage des Snyk Advisor in einem guten Gesamtzustand befunden:
Aus dem obigen Screenshot des npm-Pakets js-yaml geht hervor, dass frühere Versionen Sicherheitslücken aufwiesen. Sie fragen sich welche?
Versionen von js-yaml wurden als anfällig für Code-Ausführung durch Deserialisierung eingestuft. Die Art und Weise, wie sich die Schwachstelle manifestiert, ist auf die folgende Verwendung des Konstruktors new Function
() zurückzuführen:
function resolveJavascriptFunction(object /*, explicit*/) {
/*jslint evil:true*/ var func;
try {
func = new Function('return ' + object);
return func();
} catch (error) {
return NIL;
}
}
Sehen wir uns nun an, wie ein Proof-of-Concept-Exploit für diese Schwachstelle aussieht:
var yaml = require('js-yaml');
x = "test: !!js/function > \n \
function f() { \n \
console.log(1); \n \
}();"
yaml.load(x);
Wenn also böswillige Akteurinnen und Akteure in der Lage sind, solche Eingaben oder Teile davon bereitzustellen, wie sie zur Erstellung der x
-Variable im obigen Proof-of-Concept-Code verwendet werden, dann wird eine potenzielle Schwachstelle zu einer echten Gefahr.
Die oben genannte Schwachstelle stammt aus dem Jahr 2013, aber in einem Bericht über Sicherheitslücken aus dem Jahr 2019 wurde ein weiterer Fall von Ausführung von beliebigem Code in js-yaml gefunden. Seien Sie also vorsichtig, oder um Ihnen einen praktischen Tipp zu geben: Vermeiden Sie new Function()
und scannen Sie Ihre Open-Source-Pakete von Drittanbietern, um sicherzustellen, dass Sie diese Schwachstellen nicht übernommen haben und dass Sie sie, falls doch, automatisch mit einem Fix-Pull-Request beheben können.
4\. Verwenden Sie einen Node.js-Sicherheits-Linter
Kommen wir nun zum Abschnitt über Tools in diesem Guide und sprechen wir über Linter. JavaScript-Entwicklungsteams mögen ihre Linter. Ganz gleich, ob Sie standardjs oder eslint verwenden, um einen Code-Stil zu gewährleisten – dies sind ziemlich gängige Tools in jedem JavaScript- oder Node.js-Projekt.
Warum nicht bewährte Sicherheitsmaßnahmen durchsetzen? An dieser Stelle betritt eslint-plugin-security die Bühne. Wie in der README-Anleitung beschrieben, ist die Verwendung des Plugins recht einfach. Fügen Sie einfach folgende eslint-Plugin-Konfiguration hinzu, um die empfohlene Konfiguration zu aktivieren:
"plugins": [
"security"
],
"extends": [
"plugin:security/recommended"
Wie kann der Linter helfen?
Er verfügt über Regeln zur Erkennung unsicherer Codes, wie z. B. detect-eval-with-expression zur Erkennung der Verwendung von eval()
mit Ausdrücken oder String-Literalen. Der Linter setzt auf einige andere Regeln wie die Verwendung von child_process Node.js APIs.
Beachten Sie, dass das letzte Veröffentlichungsdatum von eslint-plugin-security über vier Jahre zurückliegt. Auch wenn es immer noch gut funktioniert, sollten Sie vielleicht andere Nachfolgepakete wie eslint-plugin-security-node in Betracht ziehen.
5\. Verwenden Sie Tools zur Static Code Analysis zum Auffinden und Beheben von Code-Injection-Problemen
Static Code Analysis (SCA) Linter in den Grundformen, wie sie mit ESLint verwendet werden, sind ein guter Ausgangspunkt. Sie bieten ausreichend Kontext, um den Code-Stil durchzusetzen, aber wie wir mit dem Node.js-Sicherheits-Linter gesehen haben, sind sie nicht flexibel genug, um Sicherheitsprobleme tatsächlich anzugehen.
Einige der Bedenken, die Entwickler/innen mit einem Node.js-Sicherheits-Linter wie eslint-plugin-security haben, sind Folgende:
Falsch positive Ergebnisse: Die Linter-Regeln sind recht einfach und können zu einigen Fehlalarmen führen, die beim Entwicklungsteam nur noch mehr Frustration hervorrufen und Verwirrung stiften. Beispielsweise führt
RegExp(matchEmailRegEx)
zu einem Fehler des Node.js-Sicherheitslinters, weil die RegExp-Funktion nicht wörtlich verwendet wird. Vielleicht istmatchEmailRegEx
nur eine Konstante in meiner Datei shared/variables.js? Der Linter ist nicht fortschrittlich genug, um das zu wissen.Zu strenge Regeln: Wie bereits erwähnt, ist die Regel zu streng festgelegt. Entweder verwenden Sie
child_process.exec(someCommand, \[])
, oder eben nicht. Der Prozess zur Static Code Analysis, wie er hier mit dem Linter verwendet wird, ist nicht intelligent genug, um zu erkennen, dasssomeCommand
eine Konstante ist, die Sie hartkodiert haben. Die Tatsache, dass Sie child_process.exec() mit einem Nicht-Literal verwenden, reicht aus, um einen Linter-Fehler auszulösen – das führt zu Frustration beim Entwicklungsteam, das die Regel schließlich deaktiviert.Zu einfach gehaltene Regeln: Die Regelsammlung ist zu dünn, und die Ergebnisse sind zu rudimentär. Sie sind im Grunde ein Alles-oder-Nichts-Verfahren ohne viel Kontext dazu, wie die Daten tatsächlich von einem bestimmten User-Input zu einem potenziell sensiblen Code wie Befehlsausführungen, SQL-Abfragen oder anderem fließen.
Um die vorher getroffene Aussage zu wiederholen, ist ein Sicherheits-Linter wie eslint-plugin-security-node ein guter Ausgangspunkt. Das ist auf jeden Fall besser als gar nichts.
Aber es gibt bessere Möglichkeiten, Sicherheitsprobleme in Ihrem eigenen Code zu finden, während Sie programmieren.
Ich möchte Ihnen Snyk Code vorstellen, ein Tool für Static Apllication Security Testing (SAST), das für Entwicklungsteams konzipiert wurde.
Auffinden einer Befehlsinjektion in einer node.js-Anwendung
Snyk Code wird bald veröffentlicht, aber ich werde Ihnen schon jetzt einen kleinen Einblick in die Funktionsweise geben.
Zunächst verbinden Sie Snyk mit einem GitHub-Account and importieren das GitHub-Repository. Klicken Sie dazu auf Projekt hinzufügen und anschließend auf das GitHub-Symbol:
Suchen Sie dann entweder ein Repository aus der entsprechenden Liste oder geben Sie es in das Suchfeld ein und aktivieren Sie das jeweilige Repository, um die Suche zu starten:
Snyk importiert dann das GitHub-Repository und scannt es zügig.
Es erkennt automatisch andere Manifest-Dateien, die sich auf potenzielle Sicherheitsprobleme beziehen, z. B. wenn Sie Open-Source-Abhängigkeiten mit bekannten Schwachstellen verwenden oder wenn Ihr Docker-Image ebenfalls eine Reihe von Sicherheitslücken enthält.
Konzentrieren wir uns auf unseren eigenen Code in dieser Node.js-Anwendung und klicken daher auf die Code-Analyse, um zu sehen, was wir finden:
Snyk Code findet mehrere Schwachstellen. Darunter auch diese Command-Injection-Schwachstelle:
Die problematischen Sicherheitslücken, die in dieser Codezeile zu finden sind, erklären das Problem:
“Unsanitized input from the HTTP request body flows into child_process.exec, where it is used to build a shell command. This may result in a Command Injection vulnerability.”
Aber wie gelangen die Daten von diesem url
-Parameter in die unsichere exec()
-Funktion? Klicken Sie auf die Schaltfläche alle Details, um eine ausführlichere Version des Informationsflusses zu erhalten und den Zusammenhang zu verdeutlichen:
Hier können wir das Gesamtbild der Analyse durch Snyk Code klar erkennen.
Der Parameter url
wird aus dem item
-Array erstellt, das selbst die Quelle eines User-Inputs ist, der als Eingabe eines Nachrichtentexts in die Variable req.body.content
einfließt.
Behebung der Befehlsinjektion
Wir können nun weitere Maßnahmen ergreifen, um das Sicherheitsproblem zu lösen:
Anstatt die unsichere API
exec()
zu verwenden, können wir deren sichere Variante benutzen: MitexecFile()
werden die Eingaben in eine Array-Funktion übergeben.Wir können und sollten die Variable des Elements aus dem User-Input validieren, umgehen oder bereinigen, bevor sie in sensiblen Code wie die Ausführung von Systemprozessen einfließt.
Zusammenfassung
Tolle Leistung, wenn Sie so weit gelangt sind!
Hoffentlich sind Sie sich jetzt der Probleme bewusster, die durch Code-Injection-Schwachstellen entstehen können. Sei es, dass sie aus Ihrem eigenen Code stammen oder aus Abhängigkeiten von Drittanbietern, die Sie in Ihre Anwendung importieren.
Wenn Sie diesen Beitrag nützlich fanden, finden Sie hier einige weiterführende Informationen von meinen Kollegen bei Snyk:
Wenn Sie oder Ihr Team-Go entwickeln, sollten Sie sich dieses Go-Security-Cheatsheet ansehen: 8 Security Best Practices für Go-Entwickler
Wenn Sie sich mit Java und insbesondere mit Spring MVC beschäftigen, wäre dies eine interessante Lektüre: Java-Sicherheitsprobleme in meiner Spring MVC-Anwendung lösen, die ebenfalls Sicherheitslücken mithilfe von Snyk Code ausfindig macht
Beginnen Sie mit Capture the Flag
Lernen Sie, wie Sie Capture the Flag-Herausforderungen lösen, indem Sie sich unseren virtuellen 101-Workshop auf Abruf ansehen.