Skip to content

Commit 8158cf5

Browse files
committed
Add Reset and Change_NTLM actions
1 parent 479078a commit 8158cf5

File tree

2 files changed

+73
-19
lines changed

2 files changed

+73
-19
lines changed

lib/msf/core/exploit/remote/smb/client.rb

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,19 @@ def unicode(str)
149149
# You should call {#connect} before calling this
150150
#
151151
# @param simple_client [Rex::Proto::SMB::SimpleClient] Optional SimpleClient instance to use
152+
# @param opts [Hash] Options to override the datastore options
153+
# @option :username [String] Override SMBUser datastore option
154+
# @option :domain [String] Override SMBDomain datastore option
155+
# @option :password [String] Override SMBPass datastore option
156+
# @option :auth_protocol [String] Override SMB::Auth datastore option
152157
# @return [void]
153-
def smb_login(simple_client = self.simple)
158+
def smb_login(simple_client = self.simple, opts: {})
159+
username = opts.fetch(:username) {datastore['SMBUser']}
160+
domain = opts.fetch(:domain) {datastore['SMBDomain']}
161+
password = opts.fetch(:password) {datastore['SMBPass']}
162+
smb_auth = opts.fetch(:auth_protocol) {datastore['SMB::Auth']}
154163
# Override the default RubySMB capabilities with Kerberos authentication
155-
if datastore['SMB::Auth'] == Msf::Exploit::Remote::AuthOption::KERBEROS
164+
if smb_auth == Msf::Exploit::Remote::AuthOption::KERBEROS
156165
fail_with(Msf::Exploit::Failure::BadConfig, 'The Smb::Rhostname option is required when using Kerberos authentication.') if datastore['Smb::Rhostname'].blank?
157166
fail_with(Msf::Exploit::Failure::BadConfig, 'The SMBDomain option is required when using Kerberos authentication.') if datastore['SMBDomain'].blank?
158167
offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(datastore['Smb::KrbOfferedEncryptionTypes'])
@@ -162,9 +171,9 @@ def smb_login(simple_client = self.simple)
162171
host: datastore['DomainControllerRhost'].blank? ? nil : datastore['DomainControllerRhost'],
163172
hostname: datastore['Smb::Rhostname'],
164173
proxies: datastore['Proxies'],
165-
realm: datastore['SMBDomain'],
166-
username: datastore['SMBUser'],
167-
password: datastore['SMBPass'],
174+
realm: domain,
175+
username: username,
176+
password: password,
168177
framework: framework,
169178
framework_module: self,
170179
cache_file: datastore['Smb::Krb5Ccname'].blank? ? nil : datastore['Smb::Krb5Ccname'],
@@ -178,9 +187,9 @@ def smb_login(simple_client = self.simple)
178187

179188
simple_client.login(
180189
datastore['SMBName'],
181-
datastore['SMBUser'],
182-
datastore['SMBPass'],
183-
datastore['SMBDomain'],
190+
username,
191+
password,
192+
domain,
184193
datastore['SMB::VerifySignature'],
185194
datastore['NTLM::UseNTLMv2'],
186195
datastore['NTLM::UseNTLM2_session'],

modules/auxiliary/admin/smb/change_password.rb

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ def initialize(info = {})
3434
'Actions' => [
3535
[ 'RESET', { 'Description' => "Reset the target's password without knowing the existing one (requires appropriate permissions)" } ],
3636
[ 'RESET_NTLM', { 'Description' => "Reset the target's NTLM hash, without knowing the existing password. This will not update kerberos keys." } ],
37-
[ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one.' } ]
37+
[ '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' } ]
3839
],
3940
'DefaultAction' => 'RESET'
4041
)
@@ -43,8 +44,8 @@ def initialize(info = {})
4344
register_options(
4445
[
4546
OptString.new('NEW_PASSWORD', [false, 'The new password to change to', '']),
46-
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]]),
47-
OptString.new('NEW_NTLM', [false, 'The new NTLM hash to change to', ''])
47+
OptString.new('NEW_NTLM', [false, 'The new NTLM hash to change to', '']),
48+
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]])
4849
]
4950
)
5051
end
@@ -68,13 +69,17 @@ def connect_samr
6869
end
6970

