How to authorize/deny write access to a directory on Windows using Python?

醉酒当歌 提交于 2019-12-05 22:05:25
Vyktor

This was just challenging thing to do. I've started with this really great answer which helps you with a similar thing.

You can start by just listing ACLs for directory, which could be done using this code:

import win32security
import ntsecuritycon as con

FILENAME = r'D:\tmp\acc_test' 

sd = win32security.GetFileSecurity(FILENAME, win32security.DACL_SECURITY_INFORMATION)
dacl = sd.GetSecurityDescriptorDacl()

ace_count = dacl.GetAceCount()
print('Ace count:', ace_count)

for i in range(0, ace_count):
    rev, access, usersid = dacl.GetAce(i)
    user, group, type  = win32security.LookupAccountSid('', usersid)
    print('User: {}/{}'.format(group, user), rev, access)

You can find method PyACL.GetAceCount() which returns number of ACEs.

The GetAce(i) function returns ACCESS_ALLOWED_ACE header as a tuple:

Now you are able to read old ACEs and deleting old ones is quite simple:

for i in range(0, ace_count):
    dacl.DeleteAce(0)

And after that you can just add privileges by calling AddAccessAllowedAceEx() [MSDN]:

userx, domain, type = win32security.LookupAccountName ("", "your.user")
usery, domain, type = win32security.LookupAccountName ("", "other.user")

dacl.AddAccessAllowedAceEx(win32security.ACL_REVISION, 3, 2032127, userx) # Full control
dacl.AddAccessAllowedAceEx(win32security.ACL_REVISION, 3, 1179785, usery) # Read only

sd.SetSecurityDescriptorDacl(1, dacl, 0)   # may not be necessary
win32security.SetFileSecurity(FILENAME, win32security.DACL_SECURITY_INFORMATION, sd)

I've taken numbers 3, 2032127 and 1179785 from the listing in first half of the script (before running the script I've set up privileges in Explorer->Right Click->Properties->Security->Advanced):

Just illustrative image borrowed from http://technet.microsoft.com/

User: DOMAIN/user (0, 3) 2032127
User: DOMAIN/user2 (0, 3) 1179785

But it corresponds to:

  • 3 -> OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE
  • 2032127 -> FILE_ALL_ACCESS (well, actually con.FILE_ALL_ACCESS = 2032639, but once you apply it on file and read it back you'll get 2032127; the difference is 512 - 0x0200 - the constant I haven't found in ntsecuritycon.py/file security permissions)
  • 1179785 -> FILE_GENERIC_READ

You can also remove access, change it or remove it but this should be very solid start for you.


TL;DR - codes

import win32security
import ntsecuritycon as con

FILENAME = r'D:\tmp\acc_test'

userx, domain, type = win32security.LookupAccountName ("", "your.user")
usery, domain, type = win32security.LookupAccountName ("", "other.user")

sd = win32security.GetFileSecurity(FILENAME, win32security.DACL_SECURITY_INFORMATION)
dacl = sd.GetSecurityDescriptorDacl()

ace_count = dacl.GetAceCount()
print('Ace count:', ace_count)

# Listing
for i in range(0, ace_count):
    rev, access, usersid = dacl.GetAce(i)
    user, group, type  = win32security.LookupAccountSid('', usersid)

    print('User: {}/{}'.format(group, user), rev, access)

# Removing the old ones
for i in range(0, ace_count):
    dacl.DeleteAce(0)

# Add full control for user x
dacl.AddAccessAllowedAceEx(win32security.ACL_REVISION, 
    con.OBJECT_INHERIT_ACE|con.CONTAINER_INHERIT_ACE, con.FILE_ALL_ACCESS, userx)

# Add read only access for user y
dacl.AddAccessAllowedAceEx(win32security.ACL_REVISION, 
    con.OBJECT_INHERIT_ACE|con.CONTAINER_INHERIT_ACE, con.FILE_GENERIC_READ, usery)

sd.SetSecurityDescriptorDacl(1, dacl, 0)   # may not be necessary
win32security.SetFileSecurity(FILENAME, win32security.DACL_SECURITY_INFORMATION, sd)

Mini utility for complete ACE listing

I just wrote small script for parsing all file ACEs:

import win32security
import ntsecuritycon as con
import sys


# List of all file masks that are interesting
ACCESS_MASKS = ['FILE_READ_DATA', 'FILE_LIST_DIRECTORY', 'FILE_WRITE_DATA', 'FILE_ADD_FILE', 
                     'FILE_APPEND_DATA', 'FILE_ADD_SUBDIRECTORY', 'FILE_CREATE_PIPE_INSTANCE', 'FILE_READ_EA',
                     'FILE_WRITE_EA', 'FILE_EXECUTE', 'FILE_TRAVERSE', 'FILE_DELETE_CHILD', 
                     'FILE_READ_ATTRIBUTES', 'FILE_WRITE_ATTRIBUTES', 'FILE_ALL_ACCESS', 'FILE_GENERIC_READ',
                     'FILE_GENERIC_WRITE', 'FILE_GENERIC_EXECUTE'] 

