避免在执行来自Ajax调用的JavaScript时使用eval函数

3
我希望进行一个ajax调用,返回一个json对象。其中一个JSON对象的属性将是客户端要执行的函数的字符串。我意识到可以使用eval轻松解决此问题,但看到eval的许多 缺点,我宁愿避免使用它。我的问题是:
是否可以以某种方式从服务器返回一些js代码并在不使用eval的情况下执行它?
按照要求,这里是一些示例代码:
服务器(Node.js):
var testFunc = function() {
    alert('h1');
};

app.get('/testPack', function(req, res) {
    var template = jade.render('h1 hi');
    res.send({
        template : template,
        entity : testFunc.toString(),
        data : { 
            id: "OMG I love this"
        }
    });
});

客户端:

$(document).ready(function() {
    $.ajax({
        url: '/testPack',
        success: function(data) {
            $('body').append($(data.template))
            alert(data.data.id);
            var entity = eval(data.entity);
            entity();
        }
    })
})

当然,返回的函数称为“entity”的函数不会做这样愚蠢的事情,它将公开返回的小部件的API。
只是为了澄清,我想避免必须单独调用JavaScript。我更喜欢将其与模板和数据捆绑在一起以进行呈现。

迭戈,我在我的答案中加入了一个例子。我不记得编辑是否会出现在你的通知中,所以只留下了一个评论让你看到更新。 - Ilya Volodin
9个回答

4
最简单的方法是不通过ajax调用服务器,而是在页面上创建一个新的脚本标签,并将其指向输出纯JavaScript(而非JSON)的RESTful网络服务。这样,浏览器将直接评估您的输出,而不需要使用eval函数。
进一步扩展我的答案: 为了避免在全局上下文中运行脚本的问题,您可以做一些技巧。例如,当您将脚本标签添加到头部时,您可以将onload事件绑定到它(或者更确切地说,模拟onload事件,因为IE不支持脚本标签的onload事件)。如果来自服务器的响应始终包装在具有已知名称的函数中,您可以从对象内部应用该函数。以下是示例代码(仅供参考):
function test ()
{
    this.init = function ()
    {
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.language = "javascript";
        script.src = "test.js";
        var me = this;
        window.callMe = function () { me.scriptReady(me); };
        var head = document.getElementsByTagName("head")[0];
        head.appendChild(script);
    };

    this.scriptReady = function (object)
    {
        serverResponse.call(object);
    };

    this.name = "From inside the object";

    this.init();
}

var t=new test();

服务器响应应该是这样的:
function serverResponse()
{
    alert(this.name);
}

window.callMe();

在这种情况下,serverResponse()函数中的所有内容都将使用你的对象作为“this”。现在,如果你以这种方式修改你的服务器响应:

function serverResponse()
{
    this.serverJSONString = { "testVar1": "1", "testVar2": 2 };

    function Test()
    {
        alert("From the server");
    }

    Test();
}

window.callMe();

您可以从服务器获取多个内容,只需一个响应。如果您不希望仅设置变量,则可以在主对象中创建一个函数来处理JSON字符串,并通过从响应中调用此函数来提供该字符串。
正如您所看到的,这一切都是可行的,看起来确实不太美观,但是再怎么说,您要做的事情本来就不漂亮。
附言:仅将字符串插入标签中将无法在IE上工作,它不允许您这样做。如果您不必支持IE,则可以将服务器响应插入新创建的脚本标签中并完成它。
另外,请不要直接使用此代码,因为我没有花太多时间编写它。它非常丑陋,只是作为示例而存在 :-)

请注意,这将导致多个请求(假设OP想要返回多个东西)。 - HChen
这将使输出的js处于全局范围内,我想避免这种情况。并且避免进行多个请求。 - Diego
如果我有函数的字符串,我能否在body中创建一个脚本标签,并将函数的字符串插入其中,希望浏览器会执行它? - Diego
这是最佳方法,并且可以缓解Stephen Chung所概述的严重安全风险。 - Slappy
我不认为这种解决方案在安全性方面比使用eval的解决方案具有什么优势。你仍然会从服务器获取将在浏览器中评估的javascript代码,因此如果你成为中间人攻击的对象,浏览器无法判断返回的js是否来自另一个服务器。 - Diego

