Question

J'écris du code JavaScript pour analyser les fonctions entrées par l'utilisateur (pour une fonctionnalité semblable à celle d'un tableur). Après avoir analysé la formule, je pourrais la convertir en JavaScript et y exécuter eval () pour obtenir le résultat.

Cependant, j'ai toujours craint d'utiliser eval () si je peux l'éviter parce que c'est mauvais (et, à tort ou à raison, j'ai toujours pensé que c'était encore plus mauvais en JavaScript , car le code à évaluer peut être modifié par l'utilisateur).

Alors, quand est-il possible de l'utiliser?

Était-ce utile?

La solution

J'aimerais prendre un moment pour répondre à la prémisse de votre question, à savoir que eval () est " evil ". Le mot " evil ", tel qu'utilisé par les utilisateurs du langage de programmation, signifie généralement "dangereux", ou plus précisément "capable de causer beaucoup de dommages avec une commande simple". Alors, quand est-il acceptable d'utiliser quelque chose de dangereux? Lorsque vous savez quel est le danger et que vous prenez les précautions appropriées.

En fait, examinons les dangers de l’utilisation de eval (). Il y a probablement beaucoup de petits dangers cachés, comme tout le reste, mais les deux gros risques - la raison pour laquelle eval () est considérée comme diabolique - sont les performances et l’injection de code.

  • Performance - eval () exécute l’interpréteur / compilateur. Si votre code est compilé, il s'agit d'un succès, car vous devez appeler un compilateur potentiellement lourd au milieu de l'exécution. Cependant, JavaScript est toujours principalement un langage interprété, ce qui signifie que l'appel de eval () n'est pas un gros problème de performances dans le cas général (voir mes remarques spécifiques ci-dessous).
  • Injection de code - eval () exécute potentiellement une chaîne de code sous des privilèges élevés. Par exemple, un programme exécuté en tant qu'administrateur / root ne voudra jamais utiliser l'entrée utilisateur eval (), car cette entrée pourrait potentiellement être "rm -rf / etc / important-file". ou pire. Encore une fois, JavaScript dans un navigateur ne rencontre pas ce problème, car le programme est exécuté dans le compte de l'utilisateur. Ce problème pourrait être causé par JavaScript côté serveur.

Passons à votre cas particulier. D'après ce que j'ai compris, vous générez vous-même les chaînes. Supposons donc que vous prenez soin de ne pas autoriser une chaîne du type "rm -rf quelque chose d'important". pour être généré, il n'y a pas de risque d'injection de code (mais rappelez-vous, il est très très difficile de le garantir dans le cas général). De plus, si vous utilisez le navigateur, l’injection de code est un risque plutôt mineur, je crois.

En ce qui concerne les performances, vous devrez peser cela par rapport à la facilité de codage. À mon avis, si vous analysez la formule, vous pouvez également calculer le résultat au cours de l'analyse plutôt que d'exécuter un autre analyseur (celui situé dans eval ()). Mais il peut être plus facile de coder en utilisant eval (), et l'impact sur les performances sera probablement invisible. Dans ce cas, eval () ne semble pas plus mal que toute autre fonction susceptible de vous faire gagner du temps.

Autres conseils

eval () n'est pas mauvais. Ou bien, si c’est le cas, c’est un mal de la même manière que la réflexion, les E / S sur les fichiers / réseaux, les threads et l’IPC sont "diaboliques". dans d'autres langues.

Si, pour votre usage , eval () est plus rapide que l’interprétation manuelle, ou rend votre code plus simple, ou plus clair ... alors vous devriez l’utiliser. Si ni l'un ni l'autre, alors vous ne devriez pas. Aussi simple que cela.

Lorsque vous faites confiance à la source.

Dans le cas de JSON, il est plus ou moins difficile de manipuler le code source, car il provient d'un serveur Web que vous contrôlez. Tant que le JSON lui-même ne contient aucune donnée téléchargée par un utilisateur, l'utilisation d'eval ne présente pas d'inconvénient majeur.

Dans tous les autres cas, je ferais beaucoup pour que les données fournies par l'utilisateur soient conformes à mes règles avant de les transmettre à eval ().

Mettons-nous de vrais amis:

  1. Tous les principaux navigateurs ont maintenant une console intégrée que votre pirate potentiel peut utiliser en abondance pour invoquer n'importe quelle fonction avec n'importe quelle valeur. Pourquoi s'embarrasseraient-ils d'utiliser une instruction eval, même s'ils le pouvaient? / p>

  2. S'il faut 0,2 seconde pour compiler 2000 lignes de JavaScript, quelle est la dégradation de mes performances si j'évalue quatre lignes de JSON?

Même l'explication de Crockford pour "eval is evil" est faible.

  

eval is Evil, la fonction eval est la fonctionnalité la plus mal utilisée de   JavaScript. Evitez-le

Comme pourrait le dire Crockford lui-même "Ce type de déclaration tend à générer une névrose irrationnelle. Ne l'achetez pas. & Quot;

Comprendre eval et savoir quand cela peut être utile est bien plus important. Par exemple, eval est un outil utile pour évaluer les réponses du serveur générées par votre logiciel.

BTW: Prototype.js appelle directement eval cinq fois (y compris dans evalJSON () et evalResponse ()). jQuery l'utilise dans parseJSON (via le constructeur de fonctions).

J'ai tendance à suivre les conseils de Crockford pour eval () , et l'éviter complètement. Même les moyens qui semblent l'exiger ne le font pas. Par exemple, setTimeout () vous permet de transmettre une fonction plutôt que eval.

setTimeout(function() {
  alert('hi');
}, 1000);

Même s’il s’agit d’une source de confiance , je ne l’utilise pas, car le code renvoyé par JSON pourrait être tronqué, ce qui pourrait, au mieux, faire quelque chose de malicieux, au pire, révéler une erreur.

Eval est complémentaire à la compilation utilisée dans la modélisation du code. Par modèles, je veux dire que vous écrivez un générateur de modèles simplifié qui génère un code de modèle utile qui augmente la vitesse de développement.

J'ai écrit un cadre dans lequel les développeurs n'utilisent pas EVAL, mais ils utilisent notre cadre et ce dernier doit utiliser EVAL pour générer des modèles.

Les performances d’EVAL peuvent être améliorées à l’aide de la méthode suivante; au lieu d'exécuter le script, vous devez renvoyer une fonction.

var a = eval("3 + 5");

Il devrait être organisé comme

var f = eval("(function(a,b) { return a + b; })");

var a = f(3,5);

La mise en cache f va certainement améliorer la vitesse.

De plus, Chrome permet de déboguer très facilement de telles fonctions.

En ce qui concerne la sécurité, utiliser eval ou non ne changera presque rien,

  1. Tout d'abord, le navigateur appelle l'intégralité du script dans un bac à sable.
  2. Tout code maléfique dans EVAL l'est également dans le navigateur. L’attaquant ou toute autre personne peut facilement injecter un nœud de script dans DOM et tout faire s’il peut évaluer quelque chose. Ne pas utiliser EVAL ne fera aucune différence.
  3. C’est principalement une sécurité médiocre côté serveur qui est nuisible. Une mauvaise validation des cookies ou une mauvaise implémentation des ACL sur le serveur sont à l’origine de la plupart des attaques.
  4. Une vulnérabilité récente de Java, etc., figurait dans le code natif de Java. JavaScript était et est conçu pour s'exécuter dans un bac à sable, alors que les applets ont été conçus pour s'exécuter en dehors d'un bac à sable avec des certificats, etc. menant à des vulnérabilités et à bien d'autres choses.
  5. L'écriture de code pour imiter un navigateur n'est pas difficile. Il vous suffit d'envoyer une requête HTTP au serveur avec votre chaîne d'agent d'utilisateur préférée. Tous les outils de test simulent les navigateurs de toute façon. si un attaquant veut vous faire du mal, EVAL est leur dernier recours. Ils ont de nombreux autres moyens de gérer votre sécurité côté serveur.
  6. Le navigateur DOM n'a pas accès aux fichiers et pas à un nom d'utilisateur. En fait, rien sur la machine à laquelle eval peut donner accès.

Si la sécurité côté serveur est suffisamment solide pour permettre à quiconque d'attaquer de n'importe où, ne vous inquiétez pas pour EVAL. Comme je l'ai mentionné précédemment, si EVAL n'existait pas, les attaquants disposeront de nombreux outils pour pirater votre serveur, quelles que soient les capacités EVAL de votre navigateur.

Eval n’est utile que pour générer des modèles permettant de traiter des chaînes complexes en fonction de quelque chose qui n’est pas utilisé à l’avance. Par exemple, je préférerais

"FirstName + ' ' + LastName"

Par opposition à

"LastName + ' ' + FirstName"

Comme mon nom d'affichage, qui peut provenir d'une base de données et qui n'est pas codé en dur.

J'ai vu des personnes préconiser de ne pas utiliser eval, car c'est diabolique , mais j'ai vu les mêmes personnes utiliser les fonctions Function et setTimeout de manière dynamique, c'est pourquoi elles utilisent eval sous le capot : D

BTW, si votre sandbox n’est pas assez sûr (par exemple, si vous travaillez sur un site qui autorise l’injection de code), eval est le dernier de vos problèmes. La règle de base en matière de sécurité est que toutes toutes les entrées sont diaboliques, mais dans le cas de JavaScript même , JavaScript lui-même pourrait être diabolique, car en JavaScript, vous pouvez écraser n'importe quelle fonction. Assurez-vous que vous utilisez le vrai, donc, si un code malveillant commence avant vous, vous ne pouvez faire confiance à aucune fonction JavaScript intégrée: D

Maintenant, l'épilogue de ce billet est:

Si vous en avez VRAIMENT (80% du temps nécessaire à l'évaluation, PAS ) et que vous êtes certain de ce que vous faites, utilisez simplement eval (ou better Function;)), les fermetures et la POO couvrent les 80/90% des cas où eval peut être remplacé par un autre type de logique, le reste est du code généré de manière dynamique (par exemple, si vous écrivez un interprète). déjà dit évaluer JSON (ici, vous pouvez utiliser l'évaluation de Crockford Safe;))

Lors du débogage dans Chrome (v28.0.1500.72), j'ai constaté que les variables ne sont pas liées aux fermetures si elles ne sont pas utilisées dans une fonction imbriquée qui produit la fermeture. Je suppose que c'est une optimisation du moteur JavaScript.

MAIS : lorsque eval () est utilisé dans une fonction qui provoque la fermeture, ALL , les variables des fonctions externes sont liées au fermeture, même s’ils ne sont pas utilisés du tout. Si quelqu'un a le temps de vérifier si des fuites de mémoire peuvent être générées, laissez-moi un commentaire ci-dessous.

Voici mon code de test:

(function () {
    var eval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();   // Variable "unused" is visible in debugger
            eval("1");
        })();
    }

    evalTest();
})();

