Skip to content

Commit 1ca32ee

Browse files
committed
Implement Reset NTLM behaviour.
1 parent 8158cf5 commit 1ca32ee

File tree

1 file changed

+73
-30
lines changed

1 file changed

+73
-30
lines changed

modules/auxiliary/admin/smb/change_password.rb

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
##
33

44
require 'ruby_smb/dcerpc/client'
5-
require 'pry-byebug'
65

76
class MetasploitModule < Msf::Auxiliary
87
include Msf::Exploit::Remote::SMB::Client
@@ -35,16 +34,16 @@ def initialize(info = {})
3534
[ 'RESET', { 'Description' => "Reset the target's password without knowing the existing one (requires appropriate permissions)" } ],
3635
[ 'RESET_NTLM', { 'Description' => "Reset the target's NTLM hash, without knowing the existing password. This will not update kerberos keys." } ],
3736
[ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one.' } ],
38-
[ 'CHANGE_NTLM', { 'Description' => 'Change the password to a NTLM hash value, knowing the existing password. Can be either an NT hash or a colon-delimited NTLM hash' } ]
37+
[ 'CHANGE_NTLM', { 'Description' => 'Change the password to a NTLM hash value, knowing the existing password. This will not update kerberos keys.' } ]
3938
],
4039
'DefaultAction' => 'RESET'
4140
)
4241
)
4342

4443
register_options(
4544
[
46-
OptString.new('NEW_PASSWORD', [false, 'The new password to change to', '']),
47-
OptString.new('NEW_NTLM', [false, 'The new NTLM hash to change to', '']),
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]]),
4847
OptString.new('TARGET_USER', [false, 'The user to change the password of. If not provided, will change for the account provided in SMBUser'], conditions: ['ACTION', 'in', %w[RESET RESET_NTLM]])
4948
]
5049
)
@@ -69,8 +68,6 @@ def connect_samr
6968
end
7069

7170
def run
72-
fail_with('Must set NEW_PASSWORD on NEW_NTLM') if datastore['NEW_PASSWORD'].blank? && datastore['NEW_NTLM'].blank?
73-
7471
case action.name
7572
when 'CHANGE'
7673
run_change
@@ -82,11 +79,25 @@ def run
8279
run_change_ntlm
8380
end
8481

85-
# Don't disconnect the client if it's coming from the session so it can be reused
86-
unless session
87-
simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client)
88-
disconnect
89-
end
82+
rescue RubySMB::Error::RubySMBError => e
83+
fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}")
84+
rescue Rex::ConnectionError => e
85+
fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}")
86+
rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e
87+
fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}")
88+
rescue ::StandardError => e
89+
raise e
90+
ensure
91+
@samr.close_handle(@domain_handle) if @domain_handle
92+
@samr.close_handle(@server_handle) if @server_handle
93+
@samr.close if @samr
94+
@tree.disconnect! if @tree
95+
96+
# Don't disconnect the client if it's coming from the session so it can be reused
97+
unless session
98+
simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client)
99+
disconnect
100+
end
90101
end
91102

92103
def authenticate(anonymous_on_expired: false)
@@ -101,7 +112,6 @@ def authenticate(anonymous_on_expired: false)
101112
begin
102113
smb_login
103114
rescue Rex::Proto::SMB::Exceptions::LoginError => e
104-
binding.pry
105115
if anonymous_on_expired &&
106116
(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) ||
107117
e.source.is_a?(::WindowsError::ErrorCode) && [::WindowsError::NTStatus::STATUS_PASSWORD_EXPIRED, ::WindowsError::NTStatus::STATUS_PASSWORD_MUST_CHANGE].include?(e.source))
@@ -147,6 +157,7 @@ def authenticate(anonymous_on_expired: false)
147157

148158
def parse_ntlm_from_config
149159
new_ntlm = datastore['NEW_NTLM']
160+
fail_with(Msf::Exploit::Failure::BadConfig, 'Must provide NEW_NTLM value') if new_ntlm.blank?
150161
case new_ntlm.count(':')
151162
when 0
152163
new_nt = new_ntlm
@@ -166,6 +177,7 @@ def parse_ntlm_from_config
166177
end
167178

168179
def get_user_handle(domain, username)
180+
vprint_status("Opening handle for #{domain}\\#{username}")
169181
@server_handle = @samr.samr_connect
170182
domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: domain)
171183
@domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: domain_sid)
@@ -174,26 +186,63 @@ def get_user_handle(domain, username)
174186
rid = user_rids[username][:rid]
175187

