- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
导读
本文基于 Node.js 最佳实践清单(nodebestpractices)安全章节的 6.15 条规则,深入剖析eval()、new Function()、setTimeout()、setInterval()四类可执行字符串代码的全局函数为何是服务器安全的头号隐患,并给出完整的重构方案、检测手段与兜底沙箱策略。读完本文,你将掌握:识别一切可能被用户输入污染的动态代码执行点、用等价安全写法完成重构、用 ESLint 安全规则在编码阶段拦截eval滥用,以及在不得不执行不可信代码时的最小化风险方案。
一、问题本质:字符串即代码,输入即漏洞
eval()、setTimeout()、setInterval()和new Function()是 JavaScript(尤其在 Node.js 中高频使用)的全局函数,它们的共同特征是接受一个字符串参数,该字符串代表一段 JavaScript 表达式、语句或语句序列,并在运行时被解析与执行。
其安全风险的核心在于:任何一段字符串一旦被交给这些函数,就不再是数据,而是代码。如果不可信的用户输入(HTTP 请求参数、请求体、文件内容、环境变量、外部服务返回值等)能够流入这些函数,攻击者实际上就获得了与你的进程同等的代码执行权限——他可以调用 Node.js 内置模块、访问文件系统、发起网络请求、读取环境变量,甚至通过child_process直接操纵操作系统。
用一句话概括这份危险的本质:评估(evaluate)用户代码,本质上等于允许攻击者执行你所能执行的任何操作。这正是原始文档(sections/security/avoideval.brazilian-portuguese.md)反复强调的核心论断。
二、恶意代码的真实形态:一个示例看穿攻击路径
原始文档给出了一个极具代表性的攻击示例:
// 攻击者设法注入的恶意代码示例 const userInput = "require('child_process').spawn('rm', ['-rf', '/'])"; // 恶意代码被执行 eval(userInput);这段示例清晰地展示了攻击链条的完整闭环:
- 注入:攻击者将一段精心构造的字符串作为"用户输入"提交给服务端(可能是请求参数、JSON 字段、表单内容等);
- 传递:服务端代码在未做任何校验的情况下,把该字符串直接传给
eval(); - 执行:
eval()将该字符串解析为 JavaScript 代码并执行,require('child_process').spawn('rm', ['-rf', '/'])被真实调用——攻击者得以在服务端递归删除根目录。
注意示例中的require之所以能在eval内生效,是因为在 Node.js 的模块作用域中,eval内的代码可以访问到require、process、Buffer等全局与模块级对象。这进一步放大了风险:同样的代码在浏览器端可能只算 XSS,而在 Node.js 服务端直接升级为远程代码执行(RCE)与系统级破坏。
原始文档中这段示例的英文原版(sections/security/avoideval.md)同样以const userInput声明,可见这不是笔误而是有意的教学表达——它暗示这段"用户输入"甚至可能来自服务端自己的变量赋值路径,而攻击者只需控制其内容来源即可。
三、不止 eval:四类高危函数的完整清单
原始文档明确点名了四类函数,它们虽然 API 形态各异,但共享同一危险特性——把字符串当代码执行:
| 函数 | 行为 | 危险用法示例 |
|---|---|---|
eval(code) | 解析并执行字符串代码,可访问调用处作用域 | eval(userInput) |
new Function(code) | 从字符串构造一个函数,代码在全局作用域执行(不访问闭包,但可访问全局对象) | new Function(userInput)() |
setTimeout(code, delay) | 第一个参数若为字符串,会在延迟后被当作代码执行 | setTimeout(userInput, 1000) |
setInterval(code, delay) | 同上,但会周期性反复执行 | setInterval(userInput, 1000) |
容易忽视的细节是setTimeout与setInterval:开发者通常只把它们当作定时器使用,忘记其第一参数的重载形态——当传入的是字符串而非函数时,该字符串同样会被动态执行。README 6.15 节的 TL;DR(README.md)对此有明确警告:setTimeout和setInterval也绝不应被传入动态 JavaScript 代码。
此外,README 该节还补充了两个关键判断:
- 这是性能问题,更是安全问题:动态解析字符串代码绕过了 V8 引擎的静态优化,运行时解析开销显著;而真正的威胁在于恶意代码可能来源于用户输入。
- 漏洞形态往往表现为 XSS 攻击:在 Web 场景中,
eval类漏洞常被利用为跨站脚本注入的载体,而在 Node.js 服务端场景中则进一步升级为任意代码执行。
四、重构方案:用安全的等价写法替换动态执行
原始文档给出的建议非常明确:重构代码,使其不再依赖这些函数,尤其是在用户输入可能被传入并执行的地方。下面给出可直接落地的替换策略。
4.1 数据解析:用JSON.parse替代eval
这是最常见的误用场景——开发者为了"方便"地解析一个对象字面量字符串而调用eval:
// 危险:字符串中的任意代码都会被解析执行 const data = eval("({name: 'node', version: '20'})"); // 安全:只解析 JSON 数据,任何非 JSON 内容都会抛出语法错误 const data = JSON.parse('{"name": "node", "version": "20"}');JSON.parse只做数据解析,绝不执行代码,是处理不可信字符串数据的唯一正确入口。
4.2 动态构造函数:用显式参数 + 固定函数体替代new Function
如果确实需要"根据参数动态生成函数逻辑",应把参数与逻辑分离:逻辑部分写成固定代码,仅数据部分动态传入:
// 危险:函数体来自字符串拼接,拼接点即可被注入 const fn = new Function('prefix', prefix + ' + userInput'); // 安全:函数体完全固定,只通过参数注入数据 const fn = (prefix, data) => prefix + data;原则是:可执行的代码永远来自开发者自己写死的源码,而非任何形式的运行时拼接。
4.3 定时器:永远传函数引用,绝不传字符串
// 危险:字符串被当作代码执行 setTimeout('alert("pwned")', 1000); // 安全:传入函数引用 setTimeout(() => { doSomething(); }, 1000);如果定时器逻辑依赖动态参数,把它放进函数体内作为参数即可,而不是把整段逻辑拼成字符串。
4.4 需要表达式计算时:优先白名单 + 安全算法
某些场景(如规则引擎、模板引擎)确实需要"动态计算"。此时不应引入eval,而应采用受控方案:
- 预定义一组允许的操作,用分支/映射表实现,杜绝任意表达式;
- 对可接受的数据类型与取值范围做严格校验白名单;
- 确实需要解析表达式时,使用专门的安全表达式解析库(将表达式解析为 AST 后按白名单求值),而不是原生
eval。
五、用 Linter 在编码阶段拦截:eslint-plugin-security 实战
仅仅"注意"是不够的,动态执行代码的误用往往藏在代码评审视野之外。nodebestpractices 项目的安全规则章节(sections/security/lintrules.md)给出了工程化拦截方案:为 ESLint 启用安全插件eslint-plugin-security,其中专门有一条规则用于捕捉本文讨论的漏洞模式。
detect-eval-with-expression规则能够识别"把变量表达式直接传入 eval"的危险写法:
// 会被 detect-eval-with-expression 规则标记的不安全代码 const userinput = req.body.userinput; eval(userinput);同插件还会标记其他同类高危模式,形成完整防护网:
// detect-non-literal-fs-filename:用户输入直接作为文件路径访问文件系统 const path = req.body.userinput; fs.readFile(path); // detect-non-literal-regexp:非字面量的正则构造(正则注入) const unsafe = new RegExp('/(x+x+)+y/)'); // detect-pseudoRandomBytes:不安全的伪随机数生成 const insecure = crypto.pseudoRandomBytes(5);配置方式(在 ESLint 配置中启用该插件即可):
{ "plugins": ["security"], "extends": ["plugin:security/recommended"] }启用后,eval(userInput)这类代码在编写阶段就会被静态检查直接拦截,而不是等到生产环境被攻击者利用。README 6.15 节的 TL;DR 同样建议"尽早使用安全相关的 linter 插件,最好是在编码过程中就发现问题"。
六、纵深防御:当确实需要执行不可信代码时,把它关进沙箱
重构与拦截解决了"不该用"的问题,但现实场景中仍存在必须动态执行外部 JavaScript 的情况(例如动态插件、构建期自定义 loader、在线代码评测等)。nodebestpractices 的沙箱章节(sections/security/sandbox.md)提供了三条兜底路线:
- 专用子进程:将不可信代码放入独立 child process 运行,实现信息隔离,同时必须限制子进程执行时间、控制其崩溃恢复;
- 云 Serverless 函数:以 FaaS 函数的形式隔离执行,隔离性最彻底,但动态部署与调用成本较高;
- 沙箱 npm 库:如
sandbox、vm2类库,一行代码即可隔离执行,最简便但保护能力有限。
例如用sandbox库隔离不可信代码时,恶意代码的破坏会被限制在沙箱内:
const Sandbox = require('sandbox'); const s = new Sandbox(); // 语法错误被捕获而非崩溃进程 s.run('lol)hai', (output) => { console.log(output); // output = 'Syntax error' }); // 敏感全局对象被隔离 s.run('process.platform', (output) => { console.log(output); // output = Null }); // 死循环被超时终止 s.run('while (true) {}', (output) => { console.log(output); // output = 'Timeout' });需要强调:沙箱只是风险兜底,不是免责许可。沙箱内的隔离并不等于绝对安全,最佳实践永远是优先采用第四节的重构方案,让不可信内容永远不进入代码执行路径。
七、相关高危实践联动排查
eval类漏洞并非孤立存在,nodebestpractices 安全章节中还有两条与之高度关联的规则,建议在同一轮安全审计中一并排查:
- 避免动态模块加载(sections/security/safemoduleloading.md):
require(helperPath)这类"用变量做路径"的模块加载,若路径源自用户输入,攻击者可加载任意文件,与eval同样危险:
// 不安全:helperPath 可能被用户输入篡改 const badWayToRequireUploadHelpers = require(helperPath); // 安全:使用固定路径 const uploadHelpers = require('./helpers/upload');- 谨慎使用子进程(sections/security/childprocesses.md):
child_process.exec()接受命令字符串,未消毒的用户输入拼接进去即可触发任意命令执行。Node.js 官方文档对此有明确警告:"绝不向此函数传递未消毒的用户输入,任何包含 shell 元字符的输入都可能被用于触发任意命令执行。"该章节给出的准备清单包括:任何情况下都避免使用用户输入,否则必须校验与消毒;用 user/group 身份限制父子进程权限;在隔离环境中运行进程以防备前两项失效。
八、行业共识:来自安全专著的原话
原始文档引用了 Liran Tal 所著《Essential Node.js Security》中的论断,这段话精准概括了eval在 JavaScript 安全版图中的位置:
eval()函数或许是 JavaScript 中从安全角度来看最受诟病的一环。它把一段 JavaScript 字符串当作文本解析,再将其作为 JavaScript 代码执行。将不可信的用户输入与可能流入eval()的路径混在一起,无异于灾难配方,最终可能以服务器被攻陷收场。
这一判断与本文全部论点一致:风险不来自eval函数本身,而来自"不可信输入 + 动态执行"的组合。只要切断这条通路——通过重构、linter 拦截、沙箱兜底三重防线——就能将这一类远程代码执行风险从你的 Node.js 服务中彻底清除。
参考链接(仓库内)
- 规则原文(葡萄牙语版) / 规则原文(英文版)
- README 6.15 节 TL;DR
- Linter 安全规则:eslint-plugin-security
- 运行不可信代码的沙箱方案
- 避免动态模块加载
- 谨慎使用子进程
- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
相关推荐
Node.js 安全实践:彻底规避 eval 与动态代码执行(nodebestpractices 安全指南)
Node.js 安全实践:彻底规避 eval 与动态代码执行(nodebestpractices 安全指南) 导读 在 Node.js 服务端代码中, eval
文档教程后端Node.js 安全实践:彻底避开 eval 与动态代码执行(nodebestpractices 安全指南)
Node.js 安全实践:彻底避开 eval 与动态代码执行(nodebestpractices 安全指南) eval 、 setTimeout 、 setIn
文档教程后端Node.js 安全实践:彻底规避 eval 等动态代码执行,防止远程代码执行(RCE)
Node.js 安全实践:彻底规避 eval 等动态代码执行,防止远程代码执行(RCE) eval 及其同类函数( setTimeout 、 setInterv
文档教程后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考