CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

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