Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/smb/change_password.rb
28693 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'ruby_smb/dcerpc/client'
7
8
class MetasploitModule < Msf::Auxiliary
9
include Msf::Exploit::Remote::SMB::Client
10
include Msf::Exploit::Remote::SMB::Client::Authenticated
11
include Msf::Auxiliary::Report
12
include Msf::OptionalSession::SMB
13
14
def initialize(info = {})
15
super(
16
update_info(
17
info,
18
'Name' => 'SMB Password Change',
19
'Description' => %q{
20
Change the password of an account using SMB. This provides several different
21
APIs, each of which have their respective benefits and drawbacks.
22
},
23
'License' => MSF_LICENSE,
24
'Author' => [
25
'smashery'
26
],
27
'References' => [
28
['URL', 'https://github.com/fortra/impacket/blob/master/examples/changepasswd.py'],
29
[ 'ATT&CK', Mitre::Attack::Technique::T1021_002_SMB_WINDOWS_ADMIN_SHARES ]
30
],
31
'Notes' => {
32
'Stability' => [CRASH_SAFE],
33
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES],
34
'Reliability' => []
35
},
36
'Actions' => [
37
[ 'RESET', { 'Description' => "Reset the target's password without knowing the existing one (requires appropriate permissions). New AES kerberos keys will be generated." } ],
38
[ 'RESET_NTLM', { 'Description' => "Reset the target's NTLM hash, without knowing the existing password. AES kerberos authentication will not work until a standard password change occurs." } ],
39
[ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one. New AES kerberos keys will be generated.' } ],
40
[ 'CHANGE_NTLM', { 'Description' => 'Change the password to a NTLM hash value, knowing the existing password. AES kerberos authentication will not work until a standard password change occurs.' } ]
41
],
42
'DefaultAction' => 'RESET'
43
)
44
)
45
46
register_options(
47
[
48
OptString.new('NEW_PASSWORD', [false, 'The new password to change to', ''], conditions: ['ACTION', 'in', %w[CHANGE RESET]]),
49
OptString.new('NEW_NTLM', [false, 'The new NTLM hash to change to. Can be either an NT hash or a colon-delimited NTLM hash'], conditions: ['ACTION', 'in', %w[CHANGE_NTLM RESET_NTLM]], regex: /^([0-9a-fA-F]{32}:)?[0-9a-fA-F]{32}$/),
50
OptString.new('TARGET_USER', [false, 'The user to reset the password of.'], conditions: ['ACTION', 'in', %w[RESET RESET_NTLM]])
51
]
52
)
53
end
54
55
def connect_samr
56
vprint_status('Connecting to Security Account Manager (SAM) Remote Protocol')
57
@samr = @tree.open_file(filename: 'samr', write: true, read: true)
58
59
vprint_status('Binding to \\samr...')
60
@samr.bind(endpoint: RubySMB::Dcerpc::Samr)
61
vprint_good('Bound to \\samr')
62
end
63
64
def run
65
case action.name
66
when 'CHANGE'
67
run_change
68
when 'RESET'
69
run_reset
70
when 'RESET_NTLM'
71
run_reset_ntlm
72
when 'CHANGE_NTLM'
73
run_change_ntlm
74
end
75
rescue RubySMB::Error::RubySMBError => e
76
fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}")
77
rescue Rex::ConnectionError => e
78
fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}")
79
rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e
80
fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}")
81
rescue ::StandardError => e
82
raise e
83
ensure
84
@samr.close_handle(@domain_handle) if @domain_handle
85
@samr.close_handle(@server_handle) if @server_handle
86
@samr.close if @samr
87
@tree.disconnect! if @tree
88
89
# Don't disconnect the client if it's coming from the session so it can be reused
90
unless session
91
simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client)
92
disconnect
93
end
94
end
95
96
def authenticate(anonymous_on_expired: false)
97
if session
98
print_status("Using existing session #{session.sid}")
99
self.simple = session.simple_client
100
simple.connect("\\\\#{simple.address}\\IPC$") # smb_login connects to this share for some reason and it doesn't work unless we do too
101
else
102
connect
103
begin
104
begin
105
smb_login
106
rescue Rex::Proto::SMB::Exceptions::LoginError => e
107
if (e.source.is_a?(Rex::Proto::Kerberos::Model::Error::KerberosError) && [Rex::Proto::Kerberos::Model::Error::ErrorCodes::KDC_ERR_KEY_EXPIRED].include?(e.source.error_code) ||
108
e.source.is_a?(::WindowsError::ErrorCode) && [::WindowsError::NTStatus::STATUS_PASSWORD_EXPIRED, ::WindowsError::NTStatus::STATUS_PASSWORD_MUST_CHANGE].include?(e.source))
109
if anonymous_on_expired
110
# Password has expired - we'll need to anonymous connect
111
print_warning('Password expired - binding anonymously')
112
opts = {
113
username: '',
114
password: '',
115
domain: '',
116
auth_protocol: Msf::Exploit::Remote::AuthOption::NTLM
117
}
118
disconnect
119
connect
120
smb_login(opts: opts)
121
elsif action.name == 'CHANGE_NTLM'
122
fail_with(Module::Failure::UnexpectedReply, 'Must change password first. Try using the CHANGE action instead')
123
else
124
raise
125
end
126
else
127
raise
128
end
129
end
130
rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e
131
fail_with(Module::Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).")
132
end
133
end
134
135
report_service(
136
host: simple.address,
137
port: simple.port,
138
host_name: simple.client.default_name,
139
proto: 'tcp',
140
name: 'smb',
141
info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})"
142
)
143
144
begin
145
@tree = simple.client.tree_connect("\\\\#{simple.address}\\IPC$")
146
rescue RubySMB::Error::RubySMBError => e
147
fail_with(Module::Failure::Unreachable,
148
"Unable to connect to the remote IPC$ share ([#{e.class}] #{e}).")
149
end
150
151
connect_samr
152
end
153
154
def parse_ntlm_from_config
155
new_ntlm = datastore['NEW_NTLM']
156
fail_with(Msf::Exploit::Failure::BadConfig, 'Must provide NEW_NTLM value') if new_ntlm.blank?
157
case new_ntlm.count(':')
158
when 0
159
new_nt = new_ntlm
160
new_lm = nil
161
when 1
162
new_lm, new_nt = new_ntlm.split(':')
163
else
164
fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid value for NEW_NTLM')
165
end
166
167
new_nt = Rex::Text.hex_to_raw(new_nt)
168
new_lm = Rex::Text.hex_to_raw(new_lm) unless new_lm.nil?
169
fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid NT hash value in NEW_NTLM') unless new_nt.length == 16
170
fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid LM hash value in NEW_NTLM') unless new_lm.nil? || new_nt.length == 16
171
172
[new_nt, new_lm]
173
end
174
175
def get_user_handle(domain, username)
176
vprint_status("Opening handle for #{domain}\\#{username}")
177
@server_handle = @samr.samr_connect
178
domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: domain)
179
@domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: domain_sid)
180
user_rids = @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [username])
181
fail_with(Module::Failure::BadConfig, "Could not find #{domain}\\#{username}") if user_rids.nil?
182
rid = user_rids[username][:rid]
183
184
@samr.samr_open_user(domain_handle: @domain_handle, user_id: rid)
185
rescue RubySMB::Dcerpc::Error::SamrError => e
186
fail_with(Msf::Exploit::Failure::BadConfig, e.to_s)
187
end
188
189
def run_change_ntlm
190
fail_with(Module::Failure::BadConfig, 'Must set NEW_NTLM') if datastore['NEW_NTLM'].blank?
191
fail_with(Module::Failure::BadConfig, 'Must set SMBUser to change password') if datastore['SMBUser'].blank?
192
fail_with(Module::Failure::BadConfig, 'Must set SMBPass to change password, or use RESET/RESET_NTLM to force-change a password without knowing the existing password') if datastore['SMBPass'].blank?
193
new_nt, new_lm = parse_ntlm_from_config
194
print_status('Changing NTLM')
195
authenticate(anonymous_on_expired: false)
196
197
user_handle = get_user_handle(datastore['SMBDomain'], datastore['SMBUser'])
198
199
if Net::NTLM.is_ntlm_hash?(Net::NTLM::EncodeUtil.encode_utf16le(datastore['SMBPass']))
200
old_lm, old_nt = datastore['SMBPass'].split(':')
201
old_lm = [old_lm].pack('H*')
202
old_nt = [old_nt].pack('H*')
203
204
@samr.samr_change_password_user(user_handle: user_handle,
205
new_nt_hash: new_nt,
206
new_lm_hash: new_lm,
207
old_nt_hash: old_nt,
208
old_lm_hash: old_lm)
209
else
210
@samr.samr_change_password_user(user_handle: user_handle,
211
old_password: datastore['SMBPass'],
212
new_nt_hash: new_nt,
213
new_lm_hash: new_lm)
214
end
215
216
print_good("Successfully changed password for #{datastore['SMBUser']}")
217
print_warning('AES Kerberos keys will not be available until user changes their password')
218
end
219
220
def run_reset_ntlm
221
fail_with(Module::Failure::BadConfig, "Must set TARGET_USER, or use CHANGE/CHANGE_NTLM to reset this user's own password") if datastore['TARGET_USER'].blank?
222
new_nt, = parse_ntlm_from_config
223
print_status('Resetting NTLM')
224
authenticate(anonymous_on_expired: false)
225
226
user_handle = get_user_handle(datastore['SMBDomain'], datastore['TARGET_USER'])
227
228
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
229
tag: RubySMB::Dcerpc::Samr::USER_INTERNAL1_INFORMATION,
230
member: RubySMB::Dcerpc::Samr::SamprUserInternal1Information.new(
231
encrypted_nt_owf_password: RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword.new(buffer: RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword.encrypt_hash(hash: new_nt, key: simple.client.application_key)),
232
encrypted_lm_owf_password: nil,
233
nt_password_present: 1,
234
lm_password_present: 0,
235
password_expired: 0
236
)
237
)
238
@samr.samr_set_information_user2(
239
user_handle: user_handle,
240
user_info: user_info
241
)
242
243
print_good("Successfully reset password for #{datastore['TARGET_USER']}")
244
print_warning('AES Kerberos keys will not be available until user changes their password')
245
end
246
247
def run_reset
248
fail_with(Module::Failure::BadConfig, "Must set TARGET_USER, or use CHANGE/CHANGE_NTLM to reset this user's own password") if datastore['TARGET_USER'].blank?
249
fail_with(Module::Failure::BadConfig, 'Must set NEW_PASSWORD') if datastore['NEW_PASSWORD'].blank?
250
print_status('Resetting password')
251
authenticate(anonymous_on_expired: false)
252
253
user_handle = get_user_handle(datastore['SMBDomain'], datastore['TARGET_USER'])
254
255
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
256
tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW,
257
member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new(
258
i1: {
259
password_expired: 0,
260
which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED
261
},
262
user_password: {
263
buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password(
264
datastore['NEW_PASSWORD'],
265
simple.client.application_key
266
)
267
}
268
)
269
)
270
@samr.samr_set_information_user2(
271
user_handle: user_handle,
272
user_info: user_info
273
)
274
print_good("Successfully reset password for #{datastore['TARGET_USER']}")
275
end
276
277
def run_change
278
fail_with(Module::Failure::BadConfig, 'Must set NEW_PASSWORD') if datastore['NEW_PASSWORD'].blank?
279
fail_with(Module::Failure::BadConfig, 'Must set SMBUser to change password') if datastore['SMBUser'].blank?
280
fail_with(Module::Failure::BadConfig, 'Must set SMBPass to change password, or use RESET/RESET_NTLM to force-change a password without knowing the existing password') if datastore['SMBPass'].blank?
281
print_status('Changing password')
282
authenticate(anonymous_on_expired: true)
283
284
if Net::NTLM.is_ntlm_hash?(Net::NTLM::EncodeUtil.encode_utf16le(datastore['SMBPass']))
285
old_lm, old_nt = datastore['SMBPass'].split(':')
286
old_lm = [old_lm].pack('H*')
287
old_nt = [old_nt].pack('H*')
288
@samr.samr_unicode_change_password_user2(target_username: datastore['SMBUser'], new_password: datastore['NEW_PASSWORD'], old_nt_hash: old_nt, old_lm_hash: old_lm)
289
else
290
@samr.samr_unicode_change_password_user2(target_username: datastore['SMBUser'], old_password: datastore['SMBPass'], new_password: datastore['NEW_PASSWORD'])
291
end
292
293
print_good("Successfully changed password for #{datastore['SMBUser']}")
294
end
295
end
296
297