# List of all inheritance flags
ACE_FLAGS = ['OBJECT_INHERIT_ACE', 'CONTAINER_INHERIT_ACE', 'NO_PROPAGATE_INHERIT_ACE', 'INHERIT_ONLY_ACE']

# List of all ACE types
ACE_TYPES = ['ACCESS_MIN_MS_ACE_TYPE', 'ACCESS_ALLOWED_ACE_TYPE', 'ACCESS_DENIED_ACE_TYPE', 'SYSTEM_AUDIT_ACE_TYPE',
             'SYSTEM_ALARM_ACE_TYPE', 'ACCESS_MAX_MS_V2_ACE_TYPE', 'ACCESS_ALLOWED_COMPOUND_ACE_TYPE',
             'ACCESS_MAX_MS_V3_ACE_TYPE', 'ACCESS_MIN_MS_OBJECT_ACE_TYPE', 'ACCESS_ALLOWED_OBJECT_ACE_TYPE',
             'ACCESS_DENIED_OBJECT_ACE_TYPE', 'SYSTEM_AUDIT_OBJECT_ACE_TYPE', 'SYSTEM_ALARM_OBJECT_ACE_TYPE',
             'ACCESS_MAX_MS_OBJECT_ACE_TYPE', 'ACCESS_MAX_MS_V4_ACE_TYPE', 'ACCESS_MAX_MS_ACE_TYPE',
             'ACCESS_ALLOWED_CALLBACK_ACE_TYPE', 'ACCESS_DENIED_CALLBACK_ACE_TYPE', 'ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE',
             'ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE', 'SYSTEM_AUDIT_CALLBACK_ACE_TYPE', 'SYSTEM_ALARM_CALLBACK_ACE_TYPE',
             'SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE', 'SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE', 'SYSTEM_MANDATORY_LABEL_ACE_TYPE',
             'ACCESS_MAX_MS_V5_ACE_TYPE']

################################################################################
def get_ace_types_str(ace_type):
    ''' Yields all matching ACE types as strings
    '''
    for t in ACE_TYPES:
        if getattr(con, t) == ace_type:
            yield t

################################################################################
def get_ace_flags_str(ace_flag):
    ''' Yields all matching ACE flags as strings 
    '''
    for t in ACE_FLAGS:
        attr = getattr(con, t)
        if (attr & ace_flag) == attr:
            yield t

################################################################################
def get_access_mask_str(access_mask):
    ''' Yields all matching ACE flags as strings 
    '''
    for t in ACCESS_MASKS:
        attr = getattr(con, t)
        if (attr & access_mask) == attr:
            yield t

################################################################################
def list_file_ace(filename):
    ''' Method for listing of file ACEs
    '''

    # Load data
    sd = win32security.GetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION)
    dacl = sd.GetSecurityDescriptorDacl()     

    # Print ACE count
    ace_count = dacl.GetAceCount()
    print('File', filename, 'has', ace_count, 'ACEs')

    # Go trough individual ACEs
    for i in range(0, ace_count):
        (ace_type, ace_flag), access_mask, usersid = dacl.GetAce(i)
        user, group, usertype = win32security.LookupAccountSid('', usersid)

        print('\tUser: {}\\{}'.format(group, user))    
        print('\t\tACE Type ({}):'.format(ace_type), '; '.join(get_ace_types_str(ace_type))) 
        print('\t\tACE Flags ({}):'.format(ace_flag), ' | '.join(get_ace_flags_str(ace_flag)))
        print('\t\tAccess Mask ({}):'.format(access_mask), ' | '.join(get_access_mask_str(access_mask)))
        print()


################################################################################
# Execute with some defaults
if __name__ == '__main__':
    for filename in sys.argv[1:]:
        list_file_ace(filename)
        print()

It prints out strings like this:

D:\tmp>acc_list.py D:\tmp D:\tmp\main.bat
File D:\tmp has 8 ACEs
        User: BUILTIN\Administrators
                ACE Type (0): ACCESS_MIN_MS_ACE_TYPE; ACCESS_ALLOWED_ACE_TYPE
                ACE Flags (0):
                Access Mask (2032127): FILE_READ_DATA | FILE_LIST_DIRECTORY | FILE_WRITE_DATA | FILE_ADD_FILE | FILE_APPEND_DATA | FILE_ADD_SUBDIRECTORY | FILE_CREATE_PIPE_INSTANCE | FILE_READ_EA | FILE_WRITE_EA | FILE_EXECUTE | FILE_TRAVERSE | FILE_DELETE_CHILD | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE

