我想验证(客户端)用户是否输入了有效的 JavaScript 代码。
引入 Javascript 解析器(例如 Acorn 或 Esprima)是一个相对较重的依赖项。但是(如果未启用 CSP),我可以这样做:
try {
new Function(userCode);
return {valid: true}
} catch (e) {
return {
valid: false,
error: e.message
};
}
虽然这确实使用了
new Function
,但用户提供的代码永远不会 run,而只会被解析/编译。还存在安全问题吗?
这看起来不错。解析/编译 JS 代码是安全的,没有副作用(模块解析和加载不适用于
Function
)。我们可以肯定,现代浏览器中使用的 JavaScript 解析器几乎没有错误。
该标准(甚至早于 ECMA-262 第一版,§15.3.2.1,以及所有后续标准)指定调用
Function
构造函数将仅解析和编译给定的代码,而不是执行它;所以在纸面上,在现代主流浏览器引擎中,这应该“可能”没问题。但即使在相对较新的版本(2016 年最新版本)下,这实际上也非常不安全。一些引擎曾经将 Function
构造函数实现为道德上的等价物:function Function() {
var args = Array.prototype.slice.call(arguments, 0, -1).join(",");
var body = arguments[arguments.length - 1];
return (0, eval)("(function anonymous(" + args + "\n) {\n" + body + "\n});");
}
与all
的后果 - 特别是,在 V8 下,您可以调用类似 new Function("}, alert(1), function () {")
的东西并让它调用任意代码:请参阅
bug #2470。同时,当向构造函数提供无效代码时,JavaScriptCore 可能会彻底崩溃:请参阅bugs #106160 或 #163748。即使在今天,不太主流的引擎也可能倾向于走同样的捷径。例如,QuickJS 就是这样做的,并且从 2024 年起仍然容易受到此欺骗的影响:https://github.com/bellard/quickjs/blob/06651314f5bbef1bb7a2689da7fd7b2b04ce6125/quickjs.c#L38342. 为了防止这种草率的实现,您可能需要检查
Function
构造函数中是否存在已知错误,并在检测到任何错误时禁用客户端检查。
function isSyntaxCheckingSafe() {
function check() {
try {
Function.apply(null, arguments);
return true;
} catch (e) {
if (!(e instanceof SyntaxError))
return true;
}
return false;
}
/* this one first, as it does not outright crash buggy JavaScriptCore */
if (check("/*", "*/){"))
return false;
if (check("},function(){"))
return false;
if (check("};{"))
return false;
/* no problems found, fingers crossed */
return true;
}
console.log(isSyntaxCheckingSafe());
上述测试很容易产生误报,因为它们会判断引擎成功解析格式错误的
Function
参数,但实际上并不执行任何易受攻击的注入代码。但安全总比后悔好。