1
这是我认为这个工作原理的一个例子。
`json`对象表示从服务器返回的内容。`c`和`d`属性包含函数名作为字符串。如果这些函数是页面上存在的其他对象的属性,那么你应该能够使用`object["property"]`访问器来调用它们。
在jsFiddle上看它运行:http://jsfiddle.net/WUY4n/1/
// This function is a child of the window object
window.winScopedFunction = function() {
    alert("ROCK THE WIN");    
}

// This function is a child of another object
var myObject = {
  myFunction : function() {
    alert("ROCK ON");
  }
};

// pretend that this json object was the result of an ajax call.
var jsonResultFromServer= {
    a : 1,
    b : 2,
    c : "myFunction", 
    d : "winScopedFunction" 
};

// you can call the local functions like so
myObject[jsonResultFromServer.c]();

window[jsonResultFromServer.d]();

在我的问题中,c和d将是函数本身,而不是名称。我想从服务器提供javascript,避免将其与其他UI代码捆绑在一起。 - Diego
啊,我想在你修订问题并提供代码示例之前回答了。 - jessegavin
@Diego,我建议您重新考虑节省带宽的需求。您的UI代码是否太大而无法包含?您似乎已经在使用jQuery或Prototype,因此已经将整个库发送到了网络上(可能带有许多不需要的函数)。大多数UI库都不会太大而无法包含。您还可以将它们优化为相当小的大小。为什么要承担通过网络动态发送它们所带来的额外安全风险呢? - Stephen Chung

1

是的,有一种方法,但它与eval具有完全相同的缺点。

您可以使用Function构造函数创建一个新函数,然后调用它。例如:

new Function(code)();

1

不行,你无法这样做,从定义上讲,因为JavaScript函数是无效的JSON格式。请参见此处的规范:

如果你返回的是一个字符串,那么它就是一个字符串。你不能在没有使用 eval 的情况下对其进行评估。你可以将你返回的任何其他内容称为任何你想要的名称,但请不要将其称为 JSON。

一个 JSON 字符串就是一个 JSON 字符串,无论它包含 JavaScript 代码还是 foobars。 :P - Christian

1

http://code.google.com/p/json-sans-eval/ 是一个快速的 JSON 解析器,它不使用 eval,而且在新浏览器中越来越普及的是 JSON.parse。这两种方法都是解析 JSON 的优秀替代品,可以避免使用 eval


我不是在谈论评估JSON,而是在评估JavaScript。 - Diego
那么你担心eval存在哪些问题呢?如果您正在使用跨域<script>标签、使用new Function或任何其他将JS加载到页面中的方法,则安全问题和性能问题也是问题。eval唯一的区别在于它可以用于在除全局范围之外的其他地方加载代码。 - Mike Samuel

0

你有一些示例案例吗?我可以想到的一些事情是,你可以在你的js文件中拥有一个常规函数,而你的服务器将返回一些参数供你的函数执行。你甚至可以指定要使用哪个函数!(这不是很棒吗?)

// your js file
var some_namespace = {
  some_function : function(a, b){
    // stuff
  }
}

// your server output
{
  some_other_data: "123",
  execute: {
    func: "some_namespace.some_function",
    params: [1, 2]
  }
}

// your ajax callback
function(r){
  window[r.execute.func].apply(this, r.execute.params);
}

0

你可以使用 Google Charts 所使用的技巧。

<html>
<head>
  <script>
   function onWorkDone(data) {
    console.log(data);
   }
  </script>
  <script src="callback.js"></script>
</head>
</html>

那么你的callback.js文件应该是:

function doWork(callback) {
  callback({result: 'foo'});
}
doWork(onWorkDone);

基本上,当doWork完成时,您的脚本将调用onWorkDone。您可以在此处查看一个工作示例:http://jsfiddle.net/ea9Gc/

0

我曾经有同样的问题,我是这样解决的:

文件:functions.js.php?f=1,3

$functions=array(
    'showMessage' => 'function(msg){ alert(msg); }',
    'confirmAction' => 'function(action){
                          return confirm("Are you sure you want to "+action+"?");
                        }',
    'getName' => 'function getName(){
                    return prompt("What is your name?");
                  }'
);

$queried = explode($_REQUEST['f']);

echo 'var FuncUtils = {'; // begin javascript object

