Path: blob/master/modules/auxiliary/admin/smb/change_password.rb
28693 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'ruby_smb/dcerpc/client'67class MetasploitModule < Msf::Auxiliary8include Msf::Exploit::Remote::SMB::Client9include Msf::Exploit::Remote::SMB::Client::Authenticated10include Msf::Auxiliary::Report11include Msf::OptionalSession::SMB1213def initialize(info = {})14super(15update_info(16info,17'Name' => 'SMB Password Change',18'Description' => %q{19Change the password of an account using SMB. This provides several different20APIs, each of which have their respective benefits and drawbacks.21},22'License' => MSF_LICENSE,23'Author' => [24'smashery'25],26'References' => [27['URL', 'https://github.com/fortra/impacket/blob/master/examples/changepasswd.py'],28[ 'ATT&CK', Mitre::Attack::Technique::T1021_002_SMB_WINDOWS_ADMIN_SHARES ]29],30'Notes' => {31'Stability' => [CRASH_SAFE],32'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES],33'Reliability' => []34},35'Actions' => [36[ 'RESET', { 'Description' => "Reset the target's password without knowing the existing one (requires appropriate permissions). New AES kerberos keys will be generated." } ],37[ '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." } ],38[ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one. New AES kerberos keys will be generated.' } ],39[ '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.' } ]40],41'DefaultAction' => 'RESET'42)43)4445register_options(46[47OptString.new('NEW_PASSWORD', [false, 'The new password to change to', ''], conditions: ['ACTION', 'in', %w[CHANGE RESET]]),48OptString.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}$/),49OptString.new('TARGET_USER', [false, 'The user to reset the password of.'], conditions: ['ACTION', 'in', %w[RESET RESET_NTLM]])50]51)52end5354def connect_samr55vprint_status('Connecting to Security Account Manager (SAM) Remote Protocol')56@samr = @tree.open_file(filename: 'samr', write: true, read: true)5758vprint_status('Binding to \\samr...')59@samr.bind(endpoint: RubySMB::Dcerpc::Samr)60vprint_good('Bound to \\samr')61end6263def run64case action.name65when 'CHANGE'66run_change67when 'RESET'68run_reset69when 'RESET_NTLM'70run_reset_ntlm71when 'CHANGE_NTLM'72run_change_ntlm73end74rescue RubySMB::Error::RubySMBError => e75fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}")76rescue Rex::ConnectionError => e77fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}")78rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e79fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}")80rescue ::StandardError => e81raise e82ensure83@samr.close_handle(@domain_handle) if @domain_handle84@samr.close_handle(@server_handle) if @server_handle85@samr.close if @samr86@tree.disconnect! if @tree8788# Don't disconnect the client if it's coming from the session so it can be reused89unless session90simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client)91disconnect92end93end9495def authenticate(anonymous_on_expired: false)96if session97print_status("Using existing session #{session.sid}")98self.simple = session.simple_client99simple.connect("\\\\#{simple.address}\\IPC$") # smb_login connects to this share for some reason and it doesn't work unless we do too100else101connect102begin103begin104smb_login105rescue Rex::Proto::SMB::Exceptions::LoginError => e106if (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) ||107e.source.is_a?(::WindowsError::ErrorCode) && [::WindowsError::NTStatus::STATUS_PASSWORD_EXPIRED, ::WindowsError::NTStatus::STATUS_PASSWORD_MUST_CHANGE].include?(e.source))108if anonymous_on_expired109# Password has expired - we'll need to anonymous connect110print_warning('Password expired - binding anonymously')111opts = {112username: '',113password: '',114domain: '',115auth_protocol: Msf::Exploit::Remote::AuthOption::NTLM116}117disconnect118connect119smb_login(opts: opts)120elsif action.name == 'CHANGE_NTLM'121fail_with(Module::Failure::UnexpectedReply, 'Must change password first. Try using the CHANGE action instead')122else123raise124end125else126raise127end128end129rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e130fail_with(Module::Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).")131end132end133134report_service(135host: simple.address,136port: simple.port,137host_name: simple.client.default_name,138proto: 'tcp',139name: 'smb',140info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})"141)142143begin144@tree = simple.client.tree_connect("\\\\#{simple.address}\\IPC$")145rescue RubySMB::Error::RubySMBError => e146fail_with(Module::Failure::Unreachable,147"Unable to connect to the remote IPC$ share ([#{e.class}] #{e}).")148end149150connect_samr151end152153def parse_ntlm_from_config154new_ntlm = datastore['NEW_NTLM']155fail_with(Msf::Exploit::Failure::BadConfig, 'Must provide NEW_NTLM value') if new_ntlm.blank?156case new_ntlm.count(':')157when 0158new_nt = new_ntlm159new_lm = nil160when 1161new_lm, new_nt = new_ntlm.split(':')162else163fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid value for NEW_NTLM')164end165166new_nt = Rex::Text.hex_to_raw(new_nt)167new_lm = Rex::Text.hex_to_raw(new_lm) unless new_lm.nil?168fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid NT hash value in NEW_NTLM') unless new_nt.length == 16169fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid LM hash value in NEW_NTLM') unless new_lm.nil? || new_nt.length == 16170171[new_nt, new_lm]172end173174def get_user_handle(domain, username)175vprint_status("Opening handle for #{domain}\\#{username}")176@server_handle = @samr.samr_connect177domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: domain)178@domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: domain_sid)179user_rids = @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [username])180fail_with(Module::Failure::BadConfig, "Could not find #{domain}\\#{username}") if user_rids.nil?181rid = user_rids[username][:rid]182183@samr.samr_open_user(domain_handle: @domain_handle, user_id: rid)184rescue RubySMB::Dcerpc::Error::SamrError => e185fail_with(Msf::Exploit::Failure::BadConfig, e.to_s)186end187188def run_change_ntlm189fail_with(Module::Failure::BadConfig, 'Must set NEW_NTLM') if datastore['NEW_NTLM'].blank?190fail_with(Module::Failure::BadConfig, 'Must set SMBUser to change password') if datastore['SMBUser'].blank?191fail_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?192new_nt, new_lm = parse_ntlm_from_config193print_status('Changing NTLM')194authenticate(anonymous_on_expired: false)195196user_handle = get_user_handle(datastore['SMBDomain'], datastore['SMBUser'])197198if Net::NTLM.is_ntlm_hash?(Net::NTLM::EncodeUtil.encode_utf16le(datastore['SMBPass']))199old_lm, old_nt = datastore['SMBPass'].split(':')200old_lm = [old_lm].pack('H*')201old_nt = [old_nt].pack('H*')202203@samr.samr_change_password_user(user_handle: user_handle,204new_nt_hash: new_nt,205new_lm_hash: new_lm,206old_nt_hash: old_nt,207old_lm_hash: old_lm)208else209@samr.samr_change_password_user(user_handle: user_handle,210old_password: datastore['SMBPass'],211new_nt_hash: new_nt,212new_lm_hash: new_lm)213end214215print_good("Successfully changed password for #{datastore['SMBUser']}")216print_warning('AES Kerberos keys will not be available until user changes their password')217end218219def run_reset_ntlm220fail_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?221new_nt, = parse_ntlm_from_config222print_status('Resetting NTLM')223authenticate(anonymous_on_expired: false)224225user_handle = get_user_handle(datastore['SMBDomain'], datastore['TARGET_USER'])226227user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(228tag: RubySMB::Dcerpc::Samr::USER_INTERNAL1_INFORMATION,229member: RubySMB::Dcerpc::Samr::SamprUserInternal1Information.new(230encrypted_nt_owf_password: RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword.new(buffer: RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword.encrypt_hash(hash: new_nt, key: simple.client.application_key)),231encrypted_lm_owf_password: nil,232nt_password_present: 1,233lm_password_present: 0,234password_expired: 0235)236)237@samr.samr_set_information_user2(238user_handle: user_handle,239user_info: user_info240)241242print_good("Successfully reset password for #{datastore['TARGET_USER']}")243print_warning('AES Kerberos keys will not be available until user changes their password')244end245246def run_reset247fail_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?248fail_with(Module::Failure::BadConfig, 'Must set NEW_PASSWORD') if datastore['NEW_PASSWORD'].blank?249print_status('Resetting password')250authenticate(anonymous_on_expired: false)251252user_handle = get_user_handle(datastore['SMBDomain'], datastore['TARGET_USER'])253254user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(255tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW,256member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new(257i1: {258password_expired: 0,259which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED260},261user_password: {262buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password(263datastore['NEW_PASSWORD'],264simple.client.application_key265)266}267)268)269@samr.samr_set_information_user2(270user_handle: user_handle,271user_info: user_info272)273print_good("Successfully reset password for #{datastore['TARGET_USER']}")274end275276def run_change277fail_with(Module::Failure::BadConfig, 'Must set NEW_PASSWORD') if datastore['NEW_PASSWORD'].blank?278fail_with(Module::Failure::BadConfig, 'Must set SMBUser to change password') if datastore['SMBUser'].blank?279fail_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?280print_status('Changing password')281authenticate(anonymous_on_expired: true)282283if Net::NTLM.is_ntlm_hash?(Net::NTLM::EncodeUtil.encode_utf16le(datastore['SMBPass']))284old_lm, old_nt = datastore['SMBPass'].split(':')285old_lm = [old_lm].pack('H*')286old_nt = [old_nt].pack('H*')287@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)288else289@samr.samr_unicode_change_password_user2(target_username: datastore['SMBUser'], old_password: datastore['SMBPass'], new_password: datastore['NEW_PASSWORD'])290end291292print_good("Successfully changed password for #{datastore['SMBUser']}")293end294end295296297