web-dev-qa-db-ja.com

eval()を使用してJavaScriptでリアルタイムに生成されたコードを実行する

やや単純なロジック(ユーザーが持つ役割とその他の論理状態)に基づいてメニュー項目を表示または非表示にする必要があるフロントエンドJavaScriptアプリケーションを考えてみます。

このロジックを簡潔な人間が読める方法で定義するために単純な言語が導入され、すべてのメニュー項目に次のような文字列条件が割り当てられました:"isLoggedIn() AND NOT role(PARTNER)"

この状態を実際にチェックするために、この言語を有効なJavaScriptコード文字列に変換し、eval()を使用して実行する単純なコンパイラーが実装され、最終的にブール結果を返します。

コンパイラーは、単純な正規表現を使用して、ANDORNOTなどのいくつかの構成要素を、&&||!などの有効なJavaScript同等物で置き換えることにより機能します。

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;

}

これは"isLoggedIn() AND NOT role(PARTNER)""Π.isLoggedIn() && !Π.role('PARTNER)"に変換し、eval()によって実行されます。

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;

}

Πは、isLoggedIn(): booleanrole(roleName: string): booleanなどのブール値を返す単純な述語関数を持つオブジェクトです。

翻訳および実行される式は、ローカルオブジェクトに文字列として静的に格納され、グローバルコンテキストからはアクセスできません。また、すべての式は開発者によって記述されたものであり、アプリケーションのユーザーからのものではありません。

このようにeval()を使用しても安全ですか、それともすべてのコストで回避する必要がありますか(たとえば、「eval is evil」、「never use eval」など)。

このようなevalの使用を危うくするために使用される可能性のある攻撃ベクトルは何ですか?

式文字列がXHR/Fetchを使用してHTTPSサーバーからロードされる場合、状況はセキュリティ面で変わりますか?

このような言語を導入し、コードでルールを直接定義しない理由は、これらの条件を文字列値で定義できることが必要なためです。 JSONファイル内。他の理由は、そのような言語は一目で読みやすいということです。

11
Slava Fomin II

このコンテキストでevalを使用しても、攻撃者がmatchConditionに渡された引数に干渉できない限り、脆弱性は発生しません。

この方法で読み/プログラムする方が簡単で、信頼できない入力が式コンパイラに入力されないことが確実である場合は、それを試してください。

evalは悪ではなく、信頼できないデータは悪です。


述語を抽出してカスタム関数で処理することにより、evalを完全に回避できることに注意してください。次に例を示します。

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

今日、すべてが開発者によって書かれています。来月または来年、誰かが「ねえ、ユーザーにそれらを自分で書かせてみませんか」と言うでしょう。バム。

また、ルールが開発者のみによって作成された場合でも、ユーザーが作成したデータが含まれますか、または含まれますか?たとえば、タイトル、名前、カテゴリなどですか?これはすぐにXSS攻撃につながる可能性があります。

正規表現は非常に「オープン」であり(検証なしで多くの.*を使用)、不都合なものが入ると、1分以内にevalに直接移動します。

少なくとも、evalを保持したい場合は、.*ではなく、より厳密な式を使用する必要があります。しかし、それらはすぐに理解するのが困難になるか、多くの実際的なケースの妨げになる可能性があります。

32
jcaron

外観と期待

何かが安全な表現のように見える場合、人々はおそらくそれを同じように扱うでしょう。フィールドが他のデータフィールドのように見える場合、人々(開発者でさえ)はおそらく信頼できないデータをそこに置くでしょう。フルレベルのアプリケーションアクセスで評価されるものは、コードのように見えます。

もう1つの問題は、プリコンパイラの微妙なバグで、不要なバグやセキュリティの欠陥を引き起こす可能性があります。ほとんどの脆弱性は、悪意のある攻撃者が何かを悪用する前に、不要なバグから始まります。そして、適切な検証/テストと厳密に定義された構文のない新しいメタ言語は、混乱とバグが発生するのを待っているもう1つのレイヤーです。

開発者だけがあなたの条件に合ったコードを書くなら、なぜ単なるJavaScriptを使わないのですか?メタ言語はほとんどメリットをもたらしません。そして、コードはコードのような人々によって処理されます。

6
Falco

フロントエンドのjavascript自体は、コードを実行しているクライアントの意思に完全に依存しています。 セキュリティをフロントエンドのJavaScriptに依存している場合は、すでにアプリケーションのセキュリティ保護に失敗しています。評価を忘れます。クライアントは、必要に応じてWebサイト全体を独自の実装に置き換えることができます。したがって、サーバーはクライアントが要求するすべてを検証する必要があります。

あなたのケースでは、ユーザーが見る特権が十分にないメニュー項目をユーザーが見ることがセキュリティ違反であるかどうかを自問する必要があります。その場合、サーバーはそれらのメニュー項目をクライアントに配信しない必要があります。これは、ユーザーが配信するjavascriptをユーザーが見ることができるためです。そうでない場合、セキュリティ上の懸念はまったくありません-他の人が述べたように、evalは、サニタイズされていないコードに対して実行された場合にのみ「悪い」ものです。評価されたコードは現在サニタイズされているため、セキュリティ上の懸念があるとは思いません。

5
bvoyelr

他の人が言ったように、あなたのルールが本当に信頼できる開発者だけから来ている限り、evalの使用によるセキュリティホールはありません。

ただし、evalには、複雑さ、保守性、デバッグ性などの点で他にも多くの欠点があります。また、正規表現とevalを使用すると、アプリケーションの方法によっては、問題が発生しやすくなります。成長します。

また、独自のインタープリターを作成する難しさを過大評価している可能性もあります。オープンソースライブラリは十分に成熟しているため、パーサーやコンパイラーの実装に精通していなくても(私のように)かなりいいインタープリターを作成できます。問題の説明に基づいて、約15分でまとめた PEG.js の文法を次に示します。デモのために、述語名を返すだけです。これを使用するには、述語オブジェクトを取得して適切な述語を呼び出す関数を返すように変更しますが、これで1つの可能なアプローチのアイデアが得られると思います。

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