flawopen.com/Incidents/windows-update-stack-symlink-eop-september-2026
CVE-2026-81963:Windows Update 链接权限提升
CVE-2026-81963 是 Windows Update 中已遭利用的链接解析漏洞。了解受影响版本、微软修复及安全的文件处理方式。[1], [2]
一名持万能钥匙的夜班门卫按墙上的箭头把包裹送到“走廊尽头的储藏室”,从不核对自己到底进了哪间屋。租户可以随意挪动这些箭头。有个租户把指向储藏室的箭头转向经理办公室,门卫的钥匙能开任何门,于是他走进去,把包裹放到经理桌上。那扇门租户自己永远打不开,是门卫的钥匙替他打开了。修复办法是:门卫核对自己真正打开的那扇门上的门牌号,不再跟随租户能挪动的箭头。[5], [9]
核心概念与专有名词
Windows 更新堆栈- Windows 中负责扫描、下载、暂存和安装更新的组件。它们以 SYSTEM 身份运行,因为安装更新需要写入操作系统的受保护区域。[1]
SYSTEM(NT AUTHORITY\SYSTEM)- Windows 服务运行时所用的本地账户。在本机上它的权限高于管理员,因此它执行的文件操作几乎从不被拒绝。[1]
联接 / 符号链接(重解析点)- NTFS 对象,能让一个路径解析到另一个位置。标准用户可以在自己控制的文件夹里创建联接,把该文件夹名重定向到自己无法写入的目录。[9]
FILE_FLAG_OPEN_REPARSE_POINT- CreateFileW 的一个标志:当路径的最后一个组件是重解析点时,打开该重解析点本身,而不是跟随最后这一级链接。它本身不会阻止前面路径组件中的重解析点被处理。[6]
TOCTOU(CWE-367)- 检查时间与使用时间之差:文件系统在检查和依赖该检查的操作之间发生变化。先检查路径字符串、再按名称打开它,就留下了这个时间窗口。[12]
框架映射
- 本指南所述弱点的 CWE 参考: CWE-59 CWE-284
- CISA KEV — 收录日期 2026-09-08 — CVE-2026-81963
事件演进时间线
微软保留 CVE-2026-81963 编号。[2]
9 月补丁星期二:微软发布修复,并将该漏洞标记为“Exploitation Detected”且未公开披露。[1]
CISA 将 CVE-2026-81963 列入 Known Exploited Vulnerabilities 目录,要求美国联邦机构在 2026 年 9 月 22 日前修复。[4]
微软发布该公告的修订版。[1]
攻击执行流程分解
SYSTEM 服务在用户可修改的路径中工作
更新堆栈组件以 SYSTEM 身份运行,处理暂存目录和工作目录中的文件。只要标准用户能在该路径上的任意位置创建、删除或重命名条目,就由他决定路径解析到哪里。微软未说明本次受影响的是哪条路径。[1]
源代码对比:漏洞与安全实现
Microsoft 尚未公开受影响函数或补丁。此防御示例不是 Microsoft 源码。仅检查对象所有者并不足够:对象所有权不能证明其 DACL 会拒绝标准用户的写入权限。此模式假设安装程序以显式 DACL 创建固定的暂存路径及其所有父目录,并且只允许 SYSTEM 和 Administrators 写入。该函数不会读取或验证 DACL;由安装程序预先配置权限是部署前提。代码验证已打开的目录句柄,然后通过 NtCreateFile 和 FILE_CREATE 相对于该句柄创建单个子文件名;不会重新打开拼接出的路径。请在受支持的部署环境中测试 DACL 和文件系统行为。安装 Microsoft 更新以修复 CVE-2026-81963。[5], [6], [7], [8], [9], [10]
源码摘录: Windows 路径处理示例;不是 Microsoft 源码
/* 示例仅用于说明,并非 Microsoft 源码:Microsoft 未公开受影响函数。以 SYSTEM 运行的服务会向普通用户可修改的暂存目录写入文件。 */
BOOL WriteStagedFile(LPCWSTR stagingDir, LPCWSTR name,
const BYTE *data, DWORD size)
{
WCHAR path[MAX_PATH];
if (FAILED(StringCchPrintfW(path, MAX_PATH, L"%s\\%s", stagingDir, name)))
return FALSE;
/* CWE-59:CreateFileW 会解析路径各级组件中的 junction 和符号链接;访问检查使用 SYSTEM 令牌。若用户将 stagingDir
* 替换为 junction,写入就会落到其目标位置。
*/
HANDLE h = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE)
return FALSE;
DWORD written = 0;
BOOL ok = WriteFile(h, data, size, &written, NULL) && written == size;
CloseHandle(h);
return ok;
}/* 这是防御模式示例,并非 Microsoft 源码或补丁。安装程序必须创建此路径及所有父目录,并设置显式 DACL,仅允许 SYSTEM 和 Administrators
* 写入。
*/
#include <windows.h>
#include <winternl.h>
static const WCHAR STAGING_DIR[] =
L"C:\\ProgramData\\Vendor\\Update\\Staging";
BOOL WriteStagedFile(LPCWSTR name, const BYTE *data, DWORD size)
{
if (name == NULL || data == NULL || name[0] == L'\0' ||
wcspbrk(name, L"\\/:") != NULL || wcscmp(name, L".") == 0 ||
wcscmp(name, L"..") == 0)
return FALSE;
/* 固定目录对象;不授予 FILE_SHARE_DELETE 可阻止重命名。 */
HANDLE dir = CreateFileW(STAGING_DIR,
FILE_LIST_DIRECTORY | FILE_TRAVERSE | FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS |
FILE_FLAG_OPEN_REPARSE_POINT,
NULL);
if (dir == INVALID_HANDLE_VALUE)
return FALSE;
FILE_ATTRIBUTE_TAG_INFO tag;
WCHAR actual[MAX_PATH];
BOOL ok = FALSE;
DWORD n;
if (!GetFileInformationByHandleEx(dir, FileAttributeTagInfo,
&tag, sizeof tag) ||
(tag.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
goto done;
n = GetFinalPathNameByHandleW(dir, actual, MAX_PATH,
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
if (n == 0 || n >= MAX_PATH ||
_wcsicmp(actual, L"\\\\?\\C:\\ProgramData\\Vendor\\Update\\Staging") != 0)
goto done;
/* 相对于此句柄打开新子项,切勿重新拼接路径。 */
size_t nameChars = wcslen(name);
if (nameChars == 0 || nameChars > 255)
goto done;
USHORT nameBytes = (USHORT)(nameChars * sizeof(WCHAR));
UNICODE_STRING relativeName = { nameBytes, nameBytes, (PWSTR)name };
OBJECT_ATTRIBUTES oa = {0};
oa.Length = sizeof oa;
oa.RootDirectory = dir;
oa.ObjectName = &relativeName;
oa.Attributes = OBJ_CASE_INSENSITIVE;
IO_STATUS_BLOCK iosb = {0};
HANDLE file = INVALID_HANDLE_VALUE;
NTSTATUS status = NtCreateFile(
&file, FILE_WRITE_DATA | SYNCHRONIZE, &oa, &iosb, NULL,
FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE,
FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, NULL, 0);
if (!NT_SUCCESS(status))
goto done; /* 如果条目已存在,FILE_CREATE 会失败。 */
DWORD written = 0;
ok = WriteFile(file, data, size, &written, NULL) && written == size;
CloseHandle(file);
done:
CloseHandle(dir);
return ok;
}工程与系统安全加固清单
- ✓安装 2026 年 9 月的累积更新:KB5124008(Windows 11 24H2 和 25H2)、KB5122880(23H2)、KB5124012(26H1)或 KB5122871(Server 2025)。修复后的版本为 10.0.26100.9445、10.0.26200.9445、10.0.22631.7582、10.0.28000.2954,Server 2025 为 10.0.26100.33438。[1]
- ✓在每个以 SYSTEM 或管理员身份运行的服务中,列出所有路径位于 %ProgramData%、%TEMP%、C:\Users 或其他用户可写位置的 CreateFileW、DeleteFileW、MoveFileExW、CopyFileW 和 SetFileSecurityW 调用,并要求逐一评审。
- ✓用 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT 打开目录,拒绝设置了 FILE_ATTRIBUTE_REPARSE_POINT 的目录,并在其中操作期间保持句柄打开且不带 FILE_SHARE_DELETE。[6], [9]
- ✓写入前在打开的句柄上用 GetFinalPathNameByHandleW 确认目标;绝不要信任自己拼接的路径字符串。[7]
- ✓在共享目录中使用 CREATE_NEW(或 NtCreateFile 的 FILE_CREATE)而非 CREATE_ALWAYS;若同名文件或链接已存在,操作应失败而不是覆盖它。[8]
- ✓为服务的工作目录设置明确的 DACL,只授予 SYSTEM 和 Administrators 写权限;如果 icacls 显示 Users 或 Authenticated Users 拥有写权限,就让安装程序测试失败。[10]
- ✓当服务代表调用方执行文件操作时,模拟该调用方(RpcImpersonateClient、ImpersonateNamedPipeClient),使访问按其令牌而非 SYSTEM 令牌进行检查。
- ✓对标准用户进程在 %ProgramData% 或 %windir% 下创建联接或挂载点的行为发出告警;正常使用中这很罕见,而链接跟随攻击正是这样开始的。
- ✓使用
NtCreateFile和FILE_CREATE相对于已验证的目录句柄创建子文件;检查目录后,不要再通过拼接路径重新打开stagingDir\name。[8]
参考来源
- 微软安全响应中心 — CVE-2026-81963,Windows 更新堆栈权限提升漏洞
- CVE-2026-81963 — CVE 记录
- NVD — CVE-2026-81963
- CISA — Known Exploited Vulnerabilities 目录
- MITRE CWE-59:文件访问前的链接解析不当
- Microsoft Learn — CreateFileW
- Microsoft Learn — GetFinalPathNameByHandleW
- Microsoft Learn — NtCreateFile (directory-relative names)
- Microsoft Learn — Reparse points and file operations
- Microsoft Learn — GetSecurityInfo (owner and DACL)
- MITRE CWE-284: Improper Access Control
- MITRE CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition