Skip to content

Commit 508ca28

Browse files
committed
Land rapid7#3404, @ringt's wordlist builder from windows AD
2 parents 84e6db7 + 71669b9 commit 508ca28

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'rex'
7+
require 'msf/core'
8+
9+
class Metasploit3 < Msf::Post
10+
11+
include Msf::Auxiliary::Report
12+
include Msf::Post::Windows::LDAP
13+
14+
DEFAULT_FIELDS = [
15+
'sn',
16+
'givenName',
17+
'state',
18+
'postalCode',
19+
'physicalDeliveryOfficeName',
20+
'telephoneNumber',
21+
'mobile',
22+
'facsimileTelephoneNumber',
23+
'displayName',
24+
'title',
25+
'department',
26+
'company',
27+
'streetAddress',
28+
'sAMAccountName',
29+
'userAccountControl',
30+
'comment',
31+
'description'
32+
]
33+
34+
def initialize(info={})
35+
super( update_info( info,
36+
'Name' => 'Windows Active Directory Wordlist Builder',
37+
'Description' => %q{
38+
This module will gather information from the default Active Domain (AD) directory
39+
and use these words to seed a wordlist. By default it enumerates user accounts to
40+
build the wordlist
41+
},
42+
'License' => MSF_LICENSE,
43+
'Author' => ['Thomas Ring'],
44+
'Platform' => ['win'],
45+
'SessionTypes' => ['meterpreter']
46+
))
47+
48+
register_options([
49+
OptString.new('FIELDS', [true, 'Fields to retrieve (ie, sn, givenName, displayName, description, comment)', DEFAULT_FIELDS.join(',')]),
50+
OptString.new('FILTER', [true, 'Search filter.','(&(objectClass=organizationalPerson)(objectClass=user)(objectClass=person)(!(objectClass=computer)))'])
51+
], self.class)
52+
end
53+
54+
def run
55+
fields = datastore['FIELDS'].gsub(/\s+/,'').split(',')
56+
search_filter = datastore['FILTER']
57+
q = nil
58+
59+
begin
60+
q = query(search_filter, datastore['MAX_SEARCH'], fields)
61+
rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e
62+
# Can't bind or in a network w/ limited accounts
63+
print_error(e.message)
64+
return
65+
end
66+
67+
return if q.nil? || q[:results].empty?
68+
69+
@words_dict = {}
70+
q[:results].each do |result|
71+
result.each do |field|
72+
search_words(field)
73+
end # result.each
74+
end # q.each
75+
76+
# build array of words to output sorted on frequency
77+
ordered_dict = @words_dict.sort_by { |k,v| v }.reverse
78+
ordered_dict.collect! { |k, v| k }
79+
80+
if ordered_dict.blank?
81+
print_error("The wordlist is empty")
82+
return
83+
end
84+
85+
print_good("Wordlist with #{ordered_dict.length} entries built")
86+
stored_path = store_loot('ad.wordlist', 'text/plain', session, ordered_dict.join("\n"))
87+
print_status("Results saved to: #{stored_path}")
88+
end
89+
90+
def search_words(field)
91+
return if field.blank?
92+
return if field =~ /^\s*$/ || field.length < 3
93+
94+
field.gsub!(/[\(\)\"]/, '') # clear up common punctuation in descriptions
95+
field.downcase! # clear up case
96+
97+
words = field.split(/\s+|=|\/|,|\+/)
98+
return if words.empty?
99+
100+
words.each do |word|
101+
next if word.length < 3 || word.length > 24
102+
if @words_dict[word]
103+
@words_dict[word] += 1
104+
else
105+
@words_dict[word] = 1
106+
end
107+
end
108+
end
109+
end
110+

0 commit comments

Comments
 (0)