Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 !

like image 626
Oz Bar-Shalom Avatar asked Jan 07 '23 20:01

Oz Bar-Shalom


1 Answers

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)
like image 122
Eryk Sun Avatar answered Jan 22 '23 23:01

Eryk Sun