7071
def run
72+
fail_with('Must set NEW_PASSWORD on NEW_NTLM') if datastore['NEW_PASSWORD'].blank? && datastore['NEW_NTLM'].blank?
73+
7174
case action.name
7275
when 'CHANGE'
7376
run_change
7477
when 'RESET'
7578
run_reset
7679
when 'RESET_NTLM'
7780
run_reset_ntlm
81+
when 'CHANGE_NTLM'
82+
run_change_ntlm
7883
end
7984

8085
# Don't disconnect the client if it's coming from the session so it can be reused
@@ -96,6 +101,7 @@ def authenticate(anonymous_on_expired: false)
96101
begin
97102
smb_login
98103
rescue Rex::Proto::SMB::Exceptions::LoginError => e
104+
binding.pry
99105
if anonymous_on_expired &&
100106
(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) ||
101107
e.source.is_a?(::WindowsError::ErrorCode) && [::WindowsError::NTStatus::STATUS_PASSWORD_EXPIRED, ::WindowsError::NTStatus::STATUS_PASSWORD_MUST_CHANGE].include?(e.source))
@@ -139,15 +145,55 @@ def authenticate(anonymous_on_expired: false)
139145

140146
end
141147

142-
def run_reset
143-
authenticate(anonymous_on_expired: false)
148+
def parse_ntlm_from_config
149+
new_ntlm = datastore['NEW_NTLM']
150+
case new_ntlm.count(':')
151+
when 0
152+
new_nt = new_ntlm
153+
new_lm = nil
154+
when 1
155+
new_nt, new_lm = new_ntlm.split(':')
156+
else
157+
fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid value for NEW_NTLM')
158+
end
144159

160+
new_nt = Rex::Text::hex_to_raw(new_nt)
161+
new_lm = Rex::Text::hex_to_raw(new_lm) unless new_lm.nil?
162+
fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid NT hash value in NEW_NTLM') unless new_nt.length == 16
163+
fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid LM hash value in NEW_NTLM') unless new_lm.nil? || new_nt.length == 16
164+
165+
[new_nt, new_lm]
166+
end
167+
168+
def get_user_handle(domain, username)
145169
@server_handle = @samr.samr_connect
146-
domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: datastore['SMBDomain'])
170+
domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: domain)
147171
@domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: domain_sid)
148-
user_rids = @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [datastore['TARGET_USER']])
149-
rid = user_rids[datastore['TARGET_USER']][:rid]
150-
user_handle = @samr.samr_open_user(domain_handle: @domain_handle, user_id: rid)
172+
user_rids = @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [username])
173+
fail_with(Module::Failure::BadConfig, "Could not find #{domain}\\#{username}") if user_rids.nil?
174+
rid = user_rids[username][:rid]
175+
176+
@samr.samr_open_user(domain_handle: @domain_handle, user_id: rid)
177+
end
178+
179+
def run_change_ntlm
180+
authenticate(anonymous_on_expired: false)
181+
182+
user_handle = get_user_handle(datastore['SMBUser'], datastore['SMBDomain'])
183+
184+
new_nt, new_lm = parse_ntlm_from_config
185+
186+
@samr.samr_change_password_user(user_handle: user_handle,
187+
old_password: datastore['SMBPass'],
188+
new_nt_hash: new_nt,
189+
new_lm_hash: new_lm)
190+
end
191+
192+
def run_reset
193+
fail_with('Must set TARGET_USER') if datastore['TARGET_USER'].blank?
194+
authenticate(anonymous_on_expired: false)
195+
196+
user_handle = get_user_handle(datastore['TARGET_USER'], datastore['SMBDomain'])
151197

152198
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
153199
tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW,
@@ -168,6 +214,7 @@ def run_reset
168214
user_handle: user_handle,
169215
user_info: user_info
170216
)
217+
print_good("Successfully changed password")
171218
end
172219

173220
def run_change
@@ -184,8 +231,6 @@ def run_change
184231
rescue ::StandardError => e
185232
raise e
186233
ensure
187-
@samr.close_handle(@domain_handle) if @domain_handle
188-
@samr.close_handle(@server_handle) if @server_handle
189234
@samr.close if @samr
190235
@tree.disconnect! if @tree
191236
end

0 commit comments

Comments
 (0)