为什么启用的 SeRestorePrivilege 允许 READ_CONTROL 访问文件

Why does enabled SeRestorePrivilege allow for READ_CONTROL access to a file

根据Windows API documentation

SeBackupPrivilege

causes the system to grant all read access control to any file, regardless of the access control list (ACL) specified for the file. Any access request other than read is still evaluated with the ACL. The following access rights are granted if this privilege is held: READ_CONTROL, ACCESS_SYSTEM_SECURITY, FILE_GENERIC_READ, FILE_TRAVERSE

SeRestorePrivilege:

causes the system to grant all write access control to any file, regardless of the ACL specified for the file. Any access request other than write is still evaluated with the ACL. The following access rights are granted if this privilege is held: WRITE_DAC, WRITE_OWNER, ACCESS_SYSTEM_SECURITY, FILE_GENERIC_WRITE, FILE_ADD_FILE, FILE_ADD_SUBDIRECTORY, DELETE

但是,我注意到,如果进程令牌具有 SeRestorePrivilege,则使用 READ_CONTROL 打开文件会成功。为什么会这样?

代码:

import os
import sys

import ntsecuritycon
import win32con
import win32file
import win32security
import win32api


def print_token_info():
    tok = win32security.OpenProcessToken(win32api.GetCurrentProcess(), win32security.TOKEN_QUERY)
    sid, attr = win32security.GetTokenInformation(tok, win32security.TokenUser)
    name, domain, sid_name_use = win32security.LookupAccountSid(None, sid)
    print(f"User: {name}@{domain}")

    sid = win32security.GetTokenInformation(tok, win32security.TokenOwner)
    name, domain, sid_name_use = win32security.LookupAccountSid(None, sid)
    print(f"Owner of new files: {name}@{domain}")

    privileges = win32security.GetTokenInformation(tok, win32security.TokenPrivileges)
    print("Privileges")
    priv_attr_flags = {
        win32security.SE_PRIVILEGE_ENABLED: 'SE_PRIVILEGE_ENABLED',
        win32security.SE_PRIVILEGE_ENABLED_BY_DEFAULT: 'SE_PRIVILEGE_ENABLED_BY_DEFAULT',
        win32security.SE_PRIVILEGE_REMOVED: 'SE_PRIVILEGE_REMOVED',
        win32security.SE_PRIVILEGE_USED_FOR_ACCESS: 'SE_PRIVILEGE_USED_FOR_ACCESS',
    }
    for luid, attr in privileges:
        priv_name = win32security.LookupPrivilegeName(None, luid)
        flag_str = []
        for flag in priv_attr_flags:
            if (attr & flag) == flag:
                flag_str.append(priv_attr_flags[flag])
        print(f"- {priv_name} -> {attr} ({', '.join(flag_str)})")
    win32api.CloseHandle(tok)


def disable_privilege(privilege_name):
    print(f"Disabling privilege {privilege_name}")

    tok = win32security.OpenProcessToken(
        win32api.GetCurrentProcess(), win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY
    )
    luid = win32security.LookupPrivilegeValue(None, privilege_name)
    se_privilege_disabled = 0
    new_state = [(luid, se_privilege_disabled)]
    win32security.AdjustTokenPrivileges(tok, 0, new_state)
    win32api.CloseHandle(tok)


def enable_privilege(privilege_name):
    print(f"Enabling privilege {privilege_name}")

    tok = win32security.OpenProcessToken(
        win32api.GetCurrentProcess(), win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY
    )
    luid = win32security.LookupPrivilegeValue(None, privilege_name)
    new_state = [(luid, win32security.SE_PRIVILEGE_ENABLED)]
    win32security.AdjustTokenPrivileges(tok, 0, new_state)
    win32api.CloseHandle(tok)


def open_handle(path):
    try:
        handle = win32file.CreateFileW(
            path,
            ntsecuritycon.READ_CONTROL,
            0,
            None,
            win32con.OPEN_EXISTING,
            win32file.FILE_FLAG_OPEN_REPARSE_POINT | win32file.FILE_FLAG_BACKUP_SEMANTICS,
            None,
        )
        handle.Close()
        print(f"Successfully opened {path}")
    except Exception as e:
        print(f"Failed to open {path}: {e}")


if __name__ == '__main__':
    path = sys.argv[1]

    with open(path, "w") as fo:
        pass

    dacl = win32security.ACL()
    dacl.SetEntriesInAcl([])
    win32security.SetNamedSecurityInfo(
        path,
        win32security.SE_FILE_OBJECT,
        win32security.DACL_SECURITY_INFORMATION | win32security.PROTECTED_DACL_SECURITY_INFORMATION,
        None,
        None,
        dacl,
        None,
    )

    print_token_info()
    open_handle(path)
    print()

    enable_privilege(win32security.SE_BACKUP_NAME)
    # print_token_info()
    open_handle(path)
    print()

    disable_privilege(win32security.SE_BACKUP_NAME)
    # print_token_info()
    open_handle(path)
    print()

    enable_privilege(win32security.SE_RESTORE_NAME)
    # print_token_info()
    open_handle(path)
    print()

    disable_privilege(win32security.SE_RESTORE_NAME)
    # print_token_info()
    open_handle(path)
    print()

提升控制台上的输出:

