Codebase list python-ldapdomaindump / ed8bb2f
Import upstream version 0.9.3+git20210407.1.ab1b4c3 Kali Janitor 2 years ago
17 changed file(s) with 387 addition(s) and 179 deletion(s). Raw diff Collapse all Expand all
+0
-13
.editorconfig less more
0 # http://editorconfig.org
1 root = true
2
3 [*]
4 indent_style = space
5 indent_size = 4
6 end_of_line = crlf
7 charset = utf-8
8 trim_trailing_whitespace = true
9 insert_final_newline = true
10
11 [*.md]
12 trim_trailing_whitespace = false
+0
-5
.gitignore less more
0 build/
1 dist/
2 ldapdomaindump.egg-info
3 domainlookup.py
4 *.pyc
00 The MIT License (MIT)
11
2 Copyright (c) 2016 Dirk-jan
2 Copyright (c) 2020 Dirk-jan Mollema
33
44 Permission is hereby granted, free of charge, to any person obtaining a copy
55 of this software and associated documentation files (the "Software"), to deal
00 include ldapdomaindump/style.css
1 include LICENSE Readme.md
0 Metadata-Version: 1.0
1 Name: ldapdomaindump
2 Version: 0.9.3
3 Summary: Active Directory information dumper via LDAP
4 Home-page: https://github.com/dirkjanm/ldapdomaindump/
5 Author: Dirk-jan Mollema
6 Author-email: [email protected]
7 License: UNKNOWN
8 Description: UNKNOWN
9 Platform: UNKNOWN
0 # LDAPDomainDump
1 Active Directory information dumper via LDAP
2
3 ## Introduction
4 In an Active Directory domain, a lot of interesting information can be retrieved via LDAP by any authenticated user (or machine).
5 This makes LDAP an interesting protocol for gathering information in the recon phase of a pentest of an internal network.
6 A problem is that data from LDAP often is not available in an easy to read format.
7
8 ldapdomaindump is a tool which aims to solve this problem, by collecting and parsing information available via LDAP and outputting it in a human readable HTML format, as well as machine readable json and csv/tsv/greppable files.
9
10 The tool was designed with the following goals in mind:
11 - Easy overview of all users/groups/computers/policies in the domain
12 - Authentication both via username and password, as with NTLM hashes (requires ldap3 >=1.3.1)
13 - Possibility to run the tool with an existing authenticated connection to an LDAP service, allowing for integration with relaying tools such as impackets ntlmrelayx
14
15 The tool outputs several files containing an overview of objects in the domain:
16 - *domain_groups*: List of groups in the domain
17 - *domain_users*: List of users in the domain
18 - *domain_computers*: List of computer accounts in the domain
19 - *domain_policy*: Domain policy such as password requirements and lockout policy
20 - *domain_trusts*: Incoming and outgoing domain trusts, and their properties
21
22 As well as two grouped files:
23 - *domain_users_by_group*: Domain users per group they are member of
24 - *domain_computers_by_os*: Domain computers sorted by Operating System
25
26 ## Dependencies and installation
27 Requires [ldap3](https://github.com/cannatag/ldap3) > 2.0 and [dnspython](https://github.com/rthalley/dnspython)
28
29 Both can be installed with `pip install ldap3 dnspython`
30
31 The ldapdomaindump package can be installed with `python setup.py install` from the git source, or for the latest release with `pip install ldapdomaindump`.
32
33 ## Usage
34 There are 3 ways to use the tool:
35 - With just the source, run `python ldapdomaindump.py`
36 - After installing, by running `python -m ldapdomaindump`
37 - After installing, by running `ldapdomaindump`
38
39 Help can be obtained with the -h switch:
40 ```
41 usage: ldapdomaindump.py [-h] [-u USERNAME] [-p PASSWORD] [-at {NTLM,SIMPLE}]
42 [-o DIRECTORY] [--no-html] [--no-json] [--no-grep]
43 [--grouped-json] [-d DELIMITER] [-r] [-n DNS_SERVER]
44 [-m]
45 HOSTNAME
46
47 Domain information dumper via LDAP. Dumps users/computers/groups and
48 OS/membership information to HTML/JSON/greppable output.
49
50 Required options:
51 HOSTNAME Hostname/ip or ldap://host:port connection string to
52 connect to (use ldaps:// to use SSL)
53
54 Main options:
55 -h, --help show this help message and exit
56 -u USERNAME, --user USERNAME
57 DOMAIN\username for authentication, leave empty for
58 anonymous authentication
59 -p PASSWORD, --password PASSWORD
60 Password or LM:NTLM hash, will prompt if not specified
61 -at {NTLM,SIMPLE}, --authtype {NTLM,SIMPLE}
62 Authentication type (NTLM or SIMPLE, default: NTLM)
63
64 Output options:
65 -o DIRECTORY, --outdir DIRECTORY
66 Directory in which the dump will be saved (default:
67 current)
68 --no-html Disable HTML output
69 --no-json Disable JSON output
70 --no-grep Disable Greppable output
71 --grouped-json Also write json files for grouped files (default:
72 disabled)
73 -d DELIMITER, --delimiter DELIMITER
74 Field delimiter for greppable output (default: tab)
75
76 Misc options:
77 -r, --resolve Resolve computer hostnames (might take a while and
78 cause high traffic on large networks)
79 -n DNS_SERVER, --dns-server DNS_SERVER
80 Use custom DNS resolver instead of system DNS (try a
81 domain controller IP)
82 -m, --minimal Only query minimal set of attributes to limit memmory
83 usage
84 ```
85
86 ## Options
87 ### Authentication
88 Most AD servers support NTLM authentication. In the rare case that it does not, use --authtype SIMPLE.
89
90 ### Output formats
91 By default the tool outputs all files in HTML, JSON and tab delimited output (greppable). There are also two grouped files (users_by_group and computers_by_os) for convenience. These do not have a greppable output. JSON output for grouped files is disabled by default since it creates very large files without any data that isn't present in the other files already.
92
93 ### DNS resolving
94 An important option is the *-r* option, which decides if a computers DNSHostName attribute should be resolved to an IPv4 address.
95 While this can be very useful, the DNSHostName attribute is not automatically updated. When the AD Domain uses subdomains for computer hostnames, the DNSHostName will often be incorrect and will not resolve. Also keep in mind that resolving every hostname in the domain might cause a high load on the domain controller.
96
97 ### Minimizing network and memory usage
98 By default ldapdomaindump will try to dump every single attribute it can read to disk in the .json files. In large networks, this uses a lot of memory (since group relationships are currently calculated in memory before being written to disk). To dump only the minimal required attributes (the ones shown by default in the .html and .grep files), use the `--minimal` switch.
99
100 ## Visualizing groups with BloodHound
101 LDAPDomainDump includes a utility that can be used to convert ldapdomaindumps `.json` files to CSV files suitable for BloodHound. The utility is called `ldd2bloodhound` and is added to your path upon installation. Alternatively you can run it with `python -m ldapdomaindump.convert` or with `python ldapdomaindump/convert.py` if you are running it from the source.
102 The conversion tool will take the users/groups/computers/trusts `.json` file and convert those to `group_membership.csv` and `trust.csv` which you can add to BloodHound.
103
104 ## License
105 MIT
0 # LDAPDomainDump
1 Active Directory information dumper via LDAP
2
3 ## Introduction
4 In an Active Directory domain, a lot of interesting information can be retrieved via LDAP by any authenticated user (or machine).
5 This makes LDAP an interesting protocol for gathering information in the recon phase of a pentest of an internal network.
6 A problem is that data from LDAP often is not available in an easy to read format.
7
8 ldapdomaindump is a tool which aims to solve this problem, by collecting and parsing information available via LDAP and outputting it in a human readable HTML format, as well as machine readable json and csv/tsv/greppable files.
9
10 The tool was designed with the following goals in mind:
11 - Easy overview of all users/groups/computers/policies in the domain
12 - Authentication both via username and password, as with NTLM hashes (requires ldap3 >=1.3.1)
13 - Possibility to run the tool with an existing authenticated connection to an LDAP service, allowing for integration with relaying tools such as impackets ntlmrelayx
14
15 The tool outputs several files containing an overview of objects in the domain:
16 - *domain_groups*: List of groups in the domain
17 - *domain_users*: List of users in the domain
18 - *domain_computers*: List of computer accounts in the domain
19 - *domain_policy*: Domain policy such as password requirements and lockout policy
20 - *domain_trusts*: Incoming and outgoing domain trusts, and their properties
21
22 As well as two grouped files:
23 - *domain_users_by_group*: Domain users per group they are member of
24 - *domain_computers_by_os*: Domain computers sorted by Operating System
25
26 ## Dependencies and installation
27 Requires [ldap3](https://github.com/cannatag/ldap3) > 2.0 and [dnspython](https://github.com/rthalley/dnspython)
28
29 Both can be installed with `pip install ldap3 dnspython`
30
31 The ldapdomaindump package can be installed with `python setup.py install` from the git source, or for the latest release with `pip install ldapdomaindump`.
32
33 ## Usage
34 There are 3 ways to use the tool:
35 - With just the source, run `python ldapdomaindump.py`
36 - After installing, by running `python -m ldapdomaindump`
37 - After installing, by running `ldapdomaindump`
38
39 Help can be obtained with the -h switch:
40 ```
41 usage: ldapdomaindump.py [-h] [-u USERNAME] [-p PASSWORD] [-at {NTLM,SIMPLE}]
42 [-o DIRECTORY] [--no-html] [--no-json] [--no-grep]
43 [--grouped-json] [-d DELIMITER] [-r] [-n DNS_SERVER]
44 [-m]
45 HOSTNAME
46
47 Domain information dumper via LDAP. Dumps users/computers/groups and
48 OS/membership information to HTML/JSON/greppable output.
49
50 Required options:
51 HOSTNAME Hostname/ip or ldap://host:port connection string to
52 connect to (use ldaps:// to use SSL)
53
54 Main options:
55 -h, --help show this help message and exit
56 -u USERNAME, --user USERNAME
57 DOMAIN\username for authentication, leave empty for
58 anonymous authentication
59 -p PASSWORD, --password PASSWORD
60 Password or LM:NTLM hash, will prompt if not specified
61 -at {NTLM,SIMPLE}, --authtype {NTLM,SIMPLE}
62 Authentication type (NTLM or SIMPLE, default: NTLM)
63
64 Output options:
65 -o DIRECTORY, --outdir DIRECTORY
66 Directory in which the dump will be saved (default:
67 current)
68 --no-html Disable HTML output
69 --no-json Disable JSON output
70 --no-grep Disable Greppable output
71 --grouped-json Also write json files for grouped files (default:
72 disabled)
73 -d DELIMITER, --delimiter DELIMITER
74 Field delimiter for greppable output (default: tab)
75
76 Misc options:
77 -r, --resolve Resolve computer hostnames (might take a while and
78 cause high traffic on large networks)
79 -n DNS_SERVER, --dns-server DNS_SERVER
80 Use custom DNS resolver instead of system DNS (try a
81 domain controller IP)
82 -m, --minimal Only query minimal set of attributes to limit memmory
83 usage
84 ```
85
86 ## Options
87 ### Authentication
88 Most AD servers support NTLM authentication. In the rare case that it does not, use --authtype SIMPLE.
89
90 ### Output formats
91 By default the tool outputs all files in HTML, JSON and tab delimited output (greppable). There are also two grouped files (users_by_group and computers_by_os) for convenience. These do not have a greppable output. JSON output for grouped files is disabled by default since it creates very large files without any data that isn't present in the other files already.
92
93 ### DNS resolving
94 An important option is the *-r* option, which decides if a computers DNSHostName attribute should be resolved to an IPv4 address.
95 While this can be very useful, the DNSHostName attribute is not automatically updated. When the AD Domain uses subdomains for computer hostnames, the DNSHostName will often be incorrect and will not resolve. Also keep in mind that resolving every hostname in the domain might cause a high load on the domain controller.
96
97 ### Minimizing network and memory usage
98 By default ldapdomaindump will try to dump every single attribute it can read to disk in the .json files. In large networks, this uses a lot of memory (since group relationships are currently calculated in memory before being written to disk). To dump only the minimal required attributes (the ones shown by default in the .html and .grep files), use the `--minimal` switch.
99
100 ## Visualizing groups with BloodHound
101 LDAPDomainDump includes a utility that can be used to convert ldapdomaindumps `.json` files to CSV files suitable for BloodHound. The utility is called `ldd2bloodhound` and is added to your path upon installation. Alternatively you can run it with `python -m ldapdomaindump.convert` or with `python ldapdomaindump/convert.py` if you are running it from the source.
102 The conversion tool will take the users/groups/computers/trusts `.json` file and convert those to `group_membership.csv` and `trust.csv` which you can add to BloodHound. *Note that these files are only compatible with **BloodHound 1.x** which is quite old. There are no plans to support the latest version as the [BloodHound.py project](https://github.com/fox-it/BloodHound.py) was made for this. With the DCOnly collection method this tool will also only talk to LDAP and collect more information than ldapdomaindump would*.
103
104 ## Visualizing dump with a pretty output like enum4linux
105 LDAPDomainDump includes a utility that can be used to output ldapdomaindumps `.json` files to an enum4linux like output. The utility is called `ldd2pretty` and is added to your path upon installation. Alternatively you can run it with `python -m ldapdomaindump.pretty` or with `python ldapdomaindump/pretty.py` if you are running it from the source.
106
107 ## License
108 MIT
0 #!/usr/bin/env python
1 from ldapdomaindump import pretty
2 pretty.main()
2323 from __future__ import unicode_literals
2424 import sys, os, re, codecs, json, argparse, getpass, base64
2525 # import class and constants
26 from datetime import datetime
26 from datetime import datetime, timedelta
2727 try:
2828 from urllib.parse import quote_plus
2929 except ImportError:
4747 'ACCOUNT_LOCKED':0x00000010,
4848 'PASSWD_NOTREQD':0x00000020,
4949 'PASSWD_CANT_CHANGE': 0x00000040,
50 'PASSWORD_STORE_CLEARTEXT': 0x00000080,
5051 'NORMAL_ACCOUNT': 0x00000200,
5152 'WORKSTATION_ACCOUNT':0x00001000,
5253 'SERVER_TRUST_ACCOUNT': 0x00002000,
8081 'TREAT_AS_EXTERNAL':0x00000040,
8182 'USES_RC4_ENCRYPTION':0x00000080,
8283 'CROSS_ORGANIZATION_NO_TGT_DELEGATION':0x00000200,
84 'CROSS_ORGANIZATION_ENABLE_TGT_DELEGATION':0x00000800,
8385 'PIM_TRUST':0x00000400}
8486
8587 # Domain trust direction
111113 'lockoutThreshold':'Lockout Threshold',
112114 'maxPwdAge':'Max password age',
113115 'minPwdAge':'Min password age',
114 'minPwdLength':'Min password length'}
116 'minPwdLength':'Min password length',
117 'pwdHistoryLength':'Password history length',
118 'pwdProperties':'Password properties',
119 'ms-DS-MachineAccountQuota':'Machine Account Quota',
120 'flatName':'NETBIOS Domain name'}
115121
116122 MINIMAL_COMPUTERATTRIBUTES = ['cn', 'sAMAccountName', 'dNSHostName', 'operatingSystem', 'operatingSystemServicePack', 'operatingSystemVersion', 'lastLogon', 'userAccountControl', 'whenCreated', 'objectSid', 'description', 'objectClass']
117123 MINIMAL_USERATTRIBUTES = ['cn', 'name', 'sAMAccountName', 'memberOf', 'primaryGroupId', 'whenCreated', 'whenChanged', 'lastLogon', 'userAccountControl', 'pwdLastSet', 'objectSid', 'description', 'objectClass']
377383 except (LDAPAttributeError, LDAPCursorError):
378384 ugroups = []
379385 #Add the user default group
380 ugroups.append(self.getGroupCnFromDn(self.groups_dnmap[user.primaryGroupId.value]))
386 try:
387 ugroups.append(self.getGroupCnFromDn(self.groups_dnmap[user.primaryGroupId.value]))
388 # Sometimes we can't query this group or it doesn't exist
389 except KeyError:
390 pass
381391 for group in ugroups:
382392 try:
383393 groupsdict[group].append(user)
430440 #In grouped view, don't include the memberOf property to reduce output size
431441 self.userattributes_grouped = ['cn', 'name', 'sAMAccountName', 'whenCreated', 'whenChanged', 'lastLogon', 'userAccountControl', 'pwdLastSet', 'objectSid', 'description']
432442 self.groupattributes = ['cn', 'sAMAccountName', 'memberOf', 'description', 'whenCreated', 'whenChanged', 'objectSid']
433 self.policyattributes = ['cn', 'lockOutObservationWindow', 'lockoutDuration', 'lockoutThreshold', 'maxPwdAge', 'minPwdAge', 'minPwdLength', 'pwdHistoryLength', 'pwdProperties']
443 self.policyattributes = ['distinguishedName', 'lockOutObservationWindow', 'lockoutDuration', 'lockoutThreshold', 'maxPwdAge', 'minPwdAge', 'minPwdLength', 'pwdHistoryLength', 'pwdProperties', 'ms-DS-MachineAccountQuota']
434444 self.trustattributes = ['cn', 'flatName', 'securityIdentifier', 'trustAttributes', 'trustDirection', 'trustType']
435445
436446 #Escape HTML special chars
445455
446456 #Convert password max age (in 100 nanoseconds), to days
447457 def nsToDays(self, length):
448 return abs(length) * .0000001 / 86400
458 # ldap3 >= 2.6 returns timedelta
459 if isinstance(length, timedelta):
460 return length.total_seconds() / 86400
461 else:
462 return abs(length) * .0000001 / 86400
449463
450464 def nsToMinutes(self, length):
451 return abs(length) * .0000001 / 60
465 # ldap3 >= 2.6 returns timedelta
466 if isinstance(length, timedelta):
467 return length.total_seconds() / 60
468 else:
469 return abs(length) * .0000001 / 60
452470
453471 #Parse bitwise flags into a list
454472 def parseFlags(self, attr, flags_def):
473 outflags = []
474 if attr is None or attr.value is None:
475 return outflags
476 for flag, val in iteritems(flags_def):
477 if int(attr.value) & val:
478 outflags.append(flag)
479 return outflags
480
481 #Parse bitwise trust direction - only one flag applies here, 0x03 overlaps
482 def parseSingleFlag(self, attr, flags_def):
455483 outflags = []
456484 if attr is None:
457485 return outflags
458486 for flag, val in iteritems(flags_def):
459 if attr.value & val:
460 outflags.append(flag)
461 return outflags
462
463 #Parse bitwise trust direction - only one flag applies here, 0x03 overlaps
464 def parseTrustDirection(self, attr, flags_def):
465 outflags = []
466 if attr is None:
467 return outflags
468 for flag, val in iteritems(flags_def):
469 if attr.value == val:
487 if int(attr.value) == val:
470488 outflags.append(flag)
471489 return outflags
472490
593611 return self.formatGroupsHtml(att.values)
594612 #Primary group
595613 if aname == 'primarygroupid':
596 return self.formatGroupsHtml([self.dd.groups_dnmap[att.value]])
614 try:
615 return self.formatGroupsHtml([self.dd.groups_dnmap[att.value]])
616 except KeyError:
617 return 'NOT FOUND!'
597618 #Pwd flags
598619 if aname == 'pwdproperties':
599620 return ', '.join(self.parseFlags(att, pwd_flags))
604625 if att.value == 0:
605626 return 'DISABLED'
606627 else:
607 return ', '.join(self.parseTrustDirection(att, trust_directions))
628 return ', '.join(self.parseSingleFlag(att, trust_directions))
608629 if aname == 'trusttype':
609 return ', '.join(self.parseFlags(att, trust_type))
630 return ', '.join(self.parseSingleFlag(att, trust_type))
610631 if aname == 'securityidentifier':
611632 return format_sid(att.raw_values[0])
612633 if aname == 'minpwdage' or aname == 'maxpwdage':
676697 if aname == 'member' or aname == 'memberof' and type(att.values) is list:
677698 return self.formatGroupsGrep(att.values)
678699 if aname == 'primarygroupid':
679 return self.formatGroupsGrep([self.dd.groups_dnmap[att.value]])
700 try:
701 return self.formatGroupsGrep([self.dd.groups_dnmap[att.value]])
702 except KeyError:
703 return 'NOT FOUND!'
680704 #Domain trust flags
681705 if aname == 'trustattributes':
682706 return ', '.join(self.parseFlags(att, trust_flags))
684708 if att.value == 0:
685709 return 'DISABLED'
686710 else:
687 return ', '.join(self.parseTrustDirection(att, trust_directions))
711 return ', '.join(self.parseSingleFlag(att, trust_directions))
688712 if aname == 'trusttype':
689 return ', '.join(self.parseFlags(att, trust_type))
713 return ', '.join(self.parseSingleFlag(att, trust_type))
690714 if aname == 'securityidentifier':
691715 return format_sid(att.raw_values[0])
692716 #Pwd flags
767791 if self.config.outputhtml:
768792 html = self.generateHtmlTable(dd.users, self.userattributes, 'Domain users')
769793 self.writeHtmlFile('%s.html' % self.config.usersfile, html)
794 if self.config.outputgrep:
795 grepout = self.generateGrepList(dd.users, self.userattributes)
796 self.writeGrepFile('%s.grep' % self.config.usersfile, grepout)
770797 if self.config.outputjson:
771798 jsonout = self.generateJsonList(dd.users)
772799 self.writeJsonFile('%s.json' % self.config.usersfile, jsonout)
773 if self.config.outputgrep:
774 grepout = self.generateGrepList(dd.users, self.userattributes)
775 self.writeGrepFile('%s.grep' % self.config.usersfile, grepout)
776800
777801 #Generate report with just a table of all computer accounts
778802 def generateComputersReport(self, dd):
779803 if self.config.outputhtml:
780804 html = self.generateHtmlTable(dd.computers, self.computerattributes, 'Domain computer accounts')
781805 self.writeHtmlFile('%s.html' % self.config.computersfile, html)
806 if self.config.outputgrep:
807 grepout = self.generateGrepList(dd.computers, self.computerattributes)
808 self.writeGrepFile('%s.grep' % self.config.computersfile, grepout)
782809 if self.config.outputjson:
783810 jsonout = self.generateJsonList(dd.computers)
784811 self.writeJsonFile('%s.json' % self.config.computersfile, jsonout)
785 if self.config.outputgrep:
786 grepout = self.generateGrepList(dd.computers, self.computerattributes)
787 self.writeGrepFile('%s.grep' % self.config.computersfile, grepout)
788812
789813 #Generate report with just a table of all computer accounts
790814 def generateGroupsReport(self, dd):
791815 if self.config.outputhtml:
792816 html = self.generateHtmlTable(dd.groups, self.groupattributes, 'Domain groups')
793817 self.writeHtmlFile('%s.html' % self.config.groupsfile, html)
818 if self.config.outputgrep:
819 grepout = self.generateGrepList(dd.groups, self.groupattributes)
820 self.writeGrepFile('%s.grep' % self.config.groupsfile, grepout)
794821 if self.config.outputjson:
795822 jsonout = self.generateJsonList(dd.groups)
796823 self.writeJsonFile('%s.json' % self.config.groupsfile, jsonout)
797 if self.config.outputgrep:
798 grepout = self.generateGrepList(dd.groups, self.groupattributes)
799 self.writeGrepFile('%s.grep' % self.config.groupsfile, grepout)
800824
801825 #Generate policy report
802826 def generatePolicyReport(self, dd):
803827 if self.config.outputhtml:
804828 html = self.generateHtmlTable(dd.policy, self.policyattributes, 'Domain policy')
805829 self.writeHtmlFile('%s.html' % self.config.policyfile, html)
830 if self.config.outputgrep:
831 grepout = self.generateGrepList(dd.policy, self.policyattributes)
832 self.writeGrepFile('%s.grep' % self.config.policyfile, grepout)
806833 if self.config.outputjson:
807834 jsonout = self.generateJsonList(dd.policy)
808835 self.writeJsonFile('%s.json' % self.config.policyfile, jsonout)
809 if self.config.outputgrep:
810 grepout = self.generateGrepList(dd.policy, self.policyattributes)
811 self.writeGrepFile('%s.grep' % self.config.policyfile, grepout)
812836
813837 #Generate policy report
814838 def generateTrustsReport(self, dd):
815839 if self.config.outputhtml:
816840 html = self.generateHtmlTable(dd.trusts, self.trustattributes, 'Domain trusts')
817841 self.writeHtmlFile('%s.html' % self.config.trustsfile, html)
842 if self.config.outputgrep:
843 grepout = self.generateGrepList(dd.trusts, self.trustattributes)
844 self.writeGrepFile('%s.grep' % self.config.trustsfile, grepout)
818845 if self.config.outputjson:
819846 jsonout = self.generateJsonList(dd.trusts)
820847 self.writeJsonFile('%s.json' % self.config.trustsfile, jsonout)
821 if self.config.outputgrep:
822 grepout = self.generateGrepList(dd.trusts, self.trustattributes)
823 self.writeGrepFile('%s.grep' % self.config.trustsfile, grepout)
824848
825849 #Some quick logging helpers
826850 def log_warn(text):
0 from __future__ import print_function
1 from builtins import str
2 import argparse
3 import json
4 import os.path
5 import re
6 from time import strftime, gmtime
7
8 class PrettyOuput(object):
9
10 def d2b(self, a):
11 tbin = []
12 while a:
13 tbin.append(a % 2)
14 a //= 2
15
16 t2bin = tbin[::-1]
17 if len(t2bin) != 8:
18 for x in range(6 - len(t2bin)):
19 t2bin.insert(0, 0)
20 return ''.join([str(g) for g in t2bin])
21
22 def convert(self, time):
23 if isinstance(time, str):
24 return time
25 if time == 0:
26 return "None"
27 if time == -9223372036854775808:
28 return "Not Set"
29 sec = abs(time) // 10000000
30 days = sec // 86400
31 sec -= 86400*days
32 hrs = sec // 3600
33 sec -= 3600*hrs
34 mins = sec // 60
35 sec -= 60*mins
36 result = ""
37 if days > 1:
38 result += "{0} days ".format(days)
39 elif days == 1:
40 result += "{0} day ".format(days)
41 if hrs > 1:
42 result += "{0} hours ".format(hrs)
43 elif hrs == 1:
44 result += "{0} hour ".format(hrs)
45 if mins > 1:
46 result += "{0} minutes ".format(mins)
47 elif mins == 1:
48 result += "{0} minute ".format(mins)
49 return result
50
51 def password_complexity(self, data):
52
53 print('''
54 +-----------------------------------------+
55 | Password Policy Information |
56 +-----------------------------------------+
57 ''')
58
59 print("[+] Password Info for Domain:", data[0]['attributes']['dc'][0].upper())
60 print("\t[+] Minimum password length: ", data[0]['attributes']['instanceType'][0])
61 print("\t[+] Password history length:", data[0]['attributes']['pwdHistoryLength'][0])
62
63 password_properties = self.d2b(data[0]['attributes']['pwdProperties'][0])
64 print("\t[+] Password Complexity Flags:", password_properties)
65 print("")
66 print("\t\t[+] Domain Refuse Password Change:", password_properties[0])
67 print("\t\t[+] Domain Password Store Cleartext:", password_properties[1])
68 print("\t\t[+] Domain Password Lockout Admins:", password_properties[2])
69 print("\t\t[+] Domain Password No Clear Change:", password_properties[3])
70 print("\t\t[+] Domain Password No Anon Change:", password_properties[4])
71 print("\t\t[+] Domain Password Complex:", password_properties[5])
72 print("")
73 print("\t[+] Maximum password age:", self.convert(data[0]['attributes']['maxPwdAge'][0]))
74 print("\t[+] Minimum password age:", self.convert(data[0]['attributes']['minPwdAge'][0]))
75 print("\t[+] Reset Account Lockout Counter:", self.convert(data[0]['attributes']['lockoutDuration'][0]))
76 print("\t[+] Account Lockout Threshold:", data[0]['attributes']['lockoutThreshold'][0])
77 print("\t[+] Forced Log off Time:", self.convert(data[0]['attributes']['forceLogoff'][0]))
78
79 def domain_info(self, data):
80 print('''
81 +--------------------------------------+
82 | Getting Domain Sid For |
83 +--------------------------------------+
84 ''')
85 print('[+] Domain Name:', data[0]['attributes']['dc'][0])
86 print('Domain Sid:', data[0]['attributes']['objectSid'][0])
87 print('')
88 return data[0]['attributes']['dc'][0]
89
90 def user_info(self, users, dc):
91 print('''
92 +------------------------+
93 | Users Infos |
94 +------------------------+
95 ''')
96 for user in users:
97 desc = user['attributes'].get('description')[0] if user['attributes'].get('description') else "(null)"
98 print("Account: " + dc + "\\" + user['attributes']['sAMAccountName'][0] + "\tName: " + user['attributes']['name'][0] + "\tDesc: " + desc)
99
100 print("")
101 for user in users:
102 print("user:[" + user['attributes']['sAMAccountName'][0] + "]")
103 print("")
104
105 def groups_info(self, groups, dc):
106 print('''
107 +------------------------+import os.path
108 | Groups Infos |
109 +------------------------+
110 ''')
111 for group in groups:
112 print("group:[" + group['attributes']['name'][0] + "]")
113
114 for group in groups:
115 if group['attributes'].get('member'):
116 users = re.findall(r"^CN=([\w\s\-\_\{\}\.\$\#]+)", '\n'.join(group['attributes']['member']), re.M)
117 if users:
118 print("\n[+] Getting domain group memberships:")
119 for user in users:
120 if user == "S-1-5-11":
121 user = "NT AUTHORITY\\Authenticated Users"
122 elif user == "S-1-5-4":
123 user = "NT AUTHORITY\\INTERACTIVE"
124 elif user == "S-1-5-17":
125 user = "NT AUTHORITY\\IUSR"
126 print("Group '" + group['attributes']['name'][0] + "' has member: " + dc + "\\" + user)
127
128 def main():
129 parser = argparse.ArgumentParser(description='LDAPDomainDump to pretty output like enum4linux.')
130
131 #Main parameters
132 parser.add_argument("-d", "--directory", help="The ldapdomaindump directory where the json files are saved. Required files: domain_users.json, domain_groups.json and domain_policy.json")
133 args = parser.parse_args()
134
135 if args.directory:
136 with open(args.directory + '/domain_policy.json') as f:
137 domain = json.load(f)
138 with open(args.directory + '/domain_users.json') as f:
139 users = json.load(f)
140 with open(args.directory + '/domain_groups.json') as f:
141 groups = json.load(f)
142 pretty = PrettyOuput()
143 dc = pretty.domain_info(domain)
144 pretty.password_complexity(domain)
145 pretty.user_info(users, dc.upper())
146 pretty.groups_info(groups, dc.upper())
147 else:
148 print("Missing parameter --directory /output/")
149
150 if __name__ == "__main__":
151 main()
0 Metadata-Version: 1.0
1 Name: ldapdomaindump
2 Version: 0.9.3
3 Summary: Active Directory information dumper via LDAP
4 Home-page: https://github.com/dirkjanm/ldapdomaindump/
5 Author: Dirk-jan Mollema
6 Author-email: [email protected]
7 License: UNKNOWN
8 Description: UNKNOWN
9 Platform: UNKNOWN
0 LICENSE
1 MANIFEST.in
2 Readme.md
3 setup.py
4 bin/ldapdomaindump
5 bin/ldd2bloodhound
6 bin/ldd2pretty
7 ldapdomaindump/__init__.py
8 ldapdomaindump/__main__.py
9 ldapdomaindump/convert.py
10 ldapdomaindump/pretty.py
11 ldapdomaindump/style.css
12 ldapdomaindump.egg-info/PKG-INFO
13 ldapdomaindump.egg-info/SOURCES.txt
14 ldapdomaindump.egg-info/dependency_links.txt
15 ldapdomaindump.egg-info/requires.txt
16 ldapdomaindump.egg-info/top_level.txt
0 dnspython
1 future
2 ldap3!=2.5.0,!=2.5.2,!=2.6,>=2.5
+0
-3
ldapdomaindump.py less more
0 #!/usr/bin/env python
1 import ldapdomaindump
2 ldapdomaindump.main()
0 [egg_info]
1 tag_build =
2 tag_date = 0
3
0 from setuptools import setup
1 setup(name='ldapdomaindump',
2 version='0.9.1',
3 description='Active Directory information dumper via LDAP',
4 author='Dirk-jan Mollema',
5 author_email='[email protected]',
6 url='https://github.com/dirkjanm/ldapdomaindump/',
7 packages=['ldapdomaindump'],
8 install_requires=['dnspython', 'ldap3==2.5.1', 'future'],
9 package_data={'ldapdomaindump': ['style.css']},
10 include_package_data=True,
11 scripts=['bin/ldapdomaindump', 'bin/ldd2bloodhound']
12 )
0 from setuptools import setup
1 setup(name='ldapdomaindump',
2 version='0.9.3',
3 description='Active Directory information dumper via LDAP',
4 author='Dirk-jan Mollema',
5 author_email='[email protected]',
6 url='https://github.com/dirkjanm/ldapdomaindump/',
7 packages=['ldapdomaindump'],
8 install_requires=['dnspython', 'ldap3>=2.5,!=2.5.2,!=2.5.0,!=2.6', 'future'],
9 package_data={'ldapdomaindump': ['style.css']},
10 include_package_data=True,
11 scripts=['bin/ldapdomaindump', 'bin/ldd2bloodhound', 'bin/ldd2pretty']
12 )