(function () {
    var eval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();   // Variable "unused" is NOT visible in debugger
            var noval = eval;
            noval("1");
        })();
    }

    evalTest();
})();

(function () {
    var noval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();    // Variable "unused" is NOT visible in debugger
            noval("1");
        })();
    }

    evalTest();
})();

Ce que j’aime souligner ici est que eval () ne doit pas nécessairement faire référence à la fonction native eval () . Tout dépend du nom de la fonction . Ainsi, lors de l'appel du eval () natif avec un nom d'alias (disons var noval = eval; ), puis dans une fonction interne noval (expression); ) alors l'évaluation de expression peut échouer lorsqu'elle fait référence à des variables qui devraient faire partie de la clôture, mais ne le sont pas.

Vous pouvez l'utiliser si vous avez le contrôle total sur le code transmis à la fonction eval .

eval est rarement le bon choix. Bien qu'il puisse y avoir de nombreux cas où vous pouvez accomplir ce que vous devez faire en concaténant un script et en l'exécutant à la volée, vous disposez généralement de techniques beaucoup plus puissantes et maintenables: la notation associative-array ( obj [ "prop"] est identique à obj.prop ), fermetures, techniques orientées objet, techniques fonctionnelles - utilisez-les plutôt.

En ce qui concerne le script client, je pense que la question de la sécurité est une question discutable. Tout ce qui est chargé dans le navigateur est sujet à manipulation et doit être traité comme tel. Il n'y a aucun risque à utiliser une instruction eval () lorsqu'il existe des moyens beaucoup plus simples d'exécuter du code JavaScript et / ou de manipuler des objets dans le DOM, tels que la barre d'adresse de votre navigateur.

