Are ES6 template literals safer than eval?

2020-06-01 07:53发布

Template literals smell a bit like eval to me, and it's often cited that using eval is a bad idea.

I'm not concerned with performance of template literals, but I am concerned about injection attacks (and other security concerns I may not be thinking of).

Edit

An example of something that feels odd to me

let ii = 1;
function counter() {
    return ii++;
}
console.log(`${counter()}, ${ii++}, ${counter()}`);

Which outputs

1, 2, 3

The template literal is making side effects at the global level. Both by a function, and directly.

Edit 2

An example indicating the saferness of template literals

let ii = 1;
let inc = function() { ii++; }
console.log('Starting: ' + ii);
let input = prompt('Input something evil (suggestion: inc() or ii++)');
console.log(`You input: ${input}`);
console.log('After template literal: ' + ii);
eval(input);
console.log('After eval: ' + ii);

If you input ii++ when prompted, it logs

Starting: 1

You input: ii+=1

After template literal: 1

After eval: 2

Edit 3

I've started looking into the ECMAScript specification

Though I'm not grokking the details, it feels like template literals are specified safer than eval.

3条回答
我欲成王,谁敢阻挡
2楼-- · 2020-06-01 07:55

Template literals escape quotes automatically, if that's what you're concerned about. They also do not eval or execute anything, they convert whatever you put in to string. If you are worried about SQL Injection try doing that with template literals and you'll see they get escaped.

You should avoid using eval, unless you have a really good reason to use it and you really know you need it to achieve your goal. Otherwise it is best avoided.

查看更多
兄弟一词,经得起流年.
3楼-- · 2020-06-01 08:07

I think there is one big difference between eval and template literals.

eval can evaluate dynamic expressions that are not directly visible from code. This makes it dangerous, because you can evaluate any string that may come from anywhere: client / third party / db...

However situation is different in case of template literals,

  • because you can see full template from code,
  • expressions work with your internal objects and cannot evaluate dynamic expression.

For example this will work with eval

function doSomething() {
  console.log('HELLO!');
}
    
// This will work.
var expression = 'doSomething()';
eval(expression);
  

But this will not work with template literals

// This will not.
`${expression}`;

You need to insert expression statically to make it work

// To make it work you need to insert it statically.
`${doSomething()}`;

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals

查看更多
对你真心纯属浪费
4楼-- · 2020-06-01 08:16

One difference with eval is that template literals are parsed at compile time, while the argument to eval only gets parsed at run-time, when eval is executed.

Related to this, is that eval can get a dynamically built argument, while a template literal is ... literal: it cannot be stored as a template variable, which you could build dynamically, move around and eventually parse: there is no "template variable" data type. A tag function does not actually get a template variable as argument, but the parsed components of it, which are known at compile-time.

Some examples

With eval you can have this situation:

var code = prompt('enter some evil code');
eval(code);

But that is not possible with template literals:

var literal = prompt('enter some evil template literal');
tag literal; // there is no data type or syntax for this.
`${literal}`; // and this just gives you the entered string.

What is possible, is this:

var str = prompt('enter some string');
tag`${str}`;

But that does not lead to unwanted code execution, at least not worse than this:

var str = prompt('enter some string');
myfunc(str);

Any function calls must already be encoded literally in a template literal. Values of string variables cannot change that. There is no way a variable function gets called by a template literal. This:

`${func(str)}`;

...will call func, and that function only. It is chosen by the programmer.

A rather evil template literal

Having said that, this is still possible:

var func = prompt ("enter some evil function name (suggestion: 'alert')");
var param = prompt ("now provide an argument for " + func);

`${window[func](param)}`;

But it is evident that the program willingly opens up the possibility to execute any function on the global object. Then indeed, you are getting close to the evilness of eval.

Note that the same effect is achieved with:

window[name](param);

The most evil template literal

As commented, then you might as well make this template literal:

`eval(str)`;

... and so the evil part is not so much in the template literal, but the generic function call you have designed to be in it. For that you don't need template literals or eval, but a bad programmer ;-)

On the Example

You gave this example:

let ii = 1;
function counter() {
    return ii++;
}
console.log(`${counter()}, ${ii++}, ${counter()}`);

This executes your counter function, but the difference with eval is that the string literal was already there at design time, and could not have been constructed at run-time. This code is designed to increment your counter, and is not essentially different from:

console.log(counter() + ', ' + (ii++) + ', ' + counter());

Compile Time

To stress the difference of compile/run time parsing, note that you cannot run code with a template literal that does not have valid syntax.

Compare these two scripts:

alert('press OK');
eval('alert("hello)');

and:

alert('press OK');
`${alert("hello)}`;

Note the syntax error. The first script will only notice the syntax error at run-time when the argument to eval is parsed, while the second script will not even run, and give the syntax error immediately.

Put more exactly, eval executes a new script, with its own compile and run phases. A template literal is parsed/compiled like other code.

查看更多
登录 后发表回答