首页 文章

什么时候JavaScript的eval()不是邪恶的?

提问于
浏览
229

我正在编写一些JavaScript代码来解析用户输入的函数(用于类似电子表格的功能) . 解析了公式后,我可以将其转换为JavaScript并在其上运行 eval() 以产生结果 .

但是,我总是回避使用 eval() ,如果我可以避免它,因为它总是认为它在JavaScript中更加邪恶,因为要评估的代码可能会被用户改变) .

那么,什么时候可以使用它?

23 回答

  • -4

    当您没有宏时,Eval对于代码生成很有用 .

    对于(一个愚蠢的)示例,如果您正在编写一个Brainfuck编译器,您可能希望构造一个以字符串形式执行指令序列的函数,并评估它以返回一个函数 .

  • 17

    当您使用解析函数(例如,jQuery.parseJSON)解析JSON结构时,它需要JSON文件的完美结构(每个属性名称都是双引号) . 但是,JavaScript更灵活 . 因此,您可以使用eval()来避免它 .

  • 1

    我想花一点时间来解决你的问题的前提 - eval()是“邪恶的". The word " evil ", as used by programming language people, usually means " dangerous ", or more precisely "能够通过简单的命令造成很多伤害” . 那么,什么时候可以使用危险的东西呢?当您知道危险是什么时,以及何时采取适当的预防措施 .

    至关重点,我们来看看使用eval()的危险性 . 可能存在许多小的隐藏危险,就像其他一切一样,但两个大的风险 - eval()被认为是邪恶的原因 - 是性能和代码注入 .

    • 性能 - eval()运行解释器/编译器 . 如果你的代码是编译的,那么这是一个很大的问题,因为你需要在运行时调用一个可能很重的编译器 . 但是,JavaScript仍然主要是一种解释型语言,这意味着在一般情况下调用eval()并不是一个很大的性能影响(但请参阅下面的具体说明) .

    • 代码注入 - eval()可能在提升的权限下运行一串代码 . 例如,以管理员/ root身份运行的程序永远不会想要eval()用户输入,因为该输入可能是"rm -rf /etc/important-file"或更糟 . 同样,浏览器中的JavaScript无论如何都不会拥有自己的帐户 . 服务器端JavaScript可能有这个问题 .

    根据您的具体情况而定 . 从我的理解,你're generating the strings yourself, so assuming you'小心不要生成像"rm -rf something-important"这样的字符串,在一般情况下's no code injection risk (but please remember, it'非常难以确保这一点) . 此外,如果你在浏览器中运行,那么代码注入是一个非常小的风险,我相信 .

    至于性能,你必须加重,以免编码 . 我认为,如果您正在解析公式,那么您也可以在解析期间计算结果,而不是运行另一个解析器(eval()内部的解析器) . 但使用eval()进行编码可能更容易,并且性能损失可能不明显 . 看起来eval()在这种情况下并不比任何其他可以节省你一些时间的函数更邪恶 .

  • 1

    eval() isn 't evil. Or, if it is, it' s邪恶与反射,文件/网络I / O,线程和IPC在其他语言中的方式相同 .

    如果为了您的目的, eval() 比手动解释更快,或者使您的代码更简单,或更清晰......那么您应该使用它 . 如果不是,那么你不应该 . 就那么简单 .

  • 2

    当你信任来源时 .

    对于JSON,它或多或少难以篡改源,因为它来自您控制的Web服务器 . 只要JSON本身不包含用户上传的数据,使用eval就没有主要缺点 .

    在所有其他情况下,我会竭尽全力确保用户提供的数据符合我的规则,然后再将其提供给eval() .

  • 2

    让我们真正的人:

    • 每个主要的浏览器现在都有一个内置的控制台,你可能的黑客可以充分利用它来调用任何值的任何函数 - 为什么他们懒得使用eval语句 - 即使他们可以?

    • 如果编译2000行JavaScript需要0.2秒,如果我评估四行JSON,我的性能会有什么影响?

    即使是克罗克福德对“eval is evil”的解释也很薄弱 .

    "eval is Evil
    The eval function is the most misused feature of JavaScript. Avoid it"
    

    正如克罗克福德本人可能会说的那样“这种说法往往会产生非理性的神经症 . 不要买它 . ”

    了解eval并了解它何时可能有用更为重要 . 例如,eval是评估由软件生成的服务器响应的合理工具 .

    BTW:Prototype.js直接调用eval五次(包括在evalJSON()和evalResponse()中) . jQuery在parseJSON中使用它(通过Function构造函数) .

  • 53

    我倾向于遵循Crockford's adviceeval() ,并完全避免它 . 即使看起来需要它的方式也没有 . 例如, setTimeout() 允许您传递函数而不是eval .

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

    即使它是一个受信任的来源,我也不会使用它,因为JSON返回的代码可能会出现乱码,这最多可能会做一些不好的事情,最坏的情况是暴露出一些不好的东西 .

  • 0

    我看到人们主张不使用eval,因为它是邪恶的,但是我看到同样的人动态地使用Function和setTimeout,所以他们在引擎盖下使用eval:D

    顺便说一句,如果您的沙箱不够确定(例如,如果您正在允许代码注入的网站上工作),则eval是您的最后一个问题 . 安全性的基本规则是所有输入都是邪恶的,但是在JavaScript的情况下,即使JavaScript本身也可能是邪恶的,因为在JavaScript中你可以覆盖任何功能,你只需要使用真正的功能,所以,如果恶意代码在你之前启动,你就不能信任任何JavaScript内置函数:D

    现在这篇文章的结尾是:

    如果你 REALLY 需要它(80%的时间eval是需要 NOT )而你're sure of what you'正在做,只需使用eval(或更好的函数;)),闭包和OOP覆盖可以替换eval的80/90%的情况使用另一种逻辑,其余的是动态生成的代码(例如,如果你正在编写解释器)并且正如你已经说过评估JSON(这里你可以使用Crockford安全评估;))

  • 245

    Eval是编译的补充,用于模板化代码 . 通过模板我的意思是你编写一个简化的模板生成器,生成有用的模板代码,提高开发速度 .

    我编写了一个框架,开发人员不使用EVAL,但他们使用我们的框架,反过来,框架必须使用EVAL生成模板 .

    使用以下方法可以提高EVAL的性能;而不是执行脚本,您必须返回一个函数 .

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

    它应该被组织为

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

    缓存f肯定会提高速度 .

    此外,Chrome还可以轻松调试此类功能 .

    关于安全性,使用eval与否将几乎没有任何区别,

    • 首先,浏览器调用沙箱中的整个脚本 .

    • Any code that is evil in EVAL, is evil in the browser itself. The attacker or anyone can easily inject a script node in DOM and do anything if he/she can eval anything. Not using EVAL will not make any difference.

    • 主要是糟糕的服务器端安全性是有害的 . 糟糕的cookie验证或服务器上的ACL实施不良导致大多数攻击 .

    • Java的本机代码中存在最近的Java漏洞等 . JavaScript曾经被设计为在沙箱中运行,而applet被设计为在带有证书等的沙箱之外运行,导致漏洞和许多其他事情 .

    • 编写用于模仿浏览器的代码并不困难 . 您所要做的就是使用您最喜欢的用户代理字符串向服务器发出HTTP请求 . 无论如何,所有测试工具都会模拟浏如果攻击者想要伤害你,EVAL是他们的最后手段 . 他们还有许多其他方法来处理您的服务器端安全性 .

    • 浏览器DOM无权访问文件而不能访问用户名 . 事实上,eval可以访问的机器上没有任何东西 .

    If your server-side security is solid enough for anyone to attack from anywhere, you should not worry about EVAL. As I mentioned, if EVAL would not exist, attackers have many tools to hack into your server irrespective of your browser's EVAL capability.

    Eval仅适用于生成一些模板,以根据事先未使用的内容进行复杂的字符串处理 . 例如,我更喜欢

    "FirstName + ' ' + LastName"
    

    相反

    "LastName + ' ' + FirstName"
    

    作为我的显示名称,它可以来自数据库而且不是硬编码的 .

  • 1

    在Chrome(v28.0.1500.72)中进行调试时,我发现如果变量未在生成闭包的嵌套函数中使用,则它们不会绑定到闭包 . 我猜,这是JavaScript引擎的优化 .

    BUT :当 eval() 在导致闭包的函数内部使用时, ALL 外部函数的变量绑定到闭包,即使它们根本不被使用 . 如果有人有时间测试是否可以产生内存泄漏,请在下面给我留言 .

    这是我的测试代码:

    (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();
    })();
    

    我想在此指出的是,eval()不一定是指本机 eval() 函数 . It all depends on the name of the function . 因此,当使用别名调用本机 eval() (比如 var noval = eval; 然后在内部函数 noval(expression); 中)时, expression 的评估可能会失败,因为它引用应该是闭包的一部分的变量,但实际上并非如此 .

  • 0

    微软解释了为什么eval()在IE博客上的浏览器速度缓慢,IE+JavaScript Performance Recommendations Part 2: JavaScript Code Inefficiencies .

  • 69

    您应该使用eval()的唯一实例是您需要动态运行动态JS . 我说的是你从服务器上异步下载的JS ......

    ...... 10次中的9次你可以通过重构轻松避免这样做 .

  • 26

    它's okay to use it if you have complete control over the code that'传递给 eval 函数 .

  • 4

    eval 很少是正确的选择 . 虽然可能有许多实例可以通过将脚本连接在一起并动态运行来完成您需要完成的任务,但您通常可以使用更强大和可维护的技术:关联数组表示法( obj["prop"]obj.prop ),闭包,面向对象技术,功能技术 - 改用它们 .

  • 0

    只要您可以确定代码的来源来自您或实际用户,就没有理由不使用eval() . 即使他可以操纵发送到eval()函数的内容,这也不是安全问题,因为他能够操纵网站的源代码,因此可以更改JavaScript代码本身 .

    那么......何时不使用eval()?只有在第三方有可能改变它时,才应使用Eval() . 就像拦截客户端和服务器之间的连接一样(但如果这是一个问题,请使用HTTPS) . 您不应该使用eval()来解析由其他人编写的代码,例如在论坛中 .

  • 0

    如果它真的需要eval不是邪恶的 . 但是我遇到的99.9%的eval使用需要 not (不包括setTimeout东西) .

    对我来说,邪恶不是表演甚至是安全问题(嗯,间接是两者) . eval的所有这些不必要的使用增加了维护地狱 . 重构工具被抛弃了 . 搜索代码很难 . 这些遗嘱的意外影响很大 .

  • 0

    我相信eval对于客户端Web应用程序来说是一个非常强大的功能并且安全......像JavaScript一样安全,但不是 . :-)安全问题本质上是服务器端问题,因为现在,使用像Firebug这样的工具,您可以攻击任何JavaScript应用程序 .

  • 4

    就客户端脚本而言,我认为安全问题是一个没有实际意义的问题 . 加载到浏览器中的所有内容都会受到操纵,因此应予以对待 . 当有更简单的方法来执行JavaScript代码和/或操作DOM中的对象(例如浏览器中的URL栏)时,使用eval()语句没有任何风险 .

    javascript:alert("hello");
    

    如果有人想操纵他们的DOM,我会说摆脱 . 防止任何类型的攻击的安全性应始终是服务器应用程序的责任期 .

    从务实的角度来看,在事物的情况下使用eval()没有任何好处否则就可以了 . 但是,有些情况下应该使用eval . 如果是这样,它绝对可以毫无风险地炸毁页面 .

    <html>
        <body>
            <textarea id="output"></textarea>
    <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>
  • 3

    什么时候JavaScript的eval()不是邪恶的?

    我总是试图discourage from using eval . 几乎总是,提供更清洁和可维护的解决方案 . Eval is not needed even for JSON parsing . Eval adds to maintenance hell . 不是没有理由,道格拉斯·克罗克福德这样的大师不赞成这种说法 .

    但我找到了一个应该使用它的例子:

    当您需要传递表达式时 .

    例如,我有一个为我构造一个通用google.maps.ImageMapType对象的函数,但是我需要告诉它食谱,它应该如何从 zoomcoord 参数构造tile块:

    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);
            },
            ....
        });
    }
    
  • 3

    我使用 eval :import的例子 .

    它通常如何完成 .

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

    但是在 eval 和一个辅助函数的帮助下,它看起来更好看:

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

    importable 可能看起来像(此版本不支持导入具体成员) .

    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;
    }
    
  • 1

    在服务器端,eval在处理外部脚本(如sql或Influxdb或mongo)时非常有用 . 可以在不重新部署服务的情况下在运行时进行自定义验证 .

    例如,具有以下元数据的成就服务

    {
      "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\"}`"
      }
    }
    

    然后允许,

    • 通过json中的文字字符串直接注入对象/值,对模板文本很有用

    • 可以用作比较器,比如我们制定规则如何验证CMS中的任务或事件

    这个骗局:

    • 如果没有经过全面测试,可能是代码中的错误并破坏了服务中的内容 .

    • 如果黑客可以在你的系统上编写脚本,那么你几乎搞砸了 .

    • 验证脚本的一种方法是将脚本的哈希值保存在安全的位置,以便在运行之前检查它们 .

  • 1

    仅在测试期间,如果可能的话 . 另请注意,eval()比其他专门的JSON等评估器慢得多 .

  • 0

    代码生成 . 我最近写了一个名为Hyperbars的库,它弥合了virtual-domhandlebars之间的差距 . 它通过解析把手模板并将其转换为hyperscript来实现 . 超文本首先在字符串中生成,然后在返回之前将其转换为可执行代码 . 我发现 eval() 在这种特殊情况下与邪恶完全相反 .

    基本上来自

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

    对此

    (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({}))
    

    在这种情况下, eval() 的性能也不是问题,因为您只需要解释生成的字符串一次,然后多次重复使用可执行输出 .

    如果你好奇here,你可以看到代码生成是如何实现的 .

相关问题