...

So, by pocking around and trying to understand what was going on, I managed to find something very similar to what @Vyktor posted before.

I found some help using this example.

So, first thing I did was to try to understand the flags set by Windows when I was manually changing the security information with the GUI, I built a set of function to help me with this:

import os

import win32con
import win32security
import win32process
import ntsecuritycon

d = "toto"
f = os.path.join(d, "foo")


def build_flags_map(*attrs, **kw):
    mod = kw.get('mod', win32con)

    r = {}
    for attr in attrs:
        value = getattr(mod, attr)
        r[value] = attr
    return r


ACE_TYPE = build_flags_map('ACCESS_ALLOWED_ACE_TYPE', 'ACCESS_DENIED_ACE_TYPE')

ACCESS_MASK = build_flags_map(
    'GENERIC_WRITE', 'GENERIC_ALL', 'GENERIC_EXECUTE', 'GENERIC_READ',
    'WRITE_OWNER', 'DELETE', 'READ_CONTROL', 'SYNCHRONIZE', 'WRITE_DAC',
    'ACCESS_SYSTEM_SECURITY')

ACCESS_MASK_FILES = build_flags_map(
        'FILE_ADD_FILE', 'FILE_READ_DATA', 'FILE_LIST_DIRECTORY',
        'FILE_WRITE_DATA', 'FILE_ADD_FILE', 'FILE_APPEND_DATA',
        'FILE_ADD_SUBDIRECTORY', 'FILE_CREATE_PIPE_INSTANCE', 'FILE_READ_EA',
        'FILE_WRITE_EA', 'FILE_EXECUTE', 'FILE_TRAVERSE', 'FILE_DELETE_CHILD',
        'FILE_READ_ATTRIBUTES', 'FILE_WRITE_ATTRIBUTES', 'FILE_ALL_ACCESS',
        'FILE_GENERIC_READ', 'FILE_GENERIC_WRITE', 'FILE_GENERIC_EXECUTE',
        mod=ntsecuritycon,
    )

ACE_FLAGS = build_flags_map(
    'CONTAINER_INHERIT_ACE', 'INHERITED_ACE', 'FAILED_ACCESS_ACE_FLAG',
    'INHERIT_ONLY_ACE', 'OBJECT_INHERIT_ACE',
    mod=win32security)


def display_flags(map, value):
    r = []
    for flag, name in map.items():
        if flag & value:
            r.append(name)
            value = value - flag

    if value != 0:
        # We didn't specified all the flags in the mapping :(
        r.append('(flags left 0x%x)' % value)

    return r' | '.join(r)


def show_acls(path):

    process_handler = win32process.GetCurrentProcess()
    thread_handler = win32security.OpenProcessToken(
            process_handler,
            win32security.TOKEN_ALL_ACCESS)
    current_sid = win32security.GetTokenInformation(thread_handler, win32security.TokenUser)[0]

    desc = win32security.GetNamedSecurityInfo(
            path,
            win32security.SE_FILE_OBJECT,
            win32security.DACL_SECURITY_INFORMATION)    

    dacl = desc.GetSecurityDescriptorDacl()

    print("%d ACE on %s" % (dacl.GetAceCount(), path))
    for i in range(0, dacl.GetAceCount()):

        ace = dacl.GetAce(i)
        (ace_type, ace_flags), ace_mask, ace_sid = ace

        if ace_sid == current_sid:
            user = "me"
        else:
            user = str(ace_sid)

        print("  User: %s =>\n"
              "      ACE type: %s\n"
              "      ACE flags: %s\n"
              "      ACE mask: %s\n"
              "      Raw ACE: %r\n" % (
              user,
              ACE_TYPE[ace_type],
              display_flags(ACE_FLAGS, ace_flags),
              display_flags(ACCESS_MASK_FILES, ace_mask),
              ace))

From there, I got the following informations:

