如何使用Python授权/拒绝对Windows目录的写入访问?

5 投票
2 回答
3859 浏览
提问于 2025-04-28 13:15

我想在Windows XP及更高版本中,能够控制对某个特定文件夹的写入权限,也就是决定谁可以写入这个文件夹,谁不能。

我尝试了以下几种方法,但都没有成功:

  • os.chmod():这个方法只能设置文件的只读属性,不能用来设置文件夹的权限,具体可以查看Python的文档
  • win32api.SetFileAttribute() FILE_ATTRIBUTE_READONLY:表示文件是只读的。[...] 这个属性在文件夹上是无效的,详细信息可以看MSDN的SetFileAttribute

看起来我唯一的选择就是去访问和更新这个文件夹的安全信息。我尝试了好几个小时,但没有太大进展,因为我对Win32 API不太熟悉。

有没有什么好的方法可以做到这一点呢?

暂无标签

2 个回答

2

所以,我在尝试理解发生了什么的时候,发现了一些和@Vyktor之前发的内容非常相似的东西。

我找到了一些帮助,使用了这个例子

我做的第一件事就是试着理解当我通过图形界面手动更改安全信息时,Windows设置的标志。我写了一些函数来帮助我理解这些内容:

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))

从这里,我得到了以下信息:

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>)

这个例子展示了我系统上的默认访问控制列表(ACL)和我自己创建的第一个ACL,它拒绝对这个目录的写入权限。

所以,基于之前的例子,我构建了这个:

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)

和@Vyktor的解决方案不同,我使用了一个“拒绝”访问控制条目(ACE),通过拒绝写入权限(而Vyktor是添加了一个“允许只读”ACE)。

我仍然没有找到一个合适的方法来移除这个ACE,以便我可以再次在这个目录中写入,但我还没有深入研究。一个重要的事情是,“拒绝”ACE优先于“允许”ACE,所以我尝试了一个简单的方法,使用dacl.AddAccessAllowedAceEx(),参数和我在dacl.AddAccessDeniedAceEx()中使用的一模一样,但后者的优先级高于前者,所以我仍然无法在这个目录中写入。

7

这真是个挑战性的任务。我开始时参考了这个很棒的回答,它能帮助你处理类似的问题。

你可以先列出目录的访问控制列表(ACL),可以用下面的代码来实现:

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)

你可以找到一个方法PyACL.GetAceCount(),它会返回ACE的数量。

GetAce(i)这个函数会返回一个ACCESS_ALLOWED_ACE头部,以一个tuple的形式:

现在你可以读取旧的ACE,删除旧的ACE也很简单:

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

之后你可以通过调用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)

我从脚本前半部分的列表中取了数字320321271179785(在运行脚本之前,我在资源管理器->右键点击->属性->安全性->高级中设置了权限):

资源管理器->右键点击->属性->安全性->高级

这只是从http://technet.microsoft.com/借来的示例图片

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

但它对应于:

  • 3 -> OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE
  • 2032127 -> FILE_ALL_ACCESS(实际上con.FILE_ALL_ACCESS = 2032639,但一旦你应用到文件并读取回来,你会得到2032127;差异是512 - 0x0200 - 这是我在ntsecuritycon.py/文件安全权限中没有找到的常量)
  • 1179785 -> FILE_GENERIC_READ

你也可以移除访问权限、修改它或者删除它,但这应该是一个很好的开始。


总结 - 代码

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)

一个用于完整ACE列出的迷你工具

我写了一个小脚本来解析所有文件的ACE:

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()

它会打印出这样的字符串:

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

...

撰写回答