Why Is Using the JavaScript Eval Function a Bad Idea

Why is using the JavaScript eval function a bad idea?

  1. Improper use of eval opens up your
    code for injection attacks

  2. Debugging can be more challenging
    (no line numbers, etc.)

  3. eval'd code executes slower (no opportunity to compile/cache eval'd code)

Edit: As @Jeff Walden points out in comments, #3 is less true today than it was in 2008. However, while some caching of compiled scripts may happen this will only be limited to scripts that are eval'd repeated with no modification. A more likely scenario is that you are eval'ing scripts that have undergone slight modification each time and as such could not be cached. Let's just say that SOME eval'd code executes more slowly.

what does eval do and why its evil?

eval() takes the string it is given, and runs it as if it were plain JavaScript code.

It is considered "evil" because:

  • It over-complicates things - Most cases where eval() is used, there would be a much simpler solution that didn't require it. This example in the question is a perfect case in point: there is absolutely no need for eval() for an expression like this. JS has perfectly good syntax for referencing an object property name as a string (myObject["x"] is the same as myObject.x).

  • It's much harder to debug - It's harder to work with it in a debugger, and even once you have managed to work out what's going on, you have you extra work to do because you have to debug both the eval'd code, and the code that generated the original string to eval.

  • It slows things down - The script compiler cannot pre-compile code in an eval(), because it doesn't know what the code will contain until it gets there. So you lose out on a some of the performance benefits in modern Javascript engines.

  • It is a hacker's dream - eval() runs a string as code. Hackers love this because it's much easier to inject a string into a program than to inject code; but eval() means you can inject a string, and get it to run as code. So eval() makes your code easier to hack. (this is less of an issue for browser-based Javascript than other languages, as JS code is accessible in the browser anyway, so your security model should not be based on your code being immutable, but nevertheless, injection hacks can still be a problem, particularly with cross-site attacks).

Is using Eval a good idea?

Eval is generally avoided as it allows the client to insert and evaluate their own expressions in your code.

That being said, JavaScript being a client side language already allows full access to the user, so there isn't really a good reason not to use it.

As long as the user can only mess with their own session, i wouldn't worry. Security should be handled server side anyway so: Beware but don't simply ignore Eval.

EDIT 1 - Defending Eval

The comments pointed out some issues, mainly Performance/Optimization impact, which this answer explains in depth. Basically, since it's Just-In-Time compiling anyway, you don't really lose that much in terms of performance.

As for an example on a use case, here is a template example i whipped up, which also uses the controversial with statement:

var Template = /** @class */ (function () {    function Template(html) {        this.html = html;    }    Template.prototype.apply = function (params, returnDOMObject) {        if (params === void 0) { params = {}; }        if (returnDOMObject === void 0) { returnDOMObject = false; }        with (params) {            var html = eval('`' + this.html.replace(Template.regexes.encapsulated, function (n) {                return n                    .replace(Template.regexes.start, '${')                    .replace(Template.regexes.end, '}');            }) + '`');        }        if (returnDOMObject) {            return document.createRange().createContextualFragment(html);        }        return html;    };    Template.regexes = {        encapsulated: new RegExp('{{.*?}}', 'igm'),        start: new RegExp('\{{2,}', 'igm'),        end: new RegExp('\}{2,}', 'igm')    };    return Template;}());//TESTvar persons = [    { name: "Peter", age: 25 },    { name: "Ole", age: 55 },];var templates = [];var container = document.body.appendChild(document.createElement("div"));var leftBox = container.appendChild(document.createElement("div"));var rightBox = container.appendChild(document.createElement("div"));leftBox.style.width = rightBox.style.width = "50%";leftBox.style.height = rightBox.style.height = "500px";leftBox.style.cssFloat = rightBox.style.cssFloat = "left";var leftList = leftBox.appendChild(document.createElement("select"));leftBox.appendChild(document.createElement("br"));var leftText = leftBox.appendChild(document.createElement("textarea"));leftText.style.width = "100%";leftText.style.resize = "vertical";var rightOutput = rightBox.appendChild(document.createElement("div"));function updateLists() {    leftList.innerHTML = '';    for (var i = 0; i < templates.length; i++) {        var template = templates[i];        var option = document.createElement("option");        option.value = option.innerHTML = template.name;        leftList.appendChild(option);    }}var h1Template = new Template("<h1>{{name}}</h1>");var h2Template = new Template("<h2>{{age}} is no age!</h2>");var pTemplate = new Template("<p>{{name}} may be {{age}}, but is still going strong!</p>\n<p>(When he's {{age*2}} though...)</p>");var personTemplate = new Template("<p>\n{{ h1Template.apply(params) }}\n{{ h2Template.apply(params) }}\n{{ pTemplate.apply(params) }}\n</p>");templates.push({ name: "personTemplate", template: personTemplate });templates.push({ name: "h1Template", template: h1Template });templates.push({ name: "h2Template", template: h2Template });templates.push({ name: "pTemplate", template: pTemplate });function updateOutput() {    rightOutput.innerHTML = '';    for (var pi = 0; pi < persons.length; pi++) {        var person = persons[pi];        rightOutput.appendChild(personTemplate.apply(person, true));    }}function leftTextChange() {    templates.find(function (val) { return val.name === leftList.value; }).template.html = leftText.value;    updateOutput();}function leftListChange() {    leftText.value = templates.find(function (val) { return val.name === leftList.value; }).template.html;}updateLists();leftList.onchange = leftList.onkeyup = leftListChange;leftText.onchange = leftText.onkeyup = leftTextChange;leftListChange();updateOutput();

