mirror of
https://github.com/fortra/impacket
synced 2026-06-08 14:15:13 +00:00
d029698149
* Added smbcacls * Changed example script * improve ACL manage * improve acl manage * improved acl * Closing all handles * fixed directory and handle is open bug in acl.py * Changed help a little bit * Ignore from empty ACL * fix indentation * removee example to different PR --------- Co-authored-by: john57 <your_email_address@example.com>
366 lines
18 KiB
Python
366 lines
18 KiB
Python
# Impacket - Collection of Python classes for working with network protocols.
|
|
#
|
|
# Copyright Fortra, LLC and its affiliated companies
|
|
#
|
|
# All rights reserved.
|
|
#
|
|
# This software is provided under a slightly modified version
|
|
# of the Apache Software License. See the accompanying LICENSE file
|
|
# for more information.
|
|
#
|
|
# Description:
|
|
# Tests for the acl module (Windows ACL management over SMB)
|
|
#
|
|
# Author:
|
|
# Gefen Altshuler (@gaffner)
|
|
#
|
|
from __future__ import print_function
|
|
import unittest
|
|
from unittest import mock
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from impacket.acl import (
|
|
SMBFileACL, FileNTACE, ACL_SID, FileNTUser, SecurityAttributes,
|
|
SUPPORTED_PERMISSIONS
|
|
)
|
|
from impacket.smb3structs import FileSecInformation
|
|
|
|
|
|
class TestSMBFileACL(unittest.TestCase):
|
|
"""Test suite for SMBFileACL class"""
|
|
|
|
@staticmethod
|
|
def get_mock_smb_file_acl():
|
|
"""
|
|
Create a mocked SMBFileACL instance with patched SMB connection
|
|
"""
|
|
with patch('impacket.acl.SMBConnection'), \
|
|
patch('impacket.acl.SMBTransport'):
|
|
acl_manager = SMBFileACL(
|
|
ip='192.168.1.100',
|
|
remote_name='TestServer',
|
|
username='testuser',
|
|
password='testpass',
|
|
domain='TESTDOM'
|
|
)
|
|
# Mock the policy handle
|
|
acl_manager.policy_handle = MagicMock()
|
|
return acl_manager
|
|
|
|
@patch('impacket.acl.lsat.hLsarLookupNames3')
|
|
def test_name_to_sid(self, mock_lookup):
|
|
"""Test converting username to SID"""
|
|
# Mock the LSA response
|
|
mock_sid = MagicMock()
|
|
mock_sid.getData.return_value = b'\x00\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
mock_lookup.return_value = {
|
|
'TranslatedSids': {
|
|
'Sids': [{'Sid': mock_sid}]
|
|
}
|
|
}
|
|
|
|
acl_manager = self.get_mock_smb_file_acl()
|
|
sid_bytes = acl_manager.name_to_sid('Administrator')
|
|
|
|
# Verify the SID bytes (skip first 4 bytes which are the count attribute)
|
|
self.assertEqual(len(sid_bytes), 28)
|
|
mock_lookup.assert_called_once()
|
|
|
|
def test_permissions_to_ace_grant(self):
|
|
"""Test converting permissions string to ACE for grant action"""
|
|
acl_manager = self.get_mock_smb_file_acl()
|
|
|
|
# Mock name_to_sid
|
|
test_sid = b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
with patch.object(acl_manager, 'name_to_sid', return_value=test_sid):
|
|
ace = acl_manager.permissions_to_ace('Administrator', 'R,W', 'grant')
|
|
|
|
self.assertIsInstance(ace, FileNTACE)
|
|
self.assertEqual(ace.action, 'grant')
|
|
# Check that permissions were OR'd together
|
|
expected_rights = SUPPORTED_PERMISSIONS['R'] | SUPPORTED_PERMISSIONS['W']
|
|
ace_rights = (ace['SpecificRights'] |
|
|
(ace['StandardRights'] << 16) |
|
|
(ace['GenericRights'] << 24))
|
|
self.assertGreater(ace_rights, 0)
|
|
|
|
def test_permissions_to_ace_invalid_permission(self):
|
|
"""Test handling of invalid permissions"""
|
|
acl_manager = self.get_mock_smb_file_acl()
|
|
|
|
test_sid = b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
with patch.object(acl_manager, 'name_to_sid', return_value=test_sid):
|
|
# Should raise exception when no valid permissions
|
|
with self.assertRaises(Exception) as context:
|
|
acl_manager.permissions_to_ace('Administrator', 'Z,Y,Q', 'grant')
|
|
self.assertIn("No valid permissions", str(context.exception))
|
|
|
|
def test_insert_permission_grant_new_ace(self):
|
|
"""Test granting permissions by adding a new ACE"""
|
|
# Original security descriptor with one ACE
|
|
sec_blob = (
|
|
b'\x01\x00\x04\x80\x14\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00L\x00\x00\x00'
|
|
b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xe9\x03\x00\x00'
|
|
b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\x01\x02\x00\x00'
|
|
b'\x02\x00l\x00\x04\x00\x00\x00'
|
|
b'\x00\x10$\x00\xff\x01\x1f\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xe9\x03\x00\x00'
|
|
b'\x00\x10\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00'
|
|
b'\x00\x10\x18\x00\xff\x01\x1f\x00\x01\x02\x00\x00\x00\x00\x00\x05 \x00\x00\x00 \x02\x00\x00'
|
|
b'\x00\x10\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x05\x12\x00\x00\x00'
|
|
)
|
|
|
|
sec = FileSecInformation(sec_blob)
|
|
|
|
# Create a new ACE for a different user with R,W permissions
|
|
new_sid = b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
permissions = SUPPORTED_PERMISSIONS['R'] | SUPPORTED_PERMISSIONS['W']
|
|
ace_bytes = b'\x00\x00$\x00' + permissions.to_bytes(4, 'little') + new_sid
|
|
new_ace = FileNTACE(ace_bytes)
|
|
new_ace.action = 'grant'
|
|
|
|
result = SMBFileACL.insert_permission(sec, new_ace)
|
|
|
|
# Verify the ACE count increased
|
|
result_ntuser = FileNTUser(result[sec["OffsetToDACL"]:])
|
|
original_ntuser = FileNTUser(sec_blob[sec["OffsetToDACL"]:])
|
|
self.assertEqual(result_ntuser['NumACEs'], original_ntuser['NumACEs'] + 1)
|
|
def test_set_permissions_closes_file_on_permission_resolution_error(self):
|
|
"""Ensure set_permissions closes file handles if permission resolution fails"""
|
|
acl_manager = self.get_mock_smb_file_acl()
|
|
|
|
# Simulate opening file; ensure close_file is called on error
|
|
acl_manager.open_file = MagicMock(return_value=(1, 2))
|
|
acl_manager.close_file = MagicMock()
|
|
|
|
with patch.object(acl_manager, 'permissions_to_ace', side_effect=Exception('name resolution failed')):
|
|
with self.assertRaises(Exception):
|
|
acl_manager.set_permissions('SHARE', 'file.txt', 'NonExistentUser', 'R', 'grant')
|
|
|
|
acl_manager.close_file.assert_called_once()
|
|
|
|
def test_get_permissions_closes_file_on_query_error(self):
|
|
"""Ensure get_permissions closes file handles if querying file info fails"""
|
|
acl_manager = self.get_mock_smb_file_acl()
|
|
|
|
acl_manager.open_file = MagicMock(return_value=(1, 2))
|
|
acl_manager.close_file = MagicMock()
|
|
|
|
# Make queryInfo raise
|
|
acl_manager.connection._SMBConnection.queryInfo = MagicMock(side_effect=Exception('query failed'))
|
|
|
|
with self.assertRaises(Exception):
|
|
acl_manager.get_permissions('SHARE', 'file.txt')
|
|
|
|
acl_manager.close_file.assert_called_once()
|
|
|
|
def test_insert_permission_grant_existing_ace(self):
|
|
"""Test granting additional permissions to existing ACE (OR operation)"""
|
|
# Security descriptor with existing ACE
|
|
sec_blob = (
|
|
b'\x01\x00\x04\x80\x14\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00L\x00\x00\x00'
|
|
b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xe9\x03\x00\x00'
|
|
b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\x01\x02\x00\x00'
|
|
b'\x02\x00\x90\x00\x05\x00\x00\x00'
|
|
b'\x00\x00$\x00\x9f\x01\x12\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
b'\x00\x10$\x00\xff\x01\x1f\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xe9\x03\x00\x00'
|
|
b'\x00\x10\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00'
|
|
b'\x00\x10\x18\x00\xff\x01\x1f\x00\x01\x02\x00\x00\x00\x00\x00\x05 \x00\x00\x00 \x02\x00\x00'
|
|
b'\x00\x10\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x05\x12\x00\x00\x00'
|
|
)
|
|
|
|
sec = FileSecInformation(sec_blob)
|
|
original_ntuser = FileNTUser(sec_blob[sec["OffsetToDACL"]:])
|
|
|
|
# Grant X,D permissions to existing user (already has R,W)
|
|
existing_sid = b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
permissions = SUPPORTED_PERMISSIONS['X'] | SUPPORTED_PERMISSIONS['D']
|
|
ace_bytes = b'\x00\x00$\x00' + permissions.to_bytes(4, 'little') + existing_sid
|
|
new_ace = FileNTACE(ace_bytes)
|
|
new_ace.action = 'grant'
|
|
|
|
result = SMBFileACL.insert_permission(sec, new_ace)
|
|
|
|
# ACE count should remain the same
|
|
result_ntuser = FileNTUser(result[sec["OffsetToDACL"]:])
|
|
self.assertEqual(result_ntuser['NumACEs'], original_ntuser['NumACEs'])
|
|
|
|
# Verify permissions were OR'd (should now have R,W,X,D)
|
|
result_ace = FileNTACE(result_ntuser['Buffer'])
|
|
self.assertGreater(result_ace['SpecificRights'], 0)
|
|
|
|
def test_insert_permission_revoke_partial(self):
|
|
"""Test revoking some permissions from existing ACE (AND NOT operation)"""
|
|
# Security descriptor with ACE that has R,W,X,D permissions
|
|
sec_blob = (
|
|
b'\x01\x00\x04\x80\x14\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00L\x00\x00\x00'
|
|
b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xe9\x03\x00\x00'
|
|
b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\x01\x02\x00\x00'
|
|
b'\x02\x00\x90\x00\x05\x00\x00\x00'
|
|
b'\x00\x00$\x00\xbf\x01\x13\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
b'\x00\x10$\x00\xff\x01\x1f\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xe9\x03\x00\x00'
|
|
b'\x00\x10\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00'
|
|
b'\x00\x10\x18\x00\xff\x01\x1f\x00\x01\x02\x00\x00\x00\x00\x00\x05 \x00\x00\x00 \x02\x00\x00'
|
|
b'\x00\x10\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x05\x12\x00\x00\x00'
|
|
)
|
|
|
|
sec = FileSecInformation(sec_blob)
|
|
original_ntuser = FileNTUser(sec_blob[sec["OffsetToDACL"]:])
|
|
|
|
# Revoke W,X permissions (keep R,D)
|
|
existing_sid = b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
permissions = SUPPORTED_PERMISSIONS['W'] | SUPPORTED_PERMISSIONS['X']
|
|
ace_bytes = b'\x00\x00$\x00' + permissions.to_bytes(4, 'little') + existing_sid
|
|
revoke_ace = FileNTACE(ace_bytes)
|
|
revoke_ace.action = 'revoke'
|
|
|
|
result = SMBFileACL.insert_permission(sec, revoke_ace)
|
|
|
|
# ACE should still exist (not all permissions removed)
|
|
result_ntuser = FileNTUser(result[sec["OffsetToDACL"]:])
|
|
self.assertEqual(result_ntuser['NumACEs'], original_ntuser['NumACEs'])
|
|
|
|
def test_insert_permission_revoke_all(self):
|
|
"""Test revoking all permissions removes the ACE entirely"""
|
|
# Security descriptor with ACE that has only R,W permissions
|
|
sec_blob = (
|
|
b'\x01\x00\x04\x80\x14\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00L\x00\x00\x00'
|
|
b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xe9\x03\x00\x00'
|
|
b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\x01\x02\x00\x00'
|
|
b'\x02\x00\x90\x00\x05\x00\x00\x00'
|
|
b'\x00\x00$\x00\x9f\x01\x12\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
b'\x00\x10$\x00\xff\x01\x1f\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xe9\x03\x00\x00'
|
|
b'\x00\x10\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00'
|
|
b'\x00\x10\x18\x00\xff\x01\x1f\x00\x01\x02\x00\x00\x00\x00\x00\x05 \x00\x00\x00 \x02\x00\x00'
|
|
b'\x00\x10\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x05\x12\x00\x00\x00'
|
|
)
|
|
|
|
sec = FileSecInformation(sec_blob)
|
|
original_ntuser = FileNTUser(sec_blob[sec["OffsetToDACL"]:])
|
|
|
|
# Revoke R,W permissions (all the permissions this ACE has)
|
|
existing_sid = b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
permissions = SUPPORTED_PERMISSIONS['R'] | SUPPORTED_PERMISSIONS['W']
|
|
ace_bytes = b'\x00\x00$\x00' + permissions.to_bytes(4, 'little') + existing_sid
|
|
revoke_ace = FileNTACE(ace_bytes)
|
|
revoke_ace.action = 'revoke'
|
|
|
|
result = SMBFileACL.insert_permission(sec, revoke_ace)
|
|
|
|
# ACE should be removed (ACE count decreased)
|
|
result_ntuser = FileNTUser(result[sec["OffsetToDACL"]:])
|
|
self.assertEqual(result_ntuser['NumACEs'], original_ntuser['NumACEs'] - 1)
|
|
|
|
def test_insert_permission_delete_ace(self):
|
|
"""Test deleting an entire ACE"""
|
|
# Security descriptor with multiple ACEs
|
|
sec_blob = (
|
|
b'\x01\x00\x04\x80\x14\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00L\x00\x00\x00'
|
|
b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xe9\x03\x00\x00'
|
|
b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\x01\x02\x00\x00'
|
|
b'\x02\x00\x90\x00\x05\x00\x00\x00'
|
|
b'\x00\x00$\x00\x9f\x01\x12\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
b'\x00\x10$\x00\xff\x01\x1f\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xe9\x03\x00\x00'
|
|
b'\x00\x10\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00'
|
|
b'\x00\x10\x18\x00\xff\x01\x1f\x00\x01\x02\x00\x00\x00\x00\x00\x05 \x00\x00\x00 \x02\x00\x00'
|
|
b'\x00\x10\x14\x00\xff\x01\x1f\x00\x01\x01\x00\x00\x00\x00\x00\x05\x12\x00\x00\x00'
|
|
)
|
|
|
|
sec = FileSecInformation(sec_blob)
|
|
original_ntuser = FileNTUser(sec_blob[sec["OffsetToDACL"]:])
|
|
|
|
# Delete the first ACE
|
|
existing_sid = b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
ace_bytes = b'\x00\x00$\x00\x00\x00\x00\x00' + existing_sid
|
|
delete_ace = FileNTACE(ace_bytes)
|
|
delete_ace.action = 'delete'
|
|
|
|
result = SMBFileACL.insert_permission(sec, delete_ace)
|
|
|
|
# ACE count should decrease by 1
|
|
result_ntuser = FileNTUser(result[sec["OffsetToDACL"]:])
|
|
self.assertEqual(result_ntuser['NumACEs'], original_ntuser['NumACEs'] - 1)
|
|
|
|
|
|
class TestACLStructures(unittest.TestCase):
|
|
"""Test ACL structure classes"""
|
|
|
|
def test_acl_sid_repr(self):
|
|
"""Test SID string representation"""
|
|
# S-1-5-21-4190006963-579503432-2148133447-500
|
|
sid_bytes = b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
sid = ACL_SID(sid_bytes)
|
|
|
|
sid_str = str(sid)
|
|
self.assertTrue(sid_str.startswith('S-1-5-'))
|
|
parts = sid_str.split('-')
|
|
self.assertEqual(len(parts), 8) # S-1-5-21-x-x-x-500
|
|
|
|
def test_acl_sid_build_from_string(self):
|
|
"""Test building SID from string"""
|
|
sid_str = 'S-1-5-18'
|
|
sid = ACL_SID.build_from_string(sid_str)
|
|
|
|
self.assertIsInstance(sid, ACL_SID)
|
|
# Note: build_from_string has a known issue with Authority encoding
|
|
# but the basic structure should be present
|
|
self.assertEqual(sid['Revision'], 1)
|
|
|
|
def test_acl_sid_equality(self):
|
|
"""Test SID equality comparison"""
|
|
sid_bytes = b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
sid1 = ACL_SID(sid_bytes)
|
|
sid2 = ACL_SID(sid_bytes)
|
|
|
|
self.assertEqual(sid1, sid2)
|
|
self.assertEqual(hash(sid1), hash(sid2))
|
|
|
|
def test_file_ntace_flags(self):
|
|
"""Test ACE flag parsing"""
|
|
# Create ACE with inheritance flags
|
|
ace_bytes = b'\x00\x03$\x00\xff\x01\x1f\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
ace = FileNTACE(ace_bytes)
|
|
|
|
flags = ace.get_readable_ntace_flags()
|
|
self.assertIn('(OI)', flags) # Object Inherit
|
|
self.assertIn('(CI)', flags) # Container Inherit
|
|
|
|
def test_file_ntace_full_control_display(self):
|
|
"""Test that full control doesn't show redundant standard rights"""
|
|
# ACE with full control (F)
|
|
ace_bytes = b'\x00\x10$\x00\xff\x01\x1f\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
ace = FileNTACE(ace_bytes)
|
|
|
|
ace_str = str(ace)
|
|
# Should show (I)(F) not (I)(R)(w)(D)(F)
|
|
self.assertIn('(F)', ace_str)
|
|
# Count the number of opening parentheses (should be 2: (I) and (F))
|
|
self.assertLessEqual(ace_str.count('('), 2)
|
|
|
|
def test_file_ntace_partial_permissions_display(self):
|
|
"""Test that partial permissions show all components"""
|
|
# ACE with R,W permissions (not full control)
|
|
permissions = SUPPORTED_PERMISSIONS['R'] | SUPPORTED_PERMISSIONS['W']
|
|
ace_bytes = b'\x00\x00$\x00' + permissions.to_bytes(4, 'little') + b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xb3n\xbe\xf9H\x85\x8a"G\xea\t\x80\xf4\x01\x00\x00'
|
|
ace = FileNTACE(ace_bytes)
|
|
|
|
ace_str = str(ace)
|
|
# Should not have (F) since it's not full control
|
|
self.assertNotIn('(F)', ace_str)
|
|
|
|
def test_security_attributes_repr(self):
|
|
"""Test SecurityAttributes string representation"""
|
|
owner_sid = ACL_SID.build_from_string('S-1-5-18')
|
|
group_sid = ACL_SID.build_from_string('S-1-5-32-544')
|
|
|
|
attrs = SecurityAttributes('NT AUTHORITY\\SYSTEM', 'BUILTIN\\Administrators')
|
|
attrs.readable_dacls[owner_sid] = 'NT AUTHORITY\\SYSTEM:(I)(F)'
|
|
|
|
attrs_str = str(attrs)
|
|
self.assertIn('Owner:', attrs_str)
|
|
self.assertIn('Group:', attrs_str)
|
|
self.assertIn('ACLs:', attrs_str)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|