Search code examples
pythonwindowspermissionsacl

Python - Get windows folder ACL permissions


I am looking for an example to get a folder ACL permissions with Python 27 . I need the result to be like: domain\username - FullControl, domain\username Modify

Thank you !


Solution

  • Here's WMI example that uses Tim Golden's wmi module. It selects an instance of Win32_LogicalFileSecuritySetting for a given path. It calls the GetSecurityDescriptor method to get a Win32_SecurityDescriptor. I use this to create Ace and FileSecurity namedtuple instances. I've added a few methods to test the access granted, denied, or audited by an ACE and output the data in a format that's similar to what icacls uses.

    I'm also including ctypes code to enable SeSecurityPrivilege, which is required in order to read the system access control list (SACL).

    Imports and constants

    import os
    import wmi
    import collections
    import ctypes
    from ctypes import wintypes
    
    SE_OWNER_DEFAULTED        = 0x0001
    SE_GROUP_DEFAULTED        = 0x0002
    SE_DACL_PRESENT           = 0x0004
    SE_DACL_DEFAULTED         = 0x0008
    SE_SACL_PRESENT           = 0x0010
    SE_SACL_DEFAULTED         = 0x0020
    SE_DACL_AUTO_INHERIT_REQ  = 0x0100
    SE_SACL_AUTO_INHERIT_REQ  = 0x0200
    SE_DACL_AUTO_INHERITED    = 0x0400
    SE_SACL_AUTO_INHERITED    = 0x0800
    SE_DACL_PROTECTED         = 0x1000
    SE_SACL_PROTECTED         = 0x2000
    SE_SELF_RELATIVE          = 0x8000
    
    OBJECT_INHERIT_ACE         = 0x01
    CONTAINER_INHERIT_ACE      = 0x02
    NO_PROPAGATE_INHERIT_ACE   = 0x04
    INHERIT_ONLY_ACE           = 0x08
    INHERITED_ACE              = 0x10
    SUCCESSFUL_ACCESS_ACE_FLAG = 0x40
    FAILED_ACCESS_ACE_FLAG     = 0x80
    
    ACCESS_ALLOWED_ACE_TYPE = 0
    ACCESS_DENIED_ACE_TYPE  = 1
    SYSTEM_AUDIT_ACE_TYPE   = 2
    
    DELETE                 = 0x00010000 # DE
    READ_CONTROL           = 0x00020000 # RC
    WRITE_DAC              = 0x00040000 # WDAC
    WRITE_OWNER            = 0x00080000 # WO
    SYNCHRONIZE            = 0x00100000 # S
    ACCESS_SYSTEM_SECURITY = 0x01000000 # AS
    GENERIC_READ           = 0x80000000 # GR
    GENERIC_WRITE          = 0x40000000 # GW
    GENERIC_EXECUTE        = 0x20000000 # GE
    GENERIC_ALL            = 0x10000000 # GA
    
    FILE_READ_DATA         = 0x00000001 # RD
    FILE_LIST_DIRECTORY    = 0x00000001
    FILE_WRITE_DATA        = 0x00000002 # WD
    FILE_ADD_FILE          = 0x00000002
    FILE_APPEND_DATA       = 0x00000004 # AD
    FILE_ADD_SUBDIRECTORY  = 0x00000004
    FILE_READ_EA           = 0x00000008 # REA
    FILE_WRITE_EA          = 0x00000010 # WEA
    FILE_EXECUTE           = 0x00000020 # X
    FILE_TRAVERSE          = 0x00000020
    FILE_DELETE_CHILD      = 0x00000040 # DC
    FILE_READ_ATTRIBUTES   = 0x00000080 # RA
    FILE_WRITE_ATTRIBUTES  = 0x00000100 # WA
    
    FILE_GENERIC_READ      = (FILE_READ_DATA        |
                              FILE_READ_EA          |
                              FILE_READ_ATTRIBUTES  |
                              READ_CONTROL          |
                              SYNCHRONIZE)
    
    FILE_GENERIC_WRITE     = (FILE_WRITE_DATA       |
                              FILE_APPEND_DATA      |
                              FILE_WRITE_EA         |
                              FILE_WRITE_ATTRIBUTES |
                              READ_CONTROL          |
                              SYNCHRONIZE)
    
    FILE_GENERIC_EXECUTE    = (FILE_EXECUTE         |
                               FILE_READ_ATTRIBUTES |
                               READ_CONTROL         |
                               SYNCHRONIZE)
    
    FILE_ALL_ACCESS         = 0x001F01FF
    
    FILE_MODIIFY_ACCESS     = FILE_ALL_ACCESS & ~(FILE_DELETE_CHILD |
                                                  WRITE_DAC         |
                                                  WRITE_OWNER)
    
    FILE_READ_EXEC_ACCESS   = FILE_GENERIC_READ | FILE_GENERIC_EXECUTE
    
    FILE_DELETE_ACCESS      = DELETE | SYNCHRONIZE
    

    Classes

    _Ace = collections.namedtuple('_Ace',
                'ace_type flags mask mapped_mask sid trustee')
    
    class Ace(_Ace):
        def __new__(cls, ace_type, flags, mask, sid, trustee):
            mapped_mask = cls._map_generic(mask)
            return super(Ace, cls).__new__(cls, ace_type, flags,
                                           mask, mapped_mask, sid, trustee)
    
        @staticmethod
        def _map_generic(mask):
            if mask & GENERIC_READ:
                mask = (mask & ~GENERIC_READ) | FILE_GENERIC_READ
            if mask & GENERIC_WRITE:
                mask = (mask & ~GENERIC_WRITE) | FILE_GENERIC_WRITE
            if mask & GENERIC_EXECUTE:
                mask = (mask & ~GENERIC_EXECUTE) | FILE_GENERIC_EXECUTE
            if mask & GENERIC_ALL:
                mask = (mask & ~GENERIC_ALL) | FILE_ALL_ACCESS
            return mask
    
        def inherited(self):         # I
            return bool(self.flags & INHERITED_ACE)
        def object_inherit(self):    # OI
            return bool(self.flags & OBJECT_INHERIT_ACE)
        def container_inherit(self): # CI
            return bool(self.flags & CONTAINER_INHERIT_ACE)
        def inherit_only(self):      # IO
            return bool(self.flags & INHERIT_ONLY_ACE)
        def no_propagate(self):      # NP
            return bool(self.flags & NO_PROPAGATE_INHERIT_ACE)
    
        def no_access(self):         # N
            return self.mapped_mask == 0
        def full_access(self):       # F
            return self.mapped_mask == FILE_ALL_ACCESS
        def modify_access(self):     # M
            return self.mapped_mask == FILE_MODIIFY_ACCESS
        def read_exec_access(self):  # RX
            return self.mapped_mask == FILE_READ_EXEC_ACCESS
        def read_only_access(self):  # R
            return self.mapped_mask == FILE_GENERIC_READ
        def write_only_access(self): # W
            return self.mapped_mask == FILE_GENERIC_WRITE
        def delete_access(self):     # D
            return self.mapped_mask == FILE_DELETE_ACCESS
    
        def get_file_rights(self):
            if self.no_access(): return ['N']
            if self.full_access(): return ['F']
            if self.modify_access(): return ['M']
            if self.read_exec_access(): return ['RX']
            if self.read_only_access(): return ['R']
            if self.write_only_access(): return ['W']
            if self.delete_access(): return ['D']
            rights = []
            for right, name in ((DELETE, 'DE'), (READ_CONTROL, 'RC'),
                                (WRITE_DAC, 'WDAC'), (WRITE_OWNER, 'WO'),
                                (SYNCHRONIZE, 'S'),
                                (ACCESS_SYSTEM_SECURITY, 'AS'),
                                (GENERIC_READ, 'GR'), (GENERIC_WRITE, 'GW'),
                                (GENERIC_EXECUTE, 'GE'), (GENERIC_ALL, 'GA'),
                                (FILE_READ_DATA, 'RD'), (FILE_WRITE_DATA, 'WD'),
                                (FILE_APPEND_DATA, 'AD'), (FILE_READ_EA, 'REA'),
                                (FILE_WRITE_EA, 'WEA'), (FILE_EXECUTE, 'X'),
                                (FILE_DELETE_CHILD, 'DC'),
                                (FILE_READ_ATTRIBUTES, 'RA'),
                                (FILE_WRITE_ATTRIBUTES, 'WA')):
                if self.mask & right:
                    rights.append(name)
            return rights
    
        def granted_access(self, mask):
            return bool(self.mapped_mask & self._map_generic(mask))
    
        def __str__(self):
            trustee = self.trustee if self.trustee else self.sid
            access = []
            if self.ace_type == ACCESS_DENIED_ACE_TYPE:
                access.append('(DENY)')
            elif self.ace_type == SYSTEM_AUDIT_ACE_TYPE:
                access.append('(AUDIT)')
            if self.inherited(): access.append('(I)')
            if self.object_inherit(): access.append('(OI)')
            if self.container_inherit(): access.append('(CI)')
            if self.inherit_only(): access.append('(IO)')
            if self.no_propagate(): acccess.append('(NP)')
            access.append('(%s)' % ','.join(self.get_file_rights()))
            return '%s:%s' % (trustee, ''.join(access))
    
    _FileSecurity = collections.namedtuple('_FileSecurity',
                            'path owner_permissions owner group '
                            'owner_sid group_sid flags dacl sacl')
    
    class FileSecurity(_FileSecurity):
        def __str__(self):
            owner = self.owner if self.owner else self.owner_sid
            group = self.group if self.group else self.group_sid
            items = ['Path:  %s' % self.path,
                     'Owner: %s' % owner,
                     'Group: %s' % group]
            if self.dacl:
                items += ['DACL:  %s' %
                          '\n       '.join(str(x) for x in self.dacl)]
            if self.sacl:
                items += ['SACL:  %s' %
                          '\n       '.join(str(x) for x in self.sacl)]
            return '\n'.join(items)
    

    Functions

    def list_acl(wmi_acl):
        acl = []
        for entry in wmi_acl:
            trustee = entry.Trustee.Name
            if trustee and entry.Trustee.Domain:
                trustee = '%s\\%s' % (entry.Trustee.Domain, trustee)
            mask = entry.AccessMask
            if mask < 0:
                mask += 2 ** 32
            ace = Ace(entry.AceType, entry.AceFlags, mask,
                            entry.Trustee.SIDString, trustee)
            acl.append(ace)
        return acl
    
    # Win32_LogicalFileSecuritySetting
    # https://msdn.microsoft.com/en-us/library/aa394180
    WQL_LFSS = 'SELECT * FROM Win32_LogicalFileSecuritySetting WHERE Path="%s"'
    wmi_ns = wmi.WMI()
    
    def get_file_security(path):
        path = os.path.abspath(path)
        os.stat(path) # ensure path exists
        lfss = wmi_ns.query(WQL_LFSS % (path,))[0]
        sd = lfss.GetSecurityDescriptor()[0]
        owner = sd.Owner.Name
        if owner and sd.Owner.Domain:
            owner = '%s\\%s' % (sd.Owner.Domain, owner)
        group = sd.Group.Name
        if group and sd.Group.Domain:
            group = '%s\\%s' % (sd.Group.Domain, group)
        dacl = sacl = ()
        if sd.ControlFlags & SE_DACL_PRESENT:
            dacl = tuple(list_acl(sd.DACL))
        if sd.ControlFlags & SE_SACL_PRESENT:
            sacl = tuple(list_acl(sd.SACL))
        return FileSecurity(lfss.Path,
                            lfss.OwnerPermissions,
                            owner, group,
                            sd.Owner.SIDString,
                            sd.Group.SIDString,
                            sd.ControlFlags,
                            dacl, sacl)
    

    Accessing the SACL requires SeSecurityPrivilege. Here's some ctypes code to enable a privilege:

    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
    advapi32 = ctypes.WinDLL('advapi32', use_last_error=True)
    
    ERROR_NOT_ALL_ASSIGNED = 0x0514
    SE_PRIVILEGE_ENABLED = 0x00000002
    TOKEN_ALL_ACCESS = 0x000F0000 | 0x01FF
    
    class LUID(ctypes.Structure):
        _fields_ = (('LowPart',  wintypes.DWORD),
                    ('HighPart', wintypes.LONG))
    
    class LUID_AND_ATTRIBUTES(ctypes.Structure):
        _fields_ = (('Luid',       LUID),
                    ('Attributes', wintypes.DWORD))
    
    class TOKEN_PRIVILEGES(ctypes.Structure):
        _fields_ = (('PrivilegeCount', wintypes.DWORD),
                    ('Privileges', LUID_AND_ATTRIBUTES * 1))
        def __init__(self, PrivilegeCount=1, *args):
            super(TOKEN_PRIVILEGES, self).__init__(PrivilegeCount, *args)
    
    PDWORD = ctypes.POINTER(wintypes.DWORD)
    PHANDLE = ctypes.POINTER(wintypes.HANDLE)
    PLUID = ctypes.POINTER(LUID)
    PTOKEN_PRIVILEGES = ctypes.POINTER(TOKEN_PRIVILEGES)
    
    def errcheck_bool(result, func, args):
        if not result:
            raise ctypes.WinError(ctypes.get_last_error())
        return args
    
    kernel32.CloseHandle.argtypes = (wintypes.HANDLE,)
    
    kernel32.GetCurrentProcess.errcheck = errcheck_bool
    kernel32.GetCurrentProcess.restype = wintypes.HANDLE
    
    # https://msdn.microsoft.com/en-us/library/aa379295
    advapi32.OpenProcessToken.errcheck = errcheck_bool
    advapi32.OpenProcessToken.argtypes = (
        wintypes.HANDLE,  # _In_  ProcessHandle
        wintypes.DWORD,   # _In_  DesiredAccess
        PHANDLE)          # _Out_ TokenHandle
    
    # https://msdn.microsoft.com/en-us/library/aa379180
    advapi32.LookupPrivilegeValueW.errcheck = errcheck_bool
    advapi32.LookupPrivilegeValueW.argtypes = (
        wintypes.LPCWSTR, # _In_opt_ lpSystemName
        wintypes.LPCWSTR, # _In_     lpName
        PLUID)            # _Out_    lpLuid
    
    # https://msdn.microsoft.com/en-us/library/aa375202
    advapi32.AdjustTokenPrivileges.errcheck = errcheck_bool
    advapi32.AdjustTokenPrivileges.argtypes = (
        wintypes.HANDLE,   # _In_      TokenHandle
        wintypes.BOOL,     # _In_      DisableAllPrivileges
        PTOKEN_PRIVILEGES, # _In_opt_  NewState
        wintypes.DWORD,    # _In_      BufferLength
        PTOKEN_PRIVILEGES, # _Out_opt_ PreviousState
        PDWORD)            # _Out_opt_ ReturnLength
    
    def enable_privilege(privilege):
        hToken = wintypes.HANDLE()
        luid = LUID()
        tp = TOKEN_PRIVILEGES()
        advapi32.LookupPrivilegeValueW(None, privilege, ctypes.byref(luid))
        tp.Privileges[0].Luid = luid
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
        advapi32.OpenProcessToken(kernel32.GetCurrentProcess(),
                                  TOKEN_ALL_ACCESS,
                                  ctypes.byref(hToken))
        try:
            advapi32.AdjustTokenPrivileges(hToken, False,
                                           ctypes.byref(tp),
                                           ctypes.sizeof(tp),
                                           None, None)
            if ctypes.get_last_error() == ERROR_NOT_ALL_ASSIGNED:
                raise ctypes.WinError(ERROR_NOT_ALL_ASSIGNED)
        finally:
            kernel32.CloseHandle(hToken)
    
    def disable_privilege(privilege):
        hToken = wintypes.HANDLE()
        luid = LUID()
        tp = TOKEN_PRIVILEGES()
        advapi32.LookupPrivilegeValueW(None, privilege, ctypes.byref(luid))
        tp.Privileges[0].Luid = luid
        tp.Privileges[0].Attributes = 0
        advapi32.OpenProcessToken(kernel32.GetCurrentProcess(),
                                  TOKEN_ALL_ACCESS,
                                  ctypes.byref(hToken))
        try:
            advapi32.AdjustTokenPrivileges(hToken, False,
                                           ctypes.byref(tp),
                                           ctypes.sizeof(tp),
                                           None, None)
            if ctypes.get_last_error() == ERROR_NOT_ALL_ASSIGNED:
                raise ctypes.WinError(ERROR_NOT_ALL_ASSIGNED)
        finally:
            kernel32.CloseHandle(hToken)
    

    For example, I added an audit ACE to the "Program Files" directory to log any attempt by anyone to change the permissions or owner of the directory. This ACE type is stored in the system access control list (SACL).

    >>> enable_privilege('SeSecurityPrivilege')
    
    >>> print get_file_security('C:\\Program Files')
    Path : C:\Program Files
    Owner: NT SERVICE\TrustedInstaller
    Group: NT SERVICE\TrustedInstaller
    DACL : NT SERVICE\TrustedInstaller:(F)
           NT SERVICE\TrustedInstaller:(CI)(IO)(F)
           NT AUTHORITY\SYSTEM:(M)
           NT AUTHORITY\SYSTEM:(OI)(CI)(IO)(F)
           BUILTIN\Administrators:(M)
           BUILTIN\Administrators:(OI)(CI)(IO)(F)
           BUILTIN\Users:(RX)
           BUILTIN\Users:(OI)(CI)(IO)(RX)
           CREATOR OWNER:(OI)(CI)(IO)(F)
           APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(RX)
           APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(OI)(CI)(IO)(RX)
    SACL : Everyone:(AUDIT)(WDAC,WO)
    

    The following shows how a deny ACE appears:

    >>> f = open('tempfile', 'w'); f.close()
    >>> os.system('icacls tempfile /deny Guests:(M)')
    processed file: tempfile
    Successfully processed 1 files; Failed processing 0 files
    0
    >>> print get_file_security('tempfile')
    Path : C:\Temp\tempfile
    Owner: BUILTIN\Administrators
    Group: THISPC\None
    DACL : BUILTIN\Guests:(DENY)(M)
           BUILTIN\Administrators:(I)(F)
           NT AUTHORITY\SYSTEM:(I)(F)
           BUILTIN\Users:(I)(RX)
           NT AUTHORITY\Authenticated Users:(I)(M)