-
-
Save dirkjanm/a2087c27888a15410b4622009c7f2d41 to your computer and use it in GitHub Desktop.
| #!/usr/bin/env python | |
| #################### | |
| # | |
| # Copyright (c) 2018 Dirk-jan Mollema - Fox-IT | |
| # | |
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |
| # of this software and associated documentation files (the "Software"), to deal | |
| # in the Software without restriction, including without limitation the rights | |
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| # copies of the Software, and to permit persons to whom the Software is | |
| # furnished to do so, subject to the following conditions: | |
| # | |
| # The above copyright notice and this permission notice shall be included in all | |
| # copies or substantial portions of the Software. | |
| # | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| # SOFTWARE. | |
| # | |
| # Test nTSecurityDescriptor encoding/decoding for impacket | |
| # | |
| # Uses impacket and ldap3 | |
| # Install impacket from git, and ldap3 via pip | |
| # | |
| #################### | |
| import sys | |
| import argparse | |
| import ldapdomaindump | |
| import impacket | |
| import getpass | |
| import binascii | |
| from impacket.ldap.ldaptypes import ACE, ACCESS_ALLOWED_OBJECT_ACE, ACCESS_MASK, LDAP_SID, SR_SECURITY_DESCRIPTOR | |
| from struct import unpack, pack | |
| from ldap3 import NTLM, Server, Connection, ALL, LEVEL, MODIFY_REPLACE | |
| from ldap3.core.exceptions import LDAPProtocolErrorResult | |
| from pyasn1.type.namedtype import NamedTypes, NamedType | |
| from pyasn1.type.univ import Sequence, OctetString, Integer | |
| from ldap3.protocol.controls import build_control | |
| from impacket.uuid import string_to_bin | |
| class SdFlags(Sequence): | |
| # SDFlagsRequestValue ::= SEQUENCE { | |
| # Flags INTEGER | |
| # } | |
| componentType = NamedTypes(NamedType('Flags', Integer()) | |
| ) | |
| def get_sd_controls(sdflags=0x04): | |
| sdcontrol = SdFlags() | |
| sdcontrol.setComponentByName('Flags', sdflags) | |
| controls = [build_control('1.2.840.113556.1.4.801', True, sdcontrol)] | |
| return controls | |
| def create_object_ace(privguid, sid): | |
| nace = ACE() | |
| nace['AceType'] = ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE | |
| nace['AceFlags'] = 0x00 | |
| acedata = ACCESS_ALLOWED_OBJECT_ACE() | |
| acedata['Mask'] = ACCESS_MASK() | |
| acedata['Mask']['Mask'] = ACCESS_ALLOWED_OBJECT_ACE.ADS_RIGHT_DS_CONTROL_ACCESS | |
| acedata['ObjectType'] = string_to_bin(privguid) | |
| acedata['InheritedObjectType'] = '' | |
| acedata['Sid'] = LDAP_SID() | |
| acedata['Sid'].fromCanonical(sid) | |
| acedata['Flags'] = ACCESS_ALLOWED_OBJECT_ACE.ACE_OBJECT_TYPE_PRESENT | |
| nace['Ace'] = acedata | |
| return nace | |
| def print_m(string): | |
| sys.stderr.write('\033[94m[-]\033[0m %s\n' % (string)) | |
| def print_o(string): | |
| sys.stderr.write('\033[92m[+]\033[0m %s\n' % (string)) | |
| def print_f(string): | |
| sys.stderr.write('\033[91m[!]\033[0m %s\n' % (string)) | |
| def main(): | |
| parser = argparse.ArgumentParser(description='Test nTSecurityDescriptor encoding/decoding for impacket') | |
| parser._optionals.title = "Main options" | |
| parser._positionals.title = "Required options" | |
| #Main parameters | |
| #maingroup = parser.add_argument_group("Main options") | |
| parser.add_argument("host", type=str,metavar='HOSTNAME',help="Hostname/ip or ldap://host:port connection string to connect to") | |
| parser.add_argument("-u","--user",type=str,metavar='USERNAME',help="DOMAIN\username for authentication, leave empty for anonymous authentication") | |
| parser.add_argument("-p","--password",type=str,metavar='PASSWORD',help="Password or LM:NTLM hash, will prompt if not specified") | |
| args = parser.parse_args() | |
| #Prompt for password if not set | |
| authentication = None | |
| if args.user is not None: | |
| authentication = NTLM | |
| if not '\\' in args.user: | |
| print_f('Username must include a domain, use: DOMAIN\username') | |
| sys.exit(1) | |
| if args.password is None: | |
| args.password = getpass.getpass() | |
| # define the server and the connection | |
| s = Server(args.host, get_info=ALL) | |
| print_m('Connecting to host...') | |
| c = Connection(s, user=args.user, password=args.password, authentication=authentication) | |
| print_m('Binding to host') | |
| # perform the Bind operation | |
| if not c.bind(): | |
| print_f('Could not bind with specified credentials') | |
| print_f(c.result) | |
| sys.exit(1) | |
| rootdn = s.info.other['rootDomainNamingContext'][0] | |
| print_o('Bind OK') | |
| perms = lookup_permissions(c, rootdn) | |
| controls = get_sd_controls() | |
| entries = c.extend.standard.paged_search(rootdn, '(objectClass=*)', attributes=['nTSecurityDescriptor'], controls=controls, generator=True) | |
| print_m('Will now enumerate all domain objects. This may take a while depending on the domain size.') | |
| impacket.ldap.ldaptypes.RECALC_ACE_SIZE = False | |
| c_all = 0 | |
| c_ok = 0 | |
| c_fail = 0 | |
| c_no_access = 0 | |
| while True: | |
| try: | |
| e = entries.next() | |
| except LDAPProtocolErrorResult: | |
| pass | |
| except StopIteration: | |
| break | |
| if e['type'] != 'searchResEntry': | |
| continue | |
| dn = e['dn'] | |
| c_all += 1 | |
| if c_all % 100 == 0: | |
| print_m('Processed %d objects' % c_all) | |
| try: | |
| sd = e['raw_attributes']['nTSecurityDescriptor'][0] | |
| except IndexError: | |
| c_no_access += 1 | |
| continue | |
| if len(controls) > 1: | |
| controls.pop() | |
| a = SR_SECURITY_DESCRIPTOR() | |
| a.fromString(sd) | |
| b = a.getData() | |
| if b == sd: | |
| c_ok += 1 | |
| else: | |
| c_fail += 1 | |
| print 'Failed: %d' % c_fail | |
| print 'Ok: %d' % c_ok | |
| print 'No access (or empty SD): %d' % c_no_access | |
| if c_fail == 0: | |
| print_o('No objects failed to encode/decode, yay!') | |
| else: | |
| print_f('Whoops, not all objects were encoded/decoded succesfully') | |
| def lookup_permissions(ldapconnection, rootdn): | |
| ldapconnection.extend.standard.paged_search('CN=Extended-Rights,CN=Configuration,%s' % rootdn, '(rightsGuid=*)', search_scope=LEVEL, attributes=['displayName','rightsGuid'], paged_size=500, generator=False) | |
| ep = {} | |
| for e in ldapconnection.entries: | |
| ep[e['rightsGuid'].values[0].lower()] = e['displayName'].values[0] | |
| ldapconnection.extend.standard.paged_search('CN=Schema,CN=Configuration,%s' % rootdn, '(schemaIdGuid=*)', search_scope=LEVEL, attributes=['name','schemaIdGuid'], paged_size=500, generator=False) | |
| for e in ldapconnection.entries: | |
| ep[e['schemaIdGuid'].values[0].lower()] = e['name'].values[0] | |
| return ep | |
| if __name__ == '__main__': | |
| main() |
Unable to decode this kind of ntsecuritydescriptor b'0100049c000000000000000000000000140000000400d4000500000005003800300100000100000068c9100efb78d21190d400c04f79dc550105000000000005150000009328446371b3986185a90c5c0002000005003800300100000100000068c9100efb78d21190d400c04f79dc550105000000000005150000009328446371b3986185a90c5c0702000000002400ff000f000105000000000005150000009328446371b3986185a90c5c0002000000002400ff000f000105000000000005150000009328446371b3986185a90c5c07020000000014009400020001010000000000050b00' on python3
on sd.fromString(secDesc)
Getting exception as
struct.error: ('unpack requires a buffer of 1 bytes', "When unpacking field 'Revision | <B | b''[:1]'") Same data works perfectly when I use C# RawSecurityDescriptor. Am I doing something silly ?
Did you solve this im having a similar issue
if secDesc[0:2] == '0x':
secDesc = secDesc[2:]
sd.fromString(binascii.unhexlify(secDesc)) #worked for me.
Awesome! The get_sd_controls function gave me what I needed to retrieve the DACLs from AD objects using a non-privileged account. Thanks :)
Unable to decode this kind of ntsecuritydescriptor
b'0100049c000000000000000000000000140000000400d4000500000005003800300100000100000068c9100efb78d21190d400c04f79dc550105000000000005150000009328446371b3986185a90c5c0002000005003800300100000100000068c9100efb78d21190d400c04f79dc550105000000000005150000009328446371b3986185a90c5c0702000000002400ff000f000105000000000005150000009328446371b3986185a90c5c0002000000002400ff000f000105000000000005150000009328446371b3986185a90c5c07020000000014009400020001010000000000050b00' on python3
on sd.fromString(secDesc)
Getting exception as
struct.error: ('unpack requires a buffer of 1 bytes', "When unpacking field 'Revision | <B | b''[:1]'")
Same data works perfectly when I use C# RawSecurityDescriptor. Am I doing something silly ?