Codebase list python-ldapdomaindump / 99be729
New upstream version 0.9.1 Sophie Brun 5 years ago
3 changed file(s) with 68 addition(s) and 38 deletion(s). Raw diff Collapse all Expand all
2020 # SOFTWARE.
2121 #
2222 ####################
23
23 from __future__ import unicode_literals
2424 import sys, os, re, codecs, json, argparse, getpass, base64
2525 # import class and constants
2626 from datetime import datetime
27 from urllib import quote_plus
28
27 try:
28 from urllib.parse import quote_plus
29 except ImportError:
30 from urllib import quote_plus
2931 import ldap3
3032 from ldap3 import Server, Connection, SIMPLE, SYNC, ALL, SASL, NTLM
31 from ldap3.core.exceptions import LDAPKeyError, LDAPAttributeError, LDAPCursorError
33 from ldap3.core.exceptions import LDAPKeyError, LDAPAttributeError, LDAPCursorError, LDAPInvalidDnError
3234 from ldap3.abstract import attribute, attrDef
3335 from ldap3.utils import dn
3436 from ldap3.protocol.formatters.formatters import format_sid
37 from builtins import str
38 from future.utils import itervalues, iteritems, native_str
3539
3640 # dnspython, for resolving hostnames
3741 import dns.resolver
451455 outflags = []
452456 if attr is None:
453457 return outflags
454 for flag, val in flags_def.items():
458 for flag, val in iteritems(flags_def):
455459 if attr.value & val:
456460 outflags.append(flag)
457461 return outflags
461465 outflags = []
462466 if attr is None:
463467 return outflags
464 for flag, val in flags_def.items():
468 for flag, val in iteritems(flags_def):
465469 if attr.value == val:
466470 outflags.append(flag)
467471 return outflags
472476 #Only if this is the first table it is an actual table, the others are just bodies of the first table
473477 #This makes sure that multiple tables have their columns aligned to make it less messy
474478 if firstTable:
475 of.append(u'<table>')
479 of.append('<table>')
476480 #Table header
477481 if header != '':
478 of.append(u'<thead><tr><td colspan="%d" id="cn_%s">%s</td></tr></thead>' % (len(attributes), self.formatId(header), header))
479 of.append(u'<tbody><tr>')
482 of.append('<thead><tr><td colspan="%d" id="cn_%s">%s</td></tr></thead>' % (len(attributes), self.formatId(header), header))
483 of.append('<tbody><tr>')
480484 for hdr in attributes:
481485 try:
482486 #Print alias of this attribute if there is one
483 of.append(u'<th>%s</th>' % self.htmlescape(attr_translations[hdr]))
487 of.append('<th>%s</th>' % self.htmlescape(attr_translations[hdr]))
484488 except KeyError:
485 of.append(u'<th>%s</th>' % self.htmlescape(hdr))
486 of.append(u'</tr>\n')
489 of.append('<th>%s</th>' % self.htmlescape(hdr))
490 of.append('</tr>\n')
487491 for li in listable:
488492 #Whether we should format group objects separately
489493 if specialGroupsFormat and 'group' in li['objectClass'].values:
490494 #Give it an extra class and pass it to the function below to make sure the CN is a link
491495 liIsGroup = True
492 of.append(u'<tr class="group">')
496 of.append('<tr class="group">')
493497 else:
494498 liIsGroup = False
495 of.append(u'<tr>')
499 of.append('<tr>')
496500 for att in attributes:
497501 try:
498 of.append(u'<td>%s</td>' % self.formatAttribute(li[att], liIsGroup))
502 of.append('<td>%s</td>' % self.formatAttribute(li[att], liIsGroup))
499503 except (LDAPKeyError, LDAPCursorError):
500 of.append(u'<td>&nbsp;</td>')
501 of.append(u'</tr>\n')
502 of.append(u'</tbody>\n')
503 return u''.join(of)
504 of.append('<td>&nbsp;</td>')
505 of.append('</tr>\n')
506 of.append('</tbody>\n')
507 return ''.join(of)
504508
505509 #Generate several HTML tables for grouped reports
506510 def generateGroupedHtmlTables(self, groups, attributes):
507511 first = True
508 for groupname, members in groups.iteritems():
512 for groupname, members in iteritems(groups):
509513 yield self.generateHtmlTable(members, attributes, groupname, first, specialGroupsFormat=True)
510514 if first:
511515 first = False
565569 return value.strftime('%x %X')
566570 except ValueError:
567571 #Invalid date
568 return u'0'
569 if type(value) is unicode:
572 return '0'
573 # Make sure it's a unicode string
574 if type(value) is bytes:
575 return value.encode('utf8')
576 if type(value) is str:
570577 return value#.encode('utf8')
571 if type(value) is str:
572 return unicode(value, errors='replace')#.encode('utf8')
573578 if type(value) is int:
574 return unicode(value)
579 return str(value)
575580 if value is None:
576581 return ''
577582 #Other type: just return it
618623
619624
620625 def formatCnWithGroupLink(self, cn):
621 return u'Group: <a href="#cn_%s" title="%s">%s</a>' % (self.formatId(cn), self.htmlescape(cn), self.htmlescape(cn))
626 return 'Group: <a href="#cn_%s" title="%s">%s</a>' % (self.formatId(cn), self.htmlescape(cn), self.htmlescape(cn))
622627
623628 #Convert a CN to a valid HTML id by replacing all non-ascii characters with a _
624629 def formatId(self, cn):
625630 return re.sub(r'[^a-zA-Z0-9_\-]+', '_', cn)
631
632 # Fallback function for dirty DN parsing in case ldap3 functions error out
633 def parseDnFallback(self, dn):
634 try:
635 indcn = dn[3:].index(',CN=')
636 indou = dn[3:].index(',OU=')
637 if indcn < indou:
638 cn = dn[3:].split(',CN=')[0]
639 else:
640 cn = dn[3:].split(',OU=')[0]
641 except ValueError:
642 cn = dn
643 return cn
626644
627645 #Format groups to readable HTML
628646 def formatGroupsHtml(self, grouplist):
629647 outcache = []
630648 for group in grouplist:
631 cn = self.unescapecn(dn.parse_dn(group)[0][1])
632 outcache.append(u'<a href="%s.html#cn_%s" title="%s">%s</a>' % (self.config.users_by_group, quote_plus(self.formatId(cn)), self.htmlescape(group), self.htmlescape(cn)))
649 try:
650 cn = self.unescapecn(dn.parse_dn(group)[0][1])
651 except LDAPInvalidDnError:
652 # Parsing failed, do it manually
653 cn = self.unescapecn(self.parseDnFallback(group))
654 outcache.append('<a href="%s.html#cn_%s" title="%s">%s</a>' % (self.config.users_by_group, quote_plus(self.formatId(cn)), self.htmlescape(group), self.htmlescape(cn)))
633655 return ', '.join(outcache)
634656
635657 #Format groups to readable HTML
636658 def formatGroupsGrep(self, grouplist):
637659 outcache = []
638660 for group in grouplist:
639 cn = self.unescapecn(dn.parse_dn(group)[0][1])
661 try:
662 cn = self.unescapecn(dn.parse_dn(group)[0][1])
663 except LDAPInvalidDnError:
664 # Parsing failed, do it manually
665 cn = self.unescapecn(self.parseDnFallback(group))
640666 outcache.append(cn)
641667 return ', '.join(outcache)
642668
706732 #Start of the list
707733 yield '['
708734 firstGroup = True
709 for group in groups.iteritems():
735 for group in iteritems(groups):
710736 if not firstGroup:
711737 #Separate items
712738 yield ','
798824
799825 #Some quick logging helpers
800826 def log_warn(text):
801 print '[!] %s' % text
827 print('[!] %s' % text)
802828 def log_info(text):
803 print '[*] %s' % text
829 print('[*] %s' % text)
804830 def log_success(text):
805 print '[+] %s' % text
831 print('[+] %s' % text)
806832
807833 def main():
808834 parser = argparse.ArgumentParser(description='Domain information dumper via LDAP. Dumps users/computers/groups and OS/membership information to HTML/JSON/greppable output.')
812838 #Main parameters
813839 #maingroup = parser.add_argument_group("Main options")
814840 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)")
815 parser.add_argument("-u", "--user", type=str, metavar='USERNAME', help="DOMAIN\\username for authentication, leave empty for anonymous authentication")
816 parser.add_argument("-p", "--password", type=str, metavar='PASSWORD', help="Password or LM:NTLM hash, will prompt if not specified")
841 parser.add_argument("-u", "--user", type=native_str, metavar='USERNAME', help="DOMAIN\\username for authentication, leave empty for anonymous authentication")
842 parser.add_argument("-p", "--password", type=native_str, metavar='PASSWORD', help="Password or LM:NTLM hash, will prompt if not specified")
817843 parser.add_argument("-at", "--authtype", type=str, choices=['NTLM', 'SIMPLE'], default='NTLM', help="Authentication type (NTLM or SIMPLE, default: NTLM)")
818844
819845 #Output parameters
878904 # define the server and the connection
879905 s = Server(args.host, get_info=ALL)
880906 log_info('Connecting to host...')
907
881908 c = Connection(s, user=args.user, password=args.password, authentication=authentication)
882909 log_info('Binding to host')
883910 # perform the Bind operation
0 from __future__ import unicode_literals
01 import argparse
12 import os
23 import logging
45 import codecs
56 import re
67 from ldapdomaindump import trust_flags, trust_directions
8 from builtins import str
9 from future.utils import itervalues, iteritems
710
811 logging.basicConfig()
912 logger = logging.getLogger('ldd2bloodhound')
8386 # Read group mapping - write to csv
8487 # file is already created here, we just append
8588 with codecs.open('group_membership.csv', 'a', 'utf-8') as outfile:
86 for group in self.groups_by_dn.itervalues():
89 for group in itervalues(self.groups_by_dn):
8790 for membergroup in group['memberOf']:
8891 try:
8992 outfile.write('%s,%s,%s\n' % (self.groups_by_dn[membergroup]['principal'], group['principal'], 'group'))
00 from setuptools import setup
11 setup(name='ldapdomaindump',
2 version='0.8.7',
2 version='0.9.1',
33 description='Active Directory information dumper via LDAP',
44 author='Dirk-jan Mollema',
55 author_email='[email protected]',
66 url='https://github.com/dirkjanm/ldapdomaindump/',
77 packages=['ldapdomaindump'],
8 install_requires=['dnspython','ldap3>=2.0'],
8 install_requires=['dnspython', 'ldap3==2.5.1', 'future'],
99 package_data={'ldapdomaindump': ['style.css']},
1010 include_package_data=True,
1111 scripts=['bin/ldapdomaindump', 'bin/ldd2bloodhound']