From 479078a5f26b7a2f5e425ac4b4c006054582e8af Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Tue, 19 Nov 2024 17:44:59 +1100 Subject: [PATCH 01/11] Adding changing/resetting password module --- .../auxiliary/admin/smb/change_password.rb | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100755 modules/auxiliary/admin/smb/change_password.rb diff --git a/modules/auxiliary/admin/smb/change_password.rb b/modules/auxiliary/admin/smb/change_password.rb new file mode 100755 index 000000000000..3e093d2a11d3 --- /dev/null +++ b/modules/auxiliary/admin/smb/change_password.rb @@ -0,0 +1,192 @@ +## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework +## + +require 'ruby_smb/dcerpc/client' +require 'pry-byebug' + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated + include Msf::Auxiliary::Report + include Msf::OptionalSession::SMB + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'SMB Password Change', + 'Description' => %q{ + Change the password of an account using SMB. This provides several different + APIs, each of which have their respective benefits and drawbacks. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'smashery' + ], + 'References' => [ + ['URL', 'https://github.com/fortra/impacket/blob/master/examples/changepasswd.py'], + ], + 'Notes' => { + 'Reliability' => [], + 'Stability' => [], + 'SideEffects' => [ IOC_IN_LOGS ] + }, + 'Actions' => [ + [ 'RESET', { 'Description' => "Reset the target's password without knowing the existing one (requires appropriate permissions)" } ], + [ 'RESET_NTLM', { 'Description' => "Reset the target's NTLM hash, without knowing the existing password. This will not update kerberos keys." } ], + [ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one.' } ] + ], + 'DefaultAction' => 'RESET' + ) + ) + + register_options( + [ + OptString.new('NEW_PASSWORD', [false, 'The new password to change to', '']), + 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]]), + OptString.new('NEW_NTLM', [false, 'The new NTLM hash to change to', '']) + ] + ) + end + + def connect_samr(domain_name, target_user) + vprint_status('Connecting to Security Account Manager (SAM) Remote Protocol') + @samr = @tree.open_file(filename: 'samr', write: true, read: true) + + vprint_status('Binding to \\samr...') + @samr.bind(endpoint: RubySMB::Dcerpc::Samr) + vprint_good('Bound to \\samr') + end + + def connect_samr + vprint_status('Connecting to Security Account Manager (SAM) Remote Protocol') + @samr = @tree.open_file(filename: 'samr', write: true, read: true) + + vprint_status('Binding to \\samr...') + @samr.bind(endpoint: RubySMB::Dcerpc::Samr) + vprint_good('Bound to \\samr') + end + + def run + case action.name + when 'CHANGE' + run_change + when 'RESET' + run_reset + when 'RESET_NTLM' + run_reset_ntlm + end + + # Don't disconnect the client if it's coming from the session so it can be reused + unless session + simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client) + disconnect + end + end + + def authenticate(anonymous_on_expired: false) + if session + print_status("Using existing session #{session.sid}") + client = session.client + self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) + simple.connect("\\\\#{simple.address}\\IPC$") # smb_login connects to this share for some reason and it doesn't work unless we do too + else + connect + begin + begin + smb_login + rescue Rex::Proto::SMB::Exceptions::LoginError => e + if anonymous_on_expired && + (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) || + e.source.is_a?(::WindowsError::ErrorCode) && [::WindowsError::NTStatus::STATUS_PASSWORD_EXPIRED, ::WindowsError::NTStatus::STATUS_PASSWORD_MUST_CHANGE].include?(e.source)) + # Password has expired - we'll need to anonymous connect + opts = { + :username => '', + :password => '', + :domain => '', + :auth_protocol => Msf::Exploit::Remote::AuthOption::NTLM + } + disconnect + connect + smb_login(opts: opts) + else + raise + end + end + + rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e + fail_with(Module::Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).") + end + end + + report_service( + host: simple.address, + port: simple.port, + host_name: simple.client.default_name, + proto: 'tcp', + name: 'smb', + info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})" + ) + + begin + @tree = simple.client.tree_connect("\\\\#{simple.address}\\IPC$") + rescue RubySMB::Error::RubySMBError => e + fail_with(Module::Failure::Unreachable, + "Unable to connect to the remote IPC$ share ([#{e.class}] #{e}).") + end + + connect_samr + + end + + def run_reset + authenticate(anonymous_on_expired: false) + + @server_handle = @samr.samr_connect + domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: datastore['SMBDomain']) + @domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: domain_sid) + user_rids = @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [datastore['TARGET_USER']]) + rid = user_rids[datastore['TARGET_USER']][:rid] + user_handle = @samr.samr_open_user(domain_handle: @domain_handle, user_id: rid) + + user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new( + tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW, + member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new( + i1: { + password_expired: 0, + which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED + }, + user_password: { + buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password( + datastore['NEW_PASSWORD'], + simple.client.application_key + ) + } + ) + ) + @samr.samr_set_information_user2( + user_handle: user_handle, + user_info: user_info + ) + end + + def run_change + authenticate(anonymous_on_expired: true) + + @samr.samr_unicode_change_password_user2(target_username: datastore['SMBUser'], old_password: datastore['SMBPass'], new_password: datastore['NEW_PASSWORD']) + + rescue RubySMB::Error::RubySMBError => e + fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}") + rescue Rex::ConnectionError => e + fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}") + rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e + fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}") + rescue ::StandardError => e + raise e + ensure + @samr.close_handle(@domain_handle) if @domain_handle + @samr.close_handle(@server_handle) if @server_handle + @samr.close if @samr + @tree.disconnect! if @tree + end +end From 8158cf5bae2eadf1a2fa8b73b7a4e34ac0aaa44b Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Wed, 20 Nov 2024 12:11:52 +1100 Subject: [PATCH 02/11] Add Reset and Change_NTLM actions --- lib/msf/core/exploit/remote/smb/client.rb | 25 ++++--- .../auxiliary/admin/smb/change_password.rb | 67 ++++++++++++++++--- 2 files changed, 73 insertions(+), 19 deletions(-) diff --git a/lib/msf/core/exploit/remote/smb/client.rb b/lib/msf/core/exploit/remote/smb/client.rb index 25f41b2abcb6..0f1653d09c7f 100644 --- a/lib/msf/core/exploit/remote/smb/client.rb +++ b/lib/msf/core/exploit/remote/smb/client.rb @@ -149,10 +149,19 @@ def unicode(str) # You should call {#connect} before calling this # # @param simple_client [Rex::Proto::SMB::SimpleClient] Optional SimpleClient instance to use + # @param opts [Hash] Options to override the datastore options + # @option :username [String] Override SMBUser datastore option + # @option :domain [String] Override SMBDomain datastore option + # @option :password [String] Override SMBPass datastore option + # @option :auth_protocol [String] Override SMB::Auth datastore option # @return [void] - def smb_login(simple_client = self.simple) + def smb_login(simple_client = self.simple, opts: {}) + username = opts.fetch(:username) {datastore['SMBUser']} + domain = opts.fetch(:domain) {datastore['SMBDomain']} + password = opts.fetch(:password) {datastore['SMBPass']} + smb_auth = opts.fetch(:auth_protocol) {datastore['SMB::Auth']} # Override the default RubySMB capabilities with Kerberos authentication - if datastore['SMB::Auth'] == Msf::Exploit::Remote::AuthOption::KERBEROS + if smb_auth == Msf::Exploit::Remote::AuthOption::KERBEROS fail_with(Msf::Exploit::Failure::BadConfig, 'The Smb::Rhostname option is required when using Kerberos authentication.') if datastore['Smb::Rhostname'].blank? fail_with(Msf::Exploit::Failure::BadConfig, 'The SMBDomain option is required when using Kerberos authentication.') if datastore['SMBDomain'].blank? offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(datastore['Smb::KrbOfferedEncryptionTypes']) @@ -162,9 +171,9 @@ def smb_login(simple_client = self.simple) host: datastore['DomainControllerRhost'].blank? ? nil : datastore['DomainControllerRhost'], hostname: datastore['Smb::Rhostname'], proxies: datastore['Proxies'], - realm: datastore['SMBDomain'], - username: datastore['SMBUser'], - password: datastore['SMBPass'], + realm: domain, + username: username, + password: password, framework: framework, framework_module: self, cache_file: datastore['Smb::Krb5Ccname'].blank? ? nil : datastore['Smb::Krb5Ccname'], @@ -178,9 +187,9 @@ def smb_login(simple_client = self.simple) simple_client.login( datastore['SMBName'], - datastore['SMBUser'], - datastore['SMBPass'], - datastore['SMBDomain'], + username, + password, + domain, datastore['SMB::VerifySignature'], datastore['NTLM::UseNTLMv2'], datastore['NTLM::UseNTLM2_session'], diff --git a/modules/auxiliary/admin/smb/change_password.rb b/modules/auxiliary/admin/smb/change_password.rb index 3e093d2a11d3..6c17bddc1f08 100755 --- a/modules/auxiliary/admin/smb/change_password.rb +++ b/modules/auxiliary/admin/smb/change_password.rb @@ -34,7 +34,8 @@ def initialize(info = {}) 'Actions' => [ [ 'RESET', { 'Description' => "Reset the target's password without knowing the existing one (requires appropriate permissions)" } ], [ 'RESET_NTLM', { 'Description' => "Reset the target's NTLM hash, without knowing the existing password. This will not update kerberos keys." } ], - [ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one.' } ] + [ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one.' } ], + [ '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' } ] ], 'DefaultAction' => 'RESET' ) @@ -43,8 +44,8 @@ def initialize(info = {}) register_options( [ OptString.new('NEW_PASSWORD', [false, 'The new password to change to', '']), - 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]]), - OptString.new('NEW_NTLM', [false, 'The new NTLM hash to change to', '']) + OptString.new('NEW_NTLM', [false, 'The new NTLM hash to change to', '']), + 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]]) ] ) end @@ -68,6 +69,8 @@ def connect_samr end def run + fail_with('Must set NEW_PASSWORD on NEW_NTLM') if datastore['NEW_PASSWORD'].blank? && datastore['NEW_NTLM'].blank? + case action.name when 'CHANGE' run_change @@ -75,6 +78,8 @@ def run run_reset when 'RESET_NTLM' run_reset_ntlm + when 'CHANGE_NTLM' + run_change_ntlm end # 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) begin smb_login rescue Rex::Proto::SMB::Exceptions::LoginError => e + binding.pry if anonymous_on_expired && (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) || 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) end - def run_reset - authenticate(anonymous_on_expired: false) + def parse_ntlm_from_config + new_ntlm = datastore['NEW_NTLM'] + case new_ntlm.count(':') + when 0 + new_nt = new_ntlm + new_lm = nil + when 1 + new_nt, new_lm = new_ntlm.split(':') + else + fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid value for NEW_NTLM') + end + new_nt = Rex::Text::hex_to_raw(new_nt) + new_lm = Rex::Text::hex_to_raw(new_lm) unless new_lm.nil? + fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid NT hash value in NEW_NTLM') unless new_nt.length == 16 + fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid LM hash value in NEW_NTLM') unless new_lm.nil? || new_nt.length == 16 + + [new_nt, new_lm] + end + + def get_user_handle(domain, username) @server_handle = @samr.samr_connect - domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: datastore['SMBDomain']) + domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: domain) @domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: domain_sid) - user_rids = @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [datastore['TARGET_USER']]) - rid = user_rids[datastore['TARGET_USER']][:rid] - user_handle = @samr.samr_open_user(domain_handle: @domain_handle, user_id: rid) + user_rids = @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [username]) + fail_with(Module::Failure::BadConfig, "Could not find #{domain}\\#{username}") if user_rids.nil? + rid = user_rids[username][:rid] + + @samr.samr_open_user(domain_handle: @domain_handle, user_id: rid) + end + + def run_change_ntlm + authenticate(anonymous_on_expired: false) + + user_handle = get_user_handle(datastore['SMBUser'], datastore['SMBDomain']) + + new_nt, new_lm = parse_ntlm_from_config + + @samr.samr_change_password_user(user_handle: user_handle, + old_password: datastore['SMBPass'], + new_nt_hash: new_nt, + new_lm_hash: new_lm) + end + + def run_reset + fail_with('Must set TARGET_USER') if datastore['TARGET_USER'].blank? + authenticate(anonymous_on_expired: false) + + user_handle = get_user_handle(datastore['TARGET_USER'], datastore['SMBDomain']) user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new( tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW, @@ -168,6 +214,7 @@ def run_reset user_handle: user_handle, user_info: user_info ) + print_good("Successfully changed password") end def run_change @@ -184,8 +231,6 @@ def run_change rescue ::StandardError => e raise e ensure - @samr.close_handle(@domain_handle) if @domain_handle - @samr.close_handle(@server_handle) if @server_handle @samr.close if @samr @tree.disconnect! if @tree end From 1ca32eea7ea27cfa6270c47684c988c490fe8723 Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Wed, 20 Nov 2024 15:00:42 +1100 Subject: [PATCH 03/11] Implement Reset NTLM behaviour. --- .../auxiliary/admin/smb/change_password.rb | 103 +++++++++++++----- 1 file changed, 73 insertions(+), 30 deletions(-) diff --git a/modules/auxiliary/admin/smb/change_password.rb b/modules/auxiliary/admin/smb/change_password.rb index 6c17bddc1f08..3c82df5be9d7 100755 --- a/modules/auxiliary/admin/smb/change_password.rb +++ b/modules/auxiliary/admin/smb/change_password.rb @@ -2,7 +2,6 @@ ## require 'ruby_smb/dcerpc/client' -require 'pry-byebug' class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::SMB::Client @@ -35,7 +34,7 @@ def initialize(info = {}) [ 'RESET', { 'Description' => "Reset the target's password without knowing the existing one (requires appropriate permissions)" } ], [ 'RESET_NTLM', { 'Description' => "Reset the target's NTLM hash, without knowing the existing password. This will not update kerberos keys." } ], [ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one.' } ], - [ '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' } ] + [ 'CHANGE_NTLM', { 'Description' => 'Change the password to a NTLM hash value, knowing the existing password. This will not update kerberos keys.' } ] ], 'DefaultAction' => 'RESET' ) @@ -43,8 +42,8 @@ def initialize(info = {}) register_options( [ - OptString.new('NEW_PASSWORD', [false, 'The new password to change to', '']), - OptString.new('NEW_NTLM', [false, 'The new NTLM hash to change to', '']), + OptString.new('NEW_PASSWORD', [false, 'The new password to change to', ''], conditions: ['ACTION', 'in', %w[CHANGE RESET]]), + 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]]), 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]]) ] ) @@ -69,8 +68,6 @@ def connect_samr end def run - fail_with('Must set NEW_PASSWORD on NEW_NTLM') if datastore['NEW_PASSWORD'].blank? && datastore['NEW_NTLM'].blank? - case action.name when 'CHANGE' run_change @@ -82,11 +79,25 @@ def run run_change_ntlm end - # Don't disconnect the client if it's coming from the session so it can be reused - unless session - simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client) - disconnect - end + rescue RubySMB::Error::RubySMBError => e + fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}") + rescue Rex::ConnectionError => e + fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}") + rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e + fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}") + rescue ::StandardError => e + raise e + ensure + @samr.close_handle(@domain_handle) if @domain_handle + @samr.close_handle(@server_handle) if @server_handle + @samr.close if @samr + @tree.disconnect! if @tree + + # Don't disconnect the client if it's coming from the session so it can be reused + unless session + simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client) + disconnect + end end def authenticate(anonymous_on_expired: false) @@ -101,7 +112,6 @@ def authenticate(anonymous_on_expired: false) begin smb_login rescue Rex::Proto::SMB::Exceptions::LoginError => e - binding.pry if anonymous_on_expired && (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) || 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) def parse_ntlm_from_config new_ntlm = datastore['NEW_NTLM'] + fail_with(Msf::Exploit::Failure::BadConfig, 'Must provide NEW_NTLM value') if new_ntlm.blank? case new_ntlm.count(':') when 0 new_nt = new_ntlm @@ -166,6 +177,7 @@ def parse_ntlm_from_config end def get_user_handle(domain, username) + vprint_status("Opening handle for #{domain}\\#{username}") @server_handle = @samr.samr_connect domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: domain) @domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: domain_sid) @@ -174,26 +186,63 @@ def get_user_handle(domain, username) rid = user_rids[username][:rid] @samr.samr_open_user(domain_handle: @domain_handle, user_id: rid) + rescue RubySMB::Dcerpc::Error::SamrError => e + fail_with(Msf::Exploit::Failure::BadConfig, "#{e}") end def run_change_ntlm + fail_with(Module::Failure::BadConfig, 'Must set NEW_NTLM') if datastore['NEW_NTLM'].blank? + fail_with(Module::Failure::BadConfig, 'Must set SMBUser to change password') if datastore['SMBUser'].blank? + 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? + new_nt, new_lm = parse_ntlm_from_config + print_status('Changing NTLM') authenticate(anonymous_on_expired: false) - user_handle = get_user_handle(datastore['SMBUser'], datastore['SMBDomain']) - - new_nt, new_lm = parse_ntlm_from_config + user_handle = get_user_handle(datastore['SMBDomain'], datastore['SMBUser']) @samr.samr_change_password_user(user_handle: user_handle, old_password: datastore['SMBPass'], new_nt_hash: new_nt, new_lm_hash: new_lm) + + print_good("Successfully changed password for #{datastore['SMBUser']}") + print_warning("AES Kerberos keys will not be available until user changes their password") + end + + def run_reset_ntlm + 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? + new_nt, new_lm = parse_ntlm_from_config + print_status('Resetting NTLM') + authenticate(anonymous_on_expired: false) + + user_handle = get_user_handle(datastore['SMBDomain'], datastore['TARGET_USER']) + + user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new( + tag: RubySMB::Dcerpc::Samr::USER_INTERNAL1_INFORMATION, + member: RubySMB::Dcerpc::Samr::SamprUserInternal1Information.new( + encrypted_nt_owf_password: RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword.new(buffer: RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword.encrypt_hash(hash: new_nt, key: simple.client.application_key)), + encrypted_lm_owf_password: nil, + nt_password_present: 1, + lm_password_present: 0, + password_expired: 0 + ) + ) + @samr.samr_set_information_user2( + user_handle: user_handle, + user_info: user_info + ) + + print_good("Successfully reset password for #{datastore['TARGET_USER']}") + print_warning("AES Kerberos keys will not be available until user changes their password") end def run_reset - fail_with('Must set TARGET_USER') if datastore['TARGET_USER'].blank? + 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? + fail_with(Module::Failure::BadConfig, 'Must set NEW_PASSWORD') if datastore['NEW_PASSWORD'].blank? + print_status('Resetting password') authenticate(anonymous_on_expired: false) - user_handle = get_user_handle(datastore['TARGET_USER'], datastore['SMBDomain']) + user_handle = get_user_handle(datastore['SMBDomain'], datastore['TARGET_USER']) user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new( tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW, @@ -214,24 +263,18 @@ def run_reset user_handle: user_handle, user_info: user_info ) - print_good("Successfully changed password") + print_good("Successfully reset password for #{datastore['TARGET_USER']}") end def run_change + fail_with(Module::Failure::BadConfig, 'Must set NEW_PASSWORD') if datastore['NEW_PASSWORD'].blank? + fail_with(Module::Failure::BadConfig, 'Must set SMBUser to change password') if datastore['SMBUser'].blank? + 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? + print_status('Changing password') authenticate(anonymous_on_expired: true) @samr.samr_unicode_change_password_user2(target_username: datastore['SMBUser'], old_password: datastore['SMBPass'], new_password: datastore['NEW_PASSWORD']) - - rescue RubySMB::Error::RubySMBError => e - fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}") - rescue Rex::ConnectionError => e - fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}") - rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e - fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}") - rescue ::StandardError => e - raise e - ensure - @samr.close if @samr - @tree.disconnect! if @tree + + print_good("Successfully changed password for #{datastore['SMBUser']}") end end From afc735f4a4c7a6f978a6c581d64f388aee22eb42 Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Wed, 20 Nov 2024 15:26:03 +1100 Subject: [PATCH 04/11] Add documentation --- .../auxiliary/admin/smb/change_password.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100755 documentation/modules/auxiliary/admin/smb/change_password.md diff --git a/documentation/modules/auxiliary/admin/smb/change_password.md b/documentation/modules/auxiliary/admin/smb/change_password.md new file mode 100755 index 000000000000..39bf5147feb6 --- /dev/null +++ b/documentation/modules/auxiliary/admin/smb/change_password.md @@ -0,0 +1,46 @@ +## Introduction + +Allows changing or resetting users' passwords. + +"Changing" refers to situations where you know the value of the existing password, and send that to the server as part of the password modification. +"Resetting" refers to situations where you may not know the value of the existing password, but by virtue of your permissions over the target account, you can force-change the password without necessarily knowing it. + +Note that users can typically not reset their own passwords (unless they have very high privileges). + +This module works with existing sessions (or relaying), especially for Reset use cases, wherein the target's password is not required. + +## Actions + +- `RESET` - Reset the target's password without knowing the existing one (requires appropriate permissions) +- `RESET_NTLM` - Reset the target's NTLM hash, without knowing the existing password. This will not update kerberos keys. +- `CHANGE` - Change the password, knowing the existing one. +- `CHANGE_NTLM` - Change the password to a NTLM hash value, knowing the existing password. This will not update kerberos keys. + +## Options + +The required options are based on the action being performed: + +- When resetting a password, you must specify the `TARGET_USER` +- When changing a password, you must specify the `SMBUser` and `SMBPass`, even if using an existing session (since the API requires both of these to be specified, even for open SMB sessions) +- When resetting or changing a password, you must specify `NEW_PASSWORD` +- When resetting or changing an NTLM hash, you must specify `NEW_NTLM` + +**SMBUser** + +The username to use to authenticate to the server. Required for changing a password, even if using an existing session. + +**SMBPass** + +The password to use to authenticate to the server, prior to performing the password modification. Required for changing a password, even if using an existing session (since the server requires proof that you know the existing password). + +**TARGET_USER** + +For resetting passwords, the user account for which to reset the password. The authenticated account (SMBUser) must have privileges over the target user (e.g. Ownership, or the `User-Force-Change-Password` extended right) + +**NEW_PASSWORD** + +The new password to set for `RESET` and `CHANGE` actions. + +**NEW_NTLM** + +The new NTLM hash to set for `RESET_NTLM` and `CHANGE_NTLM` actions. This can either be an NT hash, or a colon-delimited NTLM hash. \ No newline at end of file From cec793f8f57407f45ee3b734c999ace25e74b087 Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Wed, 20 Nov 2024 15:36:59 +1100 Subject: [PATCH 05/11] Msftidy changes --- .../auxiliary/admin/smb/change_password.rb | 78 ++++++++----------- 1 file changed, 33 insertions(+), 45 deletions(-) diff --git a/modules/auxiliary/admin/smb/change_password.rb b/modules/auxiliary/admin/smb/change_password.rb index 3c82df5be9d7..711173d7c807 100755 --- a/modules/auxiliary/admin/smb/change_password.rb +++ b/modules/auxiliary/admin/smb/change_password.rb @@ -49,15 +49,6 @@ def initialize(info = {}) ) end - def connect_samr(domain_name, target_user) - vprint_status('Connecting to Security Account Manager (SAM) Remote Protocol') - @samr = @tree.open_file(filename: 'samr', write: true, read: true) - - vprint_status('Binding to \\samr...') - @samr.bind(endpoint: RubySMB::Dcerpc::Samr) - vprint_good('Bound to \\samr') - end - def connect_samr vprint_status('Connecting to Security Account Manager (SAM) Remote Protocol') @samr = @tree.open_file(filename: 'samr', write: true, read: true) @@ -66,7 +57,7 @@ def connect_samr @samr.bind(endpoint: RubySMB::Dcerpc::Samr) vprint_good('Bound to \\samr') end - + def run case action.name when 'CHANGE' @@ -78,26 +69,25 @@ def run when 'CHANGE_NTLM' run_change_ntlm end - - rescue RubySMB::Error::RubySMBError => e - fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}") - rescue Rex::ConnectionError => e - fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}") - rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e - fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}") - rescue ::StandardError => e - raise e - ensure - @samr.close_handle(@domain_handle) if @domain_handle - @samr.close_handle(@server_handle) if @server_handle - @samr.close if @samr - @tree.disconnect! if @tree - - # Don't disconnect the client if it's coming from the session so it can be reused - unless session - simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client) - disconnect - end + rescue RubySMB::Error::RubySMBError => e + fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}") + rescue Rex::ConnectionError => e + fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}") + rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e + fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}") + rescue ::StandardError => e + raise e + ensure + @samr.close_handle(@domain_handle) if @domain_handle + @samr.close_handle(@server_handle) if @server_handle + @samr.close if @samr + @tree.disconnect! if @tree + + # Don't disconnect the client if it's coming from the session so it can be reused + unless session + simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client) + disconnect + end end def authenticate(anonymous_on_expired: false) @@ -117,10 +107,10 @@ def authenticate(anonymous_on_expired: false) e.source.is_a?(::WindowsError::ErrorCode) && [::WindowsError::NTStatus::STATUS_PASSWORD_EXPIRED, ::WindowsError::NTStatus::STATUS_PASSWORD_MUST_CHANGE].include?(e.source)) # Password has expired - we'll need to anonymous connect opts = { - :username => '', - :password => '', - :domain => '', - :auth_protocol => Msf::Exploit::Remote::AuthOption::NTLM + username: '', + password: '', + domain: '', + auth_protocol: Msf::Exploit::Remote::AuthOption::NTLM } disconnect connect @@ -129,7 +119,6 @@ def authenticate(anonymous_on_expired: false) raise end end - rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e fail_with(Module::Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).") end @@ -152,7 +141,6 @@ def authenticate(anonymous_on_expired: false) end connect_samr - end def parse_ntlm_from_config @@ -168,8 +156,8 @@ def parse_ntlm_from_config fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid value for NEW_NTLM') end - new_nt = Rex::Text::hex_to_raw(new_nt) - new_lm = Rex::Text::hex_to_raw(new_lm) unless new_lm.nil? + new_nt = Rex::Text.hex_to_raw(new_nt) + new_lm = Rex::Text.hex_to_raw(new_lm) unless new_lm.nil? fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid NT hash value in NEW_NTLM') unless new_nt.length == 16 fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid LM hash value in NEW_NTLM') unless new_lm.nil? || new_nt.length == 16 @@ -187,7 +175,7 @@ def get_user_handle(domain, username) @samr.samr_open_user(domain_handle: @domain_handle, user_id: rid) rescue RubySMB::Dcerpc::Error::SamrError => e - fail_with(Msf::Exploit::Failure::BadConfig, "#{e}") + fail_with(Msf::Exploit::Failure::BadConfig, e.to_s) end def run_change_ntlm @@ -201,17 +189,17 @@ def run_change_ntlm user_handle = get_user_handle(datastore['SMBDomain'], datastore['SMBUser']) @samr.samr_change_password_user(user_handle: user_handle, - old_password: datastore['SMBPass'], - new_nt_hash: new_nt, - new_lm_hash: new_lm) + old_password: datastore['SMBPass'], + new_nt_hash: new_nt, + new_lm_hash: new_lm) print_good("Successfully changed password for #{datastore['SMBUser']}") - print_warning("AES Kerberos keys will not be available until user changes their password") + print_warning('AES Kerberos keys will not be available until user changes their password') end def run_reset_ntlm 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? - new_nt, new_lm = parse_ntlm_from_config + new_nt, = parse_ntlm_from_config print_status('Resetting NTLM') authenticate(anonymous_on_expired: false) @@ -233,7 +221,7 @@ def run_reset_ntlm ) print_good("Successfully reset password for #{datastore['TARGET_USER']}") - print_warning("AES Kerberos keys will not be available until user changes their password") + print_warning('AES Kerberos keys will not be available until user changes their password') end def run_reset From 47669764630543c9a34639c34ccc11bd6d383da6 Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Wed, 20 Nov 2024 17:06:53 +1100 Subject: [PATCH 06/11] Removed executable status --- modules/auxiliary/admin/smb/change_password.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 modules/auxiliary/admin/smb/change_password.rb diff --git a/modules/auxiliary/admin/smb/change_password.rb b/modules/auxiliary/admin/smb/change_password.rb old mode 100755 new mode 100644 From 1a20bed28665164d1e3af587511c317581c98478 Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Thu, 21 Nov 2024 07:48:53 +1100 Subject: [PATCH 07/11] Option description fix --- modules/auxiliary/admin/smb/change_password.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/smb/change_password.rb b/modules/auxiliary/admin/smb/change_password.rb index 711173d7c807..8ca2a55bd0eb 100644 --- a/modules/auxiliary/admin/smb/change_password.rb +++ b/modules/auxiliary/admin/smb/change_password.rb @@ -44,7 +44,7 @@ def initialize(info = {}) [ OptString.new('NEW_PASSWORD', [false, 'The new password to change to', ''], conditions: ['ACTION', 'in', %w[CHANGE RESET]]), 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]]), - 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]]) + OptString.new('TARGET_USER', [false, 'The user to reset the password of.'], conditions: ['ACTION', 'in', %w[RESET RESET_NTLM]]) ] ) end From 88bd8f6f9e23f5cad8a40037429a13810fa4873f Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Fri, 6 Dec 2024 14:21:56 +1100 Subject: [PATCH 08/11] Support SMBPass as NTLM format --- .../auxiliary/admin/smb/change_password.rb | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/modules/auxiliary/admin/smb/change_password.rb b/modules/auxiliary/admin/smb/change_password.rb index 8ca2a55bd0eb..86df6d4f8caf 100644 --- a/modules/auxiliary/admin/smb/change_password.rb +++ b/modules/auxiliary/admin/smb/change_password.rb @@ -43,7 +43,7 @@ def initialize(info = {}) register_options( [ OptString.new('NEW_PASSWORD', [false, 'The new password to change to', ''], conditions: ['ACTION', 'in', %w[CHANGE RESET]]), - 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]]), + 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}$/), OptString.new('TARGET_USER', [false, 'The user to reset the password of.'], conditions: ['ACTION', 'in', %w[RESET RESET_NTLM]]) ] ) @@ -102,19 +102,25 @@ def authenticate(anonymous_on_expired: false) begin smb_login rescue Rex::Proto::SMB::Exceptions::LoginError => e - if anonymous_on_expired && - (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) || + 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) || e.source.is_a?(::WindowsError::ErrorCode) && [::WindowsError::NTStatus::STATUS_PASSWORD_EXPIRED, ::WindowsError::NTStatus::STATUS_PASSWORD_MUST_CHANGE].include?(e.source)) - # Password has expired - we'll need to anonymous connect - opts = { - username: '', - password: '', - domain: '', - auth_protocol: Msf::Exploit::Remote::AuthOption::NTLM - } - disconnect - connect - smb_login(opts: opts) + if anonymous_on_expired + # Password has expired - we'll need to anonymous connect + print_warning("Password expired - binding anonymously") + opts = { + username: '', + password: '', + domain: '', + auth_protocol: Msf::Exploit::Remote::AuthOption::NTLM + } + disconnect + connect + smb_login(opts: opts) + else + if action.name == 'CHANGE_NTLM' + fail_with(Module::Failure::UnexpectedReply, 'Must change password first. Try using the CHANGE action instead') + end + end else raise end @@ -151,7 +157,7 @@ def parse_ntlm_from_config new_nt = new_ntlm new_lm = nil when 1 - new_nt, new_lm = new_ntlm.split(':') + new_lm, new_nt = new_ntlm.split(':') else fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid value for NEW_NTLM') end @@ -188,10 +194,22 @@ def run_change_ntlm user_handle = get_user_handle(datastore['SMBDomain'], datastore['SMBUser']) - @samr.samr_change_password_user(user_handle: user_handle, + if Net::NTLM.is_ntlm_hash?(Net::NTLM::EncodeUtil.encode_utf16le(datastore['SMBPass'])) + old_lm, old_nt = datastore['SMBPass'].split(':') + old_lm = [old_lm].pack('H*') + old_nt = [old_nt].pack('H*') + + @samr.samr_change_password_user(user_handle: user_handle, + new_nt_hash: new_nt, + new_lm_hash: new_lm, + old_nt_hash: old_nt, + old_lm_hash: old_lm) + else + @samr.samr_change_password_user(user_handle: user_handle, old_password: datastore['SMBPass'], new_nt_hash: new_nt, new_lm_hash: new_lm) + end print_good("Successfully changed password for #{datastore['SMBUser']}") print_warning('AES Kerberos keys will not be available until user changes their password') @@ -261,7 +279,14 @@ def run_change print_status('Changing password') authenticate(anonymous_on_expired: true) - @samr.samr_unicode_change_password_user2(target_username: datastore['SMBUser'], old_password: datastore['SMBPass'], new_password: datastore['NEW_PASSWORD']) + if Net::NTLM.is_ntlm_hash?(Net::NTLM::EncodeUtil.encode_utf16le(datastore['SMBPass'])) + old_lm, old_nt = datastore['SMBPass'].split(':') + old_lm = [old_lm].pack('H*') + old_nt = [old_nt].pack('H*') + @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) + else + @samr.samr_unicode_change_password_user2(target_username: datastore['SMBUser'], old_password: datastore['SMBPass'], new_password: datastore['NEW_PASSWORD']) + end print_good("Successfully changed password for #{datastore['SMBUser']}") end From 7c46d4d02d793783b54c2ad063ffff12cdf759d2 Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Fri, 6 Dec 2024 14:28:44 +1100 Subject: [PATCH 09/11] Updated text to be clearer about the AES kerberos behaviour --- .../modules/auxiliary/admin/smb/change_password.md | 8 ++++---- modules/auxiliary/admin/smb/change_password.rb | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/documentation/modules/auxiliary/admin/smb/change_password.md b/documentation/modules/auxiliary/admin/smb/change_password.md index 39bf5147feb6..7321eaa53160 100755 --- a/documentation/modules/auxiliary/admin/smb/change_password.md +++ b/documentation/modules/auxiliary/admin/smb/change_password.md @@ -11,10 +11,10 @@ This module works with existing sessions (or relaying), especially for Reset use ## Actions -- `RESET` - Reset the target's password without knowing the existing one (requires appropriate permissions) -- `RESET_NTLM` - Reset the target's NTLM hash, without knowing the existing password. This will not update kerberos keys. -- `CHANGE` - Change the password, knowing the existing one. -- `CHANGE_NTLM` - Change the password to a NTLM hash value, knowing the existing password. This will not update kerberos keys. +- `RESET` - Reset the target's password without knowing the existing one (requires appropriate permissions). New AES kerberos keys will be generated. +- `RESET_NTLM` - Reset the target's NTLM hash, without knowing the existing password. AES kerberos authentication will not work until a standard password change occurs. +- `CHANGE` - Change the password, knowing the existing one. New AES kerberos keys will be generated. +- `CHANGE_NTLM` - Change the password to a NTLM hash value, knowing the existing password. AES kerberos authentication will not work until a standard password change occurs. ## Options diff --git a/modules/auxiliary/admin/smb/change_password.rb b/modules/auxiliary/admin/smb/change_password.rb index 86df6d4f8caf..9a1209687098 100644 --- a/modules/auxiliary/admin/smb/change_password.rb +++ b/modules/auxiliary/admin/smb/change_password.rb @@ -31,10 +31,10 @@ def initialize(info = {}) 'SideEffects' => [ IOC_IN_LOGS ] }, 'Actions' => [ - [ 'RESET', { 'Description' => "Reset the target's password without knowing the existing one (requires appropriate permissions)" } ], - [ 'RESET_NTLM', { 'Description' => "Reset the target's NTLM hash, without knowing the existing password. This will not update kerberos keys." } ], - [ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one.' } ], - [ 'CHANGE_NTLM', { 'Description' => 'Change the password to a NTLM hash value, knowing the existing password. This will not update kerberos keys.' } ] + [ 'RESET', { 'Description' => "Reset the target's password without knowing the existing one (requires appropriate permissions). New AES kerberos keys will be generated." } ], + [ '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." } ], + [ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one. New AES kerberos keys will be generated.' } ], + [ '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.' } ] ], 'DefaultAction' => 'RESET' ) From 5032695d1f85bdd338f638aa8433e41d9ce35a78 Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Fri, 6 Dec 2024 14:36:05 +1100 Subject: [PATCH 10/11] MSFTidy fixes --- .../auxiliary/admin/smb/change_password.rb | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/auxiliary/admin/smb/change_password.rb b/modules/auxiliary/admin/smb/change_password.rb index 9a1209687098..8a66324e22e6 100644 --- a/modules/auxiliary/admin/smb/change_password.rb +++ b/modules/auxiliary/admin/smb/change_password.rb @@ -106,7 +106,7 @@ def authenticate(anonymous_on_expired: false) e.source.is_a?(::WindowsError::ErrorCode) && [::WindowsError::NTStatus::STATUS_PASSWORD_EXPIRED, ::WindowsError::NTStatus::STATUS_PASSWORD_MUST_CHANGE].include?(e.source)) if anonymous_on_expired # Password has expired - we'll need to anonymous connect - print_warning("Password expired - binding anonymously") + print_warning('Password expired - binding anonymously') opts = { username: '', password: '', @@ -116,10 +116,10 @@ def authenticate(anonymous_on_expired: false) disconnect connect smb_login(opts: opts) + elsif action.name == 'CHANGE_NTLM' + fail_with(Module::Failure::UnexpectedReply, 'Must change password first. Try using the CHANGE action instead') else - if action.name == 'CHANGE_NTLM' - fail_with(Module::Failure::UnexpectedReply, 'Must change password first. Try using the CHANGE action instead') - end + raise end else raise @@ -200,15 +200,15 @@ def run_change_ntlm old_nt = [old_nt].pack('H*') @samr.samr_change_password_user(user_handle: user_handle, - new_nt_hash: new_nt, - new_lm_hash: new_lm, - old_nt_hash: old_nt, - old_lm_hash: old_lm) + new_nt_hash: new_nt, + new_lm_hash: new_lm, + old_nt_hash: old_nt, + old_lm_hash: old_lm) else @samr.samr_change_password_user(user_handle: user_handle, - old_password: datastore['SMBPass'], - new_nt_hash: new_nt, - new_lm_hash: new_lm) + old_password: datastore['SMBPass'], + new_nt_hash: new_nt, + new_lm_hash: new_lm) end print_good("Successfully changed password for #{datastore['SMBUser']}") From 63bf59b51632071877ecaae55ab0794804bcec72 Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Mon, 9 Dec 2024 11:09:30 +1100 Subject: [PATCH 11/11] Updated ruby_smb with SMB Change Password structures/functionality --- Gemfile.lock | 2 +- LICENSE_GEMS | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index c09ec9f0a4ad..2bf688f6ec1c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -499,7 +499,7 @@ GEM ruby-progressbar (1.13.0) ruby-rc4 (0.1.5) ruby2_keywords (0.0.5) - ruby_smb (3.3.11) + ruby_smb (3.3.13) bindata (= 2.4.15) openssl-ccm openssl-cmac diff --git a/LICENSE_GEMS b/LICENSE_GEMS index b710942c6f02..f6f42aa61001 100644 --- a/LICENSE_GEMS +++ b/LICENSE_GEMS @@ -181,7 +181,7 @@ ruby-prof, 1.4.2, "Simplified BSD" ruby-progressbar, 1.13.0, MIT ruby-rc4, 0.1.5, MIT ruby2_keywords, 0.0.5, "ruby, Simplified BSD" -ruby_smb, 3.3.10, "New BSD" +ruby_smb, 3.3.13, "New BSD" rubyntlm, 0.6.5, MIT rubyzip, 2.3.2, "Simplified BSD" sawyer, 0.9.2, MIT