Lodash 威胁模型:信任边界、漏洞范围界定与源码级的安全防线
本文基于 Lodash 官方威胁模型文档 threat-model.md,系统讲解 Lodash 在 JavaScript 环境中“信任什么、不信任什么”的完整安全框架:哪些输入被视为不可信数据、哪些组件属于可信边界、哪些漏洞属于 Lodash 自身缺陷(in scope)、哪些报告应当被拒绝(out of scope)。读完本文,你将能够独立判定一份安全报告是否属于 Lodash 漏洞,理解 prototype pollution、code injection、DoS 三类典型漏洞的成因,并能在 lodash.js 源码中定位 Lodash 针对原型链写入的实际防护实现。
威胁模型的基本前提:同一信任边界内的工具库
Lodash 的威胁模型定义了 Lodash 在 JavaScript 环境中执行时的信任边界。作为通用工具库,Lodash 与调用它的代码运行在同一个信任边界内,因此,任何需要先攻破可信组件(如 JavaScript 运行时、宿主环境、开发者可控的输入)才能达成的漏洞,都不在本威胁模型的范围内。
原文档给出了一个清晰的判定标准:
一个漏洞要被视为 in scope,必须是 Lodash 自身违反了其文档化行为,或者在标准使用假设下无法维持完整性(integrity)、保密性(confidentiality)或可用性(availability)所致。
换句话说,Lodash 的安全承诺不是“抵御一切恶意输入”,而是“对文档承诺的行为负责”。这个前提决定了后文所有 in scope / out of scope 的划分逻辑。
Lodash 不信任的要素
威胁模型首先明确列出三类 Lodash 不信任的输入来源:
1. 传入 Lodash 函数的数据
Lodash 将所有输入数据(数组、对象、字符串、函数等)视为不可信。它不试图验证或净化输入的语义正确性——它只按给定值进行操作。
原文档强调:如果不可信输入能导致 Lodash 执行超出文档承诺的行为——例如原型链污染(prototype pollution)、类型混淆(type confusion)、内存耗尽(memory exhaustion)或代码注入(code injection)——那就说明存在安全漏洞。这一条是整个威胁模型中最关键的一条:Lodash 不对输入做语义校验,但它保证“不可信输入不能改变自身行为边界”。
2. 不可信的网络来源或用户可控数据
任何源自未经验证的用户输入、网络响应、文件内容或反序列化数据的输入都视为不可信。Lodash 不执行输入隔离或沙箱化(input isolation / sandboxing)——它没有能力也没有义务替你隔离来自 req.body 的数据。
3. 运行时对 Lodash 内部的篡改
修改 Lodash 内部符号、monkey-patch 其函数、或在运行时覆写其内部引用,都发生在信任边界之外。原文档明确指出:如果这类修改改变了 Lodash 的行为,那是“可信代码被攻破”的体现,而不是 Lodash 的漏洞。
Lodash 信任的要素
与不信任清单相对,Lodash 信任以下五类要素。理解这份清单是判定 out of scope 报告的核心依据:
- JavaScript 运行时及其标准库:Lodash 假设一个正确、未被攻破的运行时环境(如 Node.js、浏览器)。运行时本身的漏洞(如原型链问题、引擎崩溃)不在范围内。
- 宿主环境及其配置:Lodash 依赖宿主环境(Node.js、浏览器、Deno 等)的正常运转,以及它使用的全局对象和 API(如
Object、Array、Function、JSON)的可靠性。 - 调用 Lodash 的代码:使用 Lodash 的应用或库负责用户输入校验、安全检查以及执行上下文的正确处理。
- 已安装包的完整性:Lodash 假设通过 npm、CDN 等渠道安装的包未被篡改,且来自合法的 Lodash 分发渠道。原文档特别强调:供应链攻击或恶意克隆包不被视为 Lodash 漏洞。
- 执行上下特的权限与许可:Lodash 继承调用它的用户或进程的权限。误用或过度授权(如以 root 运行、浏览器权限过大)不在范围内。
In Scope:三类典型漏洞及其源码证据
威胁模型列举了三类属于 Lodash 自身缺陷的漏洞,并各自对应真实的历史 CVE。结合 lodash.js 源码,可以看到 Lodash 为其中每一类都做了防御性实现。
原型链污染(CWE-1321)
威胁模型指出:如果 Lodash 的某个函数(如 merge、defaultsDeep、set)因净化不足,允许不可信输入(如 __proto__ 键)修改 Object.prototype 的属性,则属于 in scope。该漏洞类别在 Lodash 历史版本中真实存在过,即 CVE-2019-10744(_.defaultsDeep 的嵌套污染问题)。
当前源码中可以看到多层防护:
-
baseSet直接拒绝原型链关键键。在 lodash.js 的baseSet实现中,路径遍历到__proto__、constructor、prototype任何一个键时立即返回原对象、放弃赋值:if (key === '__proto__' || key === 'constructor' || key === 'prototype') { return object; } -
baseUnset对路径做双重拦截。_.unset/_.omit等删除类函数的底层实现(lodash.js)会在路径校验阶段做两件事:只要目标对象没有把__proto__作为自有属性,路径中任何位置的__proto__都会被拦截;constructor/prototype作为非末尾遍历键时也被拦截,防止逃逸到内置构造器与原型对象:// Always block "__proto__" anywhere in the path if it's not expected if (key === '__proto__' && !hasOwnProperty.call(object, '__proto__')) { return false; } // Block constructor/prototype as non-terminal traversal keys to prevent // escaping the object graph into built-in constructors and prototypes. if ((key === 'constructor' || key === 'prototype') && index < length - 1) { return false; } -
baseAssignValue把__proto__变成普通自有属性。当__proto__作为普通键名被赋值时,lodash.js 中的baseAssignValue会用Object.defineProperty定义一个不可枚举以外的常规自有属性,避免触发隐式原型修改:function baseAssignValue(object, key, value) { if (key == '__proto__' && defineProperty) { defineProperty(object, key, { 'configurable': true, 'enumerable': true, 'value': value, 'writable': true }); } else { object[key] = value; } } -
深度合并路径绕过
safeGet。baseMerge(lodash.js)在读取目标属性时通过safeGet(object, key)而非直接object[key]取值,而 safeGet 对__proto__键和函数类型的constructor键一律返回undefined,把原型链读取从合并逻辑中隔离出去:function safeGet(object, key) { if (key === 'constructor' && typeof object[key] === 'function') { return; } if (key == '__proto__') { return; } return object[key]; }此外
baseMergeDeep(lodash.js 起)通过Stack跟踪已遍历对象,使含循环引用的对象也能安全合并,这正是针对“不可信对象图导致无界递归”这一 DoS 形态的实现。
意外的代码执行(CWE-94)
如果 Lodash 的某个方法(如 template())在没有文档化警告或净化要求的情况下把攻击者可控输入当代码执行,则属于 Lodash 漏洞,典型代表是 CVE-2021-23337。这与下一条“安全公告”直接相关。
逻辑缺陷导致的拒绝服务(CWE-400)
如果 Lodash 在处理文档使用限制内、本身合法的输入时进入无界递归、过度占用内存或挂起,这属于 Lodash 自身的漏洞(例如 CVE-2020-28500)。从源码结构看,baseMergeDeep 的 Stack 循环引用跟踪、以及 _.template 之外的多数纯计算函数保持无副作用设计,都是维持“合法输入不会击穿可用性”这一承诺的实现手段。
安全公告:_.template 被明确标记为不安全
threat-model.md 中专门设有一节针对 _.template 的公告,这是本文读者最应关注的操作级结论:
当前
_.template的实现会把模板字符串编译成可执行代码,当给定不可信输入时可能导致代码注入(即 CVE-2021-23337)。因此 Lodash 团队认为它不安全(insecure),并建议不要使用。
源码层面可以印证这一结论:lodash.js 中的 template 函数注释说明其编译机制基于 John Resig 的 tmpl 与 doT.js 的实现思路,核心逻辑是把模板文本拼接进一个 Function 构造器源码字符串再求值——这正是“模板字符串成为可执行代码”的机制根源。
文档给出了明确的时间线与使用约束:
- 路线图:Lodash 计划在 v5 中移除
_.template。在此之前它仍可用,但已被官方定性为不安全 API。 - 如果继续使用:只使用开发者可控的、静态的模板字符串和可信数据;避免把不可信输入(用户可控数据、未验证的网络内容)同时引入模板字符串和传给编译后函数的 data 对象。
对现有项目而言,这意味着:在升级到 Lodash v5 之前,任何把用户可控内容拼入模板字符串的用法都应当迁移到服务端安全的模板方案或严格白名单数据。
Out of Scope:九类常见误报场景
威胁模型用最大篇幅列举了不属于 Lodash 漏洞的报告类型。这些类别对安全研究者和应用开发者都极具参考价值——它们界定了“谁的 bug 归谁修”。
恶意第三方包(CWE-1357)
如果项目引入了恶意依赖,覆盖 Lodash 行为或向 Lodash 命名空间注入恶意代码,这不代表 Lodash 存在漏洞。依据是 Lodash 信任其运行时与安装环境(见上文“信任的要素”第 1、4 条)。
未验证的应用层输入
使用 Lodash 的应用负责输入校验。把攻击者可控数据直接传入 Lodash 函数(如 _.merge(req.body, config))是应用 bug,不是 Lodash 漏洞。这与“不信任传入数据”看似矛盾,实则互补:Lodash 承诺“不可信数据不能让我越出文档行为”,但不承诺“替你挡住攻击者数据”。
经由可信代码的原型链污染
如果开发者故意把用户输入合并进全局对象、或未能隔离数据结构,那是 Lodash 文档化 API 的误用,而非 Lodash 缺陷。
有状态或基于访问器(Accessor)的路径参数
这是一条非常有实战指导意义的边界。文档指出:Lodash 函数的 path 参数(字符串或字符串/数字数组)被预期且被文档化为数据而非代码。Getter、setter 与 Proxy 是代码——它们在属性被读取时执行 JS 代码,无法被 JSON 序列化、无法通过网络传输,必须由进程内部某段代码调用 Object.defineProperty(或等价物)预先布置。
因此,如果一份报告的利用链依赖“同一路径元素在多次读取中返回不同值”(check/use 不匹配),那么攻击需要进程内已有代码运行,而不只是攻击者提供的数据——这就落入了 Lodash 信任边界之外的“调用 Lodash 的代码”条目。
JavaScript 原型行为的固有语义
Lodash 阻止对内置原型的写入——__proto__、constructor.prototype 这类键在 _.set、_.merge 等函数中会被过滤(即上文源码中 baseSet、baseUnset 的拦截逻辑);但它不能、也不阻止读取。读取继承属性是 JavaScript 的工作方式:obj.constructor、沿 __proto__ 遍历、通过正常属性解析到达 Object.prototype,都是语言语义而非 Lodash 行为。
文档给出了判定口诀:如果报告只展示了只读的原型访问(如 _.get(obj, 'constructor.prototype') 返回 Object.prototype),那描述的是 JavaScript 语言本身,不是 Lodash 漏洞。注意一个细节——safeGet 对 __proto__/constructor 的拦截主要服务于合并等内部写路径,而面向外部的 _.get 仍遵循语言的标准属性解析语义,这与威胁模型“阻写不阻读”的表述是一致的。
漏洞链与 Gadget(小工具)组合
Lodash 漏洞必须仅通过 Lodash 自身可被利用。如果报告的影响依赖把 Lodash 行为与另一库或应用组件中的独立 bug 组合起来,缺陷在下游代码而非 Lodash。Gadget 报告同理:如果某 Lodash 函数产出的对象形状,导致另一个库中有漏洞的函数执行了代码,根因是那个库接受这种形状,而不是 Lodash 产出它。
JavaScript 运行时或平台漏洞
如果 Lodash 的某个方法触发了 JS 引擎(V8、SpiderMonkey、JavaScriptCore)的 bug 并导致内存损坏或错误行为,漏洞在引擎而非 Lodash——对应信任清单第 1 条。
环境配置错误(CWE-15)
由错误配置的执行环境引发的问题——如运行过时的 Node.js 版本、浏览器使用不安全的 CSP——不算 Lodash 漏洞。
供应链攻击
对 npm registry 中 Lodash 包的篡改、安装过程中的 MITM 攻击、本地文件系统篡改,都不是 Lodash 本身的漏洞——对应信任清单第 4 条“已安装包完整性”。
与安全流程的衔接
threat-model.md 不是孤立文档:SECURITY.md 中的“Threat Model”一节直接指向本文档,说明其用途是“让各方理解哪类漏洞属于 in scope / out of scope,以指导安全问题的分诊(triage)与披露(disclosure)”。仓库中的 incident_response_plan.md 则进一步支撑了从报告接收到响应处置的流程。从 SECURITY.md 可补充两点操作信息:当前受支持(接受安全更新)的版本为 4.x,3.x 及以下不再受支持;漏洞报告通过仓库的 Security 私有通道提交,若 6 个工作日未获确认可向 OpenJS Foundation CNA 升级。
总结
Lodash 的威胁模型可以用一句话概括:Lodash 完全运行在调用方的信任边界内,in scope 的漏洞仅限于“Lodash 在不可信输入下违背自身文档化行为”,且不得假设运行时、操作系统或调用方代码已被攻破。
落地到工程实践,有三点可直接执行:
- 不要把 Lodash 当输入校验层。
_.merge(req.body, config)这类写法是应用 bug;校验永远发生在 Lodash 调用之前。 - 停止把
_.template用于不可信内容。官方已将其定性为不安全并计划于 v5 移除,现有代码应尽快迁移到静态模板 + 可信数据的用法,或替换模板方案。 - 提交安全报告前先对照 out-of-scope 清单。仅涉及只读原型访问、需要进程内 accessor 代码、依赖其他库 gadget、或依赖供应链/运行时被攻破的报告,都会被判定为范围外。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00