コード例 #1
0
def main():
    parser = argparse.ArgumentParser(
        description=
        'Query/modify DNS records for Active Directory integrated DNS via LDAP'
    )
    parser._optionals.title = "Main options"
    parser._positionals.title = "Required options"

    #Main parameters
    #maingroup = parser.add_argument_group("Main options")
    parser.add_argument(
        "host",
        type=native_str,
        metavar='HOSTNAME',
        help="Hostname/ip or ldap://host:port connection string to connect to")
    parser.add_argument("-u",
                        "--user",
                        type=native_str,
                        metavar='USERNAME',
                        help="DOMAIN\\username for authentication.")
    parser.add_argument(
        "-p",
        "--password",
        type=native_str,
        metavar='PASSWORD',
        help="Password or LM:NTLM hash, will prompt if not specified")
    parser.add_argument(
        "--forest",
        action='store_true',
        help="Search the ForestDnsZones instead of DomainDnsZones")
    parser.add_argument(
        "--legacy",
        action='store_true',
        help="Search the System partition (legacy DNS storage)")
    parser.add_argument(
        "--zone",
        help="Zone to search in (if different than the current domain)")
    parser.add_argument(
        "--print-zones",
        action='store_true',
        help=
        "Only query all zones on the DNS server, no other modifications are made"
    )
    parser.add_argument("-v",
                        "--verbose",
                        action='store_true',
                        help="Show verbose info")
    parser.add_argument("-d",
                        "--debug",
                        action='store_true',
                        help="Show debug info")
    parser.add_argument("-r",
                        "--resolve",
                        action='store_true',
                        help="Resolve hidden recoreds via DNS")
    parser.add_argument("--dns-tcp",
                        action='store_true',
                        help="Use DNS over TCP")
    parser.add_argument("--include-tombstoned",
                        action='store_true',
                        help="Include tombstoned (deleted) records")
    parser.add_argument("--ssl",
                        action='store_true',
                        help="Connect to LDAP server using SSL")
    parser.add_argument(
        "--referralhosts",
        action='store_true',
        help="Allow passthrough authentication to all referral hosts")
    parser.add_argument(
        "--dcfilter",
        action='store_true',
        help="Use an alternate filter to identify DNS record types")
    parser.add_argument(
        "--sslprotocol",
        type=native_str,
        help=
        "SSL version for LDAP connection, can be SSLv23, TLSv1, TLSv1_1 or TLSv1_2"
    )

    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)
    if args.ssl:
        s = Server(args.host, get_info=ALL, port=636, use_ssl=True)
    if args.sslprotocol:
        v = {'SSLv23': 2, 'TLSv1': 3, 'TLSv1_1': 4, 'TLSv1_2': 5}
        if args.sslprotocol not in v.keys():
            parser.print_help(sys.stderr)
            sys.exit(1)
        s = Server(args.host,
                   get_info=ALL,
                   port=636,
                   use_ssl=True,
                   tls=Tls(validate=0, version=v[args.sslprotocol]))
    if args.referralhosts:
        s.allowed_referral_hosts = [('*', True)]
    print_m('Connecting to host...')
    c = Connection(s,
                   user=args.user,
                   password=args.password,
                   authentication=authentication,
                   auto_referrals=False)
    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')
    domainroot = s.info.other['defaultNamingContext'][0]
    forestroot = s.info.other['rootDomainNamingContext'][0]
    if args.forest:
        dnsroot = 'CN=MicrosoftDNS,DC=ForestDnsZones,%s' % forestroot
    else:
        if args.legacy:
            dnsroot = 'CN=MicrosoftDNS,CN=System,%s' % domainroot
        else:
            dnsroot = 'CN=MicrosoftDNS,DC=DomainDnsZones,%s' % domainroot

    if args.print_zones:
        domaindnsroot = 'CN=MicrosoftDNS,DC=DomainDnsZones,%s' % domainroot
        zones = get_dns_zones(c, domaindnsroot, args.verbose)
        if len(zones) > 0:
            print_m('Found %d domain DNS zones:' % len(zones))
            for zone in zones:
                print('    %s' % zone)
        forestdnsroot = 'CN=MicrosoftDNS,DC=ForestDnsZones,%s' % forestroot
        zones = get_dns_zones(c, forestdnsroot, args.verbose)
        if len(zones) > 0:
            print_m('Found %d forest DNS zones (dump with --forest):' %
                    len(zones))
            for zone in zones:
                print('    %s' % zone)
        legacydnsroot = 'CN=MicrosoftDNS,CN=System,%s' % domainroot
        zones = get_dns_zones(c, legacydnsroot, args.verbose)
        if len(zones) > 0:
            print_m('Found %d legacy DNS zones (dump with --legacy):' %
                    len(zones))
            for zone in zones:
                print('    %s' % zone)
        return

    if args.zone:
        zone = args.zone
    else:
        # Default to current domain
        zone = ldap2domain(domainroot)

    searchtarget = 'DC=%s,%s' % (zone, dnsroot)
    print_m('Querying zone for records')
    sfilter = '(objectClass=*)' if not args.dcfilter else '(DC=*)'
    c.extend.standard.paged_search(
        searchtarget,
        sfilter,
        search_scope=LEVEL,
        attributes=['dnsRecord', 'dNSTombstoned', 'name'],
        paged_size=500,
        generator=False)
    targetentry = None
    if args.resolve:
        dnsresolver = get_dns_resolver(args.host)
    else:
        dnsresolver = None
    outdata = []
    for targetentry in c.response:
        if targetentry['type'] != 'searchResEntry':
            print(targetentry)
            continue
        if not targetentry['attributes']['name']:
            # No permission to view those records
            recordname = targetentry['dn'][3:targetentry['dn'].
                                           index(searchtarget) - 1]
            if not args.resolve:
                outdata.append({'name': recordname, 'type': '?', 'value': '?'})
                if args.verbose:
                    print_o('Found hidden record %s' % recordname)
            else:
                # Resolve A query
                try:
                    res = dnsresolver.query('%s.%s.' % (recordname, zone),
                                            'A',
                                            tcp=args.dns_tcp,
                                            raise_on_no_answer=False)
                except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN,
                        dns.resolver.Timeout, dns.name.EmptyLabel) as e:
                    if args.verbose:
                        print_f(str(e))
                    print_m(
                        'Could not resolve node %s (probably no A record assigned to name)'
                        % recordname)
                    outdata.append({
                        'name': recordname,
                        'type': '?',
                        'value': '?'
                    })
                    continue
                if len(res.response.answer) == 0:
                    print_m(
                        'Could not resolve node %s (probably no A record assigned to name)'
                        % recordname)
                    outdata.append({
                        'name': recordname,
                        'type': '?',
                        'value': '?'
                    })
                    continue
                if args.verbose:
                    print_o('Resolved hidden record %s' % recordname)
                for answer in res.response.answer:
                    try:
                        outdata.append({
                            'name':
                            recordname,
                            'type':
                            RECORD_TYPE_MAPPING[answer.rdtype],
                            'value':
                            str(answer[0])
                        })
                    except KeyError:
                        print_m('Unexpected record type seen: {}'.format(
                            answer.rdtype))
        else:
            recordname = targetentry['attributes']['name']
            if args.verbose:
                print_o('Found record %s' % targetentry['attributes']['name'])

        # Skip tombstoned records unless requested
        if targetentry['attributes'][
                'dNSTombstoned'] and not args.include_tombstoned:
            continue

        for record in targetentry['raw_attributes']['dnsRecord']:
            dr = DNS_RECORD(record)
            # dr.dump()
            # print targetentry['dn']
            if args.debug:
                print_record(dr, targetentry['attributes']['dNSTombstoned'])
            if dr['Type'] == 1:
                address = DNS_RPC_RECORD_A(dr['Data'])
                outdata.append({
                    'name': recordname,
                    'type': RECORD_TYPE_MAPPING[dr['Type']],
                    'value': address.formatCanonical()
                })
            if dr['Type'] in [
                    a for a in RECORD_TYPE_MAPPING
                    if RECORD_TYPE_MAPPING[a] in ['CNAME', 'NS']
            ]:
                address = DNS_RPC_RECORD_NODE_NAME(dr['Data'])
                outdata.append({
                    'name':
                    recordname,
                    'type':
                    RECORD_TYPE_MAPPING[dr['Type']],
                    'value':
                    address[list(address.fields)[0]].toFqdn()
                })
            elif dr['Type'] == 28:
                address = DNS_RPC_RECORD_AAAA(dr['Data'])
                outdata.append({
                    'name': recordname,
                    'type': RECORD_TYPE_MAPPING[dr['Type']],
                    'value': address.formatCanonical()
                })
            elif dr['Type'] not in [
                    a for a in RECORD_TYPE_MAPPING
                    if RECORD_TYPE_MAPPING[a] in ['A', 'AAAA,'
                                                  'CNAME', 'NS']
            ]:
                if args.debug:
                    print_m('Unexpected record type seen: {}'.format(
                        dr['Type']))

            continue
    print_o('Found %d records' % len(outdata))
    with codecs.open('records.csv', 'w', 'utf-8') as outfile:
        outfile.write('type,name,value\n')
        for row in outdata:
            outfile.write('{type},{name},{value}\n'.format(**row))