$counter=1;
foreach($functions as $name=>$function){
    if(in_array($counter, $queried))
        echo '"'.$name.'":,'.$function.',';
    $counter++;
}

echo '"dummy":null };'; // end javascript object

文件名:data5.json

{
    "action" : ['confirmAction','exit']
}

文件:test.js

$(document).ready(function(){
    $.getScript('functions.js.php?f=1,3');
});

function onBeforeExit(){
    $.getJSON('data5.json', function(data) {
        var func = data.action.shift();
        FuncUtils[func].apply(null, data.action);
    });
}

0

不使用eval的原因

你已经说了。不要使用eval。但是你对于为什么这样做有一个错误的看法。

并不是因为eval是邪恶的,你误解了原因。除了性能方面的考虑外,以这种方式使用eval允许一个粗心的程序员在客户端上执行从服务器传递来的代码。请注意“从服务器传递”的部分。

为什么不要执行从服务器传递过来的代码

为什么不想要执行从服务器传递过来的代码(顺便说一下,这就是你计划要做的)?

当浏览器在网页上执行脚本时,只要网站有效 - 即确实属于你自己的,而不是冒充你的恶意软件网站试图欺骗你的用户 - 你可以相当肯定地说,浏览器运行的每一位代码都是由你自己编写的

黑客的天堂 - 脚本注入攻击

现在,如果您从服务器传递数据到您的Web应用程序,并且该数据包含可执行函数,则会引发问题。在数据从您的服务器传输到客户端浏览器的漫长而曲折的旅程中,它经过了名为互联网的荒野,可能通过多个层次的代理、过滤器和转换器,其中大部分您无法控制。
现在,如果黑客藏在中间某个地方,从服务器获取您的数据,将这些函数的代码修改为非常糟糕的东西,然后将其发送给您的客户端,那么您的客户端浏览器将接收数据并执行代码。哇!糟糕的事情发生了。更糟糕的是:您(在服务器端)永远不会知道您的客户端已被黑客攻击。
这被称为“脚本注入攻击”,是一个严重的安全风险。
因此,规则是:永远不要执行从服务器返回的函数。

只传递来自服务器的数据

如果您只从服务器接受数据,当黑客篡改它时,最糟糕的情况是您的客户端将看到奇怪的数据返回,希望您的脚本将过滤它们或将其处理为不正确的数据。您的客户端浏览器不会运行黑客愉快编写的任何任意代码

在客户端脚本中,当然您要遵循黄金法则:不信任通过互联网传输的任何数据。因此,在使用JSON数据之前,您已经进行了类型检查和验证,并禁止任何看起来可疑的内容。

不要这样做——从服务器传递函数并在客户端上执行

所以,长话短说:不要这样做

考虑另一种在浏览器上指定可插拔功能的方法——有多种方法可供选择。


2
我认为 OP 正在寻求关于如何避免使用 eval 的建议,而不是关于为什么 eval 不好的演讲。 - HChen
1
我知道如果你没有使用安全连接,中间人攻击总是可能的,但这个论点适用于你可能向服务器请求的每一件事情。如果情况容易受到MITM攻击,那么黑客甚至可能发送他提供的我的页面版本,这比仅仅接收虚假js要糟糕得多。 - Diego
你的应用程序越成功(我相信你希望它成功),就会有越多的人试图为自己的利益入侵其中的某些东西。这种安全风险可能会在未来的代码堆中被埋没,直到某个非常受欢迎的网站发生了真正的事情,才会引起人们的注意。 - Stephen Chung
Stephen,整篇文章都是虚假的。不要从服务器执行代码;这不是Web浏览器应该做的相反吗?你自己说过,安全有时候只是潜在风险,但在这种情况下,它们是零。回答一个问题,如果你不能信任自己,那么你能信任谁呢?按照同样的论点,你根本不应该编写网站。 - Christian
Diego不仅提出了一个有效的观点,而且他的帖子简而言之可以归结为“如果你不信任你的网络,就不要做任何事情”。这与“不要让网站运行代码”完全不同,顺便说一句,甚至建议这样做是愚蠢的。没有额外的攻击面,它是所有网站的相同攻击面,即使是那些在SSL上运行的网站,如果你真的想深入研究的话。 - Christian
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接