3

I want to make an ajax call that will return a json object. One of this JSON object's properties will be the string of a function to be executed in the client. I realise this can easily be solved by using eval, but seeing the many disadvantages of eval, I'd rather avoid it. My question is:

Can I in some way return from the server some js code and execute it without resorting to eval?

As requested, here's some example code:

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

Client:

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

Of course, the returned function called entity wouldn't do such a silly thing, it would expose an API of the returned widget.

Just to clarify, I'd like to avoid having to make a separate call for the javascript itself. I'd rather bundle it with the template and data to render.

Marc.2377
  • 7,807
  • 7
  • 51
  • 95
Diego
  • 5,024
  • 6
  • 38
  • 47
  • Diego, I updated my answer with an example. Can't remember if edits show up in your notifications, so just leaving a comment so you would see an update. – Ilya Volodin Mar 23 '11 at 04:21

9 Answers9

4

Easiest way to do that, is not to call a server through an ajax, but instead to create a new script tag on the page with the url pointing to a RESTful web-service that would output pure JavaScript (not JSON). That way your output will be evaluated by the browser directly without the use of eval.

To expand a little on my answer: To get around the problems of running script in the global context you could do some tricks. For example, when you are adding script tag to the head, you can bind onload event (or rather fake onload event, since IE doesn't support onload on the script tag) to it, and if your response from the server will be always wrapped in the the function with a known name, you could apply that function from within your object. Example code below (this is just an example though):

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

The server response should look something like this:

function serverResponse()
{
    alert(this.name);
}

window.callMe();

In this case, everything inside serverResponse() will use your object as "this". Now if you modify your server response in this way:

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

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

    Test();
}

window.callMe();

You can have multiple things being returned from the server and with just one response. If you don't like just setting variables, then create a function in your main object to handle JSON string that you can supply by calling this function from your response.

As you can see, it's all doable, it really doesn't look pretty, but then again, what you are trying to do is not pretty to begin with.

P.S. Just inserting a string inside tag will not work for IE, it will not allow you to do that. If you don't have to support IE, then you could get away with just inserting server response inside a newly created script tag and be done with it.

P.P.S. Please don't use this code as is, cause I didn't spend too much time writting it. It's ugly as hell, but was just ment as an example:-)

Ilya Volodin
  • 10,929
  • 2
  • 45
  • 48
  • Note that it will result in multiple requests (given that the OP wants to return a number of things). – HChen Mar 23 '11 at 02:33
  • This would leave the output js in a global scope, and I'd like to avoid that. And avoid having to make several requests. – Diego Mar 23 '11 at 02:33
  • If I have the string of the function, can I create a script tag in the body and insert the string of the function inside, hoping that the browser will execute it? – Diego Mar 23 '11 at 02:39
  • This is the best approach and mitigates the severe security risks as outlined by Stephen Chung – Slappy Mar 23 '11 at 02:51
  • I don't see what advantages this solution brings in terms of security over a solution that uses eval. You're still obtaining javascript code from the server that will be evaluated in the browser, so if you're the subject of a Man-in-the-middle attack, the browser cannot tell that the returned js comes from a different server. – Diego Mar 23 '11 at 03:07
1

No, you can't do this by definition, because JavaScript functions are not valid JSON. See the spec here:

If you're returning a string, then that's what it is: just a string. You can't evaluate it without eval. You can call whatever else you're returning whatever you want, but please don't call it JSON.

Wayne
  • 59,728
  • 15
  • 131
  • 126
1

Here's an example of how I think this could work.

The json object represents what is returned from the server. The c and d properties contain function names as strings. If those functions are properties of some other object which exists in your page, then you should be able to call them using the object["property"] accessor.

See it working on 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]();
jessegavin
  • 74,067
  • 28
  • 136
  • 164
  • In my question, c and d would be the functions themselves, not the names. I want to serve the javascript from the server, avoid having to bundle it with the rest of the UI code – Diego Mar 23 '11 at 02:32
  • Ahh. I think I answered before you revised your question with a code sample. – jessegavin Mar 23 '11 at 02:36
  • @Diego, I advise that you rethink your need to save on this bandwidth. Is your UI code too large to include? You already seem to be using jQuery or Prototype, so you are already sending in the entire library down the wire (possibly with many unneeded functions). Most UI libraries are not too large to include. And you can also optimize them to reasonably small sizes. Why incur the additional security risks of sending it together over the wire dynamically? – Stephen Chung Mar 23 '11 at 02:56
