it-swarm.com.de

Ausführen von Code, der in Echtzeit in JavaScript mit eval () generiert wurde

Stellen Sie sich eine Front-End-JavaScript-Anwendung vor, in der Menüelemente basierend auf einer etwas einfachen Logik (Rollen, die der Benutzer hat, und einem anderen logischen Status) ein- oder ausgeblendet werden mussten.

Eine einfache Sprache wurde eingeführt, um diese Logik auf eine übersichtliche, für Menschen lesbare Weise zu definieren, und jedem Menüelement wurde eine Zeichenfolgenbedingung zugewiesen, die folgendermaßen aussieht: "isLoggedIn() AND NOT role(PARTNER)".

Um diese Bedingung tatsächlich zu überprüfen, wurde ein einfacher Compiler implementiert, der diese Sprache in eine gültige JavaScript-Codezeichenfolge übersetzt und sie mit eval() ausführt, wobei am Ende das boolesche Ergebnis zurückgegeben wird.

Der Compiler ersetzt einige Konstrukte wie AND, OR und NOT durch gültige JavaScript-Entsprechungen wie &&, || Und ! Mit einfachen regulären Ausdrücken:

private compileExpression(expression: string) {

  // Adding commas around function arguments (to make them strings)
  expression = expression.replace(/(\w+)\((.*?)\)/g, `$1('$2')`);

  // Replacing "AND"
  expression = expression.replace(/\sAND\s/g, ' && ');

  // Replacing "OR"
  expression = expression.replace(/\sOR\s/g, ' || ');

  // Replacing "NOT"
  expression = expression.replace(/(\s|^)NOT\s/g, ' !');

  // Prefixing predicates
  expression = expression.replace(/(\w+)\((.*?)\)/g, 'Π.$1($2)');

  return expression;

}

Dies macht "isLoggedIn() AND NOT role(PARTNER)" zu "Π.isLoggedIn() && !Π.role('PARTNER)", das dann von eval() ausgeführt wird:

public matchCondition = (condition: string): boolean => {

  // Using greek letter "P" for predicate (for shortness and uniqueness)
  // noinspection NonAsciiCharacters
  const Π = this.predicates;

  const expression = this.compileExpression(condition);

  const result = eval(expression);

  return result;

}

Das Π Ist ein Objekt mit einfachen Prädikatfunktionen, die boolesche Werte wie isLoggedIn(): boolean und role(roleName: string): boolean zurückgeben.

Die Ausdrücke, die übersetzt und ausgeführt werden, werden statisch im lokalen Objekt als Zeichenfolgen gespeichert und sind im globalen Kontext nicht zugänglich. Außerdem werden alle Ausdrücke von den Entwicklern geschrieben und stammen in keiner Weise von Benutzern der Anwendung.

Ist es sicher, eval() auf diese Weise zu verwenden, oder sollte es um jeden Preis vermieden werden (z. B. "eval ist böse", "eval nie verwenden" usw.)?

Was sind die möglichen Angriffsvektoren, die verwendet werden könnten, um eine solche Auswertung zu gefährden?

Wenn Ausdruckszeichenfolgen mithilfe von XHR/Fetch vom HTTPS-Server geladen werden, ändert sich dadurch die Sicherheitssituation?

Der Grund, eine solche Sprache einzuführen und die Regeln nicht direkt im Code zu definieren, besteht darin, dass diese Bedingungen in Zeichenfolgenwerten definiert werden müssen, z. in einer JSON-Datei. Der andere Grund ist, dass eine solche Sprache auf einen Blick leichter zu lesen ist.

11
Slava Fomin II

Die Verwendung von eval in diesem Kontext führt zu keiner Sicherheitsanfälligkeit, solange ein Angreifer die an matchCondition übergebenen Argumente nicht stören kann.

Wenn Sie es einfacher finden, es auf diese Weise zu lesen/zu programmieren, und Sie sicher sind, dass niemals eine nicht vertrauenswürdige Eingabe in Ihren Ausdrucks-Compiler gelangen wird, dann versuchen Sie es.

eval ist nicht böse, nicht vertrauenswürdige Daten sind.


Bitte beachten Sie, dass es durchaus möglich ist, eval zu vermeiden, indem Sie die Prädikate extrahieren und sie dann mit Ihren benutzerdefinierten Funktionen behandeln, zum Beispiel:

if (predicate === 'isLoggedIn()') {
    return Π.isLoggedIn();
}
38
Benoit Esnard

Heute wird alles von Entwicklern geschrieben. Nächsten Monat oder nächstes Jahr wird jemand sagen: "Hey, warum lassen Sie die Benutzer diese nicht selbst schreiben?" Bam.

Auch wenn die Regeln nur von den Entwicklern geschrieben wurden, enthalten sie oder werden sie vom Benutzer stammende Daten enthalten? So etwas wie Titel, Namen, Kategorien zum Beispiel? Dies könnte schnell zu einem XSS-Angriff führen.

