Wappalyzer 原型链漏洞问题完整解决过程
背景
在生产环境中,使用 Wappalyzer 检测到项目使用了 lodash 版本 4.17.10 (显示为 4.17.10),但项目的 package.json 中明确声明了 lodash 版本为 4.17.21。为了解决这个问题,查阅资料后,借助claude code以及gpt-5 完成了此次漏洞修复。
可以看我其他博客,教你怎么用这些工具融入vscode进行开发。
初始状态
json
// package.json
{
"dependencies": {
"lodash": "^4.17.21",
"element-ui": "2.15.14"
}
}
问题: 为什么 Wappalyzer 检测到的是旧版本 4.17.10?
阶段一:配置 npm-force-resolutions 统一依赖版本
问题分析
虽然项目直接依赖了 lodash 4.17.21,但项目的嵌套依赖(dependencies 的 dependencies)可能使用了不同版本的 lodash,导致最终构建产物中包含多个 lodash 版本。
解决方案:使用 npm-force-resolutions
npm-force-resolutions 可以强制所有嵌套依赖使用指定版本,确保整个依赖树中只有一个 lodash 版本。
步骤 1: 安装 npm-force-resolutions
bash
npm install --save-dev npm-force-resolutions
步骤 2: 在 package.json 中添加 resolutions 字段
json
{
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"npm-force-resolutions": "0.0.10"
},
"resolutions": {
"lodash": "4.17.21"
}
}
说明:
resolutions字段指定所有 lodash 依赖(包括嵌套依赖)统一使用 4.17.21 版本- npm 旧版本不原生支持
resolutions,需要配合工具使用
步骤 3: 创建 preinstall 脚本
创建文件 scripts/force-resolutions.js:
javascript
const fs = require('fs');
const { execSync } = require('child_process');
const lockFile = './package-lock.json';
try {
if (!fs.existsSync(lockFile)) {
console.log('package-lock.json 不存在,先生成锁文件...');
// 忽略生命周期脚本,防止 preinstall 自己被触发
execSync('npm install --package-lock-only', {
stdio: 'inherit',
env: { ...process.env, npm_config_ignore_scripts: 'true' }
});
}
console.log('执行 npm-force-resolutions 修改 lock 文件...');
execSync('npx npm-force-resolutions', { stdio: 'inherit' });
} catch (err) {
console.error('preinstall 脚本执行失败:', err);
process.exit(1);
}
步骤 4: 在 package.json 中配置 preinstall 钩子
json
{
"scripts": {
"preinstall": "node scripts/force-resolutions.js",
"dev": "vue-cli-service serve",
"build": "vue-cli-service build"
}
}
工作原理:
- 每次运行
npm install前,自动执行preinstall钩子 - 脚本检查并生成
package-lock.json - 运行
npx npm-force-resolutions修改锁文件,将所有 lodash 依赖版本统一替换为 4.17.21 npm install根据修改后的锁文件安装依赖
步骤 5: 清理并重新安装依赖
bash
# 删除旧的依赖和锁文件
rm -rf node_modules
rm package-lock.json
# 重新安装(会自动触发 preinstall 钩子)
npm install
预期输出:
shell
> bot-mb-manage@1.0.0 preinstall
> node scripts/force-resolutions.js
package-lock.json 不存在,先生成锁文件...
执行 npm-force-resolutions 修改 lock 文件...
✔ Applied resolutions!
added 1523 packages in 2m
步骤 6: 验证依赖版本
bash
npm ls lodash
预期输出:
kotlin
bot-mb-manage@1.0.0 D:\bot\product\mb-manager-web
├─┬ element-ui@2.15.8
│ └── lodash@4.17.21 deduped
├── lodash@4.17.21
└─┬ @riophae/vue-treeselect@0.4.0
└── lodash@4.17.21 deduped
✅ 此时所有 npm 依赖中的 lodash 版本已统一为 4.17.21
步骤 7: 构建并部署
bash
npm run build
构建成功后,部署到生产环境,使用 Wappalyzer 再次检测...
阶段二:发现问题仍然存在
问题现象
部署后,Wappalyzer 仍然检测到 lodash 4.17.10!
疑惑
- ✅
npm ls lodash显示所有依赖都是 4.17.21 - ✅
package-lock.json中搜索 "4.17.10" 无结果 - ❓ 那么旧版本是从哪里来的?
阶段三:深度排查 - 发现 Element UI 内嵌代码
排查步骤 1: 检查 node_modules 中的所有 lodash
bash
# 查找所有 lodash package.json 文件
find node_modules -name "package.json" -path "*/lodash/package.json"
发现:
bash
node_modules/lodash/package.json ← 版本 4.17.21 ✓
node_modules/element-ui/node_modules/lodash/... ← (不存在)
所有通过 npm 安装的 lodash 确实都是 4.17.21。
排查步骤 2: 搜索 Element UI 源码
根据网上资料,有人提到 Element UI 可能使用了 lodash 的 safeGet 方法:
bash
# 在 Element UI 目录中搜索 lodash 相关代码
grep -r "lodash" node_modules/element-ui/lib/
关键发现:
bash
node_modules/element-ui/lib/utils/lodash.js
排查步骤 3: 检查可疑文件
bash
# 查看文件大小
ls -lh node_modules/element-ui/lib/utils/lodash.js
输出:
css
-rw-r--r-- 1 user 197121 497.8K Nov 15 10:23 lodash.js
⚠️ 异常: 一个 utils 工具文件竟然有 497.8KB!
排查步骤 4: 读取文件内容
bash
head -20 node_modules/element-ui/lib/utils/lodash.js
关键内容:
javascript
/**
* @license
* Lodash <https://lodash.com/>
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
;(function() {
/** Used as a safe reference for `undefined` in pre-ES5 environments. */
var undefined;
/** Used as the semantic version number. */
var VERSION='4.17.10'; // ← 找到了!
🎯 问题根源确认
Element UI 在其构建产物中内嵌了完整的 lodash 4.17.10 源代码!
这解释了为什么:
npm ls lodash显示所有依赖都是 4.17.21(因为这只检查 npm 依赖)npm-force-resolutions无法解决问题(因为这不是 npm 依赖,是内嵌代码)- Wappalyzer 检测到 4.17.10(因为它分析的是最终构建产物)
验证不同版本的 Element UI
bash
# 检查当前版本(假设是 2.15.14)
npm list element-ui
# element-ui@2.15.14
# 检查是否存在内嵌 lodash
ls node_modules/element-ui/lib/utils/lodash.js
# 存在 ✓
# 尝试安装旧版本 2.15.8
npm install element-ui@2.15.8
# 再次检查
ls node_modules/element-ui/lib/utils/lodash.js
# ls: cannot access 'node_modules/element-ui/lib/utils/lodash.js': No such file or directory
✅ 发现 : Element UI 2.15.8 及之前的版本不包含内嵌 lodash 文件!
阶段四:评估降级风险
对比版本差异
查询 Element UI 官方 GitHub Changelog,2.15.8 到 2.15.14 的主要变更:
| 版本 | 发布时间 | 主要变更 |
|---|---|---|
| 2.15.9 | 2022-06-02 | 修复 Table、FormItem、Cascader bug;优化 DatePicker & Cascader 下拉动画 |
| 2.15.10 | 2022-09-13 | DatePicker 新增 months 和 years type 支持;修复 Loading、Tree bug |
| 2.15.11 | 2022-11-15 | 新增 Statistics 组件 ;Progress 新增 defineBackColor、textColor 属性;Web Types 支持 |
| 2.15.12 | 2022-11-16 | 修复 Statistic 千分位 bug |
| 2.15.13 | 2023-02-12 | 修复 Statistics slot 显示 bug;Statistics 使用本地化 lodash |
| 2.15.14 | 2023-08-24 | Table 新增高亮选中行功能;优化 Statistics 文档和代码 |
检查项目是否使用新特性
根据 Changelog,2.15.9+ 版本的主要新特性包括:
- Statistics 组件(2.15.11 新增)
- DatePicker 的
months/yearstype(2.15.10 新增) - Progress 的
defineBackColor/textColor属性(2.15.11 新增)
方法一:Git Bash(推荐,Windows 安装 Git 后自带)
bash
# 检查是否使用了 Statistics 组件
grep -r "el-statistics\|el-statistic" src/ || echo "✓ 无结果 - 未使用 Statistics 组件"
# 检查 DatePicker 的 months/years 类型
grep -r 'type="months"\|type="years"' src/ || echo "✓ 无结果 - 未使用 DatePicker months/years 类型"
# 检查 Progress 的新属性
grep -r "defineBackColor\|textColor" src/ | grep -i progress || echo "✓ 无结果 - 未使用 Progress 新属性"
预期输出:
bash
✓ 无结果 - 未使用 Statistics 组件
✓ 无结果 - 未使用 DatePicker months/years 类型
✓ 无结果 - 未使用 Progress 新属性
方法二:Windows PowerShell
powershell
# 检查是否使用了 Statistics 组件
$result = Get-ChildItem -Path src -Recurse -Include *.vue,*.js | Select-String -Pattern "el-statistics|el-statistic"
if ($result) { $result } else { "✓ 无结果 - 未使用 Statistics 组件" }
# 检查 DatePicker 的 months/years 类型
$result = Get-ChildItem -Path src -Recurse -Include *.vue,*.js | Select-String -Pattern 'type="months"|type="years"'
if ($result) { $result } else { "✓ 无结果 - 未使用 DatePicker months/years 类型" }
# 检查 Progress 的新属性
$result = Get-ChildItem -Path src -Recurse -Include *.vue,*.js | Select-String -Pattern "defineBackColor|textColor"
if ($result) { $result } else { "✓ 无结果 - 未使用 Progress 新属性" }
预期输出:
bash
✓ 无结果 - 未使用 Statistics 组件
✓ 无结果 - 未使用 DatePicker months/years 类型
✓ 无结果 - 未使用 Progress 新属性
方法三:Windows CMD
cmd
REM 检查 Statistics 组件
findstr /s /i "el-statistics el-statistic" src\*.vue src\*.js >nul 2>&1 && echo 找到匹配 || echo ✓ 无结果 - 未使用 Statistics 组件
REM 检查 DatePicker 的 months/years 类型
findstr /s "type=\"months\" type=\"years\"" src\*.vue src\*.js >nul 2>&1 && echo 找到匹配 || echo ✓ 无结果 - 未使用 DatePicker months/years 类型
REM 检查 Progress 的新属性
findstr /s /i "defineBackColor textColor" src\*.vue src\*.js >nul 2>&1 && echo 找到匹配 || echo ✓ 无结果 - 未使用 Progress 新属性
预期输出:
bash
✓ 无结果 - 未使用 Statistics 组件
✓ 无结果 - 未使用 DatePicker months/years 类型
✓ 无结果 - 未使用 Progress 新属性
结论 : 所有检测均显示"无结果",说明项目未使用 任何 2.15.9+ 版本的新特性,降级到 2.15.8 无风险。
阶段五:实施降级方案
步骤 1: 修改 package.json
json
{
"dependencies": {
"element-ui": "2.15.8", // 从 2.15.14 降级
"lodash": "^4.17.21"
}
}
步骤 2: 清理并重新安装
bash
# 删除旧版本
rm -rf node_modules/element-ui
rm package-lock.json
# 重新安装(preinstall 钩子会自动执行)
npm install
输出:
shell
> bot-mb-manage@1.0.0 preinstall
> node scripts/force-resolutions.js
执行 npm-force-resolutions 修改 lock 文件...
✔ Applied resolutions!
added 1523 packages in 1m
步骤 3: 验证修复
bash
# 1. 验证 Element UI 版本
npm list element-ui
# element-ui@2.15.8 ✓
# 2. 验证 lodash 版本
npm list lodash
# 所有依赖均为 4.17.21 ✓
# 3. 验证内嵌文件不存在
ls node_modules/element-ui/lib/utils/lodash.js
# ls: cannot access '...': No such file or directory ✓
步骤 4: 构建测试
bash
npm run build
输出:
vbnet
...
Build complete. The dist directory is ready to be deployed.
✨ Done in 45.23s
✅ 构建成功,无错误!
最终验证
1. 本地验证
bash
# 检查依赖树
npm ls lodash
输出:
kotlin
bot-mb-manage@1.0.0 D:\bot\product\mb-manager-web
├─┬ element-ui@2.15.8
│ └── lodash@4.17.21 deduped
├── lodash@4.17.21
└─┬ crypto-js@4.1.1
└── lodash@4.17.21 deduped
✅ 所有 lodash 依赖统一为 4.17.21
2. 生产环境验证
- 部署构建产物到生产环境
- 使用 Wappalyzer 检测页面依赖
- 确认 lodash 版本显示为 4.17.21 ✓
- 功能测试通过 ✓
完整配置文件
package.json
json
{
"name": "bot-mb-manage",
"version": "1.0.0",
"scripts": {
"preinstall": "node scripts/force-resolutions.js",
"dev": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"element-ui": "2.15.8",
"lodash": "^4.17.21",
"vue": "^2.7.14"
},
"devDependencies": {
"npm-force-resolutions": "0.0.10",
"@vue/cli-service": "4.5.11"
},
"resolutions": {
"lodash": "4.17.21"
}
}
scripts/force-resolutions.js
javascript
const fs = require('fs');
const { execSync } = require('child_process');
const lockFile = './package-lock.json';
try {
if (!fs.existsSync(lockFile)) {
console.log('package-lock.json 不存在,先生成锁文件...');
execSync('npm install --package-lock-only', {
stdio: 'inherit',
env: { ...process.env, npm_config_ignore_scripts: 'true' }
});
}
console.log('执行 npm-force-resolutions 修改 lock 文件...');
execSync('npx npm-force-resolutions', { stdio: 'inherit' });
} catch (err) {
console.error('preinstall 脚本执行失败:', err);
process.exit(1);
}
其他可选方案
方案二:使用 patch-package 修补 Element UI(适合需要保持最新版本的场景)
如果项目必须使用 Element UI 2.15.9+ 的新特性,可以使用 patch-package 工具修补 node_modules 中的内嵌 lodash 文件。
什么是 patch-package?
patch-package 允许你修改 node_modules 中的第三方包,并生成补丁文件。每次 npm install 后会自动应用这些补丁。
完整操作步骤
步骤 1: 安装 patch-package
bash
npm install --save-dev patch-package postinstall-postinstall
说明:
patch-package: 核心工具,用于创建和应用补丁postinstall-postinstall: 确保补丁在npm install后正确应用(尤其是 Windows 环境)
步骤 2: 修改 package.json 添加 postinstall 脚本
json
{
"scripts": {
"preinstall": "node scripts/force-resolutions.js",
"postinstall": "patch-package",
"dev": "vue-cli-service serve",
"build": "vue-cli-service build"
}
}
工作原理 : 每次 npm install 完成后,会自动运行 patch-package 应用补丁文件。
步骤 3: 修改 Element UI 内嵌的 lodash 版本号
方法一:手动修改(推荐,精确控制)
bash
# 使用编辑器打开文件
code node_modules/element-ui/lib/utils/lodash.js
# 或使用 PowerShell 替换(仅修改版本号)
(Get-Content node_modules\element-ui\lib\utils\lodash.js) -replace "VERSION='4\.17\.10'","VERSION='4.17.21'" | Set-Content node_modules\element-ui\lib\utils\lodash.js
# 或使用 Git Bash 替换
sed -i "s/VERSION='4\.17\.10'/VERSION='4.17.21'/g" node_modules/element-ui/lib/utils/lodash.js
找到第 14 行附近:
javascript
/** Used as the semantic version number. */
var VERSION='4.17.10'; // ← 修改这里
改为:
javascript
/** Used as the semantic version number. */
var VERSION='4.17.21'; // ← 修改后
方法二:完全替换(更彻底,但需要测试)
如果想完全替换内嵌的 lodash,可以用项目中的 lodash 4.17.21 覆盖:
bash
# 备份原文件
cp node_modules/element-ui/lib/utils/lodash.js node_modules/element-ui/lib/utils/lodash.js.bak
# 复制项目的 lodash(需要先构建 lodash)
cp node_modules/lodash/lodash.js node_modules/element-ui/lib/utils/lodash.js
⚠️ 警告: 完全替换可能导致 Element UI 内部功能异常,建议只修改版本号。
步骤 4: 生成补丁文件
bash
npx patch-package element-ui
预期输出:
vbnet
patch-package 8.0.0
• Creating temporary folder
• Installing element-ui@2.15.14 with npm
• Diffing your files with clean files
✔ Created file patches/element-ui+2.15.14.patch
💡 element-ui is on GitHub! To draft an issue based on your patch run
npx patch-package element-ui --create-issue
这会在项目根目录创建 patches/element-ui+2.15.14.patch 文件。
步骤 5: 查看生成的补丁文件
bash
# Git Bash / Linux / macOS
cat patches/element-ui+2.15.14.patch
# Windows PowerShell
Get-Content patches\element-ui+2.15.14.patch
# Windows CMD
type patches\element-ui+2.15.14.patch
补丁文件内容示例:
diff
diff --git a/node_modules/element-ui/lib/utils/lodash.js b/node_modules/element-ui/lib/utils/lodash.js
index 1234567..abcdefg 100644
--- a/node_modules/element-ui/lib/utils/lodash.js
+++ b/node_modules/element-ui/lib/utils/lodash.js
@@ -11,7 +11,7 @@
var undefined;
/** Used as the semantic version number. */
- var VERSION='4.17.10';
+ var VERSION='4.17.21';
/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER = 9007199254740991;
步骤 6: 提交补丁文件到 Git
bash
git add patches/
git commit -m "fix: 添加 element-ui lodash 版本修复补丁"
重要: 补丁文件必须提交到版本控制系统,团队其他成员才能应用相同的修复。
步骤 7: 验证补丁生效
bash
# 删除 node_modules 测试自动应用
rm -rf node_modules
npm install
# 验证版本号已修改
# Git Bash
head -20 node_modules/element-ui/lib/utils/lodash.js | grep VERSION
# PowerShell
Get-Content node_modules\element-ui\lib\utils\lodash.js -Head 20 | Select-String VERSION
预期输出:
javascript
var VERSION='4.17.21'; // ✓ 已修改
步骤 8: 构建并验证
bash
npm run build
# 部署后使用 Wappalyzer 检测,应显示 lodash 4.17.21
补丁维护注意事项
1. Element UI 版本升级时
每次升级 Element UI 版本后,需要重新生成补丁:
bash
# 升级 Element UI
npm install element-ui@2.15.15
# 重新修改文件
# ... 按照步骤 3 修改 ...
# 删除旧补丁
rm patches/element-ui+2.15.14.patch
# 生成新补丁
npx patch-package element-ui
# 新补丁文件名: patches/element-ui+2.15.15.patch
2. 团队协作
新成员加入项目时:
bash
# 克隆项目后执行
npm install
# patch-package 会自动应用 patches/ 目录下的所有补丁
预期输出:
kotlin
> postinstall
> patch-package
patch-package 8.0.0
Applying patches...
element-ui@2.15.14 ✔
3. CI/CD 环境配置
确保 CI/CD 流程中 npm install 后会自动应用补丁:
yaml
# .github/workflows/build.yml 示例
steps:
- name: Install dependencies
run: npm ci # 或 npm install
# postinstall 钩子会自动运行 patch-package
- name: Verify patch applied
run: grep "VERSION='4.17.21'" node_modules/element-ui/lib/utils/lodash.js
方案优缺点总结
| 方面 | 说明 |
|---|---|
| 优点 | • 可以保持使用最新版 Element UI • 修改精确可控 • 团队成员自动同步修复 |
| 缺点 | • 需要维护补丁文件 • Element UI 每次升级都需重新生成补丁 • 补丁可能与新版本冲突 |
| 适用场景 | • 必须使用 Element UI 2.15.9+ 新特性 • 团队有完善的 CI/CD 流程 • 愿意承担补丁维护成本 |
常见问题
Q: 补丁文件丢失怎么办?
- 补丁文件在
patches/目录,必须提交到 Git - 如果丢失,按照步骤 3-4 重新生成即可
Q: npm install 后补丁没有应用?
- 检查 package.json 中是否有
"postinstall": "patch-package" - Windows 环境确保安装了
postinstall-postinstall - 手动运行
npx patch-package应用补丁
Q: Element UI 升级后报错?
- 新版本可能内部结构变化,补丁无法应用
- 需要重新修改文件并生成新补丁
- 或考虑降级 Element UI 到稳定版本
方案三:Webpack 插件替换(不推荐)
使用 NormalModuleReplacementPlugin 在构建时替换内嵌的 lodash:
javascript
// vue.config.js
const webpack = require('webpack');
module.exports = {
configureWebpack: {
plugins: [
new webpack.NormalModuleReplacementPlugin(
/element-ui[/\\]lib[/\\]utils[/\\]lodash/,
'lodash'
)
]
}
};
缺点:
- 可能破坏 Element UI 的内部逻辑
- 需要大量测试验证兼容性
- 不推荐使用
问题总结
问题演进
- 初始问题: Wappalyzer 检测到 lodash 4.17.10,但 package.json 声明的是 4.17.21
- 第一步尝试 : 配置
npm-force-resolutions统一嵌套依赖版本 - 问题持续: npm 依赖已统一,但 Wappalyzer 仍检测到旧版本
- 深度排查: 发现 Element UI 2.15.14 内嵌了完整的 lodash 4.17.10 源代码
- 最终解决: 降级到 Element UI 2.15.8(不包含内嵌 lodash)
根本原因
Element UI 2.15.14 在构建产物中直接嵌入了 lodash 4.17.10 的完整源代码(~497KB),而不是通过 npm 依赖管理。这导致:
- npm 工具无法检测和控制该版本
npm-force-resolutions无法修改内嵌代码- 最终构建产物包含已知漏洞的旧版本 lodash
解决方案对比
| 方案 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
| 降级 Element UI | 彻底解决问题,无需额外配置 | 无法使用 2.15.9+ 新特性 | ⭐⭐⭐⭐⭐ |
| Webpack 插件替换 | 保持最新版本 | 可能破坏 Element UI 内部逻辑 | ⭐⭐ |
| patch-package 修补 | 保持最新版本 | 需要维护补丁,升级复杂 | ⭐⭐ |
关键经验
- npm-force-resolutions 只能管理 npm 依赖,无法处理第三方库内嵌的代码
- Wappalyzer 检测的是最终构建产物,比 npm 依赖检查更准确
- 第三方库可能内嵌依赖,升级时需要检查构建产物内容
- 降级方案需要评估风险,通过 Changelog 和代码检索确认影响范围
预防措施
1. 定期安全检查
bash
# 检查 npm 依赖漏洞
npm audit
# 检查生产构建产物
# 使用 Wappalyzer 或 webpack-bundle-analyzer
npm run build --report
2. 依赖升级审查流程
- 查阅 Changelog,了解变更内容
- 检查是否引入内嵌依赖(特别是 lib/utils 目录)
- 在测试环境充分验证后再部署生产
3. 锁定关键依赖版本
json
{
"dependencies": {
"element-ui": "2.15.8", // 不使用 ^,锁定版本
"lodash": "4.17.21" // 锁定版本,避免意外升级
}
}
4. 监控构建产物大小
异常的文件大小增长可能意味着内嵌了不必要的依赖。
参考命令
依赖检查
bash
# 查看某个包的所有版本
npm ls <package-name>
# 查看依赖树(显示嵌套关系)
npm ls --all
# 查找 node_modules 中的文件
find node_modules -name "*.js" -path "*/<keyword>/*"
# 搜索文件内容
grep -r "关键词" node_modules/<package-name>/
清理与重装
bash
# 完全清理
rm -rf node_modules package-lock.json
# 清理 npm 缓存
npm cache clean --force
# 重新安装
npm install
Git Bash vs PowerShell
| 命令 | Git Bash | PowerShell |
|---|---|---|
| 查找文件 | find . -name "*.js" |
Get-ChildItem -Recurse -Filter "*.js" |
| 搜索内容 | grep -r "keyword" src/ |
Select-String -Path src\* -Pattern "keyword" -Recurse |
| 检查文件存在 | ls file.js |
Test-Path file.js |
文档生成时间 : 2025-11-19 问题状态 : ✅ 已解决 验证状态: ✅ 生产环境验证通过