1

Yes, there's a way, but it has the exact same disadvantages as eval.

You can use the Function constructor to create a new function, and then call it. For example:

new Function(code)();
icktoofay
  • 126,289
  • 21
  • 250
  • 231
1

http://code.google.com/p/json-sans-eval/ is a fast JSON parser that does not use eval, and JSON.parse is becoming increasing widely available in new browsers. Both are excellent alternatives to eval for parsing JSON.

Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
  • I'm not talking about eval'ing JSON, but eval'ing javascript. – Diego Mar 24 '11 at 13:42
  • Then what problems with `eval` are you worried about? The security problems and performance problems are also issues if you are using a cross domain ` – Mike Samuel Mar 24 '11 at 15:34
0

Do you have some example cases? Some things I can think of is you that you can just have a regular function inside your js file, and your server will return some parameters for your function to execute. You can even specify what function to use! (Isn't that amazing?)

// 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);
}
HChen
  • 2,141
  • 13
  • 12
0

You can use the trick that Google does with Google Charts.

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

Then your callback.js is:

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

Basically, your script will call onWorkDone when the doWork completed. You can see a working example here: http://jsfiddle.net/ea9Gc/

Mohamed Mansour
  • 39,445
  • 10
  • 116
  • 90
0

The reasons of not using eval

Well, you already said it yourself. Don't use eval. But you have a wrong picture regarding why.

It is not that eval is evil. You are getting the reason wrong. Performance considerations aside, using eval this way allows a sloppy programmer to execute code passed from server on the client. Notice the "passed from server" part.

Why never execute code passed from server

Why don't you want to execute code passed from the server (incidentally that's what you're planning to do)?

When a browser executes a script on a web page, as long as the web site is valid -- i.e. really yours, and not a malware site pretending to be yours trying to trick your users -- you can be reasonably sure that every bit of code the browser is running is written by yourself.

Hacker's heaven -- script injection attacks

Now, if you are passing data from the server to your web application, and that data contains executable functions, you're asking for trouble. In the long, twisted journey of that data going from your server to your client's browser, it goes through the wild west called the Internet, perhaps through multiple layers of proxies and filters and converters, most of which you do not control.

Now, if a hacker is hiding somewhere in the middle, takes your data from the server, modify the code to those functions to something really bad, and sends it away to your client, then your client browser takes the data and executes the code. Voila! Bad things happen. The worse is: you (at the server side) will never know that your clients are hacked.

This is called a "script injection attack" and is a serious sercurity risk.

Therefore, the rule is: Never execute functions returned from a server.

Only pass data from server

If you only accept data from a server, the most that can happen whan a hacker tempers with it is that your client will see strange data coming back, and hopefully your scripts will filter them out or handle them as incorrect data. Your client's browser will not be running any arbitrary code written by the hacker with glee.

In your client-side script, of course you're sticking to the Golden Rule: Do not trust ANY data coming through the Internet. Therefore you'd already be type-check and validating the JSON data before using it, and disallowing anything that looks suspicious.

Don't do it -- pass functions from server and execute on client

So, to make a long story short: DON'T DO IT.

Think of another way to specify pluggable functionalities on the browser -- there are multiple methods.

Stephen Chung
  • 14,497
  • 1
  • 35
  • 48
  • 2
    I think the OP is asking for recommendations on how to avoid using `eval`, not a lecture on why `eval` is bad. – HChen Mar 23 '11 at 02:42
  • 1
    I understand that Man in the middle attacks are always plausible if you're not using a secure connection, but this argument is applicable to every single thing you may request to the server. If the situation is open for a MITM attack then it may even happen that the hacker sends a version of my page served by him, which is arguably MUCH worse than just receiving bogus js. – Diego Mar 23 '11 at 02:44
  • @Haochi, I know. But my answer is **not** saying that eval is bad. My answer is saying that what he/she wants to do is not advisable. I am not providing an answer on how he/she can do it, but asking him/her to rethink whether to do it in the first place. – Stephen Chung Mar 23 '11 at 02:46
  • This is GOOD advice. Not sure why this was down voted? It clearly explains the risks involved with the approach. – Slappy Mar 23 '11 at 02:49
  • @Diego, true. My answer is theatricalized for dramatic effect. :-) However, it is still not advisable. There are levels of penetration a hacker can do -- and usually a break-in is only partial. Perhaps the hacker is only successfully in modify certain data, or some data (not take over your entire site). Perhaps he can only get access to where you store those small code fragments, not your entire server. What I am saying is that there are risks involved, and you're opening yourself up to unnecessary risks by passing executable code from server to client. – Stephen Chung Mar 23 '11 at 02:50
  • The more successful your app is (and I believe you hope it to be), the more people will try to hack *something* in it for their own profit. This kind of security risks can get buried in heaps of code in the future, and nobody will remember until something truely happens to a very popular site. – Stephen Chung Mar 23 '11 at 02:52
  • You make a good point, it is indeed another side of the app that can be attacked, particularly if I were to serve these snippets by a different server, but I don't think it's as severe as you make it to be. Much worse things can be done with a MITM attack. I can assure you that any site that doesn't use SSL and is subject to that is absolutely vulnerable, the user can be hijacked easily into feeding a false server his/her data. whether or not this app uses eval'd js or even uses javascript at all – Diego Mar 23 '11 at 03:02
  • @Diego, true. However, in security we think in terms of how much potential risks, not in terms of how secure we are. Reducing a potential injection point is definitely reducing risks (although there may be different assessments as to the magnitude of those risks). As an app gains more popularity, more and more hackers will try to exploit every potential point of attack, and the first rule in security is to eliminate the ones that we can eliminate. – Stephen Chung Mar 23 '11 at 03:30
  • Stephen, this whole post is bogus. *Don't execute code from server*; isn't this the opposite of what web browsers should do? You said it yourself, security sometimes is all on potential risks, but in this case, they're zero. Answer a question, if you can't trust yourself, who could you trust? By the same argument, you shouldn't be writing websites at all. – Christian Jul 06 '11 at 21:55
  • Diego didn't just do a valid point, his post in short can been reduced to *"if you don't trust your network, don't do anything"*. This is completely different from *"don't make websites run code"*, which, by the way, is simply stupid to even suggest. There's no additional attack surface, it is *the same* attack surface of any website out there, even those on SSL, if you really wanted to get that much into it. – Christian Jul 06 '11 at 21:58
  • @Christian Sciberras, I probably should have made it clearer. Obviously there is code passed from the server to the browser, inside – Stephen Chung Jul 07 '11 at 11:28
  • It is a smaller security risk to have all executable code residing in a set of files that you know exactly where they are and that you can secure the location. It is also a smaller risk if all code goes to the browser within – Stephen Chung Jul 07 '11 at 11:30
  • @Stephen - Code is always passed as data. If you're arguing about security considerations, I still don't see it related to the OP. Whether someone ran `test.js` from a server or download and eval'ed `test.php` doesn't really matter in the long run. It's passing the same route, to the same client in the same format. Just because it's the proper mechanism (script tag) doesn't make it any safer. On the other hand, assuming the browser is securing the code you pass through such a tag, is in my opinion, quite bad. – Christian Jul 07 '11 at 13:48
  • @Christian Sciberras, you have a point. I just believe otherwise. Regarding security, we are taught to reduce *risk*. Which doesn't mean that those risks will materialize or be exploited. The smaller attack surface, the lower the risk. That's all. It doesn't mean that, if you add all those extra attack surfaces, they *will* be attacked. – Stephen Chung Jul 07 '11 at 14:46
0

I've had this same question, and I fixed it this way:

File: 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

File: data5.json

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

File: 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);
    });
}
Christian
  • 27,509
  • 17
  • 111
  • 155