Comparison of Safe Eval and Eval

Eval and safe eval comparison with examples and stats.

Ayush Chaurasia
JavaScript in Plain English

--

Hi there, here I am sharing my learning from the performance optimization of a larger scale nodeJS application.

Here we are not going to argue should we use eval/safeEval or not, Here we will be seeing their performance comparison and security concerns, we will also see how safe eval is secure and it’s overheads, read this if you have made up your mind to use it.

A brief of what they are

Eval is used for the evaluation of the JS statements written in string format, and safe Eval does the same thing safely.

“Okay, so elaborate”

Sure, allow me.

Eval is like nuclear energy, you can power up the entire city if contained and used wisely, or can blow up the entire city if not contained.
Safe Eval is the nuclear power plant.

“Hmmm”

Eval and safeEval are used for evaluation of expressions.
To read more about them click on eval and safeEval

But, this blog is not to understand what are eval and safeEval, Here we will see the difference between them and how they work.

safeEval creates a context of its own and then does the evaluation on the contrary eval does the evaluation in the application context.

Let’s understand the above line.

If you look at the method signature of safeEval it is something like this

function safeEval (code, context, opts)https://github.com/hacksparrow/safe-eval/blob/master/index.js

Whenever we call safeEval we pass context to it, let’s say I have a function sum of “MyMath” so to call that function I will do something like this.

mobj.sum(a,b)

Look at the snippet below

//Addition example //MyMath.js
function sum(a,b){
return a+b;}
const safeEval = require('safe-eval')
const mobj = require('./MyMath');
var toBeEvaluated = "mobj.sum(5,9)"
var evaluatedString = eval(toBeEvaluated); // --------------1 console.log("evaluatedString", evaluatedString);var safelyEvaluatedString = safeEval(toBeEvaluated);//---2 console.log("safelyEvaluatedString", safelyEvaluatedString);

Above we have a string ready for evaluation and we are doing eval and safeEval for the same string and printing the output. Now, let’s see the output

In the first case

for the second case, it is throwing ReferenceError.

Let’s add few lines in the above code make it look like this.(doing change for the safeEval).

let context= {"mobj":mobj}
var safelyEvaluatedString = safeEval(script,context);----------2
console.log("safelyEvaluatedString", safelyEvaluatedString);

Now we got the same output as in the above case for eval.

Output after passing the context.

What happened here?

In eval case

Eval does the executions in the current application context.

Here it has access to the “mobj” object and any string we put for evaluation through eval, eval will use the objects created outside of it and execute the application.
Let’s see how an attacker can exploit this, have a look at the below code snippet.

app.get(‘/’, function (req, res) { const toBeEvaluated = “res.end(require(‘fs’).readdirSync(‘.’).toString())”; evaluate = e
Image snippet of an example exploit code. app.js

The output of the above snippet on a curl call is the list of the files and directory present in the directory.

Curl call output to the above server code.

In the above terminal snippet, one can see that doing a curl call has listed all of the files present in the directory in which app.js file lies.

Since eval has access to the res object hence it will return the result as stated in the data string after the evaluation.

In safeEval case

It creates it’s own context and then perform the execution of the code.

So when we pass the objects in the context of safeEval, it will have access to only those objects which are there in the context and any other object reference will result in an error.
So now we know why it was failing in the addition example and why we started working once we passed the context.

Now let’s take the exploit code example here and try executing it with safeEval. Let’s find out the result.

app.get('/', function (req, res) {const toBeEvaluated = "res.end(require('fs').readdirSync('.').toString())";
evaluate = safeEval(toBeEvaluated);
})

What will happen now?
You are right it will break, but why? You are right again since it does not have access to the “res” object it will give an error, see the execution output below. ReferenceError

Output snippet on server side (safeEval)

We now know that safeEval is safer than eval.

Where does this leave us?

You must be thinking what’s the fuss about if had to evaluation go for safeEval.

Hold your horses, making things safe has its own overheads, context creation is really a costly operation, which makes safe eval much slower than eval and it can hamper applications performance at a large scale, so use it wisely.
If you need to perform an evaluation on some data set, perform safeEval first and then eval.

Performance comparioson

Let’s modify our code above to calculate the time taken by eval and safeEval.
We will calculate the time taken for execution using the performance of perf_hooks

Using perf_hooks to calculate the time taken

And here’s the output of the above run

The output of the above code snippet (performance comparison)

In the above snippet, you can see the time taken by safeEval is almost eighteen (18) times more than eval(18 is not a standard number here, just to understand the comparison)

  • *Okay, Man we will be careful while using safeEval**

One more thing we should be careful about is the what objects we are passing into the context, let’s take the above example to understand what I am trying to say.

In our above exploit example when we used safeEval it gave res not found error since we had not provided the res object in the context, what if we do that and use safeEval:

app.get('/', function (req, res) {const toBeEvaluated = "res.end(require('fs').readdirSync('.').toString())";//I know passing require in context is extreme, I am just trying to // show something let context = {"res":res,"require":require}; 
evaluate = safeEval(toBeEvaluated,res);
})

What do you think will happen now?

Yes, it will execute successfully, and output will be the same as eval.

So we need to be careful of what we are passing in the context, maybe we can separate out objects handling sensitive information from the normal once.
Just don’t pass everything in it.

Well, you people are wise, you will handle it.

Conclusion

Eval and safeEval are very powerful tools and while using we should consider the security and performance of the application and should not overdo anything.

This is my first medium blog(probably you have already figured that out 🤞🏽), so help me make it better by your valuable comments. Do let me know if I should add some more information into this to make it better.

Thanks for your patience reading. Happy development.

Namaste.

--

--