JavaScript-Code dynamisch zur Laufzeit laden und ausführen

Das dynamische Laden von JavaScript-Code kann für verschiedene Szenarien interessant sein. Da das Standardvorgehen mittlerweile als "böse" gilt, sollten sich Entwickler auf andere Szenarien verlassen.

Know-how  –  102 Kommentare

(Bild: issaro prakalung / Shutterstock.com)

Wenn Entwickler eine Webapplikation programmieren, die Nutzer über ein Plug-in-System dynamisch erweitern können sollen, mag es sinnvoll sein, JavaScript-Code dynamisch zur Laufzeit zu laden und auszuführen. Eigene Plug-ins sollen in Form von JavaScript-Codeschnipseln über ein Webformular hochgeladen, an das Backend weitergeleitet und dann auf Serverseite ausgeführt werden. Bei diesen Anforderungen sollten schon alle Alarmglocken klingeln. Und das nicht ohne Grund. Eröffnet der beschriebene Plug-in-Mechanismus doch zumindest das Potenzial, schädlichen Code in die Applikation zu schleusen.

Dieses Programmierrezept zeigt, was Entwickler beim dynamischen Ausführen von JavaScript-Code beachten sollten. Doch zunächst ein Blick auf eine "Lösung", die sie nicht anwenden sollten.

Keine Lösung: JavaScript mit eval() ausführen

Für das Ausführen beziehungsweise das Interpretieren von JavaScript-Code zur Laufzeit sieht der ECMAScript-Standard die globale Funktion eval() vor. Der Code lässt sich dabei in Form einer Zeichenkette übergeben, dann wird er interpretiert und ausgeführt:

const code = `
const a = 5;
const b = 6;
a + b;
`;

const result = eval(code);
console.log(result); // 11

Der vorliegende Text ist ein dezent überarbeitetes Kapitel aus dem Buch "Node.js – Rezepte und Lösungen" von unserem Blogger Philip Ackermann, das kürzlich im Rheinwerk Verlag erschienen ist. Der Verlag lobt in einem Gewinnspiel einmal alle drei bisher vom Autor erschienenen Bücher aus. Leser müssen dafür bis zum 31. Oktober per Mail die folgende Frage beantworten: "Welches Package sollte genutzt werden, um JavaScript-Code dynamisch zu laden und auszuführen?" (Die Gewinner werden zufällig ermittelt, der Rechtsweg ist ausgeschlossen, Mitarbeiter der Heise-Gruppe und ihre Angehörigen dürfen am Gewinnspiel nicht teilnehmen.)

Allerdings sieht die JavaScript-Community die Verwendung der Funktion mittlerweile als Bad Practice an, auch bekannt unter dem Namen "eval() is evil". Der Grund dafür ist, dass der auf diese Weise ausgeführte Code Dinge anstellen kann, die nicht unbedingt gewollt sind. Da der Code Zugriff auf den gesamten JavaScript-Kontext hat, könnte so beispielsweise relativ einfach Code in ein System geschleust werden, der den aktuellen (Node.js-)Prozess beendet:

const code = `
console.log('Exiting process');
process.exit(0);
`;

console.log('Before executing code');
eval(code);
// Wird nicht ausgeführt
console.log('After executing code');

Mindestens genauso fies ist folgender Code, der dafür sorgt, dass die jeweilige Applikation in einer Endlosschleife hängen bleibt:

eval('while(true) console.log(1)');

Ebenfalls möglich – wobei dies wahrscheinlich nicht im Sinn des Hackers wäre – ist es, die eval()-Funktion selbst zu überschreiben:

eval('eval = undefined');

Anhand dieser kleinen Beispiele sieht man sofort, dass die Verwendung von eval() um jeden Preis zu vermeiden ist, zumindest, wenn Entwickler dynamisch Code ausführen wollen. Sie sollten genau wissen, was sie tun, und sich auf jeden Fall davor hüten, auf diese Weise externen Code auszuführen, auf den sie keinen Einfluss haben. Beispielsweise wenn sie, wie eingangs beschrieben, ein Plug-in-System implementieren und einzelne Plug-ins in Form von JavaScript-Code einlesen. Denn: Code, der über die eval()-Funktion ausgeführt wird, verfügt über die gleichen Rechte wie der Code, der die eval()-Funktion aufruft. Er wird innerhalb des gleichen Prozesses ausgeführt und hat Zugriff auf die gleichen Objekte wie der aufrufende Code, und zwar auf alle Objekte im jeweiligen Scope. Das stellt ein enormes Sicherheitsrisiko dar, weil so Objekte sehr einfach überschrieben beziehungsweise verändert werden können, wie zum Beispiel in folgendem Code:

const user = {
name: 'max',
email: 'maxmustermann@example.com'
};

const code = `
user.email = 'hacker@example.com';
`;

eval(code);
console.log(user);
// { name: 'max', email: 'hacker@example.com' }

Wenn nun die Funktion eval() nicht für das Ausführen von Code verwendet werden soll, welche Möglichkeit haben Entwickler dann? Die Antwort darauf ist, den Code in einem eigenen Kontext auszuführen, von dem aus er keinen Zugriff auf den Kontext des umgebenden Codes hat.