FlawOpen

flawopen.com/Incidents/windows-update-stack-symlink-eop-september-2026

● Microsoft · CVE-2026-81963 · September 2026 · CVSS 7.8 · 高危
安全研究 · FlawOpen

CVE-2026-81963:Windows Update 链接权限提升

CVE-2026-81963 是 Windows Update 中已遭利用的链接解析漏洞。了解受影响版本、微软修复及安全的文件处理方式。[1], [2]

💡 通俗易懂的原理解析 (ELI5)

一名持万能钥匙的夜班门卫按墙上的箭头把包裹送到“走廊尽头的储藏室”,从不核对自己到底进了哪间屋。租户可以随意挪动这些箭头。有个租户把指向储藏室的箭头转向经理办公室,门卫的钥匙能开任何门,于是他走进去,把包裹放到经理桌上。那扇门租户自己永远打不开,是门卫的钥匙替他打开了。修复办法是:门卫核对自己真正打开的那扇门上的门牌号,不再跟随租户能挪动的箭头。[5], [9]

核心概念与专有名词

Windows 更新堆栈
Windows 中负责扫描、下载、暂存和安装更新的组件。它们以 SYSTEM 身份运行,因为安装更新需要写入操作系统的受保护区域。[1]
SYSTEM(NT AUTHORITY\SYSTEM)
Windows 服务运行时所用的本地账户。在本机上它的权限高于管理员,因此它执行的文件操作几乎从不被拒绝。[1]
联接 / 符号链接(重解析点)
NTFS 对象,能让一个路径解析到另一个位置。标准用户可以在自己控制的文件夹里创建联接,把该文件夹名重定向到自己无法写入的目录。[9]
链接跟随(CWE-59)
程序按名称打开、写入、移动或删除文件,却不核实该名称是否解析到预期位置,于是他人放置的链接改变了操作的落点。[5], [2]
FILE_FLAG_OPEN_REPARSE_POINT
CreateFileW 的一个标志:当路径的最后一个组件是重解析点时,打开该重解析点本身,而不是跟随最后这一级链接。它本身不会阻止前面路径组件中的重解析点被处理。[6]
TOCTOU(CWE-367)
检查时间与使用时间之差:文件系统在检查和依赖该检查的操作之间发生变化。先检查路径字符串、再按名称打开它,就留下了这个时间窗口。[12]
CWE-284(访问控制不当)
Microsoft 为此 CVE 同时指定了 CWE-59 和 CWE-284。CWE-59 指明具体的链接解析缺陷。CWE-284 是宽泛的 Pillar CWE,MITRE 明确不建议将其用于映射现实世界中的漏洞。应将其理解为 Microsoft 对同一问题的更宽泛分类,而不是存在第二个漏洞的证据。[2], [11], [5]

框架映射

事件演进时间线

2026-08-27

微软保留 CVE-2026-81963 编号。[2]

2026-09-08

9 月补丁星期二:微软发布修复,并将该漏洞标记为“Exploitation Detected”且未公开披露。[1]

2026-09-08

CISA 将 CVE-2026-81963 列入 Known Exploited Vulnerabilities 目录,要求美国联邦机构在 2026 年 9 月 22 日前修复。[4]

2026-09-15

微软发布该公告的修订版。[1]

攻击执行流程分解

步骤 1

SYSTEM 服务在用户可修改的路径中工作

更新堆栈组件以 SYSTEM 身份运行,处理暂存目录和工作目录中的文件。只要标准用户能在该路径上的任意位置创建、删除或重命名条目,就由他决定路径解析到哪里。微软未说明本次受影响的是哪条路径。[1]

步骤 2

路径被重定向

联接或符号链接使一个目录名解析到别处,例如受保护的系统文件夹。在用户自己控制的文件夹里创建联接无需管理员权限。[5], [9]

步骤 3

服务跟随链接

Windows 打开路径时通常会解析重解析点。FILE_FLAG_OPEN_REPARSE_POINT 作用于最后一个组件;前面的组件仍可能被解析。如果服务随后通过未经验证的路径写入文件,访问检查使用的是服务的 SYSTEM 令牌。应验证目录句柄,并相对于该句柄创建子文件。[6], [8]

步骤 4

受保护位置被改动

服务在只应由 SYSTEM 或管理员掌控的位置创建、覆盖、移动或删除文件。微软的常见问题确认,利用该漏洞可使攻击者获得 SYSTEM 权限。[1], [2]

源代码对比:漏洞与安全实现

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;
}

工程与系统安全加固清单

参考来源