flawopen.com/Incidents/windows-update-stack-symlink-eop-september-2026
CVE-2026-81963: Windows Update Symlink Flaw
CVE-2026-81963 is an exploited Windows Update link-resolution flaw. See affected builds, Microsoft’s fix, and safe service file handling.[1], [2]
A night porter with a master key delivers parcels to "the storeroom at the end of the corridor", following the arrows painted on the walls without checking where he ends up. Tenants are allowed to move those arrows. One tenant turns the storeroom arrow to point at the manager's office, and the porter, whose key opens every door, walks in and leaves the parcel on the manager's desk. The tenant could never open that door; the porter's key did it for them. The fix is for the porter to read the number on the door he actually opened, and to stop following arrows that tenants can move.[5], [9]
Core Concepts & Subsystem Terms
Windows Update Stack- The Windows components that scan for, download, stage and install updates. They run as SYSTEM because installing updates means writing to protected parts of the operating system.[1]
SYSTEM (NT AUTHORITY\SYSTEM)- The local account Windows services run as. It has more rights than an administrator on the local machine, so any file operation it performs is almost never refused.[1]
Junction / symbolic link (reparse point)- NTFS objects that make one path resolve to another. A standard user can create a junction in a folder they control, redirecting that folder name to a directory they cannot write to.[9]
Link following (CWE-59)- A program opens, writes, moves or deletes a file by name without checking that the name resolves where it expects, so a link planted by someone else redirects the operation.[5], [2]
FILE_FLAG_OPEN_REPARSE_POINT- A CreateFileW flag that opens the final path component itself when it is a reparse point, rather than following that final link. It does not by itself prevent reparse processing in earlier path components.[6]
TOCTOU (CWE-367)- Time-of-check to time-of-use: the file system changes between a check and the operation that relies on it. Checking a path string and then opening it by name leaves such a window.[12]
CWE-284 (Improper Access Control)- Microsoft assigns both
CWE-59andCWE-284to this CVE.CWE-59names the specific link-resolution weakness.CWE-284is a broad Pillar CWE that MITRE explicitly discourages for mapping real-world vulnerabilities. Treat it as Microsoft’s broader classification of this same reported issue, not evidence of a second bug.[2], [11], [5]
Framework mappings
- CWE reference for the weakness described in this guide: CWE-59 CWE-284
- CISA KEV — added 2026-09-08 — CVE-2026-81963
Incident Timeline
Microsoft reserves CVE-2026-81963.[2]
September Patch Tuesday: Microsoft publishes the fix and marks the flaw "Exploitation Detected" and not publicly disclosed.[1]
CISA adds CVE-2026-81963 to the Known Exploited Vulnerabilities catalog, with a remediation due date of 22 September 2026 for US federal agencies.[4]
Microsoft publishes a revision of the advisory.[1]
Step-by-Step Attack Flow
A SYSTEM service works in a path users can change
Update Stack components run as SYSTEM and handle files in staging and working directories. If a standard user can create, delete or rename entries anywhere along such a path, they decide where the path resolves. Microsoft has not said which path was affected in this case.[1]
The service follows the link
Windows normally resolves reparse points while opening a path. FILE_FLAG_OPEN_REPARSE_POINT controls the final component; earlier components can still be resolved. If a service then writes through an unvalidated path, the access check uses its SYSTEM token. Validate the directory handle and use handle-relative creation for the child file.[6], [8]
Source Code: Flaw vs. Secure Implementation
Microsoft has not published the affected function or patch. This defensive example is not Microsoft's code. Checking only the owner is insufficient because an object's owner does not prove that its DACL denies standard-user write access. This pattern assumes the installer creates the fixed staging path and every parent with an explicit SYSTEM/Administrators-only write DACL. The function does not read or validate the DACL; installer-provisioned permissions are a deployment precondition. It validates the opened directory handle, then creates a single child name relative to that handle with NtCreateFile and FILE_CREATE; it never reopens a concatenated path. Test the ACL and filesystem behavior on supported deployments. Install Microsoft's update to remediate CVE-2026-81963.[5], [6], [7], [8], [9], [10]
Source excerpt: Illustrative Windows path-handling pattern; not Microsoft source
/* Illustrative pattern, not Microsoft's source: Microsoft has not published
the affected function. A service running as SYSTEM writes a file into a
staging directory that standard users can modify. */
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 resolves junctions and symbolic links in every
component of the path, and the access check uses the service's
SYSTEM token. If a user can swap stagingDir for a junction, this
write lands wherever the junction points. */
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;
}/* Illustrative defensive pattern only; not Microsoft's source or patch.
The installer must provision this path and every parent with an explicit
DACL that allows writes only to SYSTEM and 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;
/* Pin the directory object; omitting FILE_SHARE_DELETE blocks rename. */
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;
/* Open the new child relative to this handle, never by rebuilding a path. */
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 fails if an entry already exists. */
DWORD written = 0;
ok = WriteFile(file, data, size, &written, NULL) && written == size;
CloseHandle(file);
done:
CloseHandle(dir);
return ok;
}Engineering & System Hardening Checklist
- ✓Install the September 2026 cumulative update: KB5124008 (Windows 11 24H2 and 25H2), KB5122880 (23H2), KB5124012 (26H1) or KB5122871 (Server 2025). Fixed builds are 10.0.26100.9445, 10.0.26200.9445, 10.0.22631.7582, 10.0.28000.2954 and, for Server 2025, 10.0.26100.33438.[1]
- ✓In every service that runs as SYSTEM or an administrator, list each CreateFileW, DeleteFileW, MoveFileExW, CopyFileW and SetFileSecurityW call whose path is under %ProgramData%, %TEMP%, C:\Users or another user-writable location, and require a review of each one.
- ✓Open directories with FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, reject any with FILE_ATTRIBUTE_REPARSE_POINT set, and hold the handle without FILE_SHARE_DELETE while working inside it.[6], [9]
- ✓Confirm the target with GetFinalPathNameByHandleW on the open handle before writing; never rely on the path string you built.[7]
- ✓Use CREATE_NEW (or NtCreateFile's FILE_CREATE) instead of CREATE_ALWAYS in shared directories, so a pre-existing file or link makes the operation fail rather than overwrite it.[8]
- ✓Create service working directories with an explicit DACL that grants write access only to SYSTEM and Administrators, and fail installer tests if icacls shows Users or Authenticated Users with write access.[10]
- ✓When a service performs file operations on behalf of a caller, impersonate the caller (RpcImpersonateClient, ImpersonateNamedPipeClient) so access is checked against the caller's token, not SYSTEM's.
- ✓Alert on standard-user processes creating junctions or mount points under %ProgramData% or %windir%; this is rare in normal use and is how link-following attacks begin.
- ✓Create the child relative to the verified directory handle with
NtCreateFileandFILE_CREATE; never reopenstagingDir\nameby concatenated path after checking the directory.[8]
Sources
- Microsoft Security Response Center — CVE-2026-81963, Windows Update Stack Elevation of Privilege Vulnerability
- CVE-2026-81963 — CVE record
- NVD — CVE-2026-81963
- CISA — Known Exploited Vulnerabilities catalog
- MITRE CWE-59: Improper Link Resolution Before File Access
- 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