Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/analyze/crack_webapps.rb
76950 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Msf::Auxiliary::PasswordCracker
8
9
def initialize
10
super(
11
'Name' => 'Password Cracker: Webapps',
12
'Description' => %(
13
This module uses John the Ripper or Hashcat to identify weak passwords that have been
14
acquired from various web applications.
15
Atlassian uses PBKDF2-HMAC-SHA1 which is 12001 in hashcat.
16
PHPass uses phpass which is 400 in hashcat.
17
Mediawiki is MD5 based and is 3711 in hashcat.
18
Apache Superset, some Flask and Werkzeug apps is pbkdf2-sha256 and is 10900 in hashcat
19
),
20
'Author' => [
21
'h00die'
22
],
23
'License' => MSF_LICENSE, # JtR itself is GPLv2, but this wrapper is MSF (BSD)
24
'Actions' => [
25
['john', { 'Description' => 'Use John the Ripper' }],
26
['hashcat', { 'Description' => 'Use Hashcat' }],
27
['auto', { 'Description' => 'Auto-selection of cracker' }]
28
],
29
'DefaultAction' => 'auto',
30
'Notes' => {
31
'Stability' => [CRASH_SAFE],
32
'SideEffects' => [],
33
'Reliability' => []
34
}
35
)
36
37
register_options(
38
[
39
OptBool.new('ATLASSIAN', [false, 'Include Atlassian hashes', true]),
40
OptBool.new('MEDIAWIKI', [false, 'Include MediaWiki hashes', true]),
41
OptBool.new('PHPASS', [false, 'Include Wordpress/PHPass, Joomla, phpBB3 hashes', true]),
42
OptBool.new('PBKDF2', [false, 'Apache Superset, some Flask and Werkzeug apps hashes', true]),
43
OptBool.new('INCREMENTAL', [false, 'Run in incremental mode', true]),
44
OptBool.new('WORDLIST', [false, 'Run in wordlist mode', true])
45
]
46
)
47
end
48
49
def show_command(cracker_instance)
50
return unless datastore['ShowCommand']
51
52
if @cracker_type == 'john'
53
cmd = cracker_instance.john_crack_command
54
elsif @cracker_type == 'hashcat'
55
cmd = cracker_instance.hashcat_crack_command
56
end
57
print_status(" Cracking Command: #{cmd.join(' ')}")
58
end
59
60
def check_results(passwords, results, hash_type, method)
61
passwords.each do |password_line|
62
password_line.chomp!
63
next if password_line.blank?
64
65
fields = password_line.split(':')
66
cred = { 'hash_type' => hash_type, 'method' => method }
67
# If we don't have an expected minimum number of fields, this is probably not a hash line
68
if @cracker_type == 'john'
69
next unless fields.count >= 3
70
71
cred['username'] = fields.shift
72
cred['core_id'] = fields.pop
73
cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it
74
elsif @cracker_type == 'hashcat'
75
next unless fields.count >= 2
76
77
cred['core_id'] = fields.shift
78
next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines
79
80
case hash_type
81
when 'pbkdf2-sha256'
82
# hashcat format: cred_id:sha256:iterations:b64salt:b64hash:password
83
4.times { fields.shift } # skip sha256, iterations, b64salt, b64hash
84
else
85
cred['hash'] = fields.shift
86
end
87
cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it
88
89
# we don't have the username since we overloaded it with the core_id (since its a better fit for us)
90
# so we can now just go grab the username from the DB
91
cred['username'] = framework.db.creds(workspace: myworkspace, id: cred['core_id'])[0].public.username
92
end
93
results = process_cracker_results(results, cred)
94
end
95
96
results
97
end
98
99
def run
100
tbl = cracker_results_table
101
cracker = new_password_cracker(action.name)
102
if action.name == 'auto'
103
@cracker_type = cracker.get_type
104
else
105
@cracker_type = action.name
106
end
107
108
hash_types_to_crack = []
109
hash_types_to_crack << 'PBKDF2-HMAC-SHA1' if datastore['ATLASSIAN']
110
hash_types_to_crack << 'phpass' if datastore['PHPASS']
111
hash_types_to_crack << 'mediawiki' if datastore['MEDIAWIKI']
112
hash_types_to_crack << 'pbkdf2-sha256' if datastore['PBKDF2']
113
jobs_to_do = []
114
115
# build our job list
116
hash_types_to_crack.each do |hash_type|
117
job = hash_job(hash_type, @cracker_type)
118
if job.nil?
119
print_status("No #{hash_type} found to crack")
120
else
121
jobs_to_do << job
122
end
123
end
124
125
# bail early of no jobs to do
126
if jobs_to_do.empty?
127
print_good("No uncracked password hashes found for: #{hash_types_to_crack.join(', ')}")
128
return
129
end
130
131
# array of arrays for cracked passwords.
132
# Inner array format: db_id, hash_type, username, password, method_of_crack
133
results = []
134
135
# generate our wordlist and close the file handle.
136
wordlist = wordlist_file
137
unless wordlist
138
print_error('This module cannot run without a database connected. Use db_connect to connect to a database.')
139
return
140
end
141
142
wordlist.close
143
print_status "Wordlist file written out to #{wordlist.path}"
144
145
cleanup_files = [wordlist.path]
146
147
jobs_to_do.each do |job|
148
format = job['type']
149
hash_file = Rex::Quickfile.new("hashes_#{job['type']}_")
150
hash_file.puts job['formatted_hashlist']
151
hash_file.close
152
cracker.hash_path = hash_file.path
153
cleanup_files << hash_file.path
154
# dupe our original cracker so we can safely change options between each run
155
cracker_instance = cracker.dup
156
cracker_instance.format = format
157
if @cracker_type == 'john'
158
cracker_instance.fork = datastore['FORK']
159
end
160
161
# first check if anything has already been cracked so we don't report it incorrectly
162
print_status "Checking #{format} hashes already cracked..."
163
results = check_results(cracker_instance.each_cracked_password, results, format, 'Already Cracked/POT')
164
vprint_good(append_results(tbl, results)) unless results.empty?
165
job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list
166
next if job['cred_ids_left_to_crack'].empty?
167
168
if @cracker_type == 'john'
169
print_status "Cracking #{format} hashes in single mode..."
170
cracker_instance.mode_single(wordlist.path)
171
show_command cracker_instance
172
cracker_instance.crack do |line|
173
vprint_status line.chomp
174
end
175
results = check_results(cracker_instance.each_cracked_password, results, format, 'Single')
176
vprint_good(append_results(tbl, results)) unless results.empty?
177
job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list
178
next if job['cred_ids_left_to_crack'].empty?
179
180
print_status "Cracking #{format} hashes in normal mode..."
181
cracker_instance.mode_normal
182
show_command cracker_instance
183
cracker_instance.crack do |line|
184
vprint_status line.chomp
185
end
186
results = check_results(cracker_instance.each_cracked_password, results, format, 'Normal')
187
vprint_good(append_results(tbl, results)) unless results.empty?
188
job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list
189
next if job['cred_ids_left_to_crack'].empty?
190
end
191
192
if datastore['INCREMENTAL']
193
print_status "Cracking #{format} hashes in incremental mode..."
194
cracker_instance.mode_incremental
195
show_command cracker_instance
196
cracker_instance.crack do |line|
197
vprint_status line.chomp
198
end
199
results = check_results(cracker_instance.each_cracked_password, results, format, 'Incremental')
200
vprint_good(append_results(tbl, results)) unless results.empty?
201
job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list
202
next if job['cred_ids_left_to_crack'].empty?
203
end
204
205
if datastore['WORDLIST']
206
print_status "Cracking #{format} hashes in wordlist mode..."
207
cracker_instance.mode_wordlist(wordlist.path)
208
# Turn on KoreLogic rules if the user asked for it
209
if @cracker_type == 'john' && datastore['KORELOGIC']
210
cracker_instance.rules = 'KoreLogicRules'
211
print_status 'Applying KoreLogic ruleset...'
212
end
213
show_command cracker_instance
214
cracker_instance.crack do |line|
215
vprint_status line.chomp
216
end
217
218
results = check_results(cracker_instance.each_cracked_password, results, format, 'Wordlist')
219
vprint_good(append_results(tbl, results)) unless results.empty?
220
job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list
221
next if job['cred_ids_left_to_crack'].empty?
222
end
223
224
# give a final print of results
225
print_good(append_results(tbl, results))
226
end
227
if datastore['DeleteTempFiles']
228
cleanup_files.each do |f|
229
File.delete(f)
230
end
231
end
232
end
233
end
234
235