Ihre regulären Ausdrücke sind so "offen" (mit vielen .* Ohne Validierung), dass, wenn etwas Unangenehmes eintritt, es in einer Minute direkt zum eval durchschlüpft.

Zumindest sollten Sie, wenn Sie eval behalten möchten, viel strengere Ausdrücke anstelle von .* Haben. Diese können jedoch schnell entweder schwer verständlich oder für viele praktische Fälle hinderlich werden.

32
jcaron

Aussehen und Erwartungen

Wenn etwas wie ein sicherer Ausdruck aussieht, werden die Leute es wahrscheinlich wie einen behandeln. Wenn ein Feld wie ein anderes Datenfeld aussieht, werden Personen (sogar Entwickler) wahrscheinlich nicht vertrauenswürdige Daten dort ablegen. Wenn etwas mit vollem Anwendungszugriff ausgewertet wird, sollte es wie Code aussehen und sich anfühlen.

Ein weiteres Problem sind subtile Fehler in Ihrem Pre-Compiler, die unerwünschte Fehler/Sicherheitslücken verursachen können. Die meisten Sicherheitslücken beginnen mit unerwünschten Fehlern, bevor ein böswilliger Angreifer etwas ausnutzen kann. Und eine neue Metasprache ohne ordnungsgemäße Überprüfung/Tests und stark definierte Syntax ist nur eine weitere Ebene der Verwirrung und der Fehler, die darauf warten, passiert zu werden.

Wenn nur Entwickler Code für Ihre Bedingungen schreiben, warum nicht einfach nur JavaScript verwenden? Die Metasprache bringt kaum einen Nutzen. Und Code wird von Leuten wie Code gehandhabt.

6
Falco

Das Front-End-Javascript selbst liegt vollständig im Willen des Clients, der den Code ausführt. Wenn Sie aus Sicherheitsgründen auf Front-End-Javascript angewiesen sind, konnten Sie Ihre Anwendung bereits nicht sichern.. Vergiss eval. Der Kunde kann Ihre gesamte Website auf Wunsch durch eine eigene Implementierung ersetzen. Daher sollte Ihr Server alles validieren, was der Client von ihm verlangt.

In Ihrem Fall sollten Sie sich fragen, ob es eine Sicherheitsverletzung für Benutzer ist, Menüelemente anzuzeigen, für deren Anzeige sie nicht privilegiert genug sind. Wenn ja, sollte der Server diese Menüelemente nicht an den Client liefern, da Benutzer jedes Javascript anzeigen können, das Sie ihnen liefern. Wenn nicht, haben Sie überhaupt keine Sicherheitsbedenken - wie andere bereits erwähnt haben, ist eval nur dann "schlecht", wenn es gegen nicht bereinigten Code ausgeführt wird. Da Ihr ausgewerteter Code derzeit bereinigt ist, glaube ich nicht, dass Sie Sicherheitsbedenken haben.

5
bvoyelr

Wie andere gesagt haben, sollte es keine Sicherheitslücken bei der Verwendung von eval geben, solange Ihre Regeln wirklich nur von vertrauenswürdigen Entwicklern stammen.

eval hat jedoch viele andere Nachteile in Bezug auf Komplexität, Wartbarkeit, Debugbarkeit usw. Die Verwendung regulärer Ausdrücke plus eval kann je nach Anwendung leicht zu Problemen führen wächst.

Ich denke auch, dass es möglich ist, dass Sie die Schwierigkeit, Ihren eigenen Dolmetscher zu schreiben, überschätzen. Open-Source-Bibliotheken sind ausgereift genug, um einen hübschen Nice-Interpreter zu erstellen, selbst wenn Sie (wie ich) mit Parser- und Compiler-Implementierungen nicht vertraut sind. Hier ist eine Grammatik für PEG.js , die ich basierend auf Ihrer Problembeschreibung in ungefähr 15 Minuten zusammengestellt habe. Zu Demonstrationszwecken werden nur die Prädikatnamen zurückgegeben. Um es zu verwenden, würden Sie es ändern, um eine Funktion zurückzugeben, die das Prädikatobjekt verwendet und das entsprechende Prädikat aufruft. Hoffentlich reicht dies jedoch aus, um Ihnen eine Vorstellung von einem möglichen Ansatz zu geben.

OrExpression
  = head:AndExpression tail:(_ ("OR") _ AndExpression)* {
      return tail.reduce(function(result, element) {
        return result || element[3];
      }, head);
    }

AndExpression
  = head:NotExpression tail:(_ ("AND") _ NotExpression)* {
      return tail.reduce(function(result, element) {
        return result && element[3];
      }, head);
    }

NotExpression
  = "NOT" _ expr:NotExpression { return !expr; }
  / "(" _ expr:OrExpression _ ")" { return expr; }
  / Predicate;

Predicate
  = _ predicate:[A-Z]+ _ "(" _ arg:[A-Z]* _ ")" {
        return predicate.join('') + '(' + arg.join('') + ')';
      }

_ "whitespace"
  = [ \t\n\r]*
1
Josh Kelley