176188
@samr.samr_open_user(domain_handle: @domain_handle, user_id: rid)
189+
rescue RubySMB::Dcerpc::Error::SamrError => e
190+
fail_with(Msf::Exploit::Failure::BadConfig, "#{e}")
177191
end
178192

179193
def run_change_ntlm
194+
fail_with(Module::Failure::BadConfig, 'Must set NEW_NTLM') if datastore['NEW_NTLM'].blank?
195+
fail_with(Module::Failure::BadConfig, 'Must set SMBUser to change password') if datastore['SMBUser'].blank?
196+
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?
197+
new_nt, new_lm = parse_ntlm_from_config
198+
print_status('Changing NTLM')
180199
authenticate(anonymous_on_expired: false)
181200

182-
user_handle = get_user_handle(datastore['SMBUser'], datastore['SMBDomain'])
183-
184-
new_nt, new_lm = parse_ntlm_from_config
201+
user_handle = get_user_handle(datastore['SMBDomain'], datastore['SMBUser'])
185202

186203
@samr.samr_change_password_user(user_handle: user_handle,
187204
old_password: datastore['SMBPass'],
188205
new_nt_hash: new_nt,
189206
new_lm_hash: new_lm)
207+
208+
print_good("Successfully changed password for #{datastore['SMBUser']}")
209+
print_warning("AES Kerberos keys will not be available until user changes their password")
210+
end
211+
212+
def run_reset_ntlm
213+
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?
214+
new_nt, new_lm = parse_ntlm_from_config
215+
print_status('Resetting NTLM')
216+
authenticate(anonymous_on_expired: false)
217+
218+
user_handle = get_user_handle(datastore['SMBDomain'], datastore['TARGET_USER'])
219+
220+
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
221+
tag: RubySMB::Dcerpc::Samr::USER_INTERNAL1_INFORMATION,
222+
member: RubySMB::Dcerpc::Samr::SamprUserInternal1Information.new(
223+
encrypted_nt_owf_password: RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword.new(buffer: RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword.encrypt_hash(hash: new_nt, key: simple.client.application_key)),
224+
encrypted_lm_owf_password: nil,
225+
nt_password_present: 1,
226+
lm_password_present: 0,
227+
password_expired: 0
228+
)
229+
)
230+
@samr.samr_set_information_user2(
231+
user_handle: user_handle,
232+
user_info: user_info
233+
)
234+
235+
print_good("Successfully reset password for #{datastore['TARGET_USER']}")
236+
print_warning("AES Kerberos keys will not be available until user changes their password")
190237
end
191238

192239
def run_reset
193-
fail_with('Must set TARGET_USER') if datastore['TARGET_USER'].blank?
240+
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?
241+
fail_with(Module::Failure::BadConfig, 'Must set NEW_PASSWORD') if datastore['NEW_PASSWORD'].blank?
242+
print_status('Resetting password')
194243
authenticate(anonymous_on_expired: false)
195244

196-
user_handle = get_user_handle(datastore['TARGET_USER'], datastore['SMBDomain'])
245+
user_handle = get_user_handle(datastore['SMBDomain'], datastore['TARGET_USER'])
197246

198247
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
199248
tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW,
@@ -214,24 +263,18 @@ def run_reset
214263
user_handle: user_handle,
215264
user_info: user_info
216265
)
217-
print_good("Successfully changed password")
266+
print_good("Successfully reset password for #{datastore['TARGET_USER']}")
218267
end
219268

220269
def run_change
270+
fail_with(Module::Failure::BadConfig, 'Must set NEW_PASSWORD') if datastore['NEW_PASSWORD'].blank?
271+
fail_with(Module::Failure::BadConfig, 'Must set SMBUser to change password') if datastore['SMBUser'].blank?
272+
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?
273+
print_status('Changing password')
221274
authenticate(anonymous_on_expired: true)
222275

223276
@samr.samr_unicode_change_password_user2(target_username: datastore['SMBUser'], old_password: datastore['SMBPass'], new_password: datastore['NEW_PASSWORD'])
224-
225-
rescue RubySMB::Error::RubySMBError => e
226-
fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}")
227-
rescue Rex::ConnectionError => e
228-
fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}")
229-
rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e
230-
fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}")
231-
rescue ::StandardError => e
232-
raise e
233-
ensure
234-
@samr.close if @samr
235-
@tree.disconnect! if @tree
277+
278+
print_good("Successfully changed password for #{datastore['SMBUser']}")
236279
end
237280
end

0 commit comments

Comments
 (0)