javascript:alert("hello");

Si quelqu'un veut manipuler son DOM, je dis balancer. La sécurité pour empêcher tout type d’attaque devrait toujours être la responsabilité de l’application serveur, point final.

D'un point de vue pragmatique, il n'y a aucun avantage à utiliser eval () dans une situation où rien ne peut être fait. Cependant, il existe des cas spécifiques dans lesquels une évaluation DEVRAIT être utilisée. Dans ce cas, cela peut être fait sans risque de faire sauter la page.

<html>
    <body>
        <textarea id="output"></textarea><br/>
        <input type="text" id="input" />
        <button id="button" onclick="execute()">eval</button>

        <script type="text/javascript">
            var execute = function(){
                var inputEl = document.getElementById('input');
                var toEval = inputEl.value;
                var outputEl = document.getElementById('output');
                var output = "";

                try {
                    output = eval(toEval);
                }
                catch(err){
                    for(var key in err){
                        output += key + ": " + err[key] + "\r\n";
                    }
                }
                outputEl.value = output;
            }
        </script>
    <body>
</html>

Quand eval () de JavaScript n'est-il pas mauvais?

J'essaie toujours de dissuader d'utiliser eval . Presque toujours, une solution plus propre et maintenable est disponible. L'évaluation n'est pas nécessaire, même pour l'analyse JSON . Eval ajoute à la maintenance . Non sans raison, il est mal vu par des maîtres comme Douglas Crockford.

