- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
导读
在 Node.js 服务端代码中,eval()、new Function()以及传入字符串形式的setTimeout()/setInterval()都是一类危险的能力:它们会把一段字符串当作 JavaScript 代码在运行时解析并执行。一旦不受信任的用户输入进入这些函数,攻击者即可在服务器上执行任意操作,直接导致服务器被攻陷。本文将基于 nodebestpractices 仓库安全章节的 Avoid JS eval statements 指南,系统讲解这类动态代码执行 API 的威胁模型、典型攻击样例、为何应彻底重构规避,以及如何借助 ESLint 安全插件在 CI/提交阶段自动拦截此类危险模式。
危险的根源:运行时解析并执行字符串
eval()、setTimeout()、setInterval()与new Function()都是 Node.js 中常用的全局函数,它们的共同特征是:接受一个字符串参数,该字符串代表一段 JavaScript 表达式、语句或语句序列,并将其当作真实代码来解析和执行。仓库文档 avoideval.md 明确指出其核心安全关切:
不受信任的用户输入可能会找到进入代码执行的方式,从而导致危害服务器——因为"评估用户代码"本质上允许攻击者执行任何你(进程)能够执行的操作。
这正是此类 API 与其它普通函数调用最本质的区别:普通函数把数据当数据处理,而这类函数把数据当代码处理。在 Node.js 服务端场景下,进程通常持有文件系统、网络、子进程等高权限能力,一旦用户输入被当作代码执行,攻击者获得的将是与你的 Node.js 进程完全相同的权限。
仓库 README.md 的第 6.15 条用更直白的语言总结了同一结论:
eval是邪恶的,因为它允许在运行时执行自定义 JavaScript 代码。这不仅是性能问题,更是重要的安全问题——恶意 JavaScript 代码可能来自用户输入。同样应避免的语言特性还有new Function构造函数;setTimeout和setInterval也绝不应被传入动态 JavaScript 代码。
高危函数清单
| 函数 | 危险之处 | 建议 |
|---|---|---|
eval(string) | 将字符串作为表达式/语句序列直接执行 | 全面禁用,改用数据结构 + 条件分支 |
new Function(string) | 从字符串构造新的函数体并调用 | 全面禁用,改用普通函数与闭包 |
setTimeout(string, ...) | 第一个参数为字符串时会被当作代码执行 | 只传函数引用,绝不传字符串 |
setInterval(string, ...) | 同上,周期性执行字符串代码 | 只传函数引用,绝不传字符串 |
攻击样例:一行用户输入接管服务器
仓库文档给出了一个浓缩而极具冲击力的攻击示例。攻击者构造的输入字符串直接加载 Node.js 的child_process模块并派生子进程执行破坏性命令:
// 攻击者可能输入的恶意代码示例 const userInput = "require('child_process').spawn('rm', ['-rf', '/'])"; // 恶意代码被执行 eval(userInput);当这段userInput来自 HTTP 请求体、查询参数、上传文件内容等任何不可信来源时,eval(userInput)会让攻击者:
- 执行任意系统命令(如示例中的
rm -rf /递归删除根目录); - 读取或篡改环境变量与密钥;
- 向任意地址发起网络请求(成为攻击跳板);
- 窃取进程内持有的数据库凭据、令牌等敏感数据。
需要强调的是,示例中的破坏性命令只是一个示意。攻击者实际可以执行任何Node.js 进程能执行的动作——这也是文档强调"评估用户代码本质上允许攻击者执行任何你能够执行的操作"的含义所在。
为什么 setTimeout / setInterval 也会成为攻击面
很多人容易忽视的一点是:setTimeout与setInterval的第一个参数既可以是函数,也可以是字符串。当传入字符串时,JavaScript 引擎会在定时器触发时对该字符串执行一次隐式的代码求值(类似eval)。因此下列写法与eval同样危险:
// 危险:字符串在定时器触发时被当作代码执行 setTimeout("require('child_process').spawn('rm', ['-rf', '/'])", 0); setInterval(userInput, 1000); // 安全:只传函数引用 setTimeout(() => { /* 显式逻辑 */ }, 0); setInterval(doSomething, 1000);这也就是 README.md 中"setTimeout和setInterval绝不应被传入动态 JavaScript 代码"这一告诫的来源:任何把字符串当代码传给定时器 API 的写法都应被视为安全漏洞,而非仅仅是风格问题。
专家视角:eval 是最受诟病的 JavaScript 特性
仓库文档引用了 Liran Tal 所著《Essential Node.js Security》一书的观点,作为对上述威胁的权威佐证:
从安全的角度出发,在 JavaScript 语法中,
eval()可能是最让人不悦的函数。它将 JavaScript 字符串解析为文本,并将其作为 JavaScript 代码执行。将它与不受信任的用户输入掺和在一起,可能会是一条导致灾难的路径——最终服务器遭受破坏。
这段话点出了eval的两个核心问题:其一,它模糊了"数据"与"代码"的边界,使输入验证变得几乎不可能完备;其二,在服务端环境中,代码执行失败或成功都意味着进程权限的完全暴露,其后果远比客户端 XSS 严重。
重构替代方案:用数据与逻辑替换字符串代码
规避的核心原则是:把"要做什么"表达为数据结构和显式逻辑,而不是拼装一段待执行的字符串。以下重构策略可以直接落地:
1. 用查找表 / 条件分支替换动态计算
// 反模式:根据用户输入动态生成并执行表达式 const expr = `user.age > ${userInput}`; eval(expr); // 安全:把规则解析为数据,用显式逻辑求值 const rules = { adult: (age) => age >= 18, senior: (age) => age >= 60 }; const apply = rules[userInput] || ((age) => false); const result = apply(user.age);2. 用函数引用替换字符串定时任务
// 反模式:字符串形式的定时回调 setInterval("pollQueue()", 5000); // 安全:直接传递函数引用 setInterval(pollQueue, 5000);3. 用 JSON 解析替换任意表达式解析
如果业务确实需要解析用户提供的表达式(如报表计算、规则引擎),应使用专门的安全解析器(如jsep、expr-eval这类不暴露执行上下文的 AST 解析库),并对最终 AST 做白名单校验,而不是把字符串交给eval。
4. 处理来自 JSON 数据的不安全字符串
JSON.parse本身是安全的,但从解析结果中取出的字符串必须始终按"数据"对待——绝不允许其流入上述任何动态执行函数。应通过白名单校验、类型约束和上下文转义层层把关。
工程防线:用 ESLint 在提交前拦截 eval
依赖人工自觉无法杜绝eval这类反模式,仓库安全章节的姊妹篇 lintrules.md(Embrace linter security rules)提供了工程化手段:借助 ESLint 安全插件在代码进入仓库之前自动检出危险模式。
其中与本文主题直接相关的规则是detect-eval-with-expression,它专门用于检测将非常量表达式传入eval的写法:
// eslint-plugin-security 的 detect-eval-with-expression 规则所拦截的代码 const userinput = req.body.userinput; eval(userinput);在 Node.js 项目中启用该插件(在依赖中安装eslint-plugin-security并将其加入 ESLint 配置的 plugins 列表)后,任何将请求体等外部输入直接传给eval的代码都会在npm run lint阶段直接报错,从而把"避免 eval"从文档建议固化为可执行的持续约束:
// 安全:不再把用户输入交给 eval const userinput = req.body.userinput; const matched = ALLOWED_VALUES.includes(userinput) ? applyLogic(userinput) : reject();配合 git hooks(如 pre-commit 钩子),可以在代码推送远端之前强制拦截,保证安全规则不被绕过。仓库 package.json 中也已内置lint脚本(markdownlint ./README*.md),体现了"用工具约束代码质量"的一贯思路。
结语:把"不执行字符串代码"当作默认安全策略
eval与它的变体(new Function、字符串形式的定时器)是 Node.js 服务端为数不多能让"数据"瞬间变成"代码"的通道。仓库指南给出的结论清晰且可执行:
- 默认禁用:在代码规范与 ESLint 规则层面全面禁用
eval、new Function及字符串形式的setTimeout/setInterval; - 重构替代:用查找表、条件分支、函数引用与安全的表达式解析库替代字符串代码;
- 工具拦截:启用
eslint-plugin-security的detect-eval-with-expression等规则,让危险模式在提交前即被拦截(参见 lintrules.md); - 输入隔离:即使必须处理不可信输入,也始终把输入当作数据校验、白名单化,绝不直接送入任何可执行字符串的 API。
遵循这条规则,你就能从根上切断一大类 XSS 与服务端代码注入攻击面,守住 Node.js 进程权限的最后防线。
- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
相关推荐
Front-End-Checklist 安全规则解析:全面禁止 eval() 与不安全动态代码执行
Front End Checklist 安全规则解析:全面禁止 eval 与不安全动态代码执行 导读 本篇文章以 skills/avoid eval/refer
Node.js 沙箱安全实践:在 nodebestpractices 指南中隔离运行不可信代码
Node.js 沙箱安全实践:在 nodebestpractices 指南中隔离运行不可信代码 本篇技术指南聚焦 Node.js 项目中一个现实且棘手的安全问题
文档教程后端Node.js 安全实践:在沙箱中隔离运行不安全代码的三种方案(nodebestpractices)
Node.js 安全实践:在沙箱中隔离运行不安全代码的三种方案(nodebestpractices) 本文基于 nodebestpractices 仓库安全实践
文档教程后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考