关于javascript:ES6模板文字比eval安全吗?

Are ES6 template literals safer than eval?

模板文字对我来说有点像eval,并且经常被引用使用eval是个坏主意。

我不关心模板文字的性能,但是我关心注入攻击(以及其他我可能没有想到的安全问题)。

编辑

一个让我感到奇怪的例子

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

哪个输出

1, 2, 3

模板文字在全局级别产生副作用。既可以通过函数直接调用。

编辑2

表明模板文字更安全的示例

1
2
3
4
5
6
7
8
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);

如果在提示时输入ii++,它将记录

Starting: 1

You input: ii+=1

After template literal: 1

After eval: 2

编辑3

我已经开始研究ECMAScript规范

  • 18.2.1-全局对象的函数属性:: Eval(x)

    • 注意它在全局对象上
    • 注意,下一部分是关于eval的"运行时语义"
  • 12.2.9-主表达式::模板文字

    • 注意这是一个表达
    • 注意下一部分是关于模板字符串的"静态语义"

尽管我不担心细节,但感觉模板字面量比eval更安全。


eval的一个不同之处是模板文字在编译时进行解析,而eval的参数仅在运行eval时在运行时进行解析。

与此相关的是eval可以获取动态构建的参数,而模板文字是...文字:它不能存储为模板变量,而可以动态构建,移动并最终解析:没有"模板变量"数据类型。标记函数实际上并没有获取模板变量作为参数,而是获取了它的解析组件,这些在编译时是已知的。

一些例子

使用eval,您可能会遇到以下情况:

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

但是用模板文字是不可能的:

1
2
3
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.

这是可能的:

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

但这不会导致不必要的代码执行,至少不会比这更糟:

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

任何函数调用必须已经按原样编码在模板文本中。字符串变量的值不能改变它。模板文字无法调用变量函数。这:

1
`${func(str)}`;

...将调用func,仅该功能。它由程序员选择。

一个相当邪恶的模板文字

话虽如此,这仍然是可能的:

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

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

但是很明显,该程序愿意打开在全局对象上执行任何功能的可能性。那么确实,您正在接近eval的弊端。

请注意,使用以下方法可获得相同的效果:

1
window[name](param);

最邪恶的模板字面量

如前所述,那么您最好将这个模板文字设为:

1
`eval(str)`;

...因此,邪恶的部分不在模板文字中,而是您设计为包含在其中的泛型函数调用。为此,您不需要模板字面量或eval,而是一个不好的程序员;-)

关于例子

您给出了以下示例:

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

这将执行您的counter函数,但是与eval的区别在于,字符串文字在设计时就已经存在,并且在运行时无法构造。此代码旨在增加您的计数器,与以下代码没有本质区别:

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

编译时间

为了强调编译/运行时解析的区别,请注意,您不能使用没有有效语法的模板文字来运行代码。

比较这两个脚本:

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

和:

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

请注意语法错误。当解析eval的参数时,第一个脚本只会在运行时注意到语法错误,而第二个脚本甚至不会运行,并立即给出语法错误。

更确切地说,eval执行一个具有自己的编译和运行阶段的新脚本。模板文字与其他代码一样被解析/编译。


我认为eval和模板文字之间有一个很大的区别。

eval可以评估在代码中不直接可见的动态表达式。这很危险,因为您可以评估可能出现的任何字符串
来自任何地方:客户端/第三方/数据库...

但是对于模板文字而言,情况则有所不同,

  • 因为您可以从代码中看到完整的模板,
  • 表达式可与您的内部对象一起使用,无法评估动态表达式。

例如,这将与eval

一起使用

1
2
3
4
5
6
7
function doSomething() {
  console.log('HELLO!');
}
   
// This will work.
var expression = 'doSomething()';
eval(expression);

但这不适用于模板文字

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

您需要静态插入表达式以使其起作用

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

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Template_literals


如果您担心的是模板文字会自动转义引号。它们也不评估或执行任何操作,而是将您输入的任何内容转换为字符串。如果您担心SQL注入,请尝试使用模板文字进行操作,您会发现它们被转义了。

您应该避免使用eval,除非您有充分的理由使用它,并且您确实知道需要eval才能实现目标。否则,最好避免。