コード例 #2
0
def main():
    parser = argparse.ArgumentParser(
        description=
        'Domain information dumper via LDAP. Dumps users/computers/groups and OS/membership information to HTML/JSON/greppable output.'
    )
    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 (use ldaps:// to use SSL)"
    )
    parser.add_argument(
        "-u",
        "--user",
        type=native_str,
        metavar='USERNAME',
        help=
        "DOMAIN\\username for authentication, leave empty for anonymous authentication"
    )
    parser.add_argument(
        "-p",
        "--password",
        type=native_str,
        metavar='PASSWORD',
        help="Password or LM:NTLM hash, will prompt if not specified")
    parser.add_argument(
        "-at",
        "--authtype",
        type=str,
        choices=['NTLM', 'SIMPLE'],
        default='NTLM',
        help="Authentication type (NTLM or SIMPLE, default: NTLM)")
    parser.add_argument("--ssl",
                        action='store_true',
                        help="Connect to LDAP server using SSL")
    parser.add_argument(
        "--referralhosts",
        action='store_true',
        help="Allow passthrough authentication to all referral hosts")
    parser.add_argument(
        "--sslprotocol",
        type=native_str,
        help=
        "SSL version for LDAP connection, can be SSLv23, TLSv1, TLSv1_1 or TLSv1_2"
    )

    #Output parameters
    outputgroup = parser.add_argument_group("Output options")
    outputgroup.add_argument(
        "-o",
        "--outdir",
        type=str,
        metavar='DIRECTORY',
        help="Directory in which the dump will be saved (default: current)")
    outputgroup.add_argument("--no-html",
                             action='store_true',
                             help="Disable HTML output")
    outputgroup.add_argument("--no-json",
                             action='store_true',
                             help="Disable JSON output")
    outputgroup.add_argument("--no-grep",
                             action='store_true',
                             help="Disable Greppable output")
    outputgroup.add_argument(
        "--grouped-json",
        action='store_true',
        default=False,
        help="Also write json files for grouped files (default: disabled)")
    outputgroup.add_argument(
        "-d",
        "--delimiter",
        help="Field delimiter for greppable output (default: tab)")

    #Additional options
    miscgroup = parser.add_argument_group("Misc options")
    miscgroup.add_argument(
        "-r",
        "--resolve",
        action='store_true',
        help=
        "Resolve computer hostnames (might take a while and cause high traffic on large networks)"
    )
    miscgroup.add_argument(
        "-n",
        "--dns-server",
        help=
        "Use custom DNS resolver instead of system DNS (try a domain controller IP)"
    )
    miscgroup.add_argument(
        "-m",
        "--minimal",
        action='store_true',
        default=False,
        help="Only query minimal set of attributes to limit memmory usage")
    miscgroup.add_argument(
        "-t",
        "--types",
        type=native_str,
        default="all",
        help=
        "Only perform the specified queries out of: users,groups,computers,policy,trusts"
    )

    args = parser.parse_args()
    #Create default config
    cnf = domainDumpConfig()
    #Dns lookups?
    if args.resolve:
        cnf.lookuphostnames = True
    #Custom dns server?
    if args.dns_server is not None:
        cnf.dnsserver = args.dns_server
    #Minimal attributes?
    if args.minimal:
        cnf.minimal = True
    #Custom separator?
    if args.delimiter is not None:
        cnf.grepsplitchar = args.delimiter
    #Disable html?
    if args.no_html:
        cnf.outputhtml = False
    #Disable json?
    if args.no_json:
        cnf.outputjson = False
    #Disable grep?
    if args.no_grep:
        cnf.outputgrep = False
    #Custom outdir?
    if args.outdir is not None:
        cnf.basepath = args.outdir
    #Do we really need grouped json files?
    cnf.groupedjson = args.grouped_json

    queries = ['users', 'groups', 'computers', 'policy', 'trusts']

    reports = [a.lstrip().rstrip() for a in args.types.split(',')]
    if reports == ['all']:
        reports = queries
    elif [a for a in reports if a not in queries]:
        print('Bad output types: ' +
              ','.join([a for a in reports if a not in queries]))
        sys.exit(1)

    cnf.reports = reports

    #Prompt for password if not set
    authentication = None
    if args.user is not None:
        if args.authtype == 'SIMPLE':
            authentication = 'SIMPLE'
        else:
            authentication = NTLM
        if not '\\' in args.user:
            log_warn('Username must include a domain, use: DOMAIN\\username')
            sys.exit(1)
        if args.password is None:
            args.password = getpass.getpass()
    else:
        log_info(
            'Connecting as anonymous user, dumping will probably fail. Consider specifying a username/password to login with'
        )
    # define the server and the connection
    s = Server(args.host, get_info=ALL)
    if args.ssl:
        s = Server(args.host, get_info=ALL, port=636, use_ssl=True)
    if args.sslprotocol:
        v = {'SSLv23': 2, 'TLSv1': 3, 'TLSv1_1': 4, 'TLSv1_2': 5}
        if args.sslprotocol not in v.keys():
            print('Bad SSL Protocol: %s' % (args.sslprotocol))
            parser.print_help(sys.stderr)
            sys.exit(1)
        s = Server(args.host,
                   get_info=ALL,
                   port=636,
                   use_ssl=True,
                   tls=Tls(validate=0, version=v[args.sslprotocol]))
    if args.referralhosts:
        s.allowed_referral_hosts = [('*', True)]
    log_info('Connecting to host...')

    c = Connection(s,
                   user=args.user,
                   password=args.password,
                   authentication=authentication)

    log_info('Binding to host')
    # perform the Bind operation
    if not c.bind():
        log_warn('Could not bind with specified credentials')
        log_warn(c.result)
        sys.exit(1)
    log_success('Bind OK')
    log_info('Starting domain dump')
    #Create domaindumper object
    dd = domainDumper(s, c, cnf)

    #Do the actual dumping
    dd.domainDump()
    log_success('Domain dump finished')