CVE-2026-83991 是一个 Windows 云文件篡改漏洞,其公布的受影响范围始于 Windows 10 版本 1809,距 2026 年 9 月的修复已近八年。
我请求 Windows 打开一个文件进行写入。它回答 ERROR_ACCESS_DENIED。
我请求 Windows 删除同一个文件。再次回答 ERROR_ACCESS_DENIED。
然后我请求云文件栈取代该文件名。它返回 S_OK,并将现有文件更改为云文件占位符。
文件已经拒绝了两次。取代路径听到的却更像是“请继续”。
这个授权分裂就是 CVE-2026-83991。微软将其称为 Windows 云文件迷你筛选器驱动程序篡改漏洞,评级为 重要,并分配了 CVSS 3.1 基础评分 5.5 中等。官方向量为 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N/E:U/RL:O/RC:C。
概念验证创建了一个新目录和一个普通文件。它赋予调用者对目录的写入访问权限,然后对文件应用受保护的 DACL,仅授予调用者读取访问权限,而不授予普通的写入或删除访问权限。
使用一个受限的中等完整性线程令牌,它证明了以下序列:
| 操作 | 结果 |
|---|---|
以 GENERIC_WRITE 打开现有文件 | ERROR_ACCESS_DENIED |
使用 DeleteFileW 删除它 | ERROR_ACCESS_DENIED |
以 GENERIC_READ 打开它 | 成功 |
| 将父目录注册并连接为同步根 | S_OK |
以取代语义调用 CfCreatePlaceholders | S_OK |
| 检查生成的条目 | 云重解析点 |
该调用处理了一个条目,返回了非零的创建 USN,并赋予文件 IO_REPARSE_TAG_CLOUD 标签,值为 0x9000001A。在复现的运行中,调用前后的文件 ID 完全相同。同一个 NTFS 文件在状态变为云占位符时得以保留。
Windows 10 版本 1709 引入了 云文件 API。它为桌面同步引擎提供了一种受支持的方式来注册目录树、创建占位符条目,并在需要时实现其内容的水合。
这里涉及三个部分:
CldApi.dll 公开了用户模式的云筛选器 API。cldflt.sys 是存储路径核心的文件系统迷你筛选器。云占位符使用重解析点。重解析点是一种带有标签和相关数据的文件系统机制。它是一个比符号链接更广泛的概念,两者不应被视为同义词。
相关 API 是 CfCreatePlaceholders。它在已注册的同步根下创建一个或多个占位符文件或目录。微软的文档说明调用者必须对基础目录具有 WRITE_DATA 或 WRITE_DAC 访问权限。
逐条目标志 CF_PLACEHOLDER_CREATE_FLAG_SUPERSEDE,值为 0x4,为现有占位符提供覆盖语义。此 CVE 中有趣的细节在于,易受攻击的路径也接受普通的现有文件并将其转换为占位符。
该实验将目录上的权限与现有文件上的权限分开。
父目录授予当前用户:
FILE_GENERIC_READ
FILE_GENERIC_WRITE
FILE_GENERIC_EXECUTE
DELETE
对于目录,FILE_GENERIC_WRITE 包含 FILE_WRITE_DATA,也称为 FILE_ADD_FILE。这足以满足 CfRegisterSyncRoot 和 CfCreatePlaceholders 使用的文档化基础目录要求。
父级 ACE 不授予 FILE_DELETE_CHILD。其 DELETE 位适用于父对象本身。这一区别很重要,因为 DeleteFileW 要求对目标文件具有 DELETE 权限,或对其父目录具有 FILE_DELETE_CHILD 权限。
现有叶仅授予当前用户 FILE_GENERIC_READ。其 DACL 标记为受 PROTECTED_DACL_SECURITY_INFORMATION 保护,因此不继承父级的 ACE。
这引出了核心问题:
在目录中创建新子项的权限是否也授权对已存在的、限制更严格的子项执行改变状态的取代操作?
普通文件访问回答否。易受攻击的云文件路径回答是。
微软的文件安全文档解释了为何预期存在这一区别。对文件的访问通常由该文件的安全描述符控制。父级的描述符通常不会取代子项的访问检查,除非涉及继承和 FILE_DELETE_CHILD 等特定规则。
当设置以某个身份运行而关键操作以另一个身份运行时,访问控制演示就会变得缺乏说服力。此 PoC 避免了这个问题。
它从当前进程令牌派生一个受限令牌,使 Administrators SID 不存在或仅为拒绝,设置中等完整性,以 SecurityImpersonation 复制一个模拟令牌,并通过 SetThreadToken 将其安装到当前线程上。
确切的权限措辞需要谨慎对待。CreateRestrictedToken 配合 DISABLE_MAX_PRIVILEGE 会禁用除 SeChangeNotifyPrivilege 之外的所有权限。该剩余权限绕过了一些目录遍历检查。它不授予文件数据写入或删除权限。
随后 PoC 在同一个线程模拟令牌保持激活的状态下执行设置、控制检查、同步根注册、连接和取代调用。在“访问被拒绝”和 S_OK 之间没有便捷的身份切换。
完整源代码位于 main_poc.c,build.bat 用于 GCC 或微软的 C 编译器。
程序在当前用户的本地应用程序数据目录下创建一个以 GUID 命名的新测试目录。可以提供可选路径,但程序拒绝使用已存在的路径。
这一限制是刻意的。PoC 针对它为自己创建的数据演示该漏洞。它不需要安装第三方同步提供程序,也不针对现有的同步根。
程序创建 protected_existing.bin,写入已知的测试数据,并赋予其普通文件属性。它记录文件的基本元数据、逻辑大小、属性和文件 ID。
在云文件调用之前,该文件不是重解析点。
程序将叶 DACL 替换为三个非继承的允许 ACE:
| 主体 | 叶权限 |
|---|---|
SYSTEM | 完全控制 |
Administrators | 完全控制 |
| 当前用户 | FILE_GENERIC_READ |
由于有效令牌中的 Administrators SID 不存在或仅为拒绝,Administrators 允许 ACE 无法向线程授予管理员访问权限。
随后 PoC 执行三项控制检查。GENERIC_WRITE 以错误 5 失败,DeleteFileW 以错误 5 失败,而 GENERIC_READ 成功。它还确认 DACL 受到保护。
这些控制检查证明了两个普通操作所使用的权限。PoC 不测试 WRITE_DAC 或其合成测试文件的所有者可能影响该文件的每一种可能方式。所演示的差异具体在于被拒绝的写入和删除操作与成功的云文件取代操作之间。
PoC 以其新的目录注册渐进式水合策略和完整填充策略。它提供一个新的 GUID 作为提供程序和根标识。
然后它以仅包含终止性 CF_CALLBACK_NONE 条目的回调表调用 CfConnectSyncRoot。对于正在测试的元数据转换,不需要水合回调。
占位符条目保留原始文件的时间戳和逻辑大小,提供 GUID 文件标识,并设置映射到官方取代标志的本地常量:
#define CF_CREATE_SUPERSEDE 0x00000004UL
placeholder.RelativeFileName = leaf_name;
placeholder.FsMetadata.BasicInfo = basic_info;
placeholder.FsMetadata.FileSize = standard_info.EndOfFile;
placeholder.FileIdentity = &run_id;
placeholder.FileIdentityLength = sizeof(run_id);
placeholder.Flags = CF_CREATE_SUPERSEDE;
create_hr = cf.Create(root, &placeholder, 1, 0, &processed);
源代码动态加载 CldApi.dll 并在运行时解析公共函数。其手工声明的结构仅覆盖此测试所需的 ABI。
批处理 API 可以处理失败的条目。微软的文档明确说明 EntriesProcessed 包含失败的条目,因此值为一本身并不能确定成功。
因此 PoC 在打印 CONFIRMED 并以代码零退出之前要求满足以下所有条件:
CfCreatePlaceholders 恰好返回 S_OK。S_OK。FILE_ATTRIBUTE_REPARSE_POINT。FSCTL_GET_REPARSE_POINT 返回 IO_REPARSE_TAG_CLOUD。直接写入、删除、读取、DACL 和既有文件检查也必须在程序早期通过,否则执行会在云文件操作之前停止。
复现的状态转换是:
0x00000020 是 FILE_ATTRIBUTE_ARCHIVE。生成的 0x00401600 掩码包含 FILE_ATTRIBUTE_SPARSE_FILE、FILE_ATTRIBUTE_REPARSE_POINT、FILE_ATTRIBUTE_OFFLINE 和 FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS。
未更改的文件 ID 尤其有用。它表明在此次运行中,结果不仅仅是出现在同一路径名下的不同文件。现有文件对象在获取云文件状态时得以保留。
程序保留逻辑文件大小,但不在转换后验证原始字节。此 PoC 不提出关于任意内容控制的任何主张。
可观察的失败可以在不虚构内部调用栈的情况下陈述:
CfCreatePlaceholders 接受对该叶的取代请求。此行为与取代路径上缺少或未完成对现有叶的检查一致。它不指明负责的确切内部函数、分支、IRP 或内核回调。PoC 不包含内核调试或二进制差异分析,因此解释止步于外部验证的边界。
微软分配了 CWE-306,关键功能缺少身份验证。在 Windows 对象层面,该实验暴露了不一致的授权执行。我在元数据中使用微软的官方 CWE,并在技术分析中描述观察到的访问控制行为。
PoC 演示了所测试的直接操作无法执行的完整性和文件状态更改。具有所需基础目录访问权限的本地低权限调用者可以通过受影响路径使现有的只读叶成为云文件占位符。
微软的安全公告给出了更广泛的产品影响:攻击者可以对受保护的系统数据进行未经授权的更改,并超出正常权限更改系统状态或配置。这是微软对该漏洞的评估。孤立的 PoC 不选择受保护的系统目标,也不演示完整的利用后链。
CVSS 向量反映了同样广泛的形态:
谨慎的回答是 在微软公布的受影响范围内将近八年。
官方 CVE 记录将最旧的受影响分支起始于 Windows 10 版本 1809 和 Windows Server 2019 的 10.0.17763.0。微软的 Windows 10 发布历史 将版本 1809 的第一个构建 17763.1 列为 2018 年 10 月 2 日。微软于 2026 年 9 月 8 日发布了修复。
这使得公开范围内最早确认的时间点距修复不到八年。
云文件 API 本身于 2017 年随 Windows 10 版本 1709 推出,API 文档将 1709 列为最低支持的客户端。这并不能证明该漏洞存在于 1709 中。微软的 CVE 记录未列出 1709,本研究也未对其进行测试。API 的生日并不自动是漏洞的生日,无论标题多么吸引人。
在 NTFS 上使用受影响的测试系统。从普通的、非提升的命令提示符运行 PoC。
build.bat
main_poc.exe
构建脚本在可用时使用 MinGW-w64 GCC,否则回退到微软的 C 编译器。程序创建并注册自己的全新测试根。它在清理期间取消注册并断开连接,然后保留测试目录以供检查。
可选的路径形式是:
main_poc.exe C:\path\to\a\new-test-directory
提供的路径必须不存在。保持测试隔离,并在收集结果后删除其目录。
在已修复的系统上,完整条件不应达到 CONFIRMED。不要仅从一行控制台输出推断补丁状态。请综合检查整体结果、退出代码、逐条目结果、USN、属性和重解析标签。
最有趣的 Windows 漏洞往往是对访问检查究竟针对哪个对象的争论。
在这里,在目录下创建的权限到达了一条路径,该路径可以转换其中已存在的、限制更严格的子项。普通文件 API 尊重该子项的当前 DACL。云文件取代没有保持相同的边界。
不需要壮观的内存损坏。一个对象说不,另一个对象提供了足够的权限来继续,一个熟悉的文件名悄然变成了别的东西。
这就是全部技巧。这也是该技巧之所以重要的原因。
| 属性 | 之前 | 之后 |
|---|
| 属性 | 0x00000020 | 0x00401600 |
| 重解析点 | 否 | 是 |
| 重解析标签 | 无 | 0x9000001A |
| 条目结果 | 不适用 | S_OK |
| 创建 USN | 不适用 | 非零 |
| 文件 ID | 已记录 | 未更改 |