问题
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 docwin32api.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 thatAceFlags
set to11
meant inherit privileges and3
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, actuallycon.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