2022-07-30 12:35:55 +00:00
import binascii
import codecs
import json
import re
import datetime
from enum import Enum
from impacket . ldap import ldaptypes
2023-03-31 02:05:45 +00:00
from impacket . uuid import bin_to_string
2023-09-14 21:07:15 +00:00
from nxc . helpers . msada_guids import SCHEMA_OBJECTS , EXTENDED_RIGHTS
2022-07-30 12:35:55 +00:00
from ldap3 . protocol . formatters . formatters import format_sid
from ldap3 . utils . conv import escape_filter_chars
from ldap3 . protocol . microsoft import security_descriptor_control
2023-10-14 22:36:20 +00:00
import sys
2022-07-30 12:35:55 +00:00
OBJECT_TYPES_GUID = { }
OBJECT_TYPES_GUID . update ( SCHEMA_OBJECTS )
OBJECT_TYPES_GUID . update ( EXTENDED_RIGHTS )
# Universal SIDs
WELL_KNOWN_SIDS = {
2023-05-02 15:17:59 +00:00
" S-1-0 " : " Null Authority " ,
" S-1-0-0 " : " Nobody " ,
" S-1-1 " : " World Authority " ,
" S-1-1-0 " : " Everyone " ,
" S-1-2 " : " Local Authority " ,
" S-1-2-0 " : " Local " ,
" S-1-2-1 " : " Console Logon " ,
" S-1-3 " : " Creator Authority " ,
" S-1-3-0 " : " Creator Owner " ,
" S-1-3-1 " : " Creator Group " ,
" S-1-3-2 " : " Creator Owner Server " ,
" S-1-3-3 " : " Creator Group Server " ,
" S-1-3-4 " : " Owner Rights " ,
" S-1-5-80-0 " : " All Services " ,
" S-1-4 " : " Non-unique Authority " ,
" S-1-5 " : " NT Authority " ,
" S-1-5-1 " : " Dialup " ,
" S-1-5-2 " : " Network " ,
" S-1-5-3 " : " Batch " ,
" S-1-5-4 " : " Interactive " ,
" S-1-5-6 " : " Service " ,
" S-1-5-7 " : " Anonymous " ,
" S-1-5-8 " : " Proxy " ,
" S-1-5-9 " : " Enterprise Domain Controllers " ,
" S-1-5-10 " : " Principal Self " ,
" S-1-5-11 " : " Authenticated Users " ,
" S-1-5-12 " : " Restricted Code " ,
" S-1-5-13 " : " Terminal Server Users " ,
" S-1-5-14 " : " Remote Interactive Logon " ,
" S-1-5-15 " : " This Organization " ,
" S-1-5-17 " : " This Organization " ,
" S-1-5-18 " : " Local System " ,
" S-1-5-19 " : " NT Authority " ,
" S-1-5-20 " : " NT Authority " ,
" S-1-5-32-544 " : " Administrators " ,
" S-1-5-32-545 " : " Users " ,
" S-1-5-32-546 " : " Guests " ,
" S-1-5-32-547 " : " Power Users " ,
" S-1-5-32-548 " : " Account Operators " ,
" S-1-5-32-549 " : " Server Operators " ,
" S-1-5-32-550 " : " Print Operators " ,
" S-1-5-32-551 " : " Backup Operators " ,
" S-1-5-32-552 " : " Replicators " ,
" S-1-5-64-10 " : " NTLM Authentication " ,
" S-1-5-64-14 " : " SChannel Authentication " ,
" S-1-5-64-21 " : " Digest Authority " ,
" S-1-5-80 " : " NT Service " ,
" S-1-5-83-0 " : " NT VIRTUAL MACHINE \ Virtual Machines " ,
" S-1-16-0 " : " Untrusted Mandatory Level " ,
" S-1-16-4096 " : " Low Mandatory Level " ,
" S-1-16-8192 " : " Medium Mandatory Level " ,
" S-1-16-8448 " : " Medium Plus Mandatory Level " ,
" S-1-16-12288 " : " High Mandatory Level " ,
" S-1-16-16384 " : " System Mandatory Level " ,
" S-1-16-20480 " : " Protected Process Mandatory Level " ,
" S-1-16-28672 " : " Secure Process Mandatory Level " ,
" S-1-5-32-554 " : " BUILTIN \ Pre-Windows 2000 Compatible Access " ,
" S-1-5-32-555 " : " BUILTIN \ Remote Desktop Users " ,
" S-1-5-32-557 " : " BUILTIN \ Incoming Forest Trust Builders " ,
" S-1-5-32-556 " : " BUILTIN \\ Network Configuration Operators " ,
" S-1-5-32-558 " : " BUILTIN \ Performance Monitor Users " ,
" S-1-5-32-559 " : " BUILTIN \ Performance Log Users " ,
" S-1-5-32-560 " : " BUILTIN \ Windows Authorization Access Group " ,
" S-1-5-32-561 " : " BUILTIN \ Terminal Server License Servers " ,
" S-1-5-32-562 " : " BUILTIN \ Distributed COM Users " ,
" S-1-5-32-569 " : " BUILTIN \ Cryptographic Operators " ,
" S-1-5-32-573 " : " BUILTIN \ Event Log Readers " ,
" S-1-5-32-574 " : " BUILTIN \ Certificate Service DCOM Access " ,
" S-1-5-32-575 " : " BUILTIN \ RDS Remote Access Servers " ,
" S-1-5-32-576 " : " BUILTIN \ RDS Endpoint Servers " ,
" S-1-5-32-577 " : " BUILTIN \ RDS Management Servers " ,
" S-1-5-32-578 " : " BUILTIN \ Hyper-V Administrators " ,
" S-1-5-32-579 " : " BUILTIN \ Access Control Assistance Operators " ,
" S-1-5-32-580 " : " BUILTIN \ Remote Management Users " ,
2022-07-30 12:35:55 +00:00
}
# GUID rights enum
# GUID thats permits to identify extended rights in an ACE
# https://docs.microsoft.com/en-us/windows/win32/adschema/a-rightsguid
class RIGHTS_GUID ( Enum ) :
WriteMembers = " bf9679c0-0de6-11d0-a285-00aa003049e2 "
ResetPassword = " 00299570-246d-11d0-a768-00aa006e0529 "
DS_Replication_Get_Changes = " 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 "
DS_Replication_Get_Changes_All = " 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 "
# ACE flags enum
# New ACE at the end of SACL for inheritance and access return system-audit
# https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-addauditaccessobjectace
class ACE_FLAGS ( Enum ) :
CONTAINER_INHERIT_ACE = ldaptypes . ACE . CONTAINER_INHERIT_ACE
FAILED_ACCESS_ACE_FLAG = ldaptypes . ACE . FAILED_ACCESS_ACE_FLAG
INHERIT_ONLY_ACE = ldaptypes . ACE . INHERIT_ONLY_ACE
INHERITED_ACE = ldaptypes . ACE . INHERITED_ACE
NO_PROPAGATE_INHERIT_ACE = ldaptypes . ACE . NO_PROPAGATE_INHERIT_ACE
OBJECT_INHERIT_ACE = ldaptypes . ACE . OBJECT_INHERIT_ACE
SUCCESSFUL_ACCESS_ACE_FLAG = ldaptypes . ACE . SUCCESSFUL_ACCESS_ACE_FLAG
# ACE flags enum
# For an ACE, flags that indicate if the ObjectType and the InheritedObjecType are set with a GUID
# Since these two flags are the same for Allowed and Denied access, the same class will be used from 'ldaptypes'
# https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-access_allowed_object_ace
class OBJECT_ACE_FLAGS ( Enum ) :
2023-05-08 18:39:36 +00:00
ACE_OBJECT_TYPE_PRESENT = ldaptypes . ACCESS_ALLOWED_OBJECT_ACE . ACE_OBJECT_TYPE_PRESENT
ACE_INHERITED_OBJECT_TYPE_PRESENT = ldaptypes . ACCESS_ALLOWED_OBJECT_ACE . ACE_INHERITED_OBJECT_TYPE_PRESENT
2022-07-30 12:35:55 +00:00
# Access Mask enum
# Access mask permits to encode principal's rights to an object. This is the rights the principal behind the specified SID has
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/7a53f60e-e730-4dfe-bbe9-b21b62eb790b
# https://docs.microsoft.com/en-us/windows/win32/api/iads/ne-iads-ads_rights_enum?redirectedfrom=MSDN
class ACCESS_MASK ( Enum ) :
# Generic Rights
2023-03-31 02:05:45 +00:00
GenericRead = 0x80000000 # ADS_RIGHT_GENERIC_READ
GenericWrite = 0x40000000 # ADS_RIGHT_GENERIC_WRITE
GenericExecute = 0x20000000 # ADS_RIGHT_GENERIC_EXECUTE
GenericAll = 0x10000000 # ADS_RIGHT_GENERIC_ALL
2022-07-30 12:35:55 +00:00
# Maximum Allowed access type
MaximumAllowed = 0x02000000
# Access System Acl access type
2023-03-31 02:05:45 +00:00
AccessSystemSecurity = 0x01000000 # ADS_RIGHT_ACCESS_SYSTEM_SECURITY
2022-07-30 12:35:55 +00:00
# Standard access types
2023-03-31 02:05:45 +00:00
Synchronize = 0x00100000 # ADS_RIGHT_SYNCHRONIZE
WriteOwner = 0x00080000 # ADS_RIGHT_WRITE_OWNER
WriteDACL = 0x00040000 # ADS_RIGHT_WRITE_DAC
ReadControl = 0x00020000 # ADS_RIGHT_READ_CONTROL
Delete = 0x00010000 # ADS_RIGHT_DELETE
2022-07-30 12:35:55 +00:00
# Specific rights
2023-03-31 02:05:45 +00:00
AllExtendedRights = 0x00000100 # ADS_RIGHT_DS_CONTROL_ACCESS
ListObject = 0x00000080 # ADS_RIGHT_DS_LIST_OBJECT
DeleteTree = 0x00000040 # ADS_RIGHT_DS_DELETE_TREE
WriteProperties = 0x00000020 # ADS_RIGHT_DS_WRITE_PROP
ReadProperties = 0x00000010 # ADS_RIGHT_DS_READ_PROP
Self = 0x00000008 # ADS_RIGHT_DS_SELF
ListChildObjects = 0x00000004 # ADS_RIGHT_ACTRL_DS_LIST
DeleteChild = 0x00000002 # ADS_RIGHT_DS_DELETE_CHILD
CreateChild = 0x00000001 # ADS_RIGHT_DS_CREATE_CHILD
2022-07-30 12:35:55 +00:00
# Simple permissions enum
# Simple permissions are combinaisons of extended permissions
# https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc783530(v=ws.10)?redirectedfrom=MSDN
class SIMPLE_PERMISSIONS ( Enum ) :
2023-05-02 15:17:59 +00:00
FullControl = 0xF01FF
Modify = 0x0301BF
ReadAndExecute = 0x0200A9
ReadAndWrite = 0x02019F
2022-07-30 12:35:55 +00:00
Read = 0x20094
2023-05-02 15:17:59 +00:00
Write = 0x200BC
2022-07-30 12:35:55 +00:00
# Mask ObjectType field enum
# Possible values for the Mask field in object-specific ACE (permitting to specify extended rights in the ObjectType field for example)
# Since these flags are the same for Allowed and Denied access, the same class will be used from 'ldaptypes'
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/c79a383c-2b3f-4655-abe7-dcbb7ce0cfbe
class ALLOWED_OBJECT_ACE_MASK_FLAGS ( Enum ) :
ControlAccess = ldaptypes . ACCESS_ALLOWED_OBJECT_ACE . ADS_RIGHT_DS_CONTROL_ACCESS
CreateChild = ldaptypes . ACCESS_ALLOWED_OBJECT_ACE . ADS_RIGHT_DS_CREATE_CHILD
DeleteChild = ldaptypes . ACCESS_ALLOWED_OBJECT_ACE . ADS_RIGHT_DS_DELETE_CHILD
ReadProperty = ldaptypes . ACCESS_ALLOWED_OBJECT_ACE . ADS_RIGHT_DS_READ_PROP
WriteProperty = ldaptypes . ACCESS_ALLOWED_OBJECT_ACE . ADS_RIGHT_DS_WRITE_PROP
Self = ldaptypes . ACCESS_ALLOWED_OBJECT_ACE . ADS_RIGHT_DS_SELF
2023-09-17 20:20:40 +00:00
class NXCModule :
2023-10-15 11:18:21 +00:00
""" Module to read and backup the Discretionary Access Control List of one or multiple objects.
2023-03-31 02:05:45 +00:00
This module is essentially inspired from the dacledit . py script of Impacket that we have coauthored , @_nwodtuhs and me .
It has been converted to an LDAPConnection session , and improvements on the filtering and the ability to specify multiple targets have been added .
It could be interesting to implement the write / remove functions here , but a ldap3 session instead of a LDAPConnection one is required to write .
"""
2023-05-02 15:17:59 +00:00
name = " daclread "
2023-11-09 20:34:59 +00:00
description = " Read and backup the Discretionary Access Control List of objects. Be careful, this module cannot read the DACLS recursively, see more explanation in the options. "
2023-05-02 15:17:59 +00:00
supported_protocols = [ " ldap " ]
2023-04-04 13:39:54 +00:00
opsec_safe = True
multiple_hosts = False
2023-03-31 17:49:25 +00:00
def __init__ ( self , context = None , module_options = None ) :
self . context = context
self . module_options = module_options
2022-07-30 12:35:55 +00:00
def options ( self , context , module_options ) :
2023-03-31 02:05:45 +00:00
"""
2023-10-31 11:32:26 +00:00
Be careful , this module cannot read the DACLS recursively .
2023-10-15 11:18:21 +00:00
For example , if an object has particular rights because it belongs to a group , the module will not be able to see it directly , you have to check the group rights manually .
2023-10-31 11:32:26 +00:00
TARGET The objects that we want to read or backup the DACLs , specified by its SamAccountName
TARGET_DN The object that we want to read or backup the DACL , specified by its DN ( useful to target the domain itself )
2022-07-30 12:35:55 +00:00
PRINCIPAL The trustee that we want to filter on
ACTION The action to realise on the DACL ( read , backup )
ACE_TYPE The type of ACE to read ( Allowed or Denied )
RIGHTS An interesting right to filter on ( ' FullControl ' , ' ResetPassword ' , ' WriteMembers ' , ' DCSync ' )
RIGHTS_GUID A right GUID that specify a particular rights to filter on
2023-11-08 20:19:26 +00:00
Based on the work of @_nwodtuhs and @BlWasp_ .
2023-03-31 02:05:45 +00:00
"""
self . context = context
2023-04-25 13:47:33 +00:00
2023-10-15 11:18:21 +00:00
context . log . debug ( f " module_options: { module_options } " )
2023-04-25 13:47:33 +00:00
if not module_options :
2023-05-08 18:39:36 +00:00
context . log . fail ( " Select an option, example: -M daclread -o TARGET=Administrator ACTION=read " )
2023-10-14 22:36:20 +00:00
sys . exit ( 1 )
2023-04-25 13:47:33 +00:00
2023-05-02 15:17:59 +00:00
if module_options and " TARGET " in module_options :
2023-10-15 11:18:21 +00:00
context . log . debug ( " There is a target specified! " )
2023-05-02 15:17:59 +00:00
if re . search ( r " ^(.+) \ /([^ \ /]+)$ " , module_options [ " TARGET " ] ) is not None :
2022-07-30 12:35:55 +00:00
try :
2023-10-16 16:52:45 +00:00
self . target_file = open ( module_options [ " TARGET " ] ) # noqa: SIM115
2022-07-30 12:35:55 +00:00
self . target_sAMAccountName = None
2023-09-20 15:59:16 +00:00
except Exception :
2023-04-21 10:17:50 +00:00
context . log . fail ( " The file doesn ' t exist or cannot be openned. " )
2022-07-30 12:35:55 +00:00
else :
2023-10-15 11:18:21 +00:00
context . log . debug ( f " Setting target_sAMAccountName to { module_options [ ' TARGET ' ] } " )
2023-05-02 15:17:59 +00:00
self . target_sAMAccountName = module_options [ " TARGET " ]
2022-07-30 12:35:55 +00:00
self . target_file = None
self . target_DN = None
self . target_SID = None
2023-05-02 15:17:59 +00:00
if module_options and " TARGET_DN " in module_options :
self . target_DN = module_options [ " TARGET_DN " ]
2022-07-30 12:35:55 +00:00
self . target_sAMAccountName = None
self . target_file = None
2023-05-02 15:17:59 +00:00
if module_options and " PRINCIPAL " in module_options :
self . principal_sAMAccountName = module_options [ " PRINCIPAL " ]
2022-07-30 12:35:55 +00:00
else :
self . principal_sAMAccountName = None
2023-03-31 02:05:45 +00:00
self . principal_sid = None
2022-07-30 12:35:55 +00:00
2023-05-02 15:17:59 +00:00
if module_options and " ACTION " in module_options :
self . action = module_options [ " ACTION " ]
2022-07-30 12:35:55 +00:00
else :
2023-05-02 15:17:59 +00:00
self . action = " read "
if module_options and " ACE_TYPE " in module_options :
self . ace_type = module_options [ " ACE_TYPE " ]
2022-07-30 12:35:55 +00:00
else :
2023-05-02 15:17:59 +00:00
self . ace_type = " allowed "
if module_options and " RIGHTS " in module_options :
self . rights = module_options [ " RIGHTS " ]
2022-07-30 12:35:55 +00:00
else :
self . rights = None
2023-05-02 15:17:59 +00:00
if module_options and " RIGHTS_GUID " in module_options :
self . rights_guid = module_options [ " RIGHTS_GUID " ]
2022-07-30 12:35:55 +00:00
else :
self . rights_guid = None
self . filename = None
def on_login ( self , context , connection ) :
2023-10-31 11:32:26 +00:00
""" On a successful LDAP login we perform a search for the targets ' SID, their Security Descriptors and the principal ' s SID if there is one specified """
context . log . highlight ( " Be careful, this module cannot read the DACLS recursively. " )
2022-07-30 12:35:55 +00:00
self . baseDN = connection . ldapConnection . _baseDN
self . ldap_session = connection . ldapConnection
# Searching for the principal SID
if self . principal_sAMAccountName is not None :
_lookedup_principal = self . principal_sAMAccountName
try :
2023-03-31 02:05:45 +00:00
self . principal_sid = format_sid (
self . ldap_session . search (
searchBase = self . baseDN ,
2023-09-24 04:06:51 +00:00
searchFilter = f " (sAMAccountName= { escape_filter_chars ( _lookedup_principal ) } ) " ,
2023-05-02 15:17:59 +00:00
attributes = [ " objectSid " ] ,
2023-09-23 01:10:21 +00:00
) [ 0 ] [ 1 ] [ 0 ] [ 1 ] [ 0 ]
2023-05-02 15:17:59 +00:00
)
2023-09-24 04:06:51 +00:00
context . log . highlight ( f " Found principal SID to filter on: { self . principal_sid } " )
2023-09-20 15:59:16 +00:00
except Exception :
2023-09-24 04:06:51 +00:00
context . log . fail ( f " Principal SID not found in LDAP ( { _lookedup_principal } ) " )
2023-10-14 22:36:20 +00:00
sys . exit ( 1 )
2022-07-30 12:35:55 +00:00
2023-10-31 11:32:26 +00:00
# Searching for the targets SID and their Security Descriptors
2022-07-30 12:35:55 +00:00
# If there is only one target
2023-03-30 20:36:58 +00:00
if ( self . target_sAMAccountName or self . target_DN ) and self . target_file is None :
2022-07-30 12:35:55 +00:00
# Searching for target account with its security descriptor
try :
self . search_target_principal_security_descriptor ( context , connection )
# Extract security descriptor data
self . target_principal_dn = self . target_principal [ 0 ]
2023-05-08 18:39:36 +00:00
self . principal_raw_security_descriptor = str ( self . target_principal [ 1 ] [ 0 ] [ 1 ] [ 0 ] ) . encode ( " latin-1 " )
self . principal_security_descriptor = ldaptypes . SR_SECURITY_DESCRIPTOR ( data = self . principal_raw_security_descriptor )
2023-09-24 04:06:51 +00:00
context . log . highlight ( f " Target principal found in LDAP ( { self . target_principal [ 0 ] } ) " )
2023-09-20 15:59:16 +00:00
except Exception :
2023-09-24 04:06:51 +00:00
context . log . fail ( f " Target SID not found in LDAP ( { self . target_sAMAccountName } ) " )
2023-10-14 22:36:20 +00:00
sys . exit ( 1 )
2022-07-30 12:35:55 +00:00
2023-05-02 15:17:59 +00:00
if self . action == " read " :
2022-07-30 12:35:55 +00:00
self . read ( context )
2023-05-02 15:17:59 +00:00
if self . action == " backup " :
2022-07-30 12:35:55 +00:00
self . backup ( context )
2023-03-31 02:05:45 +00:00
2022-07-30 12:35:55 +00:00
# If there are multiple targets
else :
targets = self . target_file . readlines ( )
for target in targets :
try :
self . target_sAMAccountName = target . strip ( )
# Searching for target account with its security descriptor
2023-05-08 18:39:36 +00:00
self . search_target_principal_security_descriptor ( context , connection )
2022-07-30 12:35:55 +00:00
# Extract security descriptor data
self . target_principal_dn = self . target_principal [ 0 ]
2023-05-08 18:39:36 +00:00
self . principal_raw_security_descriptor = str ( self . target_principal [ 1 ] [ 0 ] [ 1 ] [ 0 ] ) . encode ( " latin-1 " )
self . principal_security_descriptor = ldaptypes . SR_SECURITY_DESCRIPTOR ( data = self . principal_raw_security_descriptor )
2023-09-24 04:06:51 +00:00
context . log . highlight ( f " Target principal found in LDAP ( { self . target_sAMAccountName } ) " )
2023-09-20 15:59:16 +00:00
except Exception :
2023-09-24 04:06:51 +00:00
context . log . fail ( f " Target SID not found in LDAP ( { self . target_sAMAccountName } ) " )
2022-07-30 12:35:55 +00:00
continue
2023-05-02 15:17:59 +00:00
if self . action == " read " :
2022-07-30 12:35:55 +00:00
self . read ( context )
2023-05-02 15:17:59 +00:00
if self . action == " backup " :
2022-07-30 12:35:55 +00:00
self . backup ( context )
# Main read funtion
# Prints the parsed DACL
def read ( self , context ) :
2023-05-08 18:39:36 +00:00
parsed_dacl = self . parse_dacl ( context , self . principal_security_descriptor [ " Dacl " ] )
2023-03-31 02:05:45 +00:00
self . print_parsed_dacl ( context , parsed_dacl )
2022-07-30 12:35:55 +00:00
# Permits to export the DACL of the targets
# This function is called before any writing action (write, remove or restore)
def backup ( self , context ) :
backup = { }
2023-05-08 18:39:36 +00:00
backup [ " sd " ] = binascii . hexlify ( self . principal_raw_security_descriptor ) . decode ( " latin-1 " )
2022-07-30 12:35:55 +00:00
backup [ " dn " ] = str ( self . target_principal_dn )
if not self . filename :
2023-10-12 21:06:04 +00:00
self . filename = " dacledit- {} - {} .bak " . format (
2023-05-02 15:17:59 +00:00
datetime . datetime . now ( ) . strftime ( " % Y % m %d - % H % M % S " ) ,
self . target_sAMAccountName ,
)
with codecs . open ( self . filename , " w " , " latin-1 " ) as outfile :
2022-07-30 12:35:55 +00:00
json . dump ( backup , outfile )
2023-05-02 15:17:59 +00:00
context . log . highlight ( " DACL backed up to %s " , self . filename )
2022-07-30 12:35:55 +00:00
self . filename = None
# Attempts to retrieve the DACL in the Security Descriptor of the specified target
def search_target_principal_security_descriptor ( self , context , connection ) :
_lookedup_principal = " "
# Set SD flags to only query for DACL
controls = security_descriptor_control ( sdflags = 0x04 )
if self . target_sAMAccountName is not None :
_lookedup_principal = self . target_sAMAccountName
2023-03-31 02:05:45 +00:00
target = self . ldap_session . search (
searchBase = self . baseDN ,
2023-09-24 04:06:51 +00:00
searchFilter = f " (sAMAccountName= { escape_filter_chars ( _lookedup_principal ) } ) " ,
2023-05-02 15:17:59 +00:00
attributes = [ " nTSecurityDescriptor " ] ,
searchControls = controls ,
2023-03-31 02:05:45 +00:00
)
2022-07-30 12:35:55 +00:00
if self . target_DN is not None :
_lookedup_principal = self . target_DN
2023-03-31 02:05:45 +00:00
target = self . ldap_session . search (
searchBase = self . baseDN ,
2023-09-24 04:06:51 +00:00
searchFilter = f " (distinguishedName= { _lookedup_principal } ) " ,
2023-05-02 15:17:59 +00:00
attributes = [ " nTSecurityDescriptor " ] ,
searchControls = controls ,
2023-03-31 02:05:45 +00:00
)
2022-07-30 12:35:55 +00:00
try :
self . target_principal = target [ 0 ]
2023-09-20 15:59:16 +00:00
except Exception :
2023-09-24 04:06:51 +00:00
context . log . fail ( f " Principal not found in LDAP ( { _lookedup_principal } ), probably an LDAP session issue. " )
2023-10-14 22:36:20 +00:00
sys . exit ( 0 )
2022-07-30 12:35:55 +00:00
2023-10-31 11:32:26 +00:00
# Attempts to retrieve the SID and Distinguisehd Name from the sAMAccountName
2022-07-30 12:35:55 +00:00
# Not used for the moment
# - samname : a sAMAccountName
def get_user_info ( self , context , samname ) :
2023-03-31 02:05:45 +00:00
self . ldap_session . search (
searchBase = self . baseDN ,
2023-09-24 04:06:51 +00:00
searchFilter = f " (sAMAccountName= { escape_filter_chars ( samname ) } ) " ,
2023-05-02 15:17:59 +00:00
attributes = [ " objectSid " ] ,
2023-03-31 02:05:45 +00:00
)
2022-07-30 12:35:55 +00:00
try :
dn = self . ldap_session . entries [ 0 ] . entry_dn
2023-05-02 15:17:59 +00:00
sid = format_sid ( self . ldap_session . entries [ 0 ] [ " objectSid " ] . raw_values [ 0 ] )
2022-07-30 12:35:55 +00:00
return dn , sid
2023-09-20 15:59:16 +00:00
except Exception :
2023-09-24 04:06:51 +00:00
context . log . fail ( f " User not found in LDAP: { samname } " )
2022-07-30 12:35:55 +00:00
return False
# Attempts to resolve a SID and return the corresponding samaccountname
# - sid : the SID to resolve
def resolveSID ( self , context , sid ) :
# Tries to resolve the SID from the well known SIDs
2023-10-14 19:56:22 +00:00
if sid in WELL_KNOWN_SIDS :
2022-07-30 12:35:55 +00:00
return WELL_KNOWN_SIDS [ sid ]
# Tries to resolve the SID from the LDAP domain dump
else :
try :
2023-09-20 15:59:16 +00:00
self . ldap_session . search (
2023-03-31 02:05:45 +00:00
searchBase = self . baseDN ,
2023-09-24 04:06:51 +00:00
searchFilter = f " (objectSid= { sid } ) " ,
2023-05-02 15:17:59 +00:00
attributes = [ " sAMAccountName " ] ,
2023-09-23 01:10:21 +00:00
) [ 0 ] [ 0 ]
2023-10-14 18:16:28 +00:00
return self . ldap_session . search (
2023-03-31 02:05:45 +00:00
searchBase = self . baseDN ,
2023-09-24 04:06:51 +00:00
searchFilter = f " (objectSid= { sid } ) " ,
2023-05-02 15:17:59 +00:00
attributes = [ " sAMAccountName " ] ,
2023-09-23 01:10:21 +00:00
) [ 0 ] [ 1 ] [ 0 ] [ 1 ] [ 0 ]
2023-09-20 15:59:16 +00:00
except Exception :
2023-09-24 04:06:51 +00:00
context . log . debug ( f " SID not found in LDAP: { sid } " )
2022-07-30 12:35:55 +00:00
return " "
# Parses a full DACL
# - dacl : the DACL to parse, submitted in a Security Desciptor format
2023-03-31 02:05:45 +00:00
def parse_dacl ( self , context , dacl ) :
2022-07-30 12:35:55 +00:00
parsed_dacl = [ ]
context . log . debug ( " Parsing DACL " )
i = 0
2023-05-02 15:17:59 +00:00
for ace in dacl [ " Data " ] :
2023-03-31 02:05:45 +00:00
parsed_ace = self . parse_ace ( context , ace )
2022-07-30 12:35:55 +00:00
parsed_dacl . append ( parsed_ace )
i + = 1
return parsed_dacl
# Parses an access mask to extract the different values from a simple permission
# https://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights
2023-10-15 02:13:03 +00:00
def parse_perms ( self , access_mask ) :
perms = [ PERM . name for PERM in SIMPLE_PERMISSIONS if ( access_mask & PERM . value ) == PERM . value ]
# use bitwise NOT operator (~) and sum() function to clear the bits that have been processed
access_mask & = ~ sum ( PERM . value for PERM in SIMPLE_PERMISSIONS if ( access_mask & PERM . value ) == PERM . value )
perms + = [ PERM . name for PERM in ACCESS_MASK if access_mask & PERM . value ]
return perms
2022-07-30 12:35:55 +00:00
# Parses a specified ACE and extract the different values (Flags, Access Mask, Trustee, ObjectType, InheritedObjectType)
# - ace : the ACE to parse
2023-03-31 02:05:45 +00:00
def parse_ace ( self , context , ace ) :
2022-07-30 12:35:55 +00:00
# For the moment, only the Allowed and Denied Access ACE are supported
2023-05-02 15:17:59 +00:00
if ace [ " TypeName " ] in [
" ACCESS_ALLOWED_ACE " ,
" ACCESS_ALLOWED_OBJECT_ACE " ,
" ACCESS_DENIED_ACE " ,
" ACCESS_DENIED_OBJECT_ACE " ,
] :
2023-10-15 11:18:21 +00:00
_ace_flags = [ FLAG . name for FLAG in ACE_FLAGS if ace . hasFlag ( FLAG . value ) ]
parsed_ace = { " ACE Type " : ace [ " TypeName " ] , " ACE flags " : " , " . join ( _ace_flags ) or " None " }
2022-07-30 12:35:55 +00:00
# For standard ACE
# Extracts the access mask (by parsing the simple permissions) and the principal's SID
2023-05-02 15:17:59 +00:00
if ace [ " TypeName " ] in [ " ACCESS_ALLOWED_ACE " , " ACCESS_DENIED_ACE " ] :
2023-10-15 11:18:21 +00:00
access_mask = f " { ' , ' . join ( self . parse_perms ( ace [ ' Ace ' ] [ ' Mask ' ] [ ' Mask ' ] ) ) } (0x { ace [ ' Ace ' ] [ ' Mask ' ] [ ' Mask ' ] : x } ) "
trustee_sid = f " { self . resolveSID ( context , ace [ ' Ace ' ] [ ' Sid ' ] . formatCanonical ( ) ) or ' UNKNOWN ' } ( { ace [ ' Ace ' ] [ ' Sid ' ] . formatCanonical ( ) } ) "
parsed_ace = {
" Access mask " : access_mask ,
" Trustee (SID) " : trustee_sid
}
2023-10-16 16:52:45 +00:00
elif ace [ " TypeName " ] in [ " ACCESS_ALLOWED_OBJECT_ACE " , " ACCESS_DENIED_OBJECT_ACE " ] : # for object-specific ACE
2022-07-30 12:35:55 +00:00
# Extracts the mask values. These values will indicate the ObjectType purpose
2023-10-15 11:18:21 +00:00
access_mask_flags = [ FLAG . name for FLAG in ALLOWED_OBJECT_ACE_MASK_FLAGS if ace [ " Ace " ] [ " Mask " ] . hasPriv ( FLAG . value ) ]
parsed_ace [ " Access mask " ] = " , " . join ( access_mask_flags )
2022-07-30 12:35:55 +00:00
# Extracts the ACE flag values and the trusted SID
2023-10-15 11:18:21 +00:00
object_flags = [ FLAG . name for FLAG in OBJECT_ACE_FLAGS if ace [ " Ace " ] . hasFlag ( FLAG . value ) ]
parsed_ace [ " Flags " ] = " , " . join ( object_flags ) or " None "
2022-07-30 12:35:55 +00:00
# Extracts the ObjectType GUID values
2023-05-02 15:17:59 +00:00
if ace [ " Ace " ] [ " ObjectTypeLen " ] != 0 :
obj_type = bin_to_string ( ace [ " Ace " ] [ " ObjectType " ] ) . lower ( )
2022-07-30 12:35:55 +00:00
try :
2023-10-12 21:06:04 +00:00
parsed_ace [ " Object type (GUID) " ] = f " { OBJECT_TYPES_GUID [ obj_type ] } ( { obj_type } ) "
2022-07-30 12:35:55 +00:00
except KeyError :
2023-09-24 04:06:51 +00:00
parsed_ace [ " Object type (GUID) " ] = f " UNKNOWN ( { obj_type } ) "
2022-07-30 12:35:55 +00:00
# Extracts the InheritedObjectType GUID values
2023-05-02 15:17:59 +00:00
if ace [ " Ace " ] [ " InheritedObjectTypeLen " ] != 0 :
2023-05-08 18:39:36 +00:00
inh_obj_type = bin_to_string ( ace [ " Ace " ] [ " InheritedObjectType " ] ) . lower ( )
2022-07-30 12:35:55 +00:00
try :
2023-10-12 21:06:04 +00:00
parsed_ace [ " Inherited type (GUID) " ] = f " { OBJECT_TYPES_GUID [ inh_obj_type ] } ( { inh_obj_type } ) "
2022-07-30 12:35:55 +00:00
except KeyError :
2023-09-24 04:06:51 +00:00
parsed_ace [ " Inherited type (GUID) " ] = f " UNKNOWN ( { inh_obj_type } ) "
2022-07-30 12:35:55 +00:00
# Extract the Trustee SID (the object that has the right over the DACL bearer)
2023-10-12 21:06:04 +00:00
parsed_ace [ " Trustee (SID) " ] = " {} ( {} ) " . format (
2023-05-08 18:39:36 +00:00
self . resolveSID ( context , ace [ " Ace " ] [ " Sid " ] . formatCanonical ( ) ) or " UNKNOWN " ,
2023-05-02 15:17:59 +00:00
ace [ " Ace " ] [ " Sid " ] . formatCanonical ( ) ,
)
2023-10-16 16:52:45 +00:00
else : # if the ACE is not an access allowed
2023-09-24 04:06:51 +00:00
context . log . debug ( f " ACE Type ( { ace [ ' TypeName ' ] } ) unsupported for parsing yet, feel free to contribute " )
2023-10-15 11:18:21 +00:00
_ace_flags = [ FLAG . name for FLAG in ACE_FLAGS if ace . hasFlag ( FLAG . value ) ]
parsed_ace = {
" ACE type " : ace [ " TypeName " ] ,
" ACE flags " : " , " . join ( _ace_flags ) or " None " ,
" DEBUG " : " ACE type not supported for parsing by dacleditor.py, feel free to contribute " ,
}
2022-07-30 12:35:55 +00:00
return parsed_ace
2023-03-31 02:05:45 +00:00
def print_parsed_dacl ( self , context , parsed_dacl ) :
2023-10-15 11:18:21 +00:00
""" Prints a full DACL by printing each parsed ACE
parsed_dacl : a parsed DACL from parse_dacl ( )
"""
2022-07-30 12:35:55 +00:00
context . log . debug ( " Printing parsed DACL " )
i = 0
# If a specific right or a specific GUID has been specified, only the ACE with this right will be printed
# If an ACE type has been specified, only the ACE with this type will be specified
# If a principal has been specified, only the ACE where he is the trustee will be printed
for parsed_ace in parsed_dacl :
print_ace = True
# Filter on specific rights
if self . rights is not None :
try :
2023-05-08 18:39:36 +00:00
if ( self . rights == " FullControl " ) and ( self . rights not in parsed_ace [ " Access mask " ] ) :
2022-07-30 12:35:55 +00:00
print_ace = False
2023-05-08 18:39:36 +00:00
if ( self . rights == " DCSync " ) and ( ( " Object type (GUID) " not in parsed_ace ) or ( RIGHTS_GUID . DS_Replication_Get_Changes_All . value not in parsed_ace [ " Object type (GUID) " ] ) ) :
2022-07-30 12:35:55 +00:00
print_ace = False
2023-05-08 18:39:36 +00:00
if ( self . rights == " WriteMembers " ) and ( ( " Object type (GUID) " not in parsed_ace ) or ( RIGHTS_GUID . WriteMembers . value not in parsed_ace [ " Object type (GUID) " ] ) ) :
2022-07-30 12:35:55 +00:00
print_ace = False
2023-05-08 18:39:36 +00:00
if ( self . rights == " ResetPassword " ) and ( ( " Object type (GUID) " not in parsed_ace ) or ( RIGHTS_GUID . ResetPassword . value not in parsed_ace [ " Object type (GUID) " ] ) ) :
2022-07-30 12:35:55 +00:00
print_ace = False
except Exception as e :
2023-09-24 04:06:51 +00:00
context . log . fail ( f " Error filtering ACE, probably because of ACE type unsupported for parsing yet ( { e } ) " )
2023-03-31 02:05:45 +00:00
2022-07-30 12:35:55 +00:00
# Filter on specific right GUID
if self . rights_guid is not None :
try :
2023-05-08 18:39:36 +00:00
if ( " Object type (GUID) " not in parsed_ace ) or ( self . rights_guid not in parsed_ace [ " Object type (GUID) " ] ) :
2022-07-30 12:35:55 +00:00
print_ace = False
except Exception as e :
2023-09-24 04:06:51 +00:00
context . log . fail ( f " Error filtering ACE, probably because of ACE type unsupported for parsing yet ( { e } ) " )
2022-07-30 12:35:55 +00:00
# Filter on ACE type
2023-05-02 15:17:59 +00:00
if self . ace_type == " allowed " :
2022-07-30 12:35:55 +00:00
try :
2023-05-08 18:39:36 +00:00
if ( " ACCESS_ALLOWED_OBJECT_ACE " not in parsed_ace [ " ACE Type " ] ) and ( " ACCESS_ALLOWED_ACE " not in parsed_ace [ " ACE Type " ] ) :
2022-07-30 12:35:55 +00:00
print_ace = False
except Exception as e :
2023-09-24 04:06:51 +00:00
context . log . fail ( f " Error filtering ACE, probably because of ACE type unsupported for parsing yet ( { e } ) " )
2022-07-30 12:35:55 +00:00
else :
try :
2023-05-08 18:39:36 +00:00
if ( " ACCESS_DENIED_OBJECT_ACE " not in parsed_ace [ " ACE Type " ] ) and ( " ACCESS_DENIED_ACE " not in parsed_ace [ " ACE Type " ] ) :
2022-07-30 12:35:55 +00:00
print_ace = False
except Exception as e :
2023-09-24 04:06:51 +00:00
context . log . fail ( f " Error filtering ACE, probably because of ACE type unsupported for parsing yet ( { e } ) " )
2023-03-31 02:05:45 +00:00
2022-07-30 12:35:55 +00:00
# Filter on trusted principal
2023-03-31 02:05:45 +00:00
if self . principal_sid is not None :
2022-07-30 12:35:55 +00:00
try :
2023-05-02 15:17:59 +00:00
if self . principal_sid not in parsed_ace [ " Trustee (SID) " ] :
2022-07-30 12:35:55 +00:00
print_ace = False
except Exception as e :
2023-09-24 04:06:51 +00:00
context . log . fail ( f " Error filtering ACE, probably because of ACE type unsupported for parsing yet ( { e } ) " )
2022-07-30 12:35:55 +00:00
if print_ace :
2023-04-25 13:47:33 +00:00
self . context . log . highlight ( " %-28s " % " ACE[ %d ] info " % i )
2023-03-31 02:05:45 +00:00
self . print_parsed_ace ( parsed_ace )
2022-07-30 12:35:55 +00:00
i + = 1
# Prints properly a parsed ACE
2023-03-31 02:05:45 +00:00
# - parsed_ace : a parsed ACE from parse_ace()
def print_parsed_ace ( self , parsed_ace ) :
2022-07-30 12:35:55 +00:00
elements_name = list ( parsed_ace . keys ( ) )
for attribute in elements_name :
2023-05-08 18:39:36 +00:00
self . context . log . highlight ( " %-26s : %s " % ( attribute , parsed_ace [ attribute ] ) )
2022-07-30 12:35:55 +00:00
# Retrieves the GUIDs for the specified rights
def build_guids_for_rights ( self ) :
_rights_guids = [ ]
if self . rights_guid is not None :
_rights_guids = [ self . rights_guid ]
elif self . rights == " WriteMembers " :
_rights_guids = [ RIGHTS_GUID . WriteMembers . value ]
elif self . rights == " ResetPassword " :
_rights_guids = [ RIGHTS_GUID . ResetPassword . value ]
elif self . rights == " DCSync " :
2023-05-02 15:17:59 +00:00
_rights_guids = [
RIGHTS_GUID . DS_Replication_Get_Changes . value ,
RIGHTS_GUID . DS_Replication_Get_Changes_All . value ,
]
self . context . log . highlight ( " Built GUID: %s " , _rights_guids )
2023-03-31 02:05:45 +00:00
return _rights_guids