主题
eval
概述
eval() 是 JavaScript 的全局函数,用于将字符串当作代码在运行时解析并执行,返回最后一条表达式的值(如果有)。
基本形式:
js
eval(codeString)其中 codeString 必须是字符串;如果传入非字符串的值,会先被转成字符串再执行。
行为细节
- 直接调用
eval(...)(在源代码中直接写出eval并调用)会在当前词法作用域中执行代码,因此能访问并修改局部变量(非严格模式下)。 - 间接调用(例如
(0, eval)(...)、window.eval(...)或通过别名引用)会在全局作用域(或全局执行环境)中执行。 - 在严格模式 (
'use strict') 下,eval创建的代码运行在自己的词法环境中,不能直接声明或修改外层作用域的绑定。
示例:
js
function f() {
let a = 1
eval('a = 2') // 在非严格模式下可能修改外层变量 a
}
const e = eval
e('var x = 5') // 间接调用,在非模块全局环境中可能创建全局变量 x安全风险
- 代码注入:如果
eval的输入来源不可控(例如用户输入、第三方数据),攻击者可以执行任意 JS 代码,导致 XSS、权限提权、数据泄露等严重问题。 - 攻击面扩大:
eval能访问运行时所有 API(包括网络、DOM、localStorage 等),因此一旦被滥用会带来全局风险。
最佳实践:尽量避免在任何上下文中执行未经严格校验的字符串。若必须执行,先对输入进行白名单校验或使用受限的执行环境(见“检测与缓解”)。
性能影响
- 无法被 JIT 优化:
eval在运行时才解析并编译字符串,因此会阻止 JS 引擎对包含eval的函数做某些静态优化。 - 解析与执行开销:每次调用都需重新解析字符串并生成字节码/机器码,代价比直接函数调用或已编译代码高。
替代方案
- JSON 数据解析:使用
JSON.parse(而不是eval)来解析数据结构。 - 模板与函数生成:需要动态执行可考虑
Function构造器,但它同样存在安全问题,优先使用更受控的方式。 - 插件/脚本沙箱:用
iframe、Web Worker、或专门的沙箱库(如 SES、vm2(Node))隔离执行环境。 - 动态映射:如果只是动态选择函数或属性,使用对象映射、Map 或策略模式代替动态代码生成。
常见用例与反模式
- 反模式:用
eval解析来自服务器的配置脚本、拼接并执行表达式、解析非 JSON 格式的数据等。 - 可能合理的场景:开发工具、REPL、受信任并签名的动态脚本加载、或者运行于受限沙箱中的受控脚本。这些场景仍需严格控制边界并记录审计信息。
示例
错误示例(不要这样做):
js
// 用 eval 解析 JSON —— 不安全
const data = eval('(' + jsonText + ')')安全示例:
js
// 使用 JSON.parse
const dataSafe = JSON.parse(jsonText)
// 动态调用函数表,而不是拼接执行字符串
const actions = {
add(a, b) {
return a + b
},
mul(a, b) {
return a * b
},
}
const name = 'add'
const res = actions[name](1, 2)直接与间接 eval 的差异示例:
js
function direct() {
const v = 1
eval('v = 2') // 直接 eval 在非严格模式下可修改 v
}
function indirect() {
const e = eval
e('var g = 3') // 间接调用在全局作用域(非模块)创建 g
}检测与缓解
- 静态检测:在项目中启用 ESLint 规则
no-eval来禁止或标记eval的出现。 - 白名单解析:对于必须支持的表达式,先用解析器(如 jsep)生成 AST,再以限制良好的解释器执行,避免直接执行任意代码。
- 沙箱执行:在 Node 使用
vm/vm2,在浏览器使用iframe或Worker隔离不可信脚本,并限制可用 API。
调试与维护
- 为动态执行的代码添加
//# sourceURL=注释,便于在浏览器开发者工具中定位:
js
eval('function foo(){debugger;}\n//# sourceURL=dynamic-code.js')- 日志与审计:记录为何、何时以及从何处调用
eval,便于后续安全审计。
