一个最小化的、故意存在漏洞的 Node.js/Express 应用程序,用于端到端地演示 Seal Security 如何通过将易受攻击的依赖替换为 密封的(回移植、即插即用)版本来修复已知的 CVE——而 无需更改声明的版本范围或代码。
它被设计为 Seal CLI 在 CI/CD 中的端到端冒烟测试:运行应用、触发真实漏洞、运行 Seal,然后观察同一漏洞被阻止。
| 生态系统 | JavaScript / npm |
| 易受攻击的包 | [email protected](解析为 2.7.4) |
| CVE | CVE‑2022‑29078 — EJS 服务端模板注入 → 远程代码执行 (CVSS 9.8) |
| 密封(已修复)版本 | 来自 Seal 的 npm 注册表的 ejs 2.7.4-sp1 |
| 集成方式 | Seal CLI 作为一个构建步骤 — 同时展示 GitHub Actions 和 Jenkins |
该应用还包含了其他著名的易受攻击依赖项(lodash 4.17.5、json5 0.5.1、got 6.7.1),Seal 也会将每个依赖修复为密封版本。
该应用将整个 URL 查询字符串直接传入 EJS 的渲染调用:
const data = { name: 'World', ...req.query };
res.render('page', data, ...)
EJS 接受一个 settings['view options'] 对象,其 outputFunctionName 值会被未经消毒地写入编译后的模板函数体中。因此,攻击者可以注入任意 JavaScript,这些脚本将以 Node.js 进程的权限在服务器上运行。
正常请求
/?name=alice
渲染出 Hello alice!。
漏洞利用请求
/?name=Hacker&settings[view%20options][outputFunctionName]=x;setTimeout(function()%7Bprocess.exit(1)%7D,3000);s
服务器执行 setTimeout(function(){ process.exit(1) }, 3000)。页面首先加载并明确指出 RCE 成功;几秒后重新加载将会收到 ERR_CONNECTION_REFUSED — 注入的代码杀死了服务器,证明任意代码得以执行。
3 秒延迟是有意为之:它允许响应在进程退出前到达浏览器,这样你会看到“漏洞利用成功”页面,然后是一个干净的崩溃,而不是冻结的标签页。
.
├── index.js # 存在漏洞的 Express 应用
├── views/ # EJS 模板
├── package.json / package-lock.json
├── Jenkinsfile # 包含 Seal 阶段的示例 Jenkins(Groovy)管道
└── .github/workflows/
├── build-and-run.yml # 构建并暴露应用以供浏览器测试
└── seal-security.yml # 运行 Seal 修复,然后启动应用
Seal 是 SaaS、Seal 托管的 — 无需在你的环境中安装任何内容,所有流量仅为 出站 HTTPS,TCP 443 端口。要运行修复,你需要:
| 密钥/凭据 | 用途 | 存放位置 |
|---|---|---|
| Seal token |
在 Settings → Secrets and variables → Actions(GitHub)或 Manage Jenkins → Credentials(Jenkins)中配置这些。切勿将令牌提交到仓库。
将以下 Seal 主机加入出站 443 端口白名单:app.sealsecurity.io、authorization.sealsecurity.io、cli.sealsecurity.io,以及用于密封 npm 包的 npm.sealsecurity.io。CLI 二进制文件从 github.com / objects.githubusercontent.com 下载。
npm install
npm start # → http://localhost:3001
打开 http://localhost:3001/?name=alice(正常),然后使用上面的漏洞利用 URL(服务器崩溃)。
Seal CLI 作为一个额外的步骤运行,在 npm install 之后、打包之前。它会扫描解析后的依赖,并将易受攻击的依赖重写为它们的密封版本,使用远程修复模式(策略在 Seal UI 中集中管理)。
- uses: seal-community/cli-action@latest
with:
mode: fix
fix_mode: remote
token: ${{ secrets.SEAL_TOKEN }}
target: package-lock.json # 该生态系统的锁定文件
通过 Actions → “Seal Security Remediation” → Run workflow 运行。参见 .github/workflows/seal-security.yml。
一个单一的添加阶段,在安装之后、打包之前。参见 Jenkinsfile:
stage('Seal') {
steps {
sh '''
curl -fsSL https://github.com/seal-community/cli/releases/download/latest/seal-linux-amd64-latest -o seal
chmod +x seal
./seal fix --mode remote "$SEAL_MANIFEST" # SEAL_MANIFEST=package-lock.json
'''
}
}
SEAL_TOKEN 来自 seal-token Jenkins 凭据;将 SEAL_PROJECT 设置为你的 Seal 项目 ID。
seal fix 之后,易受攻击的依赖解析为来自 Seal 注册表的密封版本——你的 package.json 版本范围保持不变:
密封版本是同一个包,只是回移植了安全修复,因此它是即插即用的替代品——无需更改代码,无需主版本升级。
对修复后的应用重新运行漏洞利用 URL。注入不再执行:密封的 ejs 会拒绝恶意的 outputFunctionName,应用会返回 “Invalid parameter” 而不是执行 payload。服务器保持运行。
seal fix 指向特定的清单/锁定文件——对于 npm 是 package-lock.json。对于包含多个清单的仓库,每个清单运行一次 seal fix。这就是整个集成——一个阶段,仅出站,无需更改应用程序代码。
| 认证 Seal CLI |
GitHub Actions 密钥 SEAL_TOKEN / Jenkins “秘密文本”凭据 seal-token |
| ngrok token (可选) | 将运行中的应用暴露给浏览器进行测试 | GitHub Actions 密钥 NGROK_TOKEN |
| 依赖 | 之前 | 之后(密封版) |
|---|
| ejs | 2.7.4 | 2.7.4‑sp1 |
| lodash | 4.17.5 | 4.17.5‑sp1 |
| json5 | 0.5.1 | 0.5.1‑sp1 |
| got | 6.7.1 | 6.7.1‑sp1 |