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 Update Stack
Компоненты Windows, которые ищут, скачивают, подготавливают и устанавливают обновления. Они работают от имени SYSTEM, потому что установка обновлений требует записи в защищённые части системы.[1]
SYSTEM (NT AUTHORITY\SYSTEM)
Локальная учётная запись, под которой работают службы Windows. На локальной машине у неё больше прав, чем у администратора, поэтому ей почти никогда не отказывают в операциях с файлами.[1]
Junction / символическая ссылка (reparse point)
Объекты NTFS, которые перенаправляют один путь на другой. Обычный пользователь может создать junction в своей папке и направить её имя на каталог, в который сам писать не может.[9]
Следование по ссылкам (CWE-59)
Программа открывает, записывает, перемещает или удаляет файл по имени, не проверяя, что имя указывает туда, куда ожидается, и подложенная кем-то ссылка перенаправляет операцию.[5], [2]
FILE_FLAG_OPEN_REPARSE_POINT
Флаг CreateFileW, который открывает сам последний компонент пути, если это reparse point, вместо перехода по этой последней ссылке. Сам по себе он не запрещает обработку reparse points в предыдущих компонентах пути.[6]
TOCTOU (CWE-367)
Time-of-check to time-of-use: файловая система меняется между проверкой и операцией, которая на неё полагается. Проверить строку пути и потом открыть её по имени — значит оставить такое окно.[12]
CWE-284 (недостаточный контроль доступа)
Microsoft указала для этой CVE CWE-59 и CWE-284. CWE-59 обозначает конкретный сбой разрешения ссылок. CWE-284 — широкая Pillar CWE, которую MITRE прямо не рекомендует использовать для сопоставления с уязвимостями реального мира. Это более широкая классификация Microsoft для той же заявленной проблемы, а не свидетельство второй ошибки.[2], [11], [5]

Связи со стандартами

Хронология инцидента

2026-08-27

Microsoft резервирует идентификатор CVE-2026-81963.[2]

2026-09-08

Сентябрьский Patch Tuesday: Microsoft выпускает исправление и помечает уязвимость как «Exploitation Detected» и не раскрытую публично.[1]

2026-09-08

CISA вносит CVE-2026-81963 в каталог Known Exploited Vulnerabilities; федеральные ведомства США должны устранить её до 22 сентября 2026 года.[4]

2026-09-15

Microsoft публикует новую редакцию бюллетеня.[1]

Пошаговый сценарий атаки

Шаг 1

Служба SYSTEM работает в пути, который могут менять пользователи

Компоненты Update Stack работают от имени SYSTEM и обрабатывают файлы в промежуточных и рабочих каталогах. Если обычный пользователь может создавать, удалять или переименовывать элементы где-либо на этом пути, он решает, куда путь разрешится. Какой путь был затронут в этом случае, Microsoft не сообщила.[1]

Шаг 2

Путь перенаправляется

Junction или символическая ссылка заставляет имя каталога указывать в другое место, например в защищённую системную папку. Для создания junction в собственной папке права администратора не нужны.[5], [9]

Шаг 3

Служба идёт по ссылке

При открытии пути Windows обычно обрабатывает reparse points. FILE_FLAG_OPEN_REPARSE_POINT относится к последнему компоненту; предыдущие компоненты всё ещё могут разрешаться. Если служба затем записывает файл по непроверенному пути, проверка доступа выполняется с её токеном SYSTEM. Проверьте дескриптор каталога и создавайте дочерний файл относительно этого дескриптора.[6], [8]

Шаг 4

Защищённое место изменено

Служба создаёт, перезаписывает, перемещает или удаляет файл там, где распоряжаться должны только SYSTEM или администраторы. В FAQ Microsoft подтверждается, что эксплуатация даёт атакующему привилегии SYSTEM.[1], [2]

Исходный код: Уязвимый vs Защищённый вариант

Microsoft не публиковала ни уязвимую функцию, ни исправление. Этот защитный пример не является кодом Microsoft. Проверки одного только владельца недостаточно: владелец объекта не доказывает, что его DACL запрещает запись обычным пользователям. Этот шаблон предполагает, что установщик создаёт фиксированный staging-путь и все родительские каталоги с явной DACL, разрешающей запись только SYSTEM и Administrators. Функция не читает и не проверяет DACL; права, заданные установщиком, являются предварительным условием развертывания. Он проверяет открытый дескриптор каталога, а затем создаёт одно имя файла относительно этого дескриптора через NtCreateFile и FILE_CREATE; составной путь повторно не открывается. Проверьте DACL и поведение файловой системы в поддерживаемых конфигурациях. Установите обновление Microsoft для устранения CVE-2026-81963.[5], [6], [7], [8], [9], [10]

Фрагмент исходного кода: Иллюстративный пример обработки путей Windows; не исходный код Microsoft

УЯЗВИМАЯ РЕАЛИЗАЦИЯ
/* Иллюстративный пример, не исходный код Microsoft: затронутая функция не опубликована.
 * Служба SYSTEM записывает файл в staging-каталог, доступный обычным пользователям для
 * изменения.
 */
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;

    /* Откройте новый элемент относительно этого handle, не собирая путь заново. */
    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;
}

Чек-лист по защите системы для инженеров

Источники