7 ACE on toto
  User: me =>
      ACE type: ACCESS_DENIED_ACE_TYPE
      ACE flags: CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE
      ACE mask: FILE_ADD_FILE | FILE_CREATE_PIPE_INSTANCE | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA
      Raw ACE: ((1, 3), 278, <PySID object at 0x00B02148>)

  User: me =>
      ACE type: ACCESS_ALLOWED_ACE_TYPE
      ACE flags: INHERITED_ACE
      ACE mask: FILE_TRAVERSE | FILE_LIST_DIRECTORY | FILE_ADD_FILE | FILE_CREATE_PIPE_INSTANCE | FILE_READ_EA | FILE_DELETE_CHILD | FILE_READ_ATTRIBUTES | FILE_GENERIC_EXECUTE | FILE_WRITE_EA | FILE_ALL_ACCESS | (flags left 0x-12039f)
      Raw ACE: ((0, 16), 2032127, <PySID object at 0x00A6FBF0>)

  User: me =>
      ACE type: ACCESS_ALLOWED_ACE_TYPE
      ACE flags: INHERITED_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE
      ACE mask: (flags left 0x10000000)
      Raw ACE: ((0, 27), 268435456, <PySID object at 0x00B02148>)

  User: PySID:S-1-5-18 =>
      ACE type: ACCESS_ALLOWED_ACE_TYPE
      ACE flags: INHERITED_ACE
      ACE mask: FILE_TRAVERSE | FILE_LIST_DIRECTORY | FILE_ADD_FILE | FILE_CREATE_PIPE_INSTANCE | FILE_READ_EA | FILE_DELETE_CHILD | FILE_READ_ATTRIBUTES | FILE_GENERIC_EXECUTE | FILE_WRITE_EA | FILE_ALL_ACCESS | (flags left 0x-12039f)
      Raw ACE: ((0, 16), 2032127, <PySID object at 0x00A6FBF0>)

  User: PySID:S-1-5-18 =>
      ACE type: ACCESS_ALLOWED_ACE_TYPE
      ACE flags: INHERITED_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE
      ACE mask: (flags left 0x10000000)
      Raw ACE: ((0, 27), 268435456, <PySID object at 0x00B02148>)

  User: PySID:S-1-5-32-544 =>
      ACE type: ACCESS_ALLOWED_ACE_TYPE
      ACE flags: INHERITED_ACE
      ACE mask: FILE_TRAVERSE | FILE_LIST_DIRECTORY | FILE_ADD_FILE | FILE_CREATE_PIPE_INSTANCE | FILE_READ_EA | FILE_DELETE_CHILD | FILE_READ_ATTRIBUTES | FILE_GENERIC_EXECUTE | FILE_WRITE_EA | FILE_ALL_ACCESS | (flags left 0x-12039f)
      Raw ACE: ((0, 16), 2032127, <PySID object at 0x00A6FBF0>)

  User: PySID:S-1-5-32-544 =>
      ACE type: ACCESS_ALLOWED_ACE_TYPE
      ACE flags: INHERITED_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE
      ACE mask: (flags left 0x10000000)
      Raw ACE: ((0, 27), 268435456, <PySID object at 0x00B02148>)

This example shows the default ACL I have on my system + the first one, which is one I created myself and which denies writes on the directory.

So,using the example before, I built this:

def pipe_str_flags(map, *flags):
    r = 0
    reverse_map = dict((value, key) for key, value in map.items())
    for flag in flags:
        r = r | reverse_map[flag]
    return r

def forbid_write(path):
    security_info = win32security.DACL_SECURITY_INFORMATION

    process_handler = win32process.GetCurrentProcess()
    thread_handler = win32security.OpenProcessToken(
            process_handler,
            win32security.TOKEN_ALL_ACCESS)

    desc = win32security.GetNamedSecurityInfo(
            path,
            win32security.SE_FILE_OBJECT,
            security_info)    

    current_sid = win32security.GetTokenInformation(thread_handler, win32security.TokenUser)[0]
    dacl = desc.GetSecurityDescriptorDacl()

    mask = pipe_str_flags(ACCESS_MASK_FILES,
            'FILE_ADD_FILE',
            'FILE_CREATE_PIPE_INSTANCE',
            'FILE_WRITE_ATTRIBUTES',
            'FILE_WRITE_EA')

    ace_flags = pipe_str_flags(ACE_FLAGS,
            'CONTAINER_INHERIT_ACE',
            'OBJECT_INHERIT_ACE')

    dacl.AddAccessDeniedAceEx(
            dacl.GetAclRevision(),
            ace_flags,
            mask,
            current_sid)

    win32security.SetNamedSecurityInfo(
        path,
        win32security.SE_FILE_OBJECT,
        security_info,
        None,
        None,
        dacl,
        None)

Contrary to @Vyktor solution, I'm using a "Denied" ACE, by denying write access (whereas Vyktor added an "Allowed read-only" ACE).

I sitll miss a proper way to remove this ACE so I can write again in this directory, but I haven't really look yet. One thing which is important is that "Denied" ACE have priority over "Allowed" ACE, so I tried the naive way of using dacl.AddAccessAllowedAceEx() with the exact same parameters as I was using on dacl.AddAccessDeniedAceEx(), but the later one has precedence over the former one, so I still can't write into the directory.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!