Skip to content

Instantly share code, notes, and snippets.

@dirkjanm
Created July 11, 2022 15:55
Show Gist options
  • Save dirkjanm/5e1e525c35ac846fa304eaa02c871c00 to your computer and use it in GitHub Desktop.
Save dirkjanm/5e1e525c35ac846fa304eaa02c871c00 to your computer and use it in GitHub Desktop.

Revisions

  1. dirkjanm created this gist Jul 11, 2022.
    123 changes: 123 additions & 0 deletions schemaquery.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,123 @@
    #!/usr/bin/env python
    ####################
    #
    # Copyright (c) 2022 Dirk-jan Mollema (@_dirkjan)
    #
    # 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.
    #
    # Get attribute sets from AD schema
    #
    ####################
    import sys
    import argparse
    from uuid import UUID
    import pprint

    import random
    import string
    import getpass
    from ldap3 import NTLM, Server, Connection, ALL
    import ldap3
    from ldap3.protocol.microsoft import security_descriptor_control

    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='Get attribute sets from AD schema')
    parser._optionals.title = "Main options"
    parser._positionals.title = "Required options"

    #Main parameters
    #maingroup = parser.add_argument_group("Main options")
    parser.add_argument("host", metavar='HOSTNAME', help="Hostname/ip or ldap://host:port connection string to connect to")
    parser.add_argument("-u", "--user", metavar='USERNAME', help="DOMAIN\\username for authentication")
    parser.add_argument("-p", "--password", 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()

    controls = security_descriptor_control(sdflags=0x04)
    # 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)
    print_o('Bind OK')

    sresult = c.extend.standard.paged_search('CN=Extended-Rights,'+ c.server.info.other['configurationNamingContext'][0],
    '(rightsGuid=*)',
    attributes=['name', 'rightsGuid', 'displayName'])
    objecttype_guid_map = {}
    guid_objecttype_map = {}
    sets = {}
    for res in sresult:
    if res['attributes']['rightsGuid']:
    guid = res['attributes']['rightsGuid'].lower()
    attname = res['attributes']['displayName']
    name = res['attributes']['name']
    guid_objecttype_map[guid] = (name, attname)

    sresult = c.extend.standard.paged_search(c.server.info.other['schemaNamingContext'][0],
    '(objectClass=*)',
    attributes=['name', 'schemaidguid', 'attributeSecurityGUID'])
    # pprint.pprint(guid_objecttype_map)
    # return
    for res in sresult:
    if res['attributes']['schemaIDGUID']:
    guid = str(UUID(bytes_le=res['attributes']['schemaIDGUID']))
    attname = res['attributes']['name'].lower()
    objecttype_guid_map[attname] = guid
    setguid_bin = res['attributes']['attributeSecurityGUID']
    if setguid_bin:
    setguid = str(UUID(bytes_le=setguid_bin))
    try:
    sets[setguid].append(attname)
    except KeyError:
    sets[setguid] = [attname]
    # pprint.pprint(objecttype_guid_map)
    # pprint.pprint(sets)
    for setguid, setcontent in sets.items():
    try:
    print("{0} [{1}] (GUID: {2}".format(guid_objecttype_map[setguid][0], guid_objecttype_map[setguid][1], setguid ))
    except KeyError:
    print(f'Unknown property set {setguid}')
    pprint.pprint(setcontent)
    if __name__ == '__main__':
    main()