- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
导读
在 Node.js 服务端代码中,eval()、setTimeout()、setInterval()与new Function()这类接受"字符串形式的代码"并直接执行它们的全局函数,是代码注入攻击(Remote Code Execution,RCE)的高危入口。本文基于开源仓库 nodebestpractices 的《Node.js 最佳实践清单》中 sections/security/avoideval.md 一节的完整内容(含日文版 avoideval.japanese.md),系统讲解为什么必须避免这些语法、恶意输入如何导致服务器沦陷、如何用静态检查(ESLint 安全规则)与代码重构来根除风险,并结合仓库内其他安全章节给出可落地的防御方案。读完本文,你将掌握识别高危代码模式、配置自动化检测规则以及设计安全替代实现的完整方法。
eval 及其"同类":四类接受字符串代码的全局函数
Node.js 中经常被误用的全局函数共有四类,它们共同的特点是接受一个字符串参数,该字符串会被解析并当作 JavaScript 表达式、语句或语句序列来执行:
| 函数 | 字符串参数含义 | 典型误用场景 |
|---|---|---|
eval(code) | 解析并执行任意 JavaScript 表达式/语句序列 | 解析 JSON(应使用JSON.parse)、动态拼装表达式 |
setTimeout(code, delay) | 延迟执行一段代码字符串 | 把回调写成字符串传给定时器 |
setInterval(code, delay) | 周期性执行一段代码字符串 | 同上 |
new Function(code) | 从字符串构造一个函数体 | 动态生成函数、模板求值 |
这些函数在语法层面"合法",但安全隐患在于:一旦不可信的用户输入流入了被执行的字符串,评估用户代码本质上等同于允许攻击者执行你(应用进程)能够执行的任何操作。攻击者输入一段代码字符串,就相当于拿到了 Node.js 进程的完整权限——读取环境变量与文件、调用child_process执行系统命令、篡改数据,直至让整个服务器沦陷。
原文档(avoideval.japanese.md)明确指出,建议对代码进行重构,避免依赖那些"用户输入可能被传入并执行"的函数。这是一条需要写进团队规范与代码评审清单的硬性约束。
攻击场景复现:一段字符串如何清空你的服务器
原文档给出了一段极具冲击力的攻击示例——攻击者只需把下面的字符串注入你的应用,恶意代码就会被原样执行:
// 攻击者が入力できる悪意のあるコードの例 // 攻击者可以输入的恶意代码示例 const userInput = "require('child_process').spawn('rm', ['-rf', '/'])"; // 恶意代码被执行 eval(userInput);这段代码的执行路径是:eval()将userInput字符串当作 JavaScript 代码解析,触发require('child_process').spawn('rm', ['-rf', '/']),即让 Node.js 进程启动一个子进程执行rm -rf /。如果承载eval(userInput)的服务以 root 权限运行,其结果就是灾难性的数据毁灭。
需要特别强调的是:恶意字符串不一定来自userInput这样一个直接变量,它可能藏在 URL 查询参数、请求体、HTTP 头、Cookie、WebSocket 消息、命令行参数,甚至数据库中被攻击者写入的字段里。只要最终拼进eval的字符串包含攻击者可控片段,注入就成立。这也是为什么原文档的结论如此斩钉截铁:只要存在"用户输入可能传入并执行"的函数调用,就必须重构。
深层原理:从代码执行到系统级命令注入
eval(userInput)之所以危险,是因为它的能力半径是整个 Node.js 运行时。攻击者注入的字符串中可以引用任何内置模块,其中最致命的是child_process。仓库中 childprocesses.md 一节专门警示了未净化的输入进入系统级命令执行的危险性,其示例与本主题互为印证:
const { exec } = require('child_process'); // 其中一个参数是未经净化的用户输入 exec('"/path/to/test file/someScript.sh" --someOption ' + input); // 如果用户输入 '&& rm -rf --no-preserve-root /' 会发生什么? // 你将得到一个"意外的惊喜"该章节引用的 Node.js 官方文档措辞同样严厉:"绝不要把未经净化的用户输入传给此函数,任何包含 shell 元字符的输入都可能被用来触发任意命令执行。" 也就是说,eval是"代码注入"的第一道闸门,而child_process是把注入升级为"命令注入"的放大器——两者叠加即构成完整的 RCE 链条。规避eval等于在链条源头就切断了攻击面。
用 Linter 安全规则自动封堵:detect-eval-with-expression
人肉评审难免遗漏,更好的做法是把"禁止 eval"变成机器强制执行的规则。仓库 lintrules.md 一节给出了配套方案:使用eslint-plugin-security(ESLint 生态)或tslint-config-security(TSLint 生态)等安全插件,基于已知漏洞模式对代码做安全检查,其中就包括对eval()不安全使用的检测。
eslint-plugin-security提供的detect-eval-with-expression规则正是针对本主题的专门检测器,它会标记把非字面量表达式(如请求体中的用户输入)传给eval()的代码:
const userinput = req.body.userinput; eval(userinput); // 触发 detect-eval-with-expression 规则eslint-plugin-security还能一并捕获其他高危模式,例如:
detect-non-literal-fs-filename:用非字面量文件名访问文件系统(fs.readFile(path),path 来自req.body);detect-non-literal-regexp:用非字面量构造正则(new RegExp(...)),可能引入 ReDoS 与注入风险;detect-pseudoRandomBytes:使用不安全的伪随机字节生成。
对包含上述不安全代码的 Node.js 项目运行eslint-plugin-security,可以得到如下直观的报告结果:
(该截图来自 lintrules.md,展示了在真实项目上执行安全 lint 后命中的问题列表。)
配合 git hooks(如pre-git)在代码提交到远端之前强制执行这些规则,可以让"禁止 eval"从口头约定升级为持续集成的硬门槛。值得记住的是 ESLint 生态中还有其他同类规则(如no-eval、no-implied-eval),实际项目中建议组合启用。
重构替代方案:四类高危场景的安全写法
规避 eval 的核心原则是:能不用动态求值就不用;必须执行动态代码时,把它隔离在受控的沙箱里;所有外部输入在进入任何敏感 API 之前先经过显式验证。结合仓库各安全章节,可按以下方案逐一替换。
方案一:为每类场景选用安全的原生替代
| 原始写法(危险) | 安全替代 |
|---|---|
eval(jsonString) | JSON.parse(jsonString)(严格解析,不执行代码) |
eval('obj.' + key) | obj[key]或 Map 索引查找 |
setTimeout('doSomething()', 1000) | setTimeout(() => doSomething(), 1000)(传入函数引用而非字符串) |
new Function('a', 'return a * 2') | 直接定义普通函数const f = (a) => a * 2 |
原则:回调一律传函数引用,绝不传字符串;需要"动态执行"的场景绝大多数都能用高阶函数、查找表或配置数据替代。
方案二:杜绝动态模块加载(避免变量路径 require)
仓库 safemoduleloading.md 指出,即使绕开 eval,用"来自用户输入的可变路径"去require/import模块同样是高危行为:
// 危险:helperPath 可能已被用户输入篡改 const badWayToRequireUploadHelpers = require(helperPath); // 安全:使用固定的字面量路径 const uploadHelpers = require('./helpers/upload');动态require本质上是"模块级代码注入"——攻击者若能控制路径,就能让进程加载任意文件。这一条规则同样适用于fs.readFile()等敏感资源访问:凡是访问路径来自用户输入,都必须先校验或改为白名单映射。
方案三:必须执行动态代码时,放入隔离沙箱
某些真实场景(如 webpack 这类支持运行时动态加载自定义 loader 的框架)确实需要执行运行时传入的代码。仓库 sandbox.md 给出了三种隔离手段,按资源隔离、崩溃隔离和信息隔离三个维度权衡:
- 独立子进程(child process):信息隔离见效快,但需要驯服子进程、限制执行时间并处理错误恢复;
- 云 Serverless 框架(FaaS):满足沙箱的全部要求,但动态部署与调用 FaaS 函数并不轻松;
- 沙箱 npm 库(如
sandbox、vm2):一行代码即可在隔离环境执行代码,简单取胜但防护能力有限。
以sandbox库为例,它能拦截语法错误、屏蔽敏感全局对象并对死循环做超时处理:
const Sandbox = require('sandbox'); const s = new Sandbox(); s.run('lol)hai', (output) => { console.log(output); // output='Syntax error'(语法错误被捕获) }); // 受限代码:沙箱内无法访问 process.platform s.run('process.platform', (output) => { console.log(output); // output=Null }); // 无限循环:触发超时而非拖垮主进程 s.run('while (true) {}', (output) => { console.log(output); // output='Timeout' });注意原文档的明确立场:"作为经验法则,只运行你自己的 JavaScript 文件"。沙箱只是把伤害控制在最小范围("希望把损害降到最低,甚至让流程成功终止")的兜底方案,绝不是放心执行任意第三方代码的通行证。
方案四:入口处显式校验输入,缩小攻击面
validation.md 一节从"缩小攻击面"的角度补充了关键一环:对应用愿意接受的载荷做显式声明,输入一旦偏离预期就快速失败(fail fast)。这能有效防止攻击者用任意结构、值、长度的载荷反复试探——代码注入类攻击依赖的正是"输入可以不受约束地进入执行路径"。实践中可以用 JSON Schema、joi 等校验库,并尽量在请求进入路由处理之前(例如 Express 中间件)尽早完成校验。
专家论断:为什么 eval 是 JavaScript 中最令人诟病的部分
原文档引用了 Liran Tal 所著《Essential Node.js Security》一书的原话,这段论断至今仍是社区共识:
从安全的角度看,
eval()函数或许是 JavaScript 中最令人诟病的部分。它把 JavaScript 字符串当作文本解析,然后像 JavaScript 代码一样执行它。把这一点与可能流入eval()的不可信用户输入混在一起,就是一场灾难的配方,最终以服务器沦陷收场。
这段话点出了本质:eval让"数据"与"代码"的边界彻底消失,而安全防御的全部前提恰恰是严格区分数据和代码。任何"用户可控字符串 + 代码执行语义"的组合,都是在亲手拆除这条边界。
落地清单:把"避免 eval"写进你的防御体系
结合本文全部内容,给出可直接落地的检查清单:
- 代码搜索:全局检索
eval(、new Function(,以及setTimeout(/setInterval(中的字符串首参(可用正则setTimeout\s*\(\s*['"]排查),逐一替换为函数引用或安全 API; - 规则固化:在 ESLint 配置中启用
eslint-plugin-security的detect-eval-with-expression及no-eval、no-implied-eval,接入 CI 与 git hooks; - 路径管控:检查所有
require()/import()/fs文件访问的参数是否可能来自用户输入,一律改为字面量路径或白名单; - 入口校验:对请求体、查询参数、头部等所有外部输入实施显式校验,异常输入快速失败;
- 兜底隔离:确需执行动态代码的业务,使用子进程或
sandbox/vm2类沙箱隔离,并限制执行时间与资源; - 最小权限:以非 root 用户运行 Node.js 进程(参考仓库安全章节关于非 root 用户与进程权限的实践),即使注入发生也限制其破坏半径。
这条清单源自 nodebestpractices 仓库 sections/security 目录下的系列最佳实践,其中 avoideval.md 是源头与主线,其余章节(lintrules、childprocesses、safemoduleloading、sandbox、validation)共同构成完整防线。记住那条核心原则:永远不要让用户输入成为可执行代码的一部分——无论是通过 eval,还是通过任何其他通向"代码执行"的路径。
- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
相关推荐
StarRocks space() 字符串函数详解:语法、边界行为与底层实现
StarRocks space 字符串函数详解:语法、边界行为与底层实现 space x 是 StarRocks 提供的一个标量字符串函数,用于根据传入的整数
数据库OLAP数据仓库大数据湖仓一体数据分析StarRocks 字符串函数 left 详解:语法、边界行为与底层实现
StarRocks 字符串函数 left 详解:语法、边界行为与底层实现 left 是 StarRocks 中用于从字符串左侧截取指定长度字符的字符串函数,在数
数据库OLAP数据仓库大数据湖仓一体数据分析如何用gps-measurement-tools处理NMEA文件?误差分析与可视化教程
如何用gps measurement tools处理NMEA文件?误差分析与可视化教程 gps measurement tools是一款功能强大的开源工具集,专
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考