lodash安全问题引发的思考
前段时间,公司在做审计,检测到我们后台的一个安全问题,一波三折才算是把问题解决掉,问题表现如下


强制使用最新的lodash版本
起初认为是版本太低了,所以我就重新安装了最新的lodash版本,并且强制所有的隐式依赖都用这个版本。
- STEP1 升级之后,我先列了lodash的版本
npm list lodash --depth all
- STEP2 我强制所有的隐式依赖都用这个版本的lodash
{
"dependencies": {
"lodash": "4.17.21"
}
}这时候自信满满去做提交,发现还是不行,又被打回了。
使用社区版本
于是我把错给了google, 发现使用的element-ui的框架上有一个issue, 于是就使用了社区的方法,使用
npm install element-ui-ce再次提交后,果真解决了。
根因分析
是因为element-ui引入了 4.17.10的死代码,导致了安全问题,并且将 _ 变量挂载到了全局(这个是次要的问题),导致了安全问题。
看了element-ui源码
- 位置`lib/statistic.js,528行中有引用

- 然后我们再看下引用的
lodash文件 格式化后第8行,我们可以看到版本是4.17.10
这时候答案就呼之欲出了,是因为webpack构建的时候把lodash的版本也打包到了全局,导致了安全问题。所以你怎么升级都不起作用。
webpack原理
至于为什么lodash 会挂载到全局呢?因为我看代码逻辑,工程代码里不存在 AMD 的用法,所以window._ 打印出来应该是undefined才对。
if (typeof define === 'function' && _typeof(define.amd) === 'object' && define.amd) {// Expose Lodash on the global object to prevent errors when Lodash is
// loaded by a script tag in the presence of an AMD loader.
// See http://requirejs.org/docs/errors.html#mismatch for more details.
// Use `_.noConflict` to remove Lodash from the global object.
root._ = _;// Define as an anonymous module so, through path mapping, it can be
// referenced as the "underscore" module.
define(function () { return _; });
}// Check for `exports` after `define` in case a build optimizer adds it.
else if (freeModule) {// Export for Node.js.
(freeModule.exports = _)._ = _;// Export for CommonJS support.
freeExports._ = _;
} else {// Export to the global object.
root._ = _;
}应该走到freeModule分支才对,于是找了找,发现webpack的一个运行机制。
- Webpack 的“自作聪明” 在 Element-UI 内部打包的那份 Lodash 代码( lodash.js )中,有一个经典的 UMD 环境判断:
if(typeof define==='function'&&_typeof
(define.amd)==='object'&&define.amd){
// ...
root._=_; // <--- 罪魁祸首在这里
define(function(){return _;});
}当 Webpack(Vue CLI 底层)去打包这个文件时,它扫描到了 define.amd 。Webpack 为了兼容老代码,会默认提供一个模拟的 AMD 环境,在构建时自动把 define 注入进去,导致 define.amd 变成了 true 。
- Lodash 走错了分支 因为 Webpack 注入了 AMD 变量,导致上面那个 if 判断在浏览器运行时成立了。Lodash 误以为自己正处于一个 AMD 加载器环境中,于是乖乖执行了 root._ = _ (这里的 root 就是 window ),从而把 _ 泄漏到了全局。
如果没有 Webpack 捣乱,它本该走到后面的 else if(freeModule) (CommonJS 分支),那样就绝对不会污染 window
后面我的最小用例可以验证这个 webpack-amd-demo
webpack解决全局变量暴露问题
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
chainWebpack: config => {
// 告诉 Webpack:遇到 lodash 不要去模拟 AMD 环境
config.module
.rule('disable-amd')
.test(/lodash\.js$/)
.parser({ amd: false })
}
})element-ui-ce为什么能解决
- 位置
lib/statistic.js, 524行中的引用

引用的是lodash-es,是treeshaking的版本,这就是根本原因
vite+element-ui+vue为什么没有
其实后面我又想了,vite构建工具应该不会有这样的问题把,最起码不会暴露出全局变量,试了一下果真如此。
因为在vite世界里,esm一等公民,在 Vite 的环境里,全局变量 define 就是 undefined ,正确走入 CommonJS 分支。 第二,tree-shaking,,能够明确推断出 typeof define === 'function' 永远是 false 。所以在最终你看到的 Vite 打包产物( dist/assets/index-xxx.js )里, 那段试图挂载到全局的 AMD 代码连同 root._ = _ 会被当作 Dead Code(死代码)直接删掉(Tree-shaking) 。