资讯动态

Node.js 安全指南:为什么必须避免 eval、new Function 等动态代码执行

发布时间:2026/10/4 2:05:31 来源:尧图企业网站定制
文档教程后端【免费下载链接】nodebestpractices✅ The Node.js best practices list (July 2026)项目地址https://gitcode.com/GitHub_Trending/no/nodebestpractices点击查看免费下载本文依据当前仓库 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.md6.17 节避免使用变量进行模块加载直接相关require(userInput)同样会把用户输入引入代码执行路径。正确做法是建立白名单目录或使用显式映射绝不让用户输入直接决定加载哪个模块。相关风险还包括 regex.md6.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)项目地址https://gitcode.com/GitHub_Trending/no/nodebestpractices点击查看免费下载相关推荐深入解读 avoid-eval为什么 eval() 与动态代码执行必须从现代 Web 开发中根除深入解读 avoid eval为什么 eval 与动态代码执行必须从现代 Web 开发中根除 本文以 Front End Checklist 仓库中的 avoNode.js 安全实践彻底规避 eval 等动态代码执行防止远程代码执行RCENode.js 安全实践彻底规避 eval 等动态代码执行防止远程代码执行RCE eval 及其同类函数 setTimeout 、 setInterv文档教程后端Node.js 安全实践彻底避开 eval 与动态代码执行nodebestpractices 安全指南Node.js 安全实践彻底避开 eval 与动态代码执行nodebestpractices 安全指南 eval 、 setTimeout 、 setIn文档教程后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