- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
本文依据当前仓库 nodebestpractices 中《Avoid JS eval statements》系列文档(英文原版、中文版、俄文版)整理成文。该章节对应 README 主清单中6.15 节 "Avoid JavaScript eval statements",并关联 OWASP Top 10 2017 的 A1 注入与 A7 跨站脚本(XSS) 两大威胁类别。
导读
在 Node.js 中,eval()、new Function()、setTimeout()与setInterval()这四个全局函数都能接收"字符串形式的 JavaScript 代码"并将其执行。本指南聚焦于这一高危模式:当不可信的用户输入流入这些函数时,等同于把服务器的执行权限拱手让给攻击者。读完本文,你将掌握:这些函数为什么危险、真实的攻击载荷长什么样、如何用 ESLint 安全插件在编码阶段自动拦截(detect-eval-with-expression规则),以及在不损失功能的前提下安全重构的替代方案。
一、风险核心:四个能"把字符串变成代码"的全局函数
根据 avoideval.md 的定义,eval()、setTimeout()、setInterval()和new Function()都是 JavaScript/Node.js 中的全局函数,它们共同的特征是:接受一个字符串参数,该字符串代表一段 JavaScript 表达式、语句或语句序列,随后在运行时被解析并执行。
从安全角度看,问题的本质并不是"函数本身有 bug",而是不可信的用户输入可能顺着数据流进入代码执行阶段。由于"执行用户提供的代码"本质上等于允许攻击者执行你(服务进程)所能执行的一切操作,其后果往往是服务器被完全攻陷(RCE,远程代码执行)。
README 主清单 6.15 节 给出的 TL;DR 进一步强调了两个维度:
- 性能层面:
eval让引擎无法对动态代码做编译期优化; - 安全层面:
eval允许在运行时执行自定义 JavaScript,而恶意代码可能恰恰来源于用户输入。
同时它明确划出了红线:new Function构造函数同样应当避免;setTimeout与setInterval永远不应被传入动态 JavaScript 字符串。
二、攻击场景演示:一条字符串如何删掉整个服务器
avoideval.md 给出了一个极具说明性的恶意载荷示例。攻击者只需把下面这段字符串作为输入提交,一旦服务端不加过滤地把它交给eval(),就会在 Node.js 进程中执行任意系统命令:
// 攻击者能够注入的恶意代码示例 const userInput = "require('child_process').spawn('rm', ['-rf', '/'])"; // 恶意代码被执行 eval(userInput);逐行拆解这条攻击链:
- 攻击者在某个可注入的输入点(如表单字段、URL 参数、消息队列消息、配置文件)写入字符串
"require('child_process').spawn('rm', ['-rf', '/'])"; - 应用代码把该字符串原样传给
eval(); eval()把字符串当作 JavaScript 源码解析执行,require('child_process')在 Node.js 运行时环境中能够成功解析;child_process.spawn('rm', ['-rf', '/'])在操作系统层面发起递归删除根目录的进程。
需要特别强调的是:被执行的代码运行在与你应用相同的进程、相同的权限上下文里。它不仅能访问require、process、fs等 Node.js 全局能力,还能读取环境变量中的密钥、连接数据库、向外部发起请求。eval之后的任何防护都无从谈起——这正是它被称为"注入之王"的原因。
三、为什么 eval 被安全界"深恶痛绝":专家视角与底层原理
3.1 Liran Tal 的论断
avoideval.md 引用了 Liran Tal 所著《Essential Node.js Security》中的原话:
从安全角度而言,
eval()或许是 JavaScript 中最令人皱眉的组成部分。它把一段 JavaScript 字符串当作文本解析,然后像执行 JavaScript 代码一样执行它。当不可信的用户输入有机会流入eval()时,灾难的配方就此形成,最终可能导致服务器被攻陷。
这段话精准地概括了两点:其一,eval是"文本 → 可执行代码"的转换器;其二,危险不在于eval本身,而在于不可信输入与eval的相遇。
3.2 底层原理:为什么动态执行难以防御
从 V8 引擎实现角度可以进一步解释其危险性:
- 无法静态分析:
eval的输入在编译期不可见,引擎无法对其做任何静态安全检查、优化或死代码消除,这在 README.md 中被明确指出是性能隐患; - 作用域泄漏:直接调用
eval时,被求值的代码能访问当前调用函数的局部变量,等于把内部状态暴露给任意字符串; - 绕过所有输入校验:应用层做的白名单、黑名单、类型校验全部发生在执行之前,一旦字符串到达
eval,之前的校验结果对执行结果没有任何约束力; - 环境权限巨大:在 Node.js 中,被求值代码可以访问
require、process.env、fs、net等全部运行时能力,其攻击面远超浏览器环境。
3.3 容易被忽视的"同族函数"
eval是最著名的一个,但下面这几种写法同样危险,且更容易在代码评审中被漏掉:
// 用 new Function 构造可执行函数 const fn = new Function('return ' + userInput); fn(); // 把用户输入作为 setTimeout / setInterval 的"代码字符串" setTimeout(userInput, 100); setInterval("doSomething(" + userInput + ")", 1000);其中setTimeout/setInterval的字符串参数形态尤其隐蔽——它们的第一个参数既可以传函数(安全),也可以传字符串(会被当作代码执行),开发者稍不注意就会把用户输入拼接进去。README 6.15 节 明确要求:setTimeout 和 setInterval 永远不要传入动态 JavaScript 代码。
四、工程防线:用安全 Linter 在编码阶段拦截 eval
既然动态执行如此危险,最佳防线就是在代码写入阶段就将其扼杀。仓库 lintrules.md("使用 linter 安全规则"章节)提供了现成的检测方案:eslint-plugin-security。
该插件基于一系列已知漏洞模式做静态检查,其中与本文主题直接对应的规则是detect-eval-with-expression,它会精准捕获"把非常量表达式传给eval"的写法:
// 会被 detect-eval-with-expression 规则拦截的代码 const userinput = req.body.userinput; eval(userinput);规则命中的关键特征是eval的实参不是字符串字面量(即来自外部输入或变量),这与我们的风险场景完全吻合。同一插件还包含detect-pseudoRandomBytes(伪随机数)、detect-non-literal-fs-filename(非字面量文件路径)、detect-non-literal-regexp(动态正则)等规则,lintrules.md 中给出了这些不安全模式的完整示例代码。
上图是 lintrules.md 章节中eslint-plugin-security对上述不安全代码实际运行检测的示例输出。将该插件接入 CI 或 git 钩子(如 pre-git),可以在代码推送到远端之前强制拦截eval、动态new Function等危险模式,与本文倡导的"从源头杜绝动态执行"形成完整的工程闭环。
五、安全重构:不依赖动态执行完成同样的功能
avoideval.md 给出的核心建议是:重构代码,使其不依赖这些函数,尤其是在用户输入可能被传入并执行的场景下。下面给出常见场景的替代方案:
5.1 解析 JSON / 配置 → 用专用解析器,别用 eval
// 危险:把用户输入当表达式求值 const config = eval('(' + userInput + ')'); // 安全:使用严格模式下的 JSON 解析器 const config = JSON.parse(userInput);JSON.parse只解析数据、不执行代码,且会拒绝函数、__proto__污染等危险载荷,是解析用户提交结构化数据的首选。
5.2 定时任务 → 永远传函数引用,不传字符串
// 危险:字符串作为代码执行 setTimeout('updateStatus(' + userId + ')', 1000); // 安全:传入函数引用 setTimeout(() => updateStatus(userId), 1000);同样地,setInterval也一律使用函数参数形式。
5.3 模板/表达式求值 → 用安全的解释器或映射表
若业务确实需要"按规则计算"(如动态公式、报表表达式),应选用不调用eval的专用表达式解析库,或改用白名单映射:
// 用查找表替代 eval 分支 const handlers = { sum: (a, b) => a + b, avg: (a, b) => (a + b) / 2, }; const result = handlers[userInput.operator]?.(userInput.a, userInput.b);5.4 动态调用模块 → 参考仓库的 safemoduleloading 实践
如果需求是"按变量加载模块",这与仓库中 safemoduleloading.md(6.17 节"避免使用变量进行模块加载")直接相关:require(userInput)同样会把用户输入引入代码执行路径。正确做法是建立白名单目录或使用显式映射,绝不让用户输入直接决定加载哪个模块。相关风险还包括 regex.md(6.16 节"防止恶意正则拖垮单线程")与 childprocesses.md(子进程安全)——它们共同构成了"杜绝不可信输入进入任意执行路径"的安全原则族。
六、总结:把"动态执行"从代码库里彻底清除
回顾 avoideval.md 及其系列译本的核心结论:
| 函数 | 危险形态 | 正确替代 |
|---|---|---|
eval() | 用户输入作为代码执行 | JSON.parse、白名单映射 |
new Function() | 用户输入构造可执行函数 | 显式函数、专用解析库 |
setTimeout(str, ms) | 字符串参数被当作代码 | 传函数引用 |
setInterval(str, ms) | 字符串参数被当作代码 | 传函数引用 |
在实际项目中,请把以下三条作为强制约束:第一,代码评审中把eval、new Function、字符串形式的setTimeout/setInterval视为必须整改的红线;第二,接入eslint-plugin-security并在 CI/提交钩子中强制执行detect-eval-with-expression,参考 lintrules.md;第三,对"必须动态计算"的少数业务场景,坚持"先白名单、后解析、绝不直接执行用户字符串"的原则。做到这三点,注入类攻击在本项目中最大的入口之一便被彻底封死。
- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
相关推荐
深入解读 avoid-eval:为什么 eval() 与动态代码执行必须从现代 Web 开发中根除
深入解读 avoid eval:为什么 eval 与动态代码执行必须从现代 Web 开发中根除 本文以 Front End Checklist 仓库中的 avo
Node.js 安全实践:彻底规避 eval 等动态代码执行,防止远程代码执行(RCE)
Node.js 安全实践:彻底规避 eval 等动态代码执行,防止远程代码执行(RCE) eval 及其同类函数( setTimeout 、 setInterv
文档教程后端Node.js 安全实践:彻底避开 eval 与动态代码执行(nodebestpractices 安全指南)
Node.js 安全实践:彻底避开 eval 与动态代码执行(nodebestpractices 安全指南) eval 、 setTimeout 、 setIn
文档教程后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考