When is JavaScript's eval() not evil?

I'd like to take a moment to address the premise of your question - that eval() is "evil". The word "evil", as used by programming language people, usually means "dangerous", or more precisely "able to cause lots of harm with a simple-looking command". So, when is it OK to use something dangerous? When you know what the danger is, and when you're taking the appropriate precautions.

To the point, let's look at the dangers in the use of eval(). There are probably many small hidden dangers just like everything else, but the two big risks - the reason why eval() is considered evil - are performance and code injection.

  • Performance - eval() runs the interpreter/compiler. If your code is compiled, then this is a big hit, because you need to call a possibly-heavy compiler in the middle of run-time. However, JavaScript is still mostly an interpreted language, which means that calling eval() is not a big performance hit in the general case (but see my specific remarks below).
  • Code injection - eval() potentially runs a string of code under elevated privileges. For example, a program running as administrator/root would never want to eval() user input, because that input could potentially be "rm -rf /etc/important-file" or worse. Again, JavaScript in a browser doesn't have that problem, because the program is running in the user's own account anyway. Server-side JavaScript could have that problem.

On to your specific case. From what I understand, you're generating the strings yourself, so assuming you're careful not to allow a string like "rm -rf something-important" to be generated, there's no code injection risk (but please remember, it's very very hard to ensure this in the general case). Also, if you're running in the browser then code injection is a pretty minor risk, I believe.

As for performance, you'll have to weight that against ease of coding. It is my opinion that if you're parsing the formula, you might as well compute the result during the parse rather than run another parser (the one inside eval()). But it may be easier to code using eval(), and the performance hit will probably be unnoticeable. It looks like eval() in this case is no more evil than any other function that could possibly save you some time.

Is javascript eval really that big of a security threat?

When you hand-over control of JavaScript, it isn't just its execution that could prove harmful. With Ajax, you could possibly load a flash object, or a pdf, or a Java applet, into the current page itself. That would present no dialog and you would be invoking plugins (which have a lot more privileges than the browser itself).

So, in theory, it could cause as much damage as your plugins themselves allow, which is usually quite a lot.

javascript eval(), controlling the input & the real dangers

Assume I'm a super legit user of your cool web application, authenticated myself, and wrote a cool script you (as developer of the application) want to run. Of course, you as super user can do much more than me as a simple user. And I'm being jealous of your rights.

It shouldn't be too difficult to write a script which, when executed:

  1. Fakes the code input to look like something it is not.
  2. Gets your authentication object and posts it to another server
  3. Get more available information from your system (See https://github.com/Valve/fingerprintjs2)

Next to these privacy issues it can load ads from rogue networks which have the power to inject viruses on your and other user's systems.

Basically just don't trust any users code. If you want to execute user submitted code be careful it only has a limited scope it can access. Such thing (a sandbox) can only alter a specific part of your page. For a specific example of a library you can use to achieve this, see https://github.com/asvd/jailed.

what are the issues javascript eval can pose

eval() may be a sign of poor design. For instance, sometimes people use it to access object properties because they don't know you can use the [] notation, i.e., eval('obj.' + prop_name). It's also a source of XSS holes if you eval() user content, since it might be interpreted as JS. It also tends to be slower than the alternatives.

This would be the most basic example of XSS while using eval() to parse JSON:

eval({"a": "b", 'c': "d" + alert("xss") + ""})

To get a hole like this you would have to be lazy about building your JSON and not escape quotes, but there are more complex examples, and using a specialized library like Douglas Crockford's (json.org) one would avoid it.

Am i using eval() safely in server?

So the question: is filtering the array from any unwanted strings (names) safe enough to counter the eval's exploitability?

No. This is not safe. In fact, it does basically nothing to safeguard you because the cookie names you allow are still completely unguarded and unsanitized. What you have is potentially unsafe because a malicious client can put anything they want in that cookie and you're "hoping" that they can't find something that will break out of the string delimiter you have. But, it is possible to break out of that string delimiter by just terminating the string and then adding a function call. This would potentially allow an attacker to execute arbitrary code on your server.

The ONLY thing you should ever use eval() with is a trusted string from your own server-side code or a completely sanitized string form the outside. But, nearly always, you don't need eval() as there is another and safer way to code it.

Here, you don't need to use eval() at all. You can just create a lookup table for the legal functions to call and then pass the function directly to it:

try {
res.locals[cookie] = validateCookie[cookie](req.cookies[cookie]);
} catch(e) {
// either invalid cookie or exception in the function
// handle that here
}

And, of course, your validateCookie[cookie]() function also has to be coded defensively to know that it may be passed anything. You don't show us the code for that function to be able to comment on it further.

In this case, validateCookie is a lookup table that contains the valid cookie names and their corresponding functions:

 // cookie processing lookup table
const validateCookie = {
cookieName1: validateCookieName1,
cookieName2: validateCookieName2
};

Lookup tables like this are often how you avoid trying to manufacture a function name and a string and use eval() to call it. This also adds the safety feature that this code can't call any function that is not in your lookup table.



Related Topics



Leave a reply



Submit