Mais j'ai trouvé un exemple où il devrait être utilisé:

Lorsque vous devez transmettre l'expression.

Par exemple, j'ai une fonction qui construit un google.maps.ImageMapType pour moi, mais je dois lui dire la recette, comment doit-il construire l'URL de la tuile à partir des zoom et paramètres :

my_func({
    name: "OSM",
    tileURLexpr: '"http://tile.openstreetmap.org/"+b+"/"+a.x+"/"+a.y+".png"',
    ...
});

function my_func(opts)
{
    return new google.maps.ImageMapType({
        getTileUrl: function (coord, zoom) {
            var b = zoom;
            var a = coord;
            return eval(opts.tileURLexpr);
        },
        ....
    });
}

Mon exemple d'utilisation de eval : import .

Comment cela se fait habituellement.

var components = require('components');
var Button = components.Button;
var ComboBox = components.ComboBox;
var CheckBox = components.CheckBox;
...
// That quickly gets very boring

Mais avec l'aide de eval et une petite fonction d'assistance, cela donne un bien meilleur aspect:

var components = require('components');
eval(importable('components', 'Button', 'ComboBox', 'CheckBox', ...));

importable pourrait ressembler à (cette version ne prend pas en charge l'importation de membres concrets).

function importable(path) {
    var name;
    var pkg = eval(path);
    var result = '\n';

    for (name in pkg) {
        result += 'if (name !== undefined) throw "import error: name already exists";\n'.replace(/name/g, name);
    }

    for (name in pkg) {
        result += 'var name = path.name;\n'.replace(/name/g, name).replace('path', path);
    }
    return result;
}

Du côté du serveur, eval est utile lorsqu’il s’agit de scripts externes tels que sql, influxdb ou mongo. Vous pouvez effectuer une validation personnalisée au moment de l’exécution sans redéployer vos services.

Par exemple, un service de réalisation avec les métadonnées suivantes

{
  "568ff113-abcd-f123-84c5-871fe2007cf0": {
    "msg_enum": "quest/registration",
    "timely": "all_times",
    "scope": [
      "quest/daily-active"
    ],
    "query": "`SELECT COUNT(point) AS valid from \"${userId}/dump/quest/daily-active\" LIMIT 1`",
    "validator": "valid > 0",
    "reward_external": "ewallet",
    "reward_external_payload": "`{\"token\": \"${token}\", \"userId\": \"${userId}\", \"amountIn\": 1, \"conversionType\": \"quest/registration:silver\", \"exchangeProvider\":\"provider/achievement\",\"exchangeType\":\"payment/quest/registration\"}`"
  },
  "efdfb506-1234-abcd-9d4a-7d624c564332": {
    "msg_enum": "quest/daily-active",
    "timely": "daily",
    "scope": [
      "quest/daily-active"
    ],
    "query": "`SELECT COUNT(point) AS valid from \"${userId}/dump/quest/daily-active\" WHERE time >= '${today}' ${ENV.DAILY_OFFSET} LIMIT 1`",
    "validator": "valid > 0",
    "reward_external": "ewallet",
    "reward_external_payload": "`{\"token\": \"${token}\", \"userId\": \"${userId}\", \"amountIn\": 1, \"conversionType\": \"quest/daily-active:silver\", \"exchangeProvider\":\"provider/achievement\",\"exchangeType\":\"payment/quest/daily-active\"}`"
  }
}

Ce qui permet alors,

  • Injection directe d'objet / de valeurs par une chaîne littérale dans un JSON, utile pour la modélisation de textes

  • Peut être utilisé comme comparateur, par exemple, nous établissons des règles pour valider une quête ou des événements dans le système de gestion de contenu

Con de ceci:

  • Peut-être des erreurs de code et des ruptures dans le service, si elles ne sont pas entièrement testées.

  • Si un pirate informatique peut écrire un script sur votre système, vous êtes plutôt foutu.

  • Une façon de valider votre script consiste à garder le hachage de vos scripts dans un endroit sûr, afin que vous puissiez les vérifier avant de les exécuter.

Je pense que tous les cas d’éval justifiés seraient rares. Vous êtes plus susceptible de l'utiliser en pensant qu'il est justifié que de l'utiliser lorsque c'est réellement justifié.

Les problèmes de sécurité sont les plus connus. Mais sachez également que JavaScript utilise la compilation JIT et que cela fonctionne très mal avec eval. Eval est un peu comme une boîte noire pour le compilateur, et JavaScript doit pouvoir prédire le code à l'avance (dans une certaine mesure) afin d'appliquer correctement et en toute sécurité des optimisations de performances et une portée. Dans certains cas, l'impact sur les performances peut même affecter un autre code en dehors de eval.

Si vous voulez en savoir plus: https: //github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md#eval

Seulement pendant les tests, si possible. Notez également que eval () est beaucoup plus lent que d’autres évaluateurs spécialisés JSON, etc.

.

Génération de code. J'ai récemment écrit une bibliothèque appelée Hyperbars qui jette un pont entre virtual-dom et guidons . Pour ce faire, il analyse un modèle de guidon et le convertit en hyperscript . L'hyperscript est d'abord généré sous forme de chaîne et, avant de le renvoyer, eval () pour le transformer en code exécutable. J'ai trouvé eval () dans cette situation particulière, l'exact opposé du mal.

Fondamentalement de

<div>
    {{#each names}}
        <span>{{this}}</span>
    {{/each}}
</div>

Pour cela

(function (state) {
    var Runtime = Hyperbars.Runtime;
    var context = state;
    return h('div', {}, [Runtime.each(context['names'], context, function (context, parent, options) {
        return [h('span', {}, [options['@index'], context])]
    })])
}.bind({}))

Les performances de eval () ne sont pas non plus un problème, car vous n'avez besoin d'interpréter la chaîne générée qu'une seule fois, puis de réutiliser plusieurs fois le résultat de l'exécutable.

Vous pouvez voir comment la génération de code a été réalisée si vous êtes curieux ici .

Il n'y a aucune raison de ne pas utiliser eval () tant que vous pouvez être sûr que la source du code provient de vous ou de l'utilisateur réel. Même s'il peut manipuler ce qui est envoyé dans la fonction eval (), ce n'est pas un problème de sécurité, car il est capable de manipuler le code source du site Web et peut donc changer le code JavaScript lui-même.

Alors ... quand ne pas utiliser eval ()? Eval () ne devrait pas être utilisé quand il y a une chance qu'un tiers puisse le changer. C'est comme intercepter la connexion entre le client et votre serveur (mais si cela pose un problème, utilisez HTTPS). Vous ne devriez pas utiliser eval () pour analyser le code écrit par d’autres personnes, comme dans un forum.

Si c'est vraiment nécessaire, eval n'est pas mauvais. Mais 99,9% des utilisations de eval que je découvre ne sont pas nécessaires (sans compter les éléments setTimeout).

Pour moi, le mal n’est pas une performance ni même un problème de sécurité (indirectement, c’est les deux à la fois). Toutes ces utilisations inutiles d’eval contribuent à un enfer de maintenance. Les outils de refactoring sont supprimés. La recherche de code est difficile. Les effets non anticipés de ces évaluations sont légion.

Je pense que eval est une fonction très puissante pour les applications Web côté client et sûre ... aussi sûre que JavaScript, qui ne le sont pas. :-) Les problèmes de sécurité sont essentiellement liés au serveur car avec un outil comme Firebug, vous pouvez attaquer n’importe quelle application JavaScript.

Eval est utile pour la génération de code lorsque vous n'avez pas de macros.

Par exemple (stupide), si vous écrivez un compilateur Brainfuck , vous Je vais probablement vouloir construire une fonction qui exécute la séquence d'instructions sous forme de chaîne et l'évaluer pour renvoyer une fonction.

Lorsque vous analysez une structure JSON avec une fonction d'analyse (par exemple, jQuery.parseJSON), une structure parfaite du fichier JSON est attendue (chaque nom de propriété est entre guillemets). Cependant, JavaScript est plus flexible. Par conséquent, vous pouvez utiliser eval () pour l'éviter.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top