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

浪子不回头ぞ 提交于 2019-12-22 10:53:01

问题


I would like to be able to authorize or deny write access to a specific directory on Windows XP and more.

I tried the following, and they all don't work:

  • os.chmod(): only a file read-only attribute can be specified, see Python's doc
  • win32api.SetFileAttribute() FILE_ATTRIBUTE_READONLY: A file that is read-only. [...] This attribute is not honored on directories, see MSDN's SetFileAttribute

It looks like the only alternative I have is to access and update the "Security info" of the directory, I've tried for several hours to get something done with this without much success (I'm really unfamiliar with Win32 API).

Any ideas on how to do that?


回答1:


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:

  • ACE_HEADER - two integers AceType, AceFlags - trial and error showed me that AceFlags set to 11 meant inherit privileges and 3 not inherited
  • ACCESS_MASK - detailed list here or in ntsecuritycon.py
  • SID

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

...



回答2:


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.



来源:https://stackoverflow.com/questions/26465546/how-to-authorize-deny-write-access-to-a-directory-on-windows-using-python

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