> python .\test_access.py C:\tmp\test\file-10
User: dev@CBIT-02
Owner of new files: Administrators@BUILTIN
Privileges
- SeIncreaseQuotaPrivilege -> 0 ()
- SeSecurityPrivilege -> 0 ()
- SeTakeOwnershipPrivilege -> 0 ()
- SeLoadDriverPrivilege -> 0 ()
- SeSystemProfilePrivilege -> 0 ()
- SeSystemtimePrivilege -> 0 ()
- SeProfileSingleProcessPrivilege -> 0 ()
- SeIncreaseBasePriorityPrivilege -> 0 ()
- SeCreatePagefilePrivilege -> 0 ()
- SeBackupPrivilege -> 0 ()
- SeRestorePrivilege -> 0 ()
- SeShutdownPrivilege -> 0 ()
- SeDebugPrivilege -> 2 (SE_PRIVILEGE_ENABLED)
- SeSystemEnvironmentPrivilege -> 0 ()
- SeChangeNotifyPrivilege -> 3 (SE_PRIVILEGE_ENABLED, SE_PRIVILEGE_ENABLED_BY_DEFAULT)
- SeRemoteShutdownPrivilege -> 0 ()
- SeUndockPrivilege -> 0 ()
- SeManageVolumePrivilege -> 0 ()
- SeImpersonatePrivilege -> 3 (SE_PRIVILEGE_ENABLED, SE_PRIVILEGE_ENABLED_BY_DEFAULT)
- SeCreateGlobalPrivilege -> 3 (SE_PRIVILEGE_ENABLED, SE_PRIVILEGE_ENABLED_BY_DEFAULT)
- SeIncreaseWorkingSetPrivilege -> 0 ()
- SeTimeZonePrivilege -> 0 ()
- SeCreateSymbolicLinkPrivilege -> 2 (SE_PRIVILEGE_ENABLED)
- SeDelegateSessionUserImpersonatePrivilege -> 0 ()
Failed to open C:\tmp\test\file-10: (5, 'CreateFileW', 'Access is denied.')

Enabling privilege SeBackupPrivilege
Successfully opened C:\tmp\test\file-10

Disabling privilege SeBackupPrivilege
Failed to open C:\tmp\test\file-10: (5, 'CreateFileW', 'Access is denied.')

Enabling privilege SeRestorePrivilege
Successfully opened C:\tmp\test\file-10

Disabling privilege SeRestorePrivilege
Failed to open C:\tmp\test\file-10: (5, 'CreateFileW', 'Access is denied.')

如问题中所述,具有备份语义的 SeRestorePrivilege 授予以下权限:ACCESS_SYSTEM_SECURITYWRITE_DACWRITE_OWNERDELETEFILE_ADD_FILEFILE_ADD_SUBDIRECTORY,以及 FILE_GENERIC_WRITE

FILE_GENERIC_WRITE 是映射到文件对象的 GENERIC_WRITE 访问权限的访问掩码。它包括 READ_CONTROLSYNCHRONIZEFILE_WRITE_ATTRIBUTES (0x0100)、FILE_WRITE_EA (0x0010)、FILE_APPEND_DATA (0x0004) 和 FILE_WRITE_DATA (0x0002) .请注意,最后两个与 FILE_ADD_SUBDIRECTORY (0x0004) 和 FILE_ADD_FILE (0x0002) 的值相同,因此 SeRestorePrivilege 的文档是多余的。

面具右边的READ_CONTROL来自STANDARD_RIGHTS_WRITE。大多数通用访问掩码包括以下标准权限掩码之一:

STANDARD_RIGHTS_READ     = READ_CONTROL
STANDARD_RIGHTS_WRITE    = READ_CONTROL
STANDARD_RIGHTS_EXECUTE  = READ_CONTROL
STANDARD_RIGHTS_REQUIRED = READ_CONTROL | WRITE_DAC | WRITE_OWNER | DELETE

以上均包含READ_CONTROL权限,确保请求通用访问权限包含查询对象的自主安全和强制安全的权限。


请注意,您的实验还需要控制隐含的所有者权利。如果文件的所有者是请求用户或用户的启用组之一,则该用户被隐式授予 READ_CONTROLWRITE_DAC 访问权限。您可以通过确保文件由不同的安全原则(例如 "SYSTEM")拥有,或者通过为 "OWNER_RIGHTS" 安全原则添加一个 ACE 来控制,该 ACE 不授予访问权限,这会覆盖隐式所有者权限.

另请注意,CreateFile 隐式请求 SYNCHRONIZEFILE_READ_ATTRIBUTES 访问权限,即使 dwDesiredAccess 为 0。SeRestorePrivilege 不授予 FILE_READ_ATTRIBUTES 访问权限。在您的实验中,此权限必须已由文件系统策略授予。无论文件的安全描述符如何,如果用户对父目录具有 FILE_READ_DATA(即 FILE_LIST_DIRECTORY)访问权限,则该用户将被授予读取属性的权限。这使得文件 API 一致,因为大多数文件元数据(例如文件属性、时间戳和大小)都可以通过列出父目录来获得。要控制您的实验,您需要使用不授予用户读取数据访问权限的目录。然后您会发现 SeRestorePrivilege 本身并不能使 CreateFile 成功。