Path: blob/master/modules/auxiliary/admin/smb/change_password.rb
19851 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],29'Notes' => {30'Stability' => [CRASH_SAFE],31'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES],32'Reliability' => []33},34'Actions' => [35[ 'RESET', { 'Description' => "Reset the target's password without knowing the existing one (requires appropriate permissions). New AES kerberos keys will be generated." } ],36[ '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." } ],37[ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one. New AES kerberos keys will be generated.' } ],38[ '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.' } ]39],40'DefaultAction' => 'RESET'41)42)4344register_options(45[46OptString.new('NEW_PASSWORD', [false, 'The new password to change to', ''], conditions: ['ACTION', 'in', %w[CHANGE RESET]]),47OptString.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}$/),48OptString.new('TARGET_USER', [false, 'The user to reset the password of.'], conditions: ['ACTION', 'in', %w[RESET RESET_NTLM]])49]50)51end5253def connect_samr54vprint_status('Connecting to Security Account Manager (SAM) Remote Protocol')55@samr = @tree.open_file(filename: 'samr', write: true, read: true)5657vprint_status('Binding to \\samr...')58@samr.bind(endpoint: RubySMB::Dcerpc::Samr)59vprint_good('Bound to \\samr')60end6162def run63case action.name64when 'CHANGE'65run_change66when 'RESET'67run_reset68when 'RESET_NTLM'69run_reset_ntlm70when 'CHANGE_NTLM'71run_change_ntlm72end73rescue RubySMB::Error::RubySMBError => e74fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}")75rescue Rex::ConnectionError => e76fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}")77rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e78fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}")79rescue ::StandardError => e80raise e81ensure82@samr.close_handle(@domain_handle) if @domain_handle83@samr.close_handle(@server_handle) if @server_handle84@samr.close if @samr85@tree.disconnect! if @tree8687# Don't disconnect the client if it's coming from the session so it can be reused88unless session89simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client)90disconnect91end92end9394def authenticate(anonymous_on_expired: false)95if session96print_status("Using existing session #{session.sid}")97self.simple = session.simple_client98simple.connect("\\\\#{simple.address}\\IPC$") # smb_login connects to this share for some reason and it doesn't work unless we do too99else100connect101begin102begin103smb_login104rescue Rex::Proto::SMB::Exceptions::LoginError => e105if (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) ||106e.source.is_a?(::WindowsError::ErrorCode) && [::WindowsError::NTStatus::STATUS_PASSWORD_EXPIRED, ::WindowsError::NTStatus::STATUS_PASSWORD_MUST_CHANGE].include?(e.source))107if anonymous_on_expired108# Password has expired - we'll need to anonymous connect109print_warning('Password expired - binding anonymously')110opts = {111username: '',112password: '',113domain: '',114auth_protocol: Msf::Exploit::Remote::AuthOption::NTLM115}116disconnect117connect118smb_login(opts: opts)119elsif action.name == 'CHANGE_NTLM'120fail_with(Module::Failure::UnexpectedReply, 'Must change password first. Try using the CHANGE action instead')121else122raise123end124else125raise126end127end128rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e129fail_with(Module::Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).")130end131end132133report_service(134host: simple.address,135port: simple.port,136host_name: simple.client.default_name,137proto: 'tcp',138name: 'smb',139info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})"140)141142begin143@tree = simple.client.tree_connect("\\\\#{simple.address}\\IPC$")144rescue RubySMB::Error::RubySMBError => e145fail_with(Module::Failure::Unreachable,146"Unable to connect to the remote IPC$ share ([#{e.class}] #{e}).")147end148149connect_samr150end151152def parse_ntlm_from_config153new_ntlm = datastore['NEW_NTLM']154fail_with(Msf::Exploit::Failure::BadConfig, 'Must provide NEW_NTLM value') if new_ntlm.blank?155case new_ntlm.count(':')156when 0157new_nt = new_ntlm158new_lm = nil159when 1160new_lm, new_nt = new_ntlm.split(':')161else162fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid value for NEW_NTLM')163end164165new_nt = Rex::Text.hex_to_raw(new_nt)166new_lm = Rex::Text.hex_to_raw(new_lm) unless new_lm.nil?167fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid NT hash value in NEW_NTLM') unless new_nt.length == 16168fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid LM hash value in NEW_NTLM') unless new_lm.nil? || new_nt.length == 16169170[new_nt, new_lm]171end172173def get_user_handle(domain, username)174vprint_status("Opening handle for #{domain}\\#{username}")175@server_handle = @samr.samr_connect176domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: domain)177@domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: domain_sid)178user_rids = @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [username])179fail_with(Module::Failure::BadConfig, "Could not find #{domain}\\#{username}") if user_rids.nil?180rid = user_rids[username][:rid]181182@samr.samr_open_user(domain_handle: @domain_handle, user_id: rid)183rescue RubySMB::Dcerpc::Error::SamrError => e184fail_with(Msf::Exploit::Failure::BadConfig, e.to_s)185end186187def run_change_ntlm188fail_with(Module::Failure::BadConfig, 'Must set NEW_NTLM') if datastore['NEW_NTLM'].blank?189fail_with(Module::Failure::BadConfig, 'Must set SMBUser to change password') if datastore['SMBUser'].blank?190fail_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?191new_nt, new_lm = parse_ntlm_from_config192print_status('Changing NTLM')193authenticate(anonymous_on_expired: false)194195user_handle = get_user_handle(datastore['SMBDomain'], datastore['SMBUser'])196197if Net::NTLM.is_ntlm_hash?(Net::NTLM::EncodeUtil.encode_utf16le(datastore['SMBPass']))198old_lm, old_nt = datastore['SMBPass'].split(':')199old_lm = [old_lm].pack('H*')200old_nt = [old_nt].pack('H*')201202@samr.samr_change_password_user(user_handle: user_handle,203new_nt_hash: new_nt,204new_lm_hash: new_lm,205old_nt_hash: old_nt,206old_lm_hash: old_lm)207else208@samr.samr_change_password_user(user_handle: user_handle,209old_password: datastore['SMBPass'],210new_nt_hash: new_nt,211new_lm_hash: new_lm)212end213214print_good("Successfully changed password for #{datastore['SMBUser']}")215print_warning('AES Kerberos keys will not be available until user changes their password')216end217218def run_reset_ntlm219fail_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?220new_nt, = parse_ntlm_from_config221print_status('Resetting NTLM')222authenticate(anonymous_on_expired: false)223224user_handle = get_user_handle(datastore['SMBDomain'], datastore['TARGET_USER'])225226user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(227tag: RubySMB::Dcerpc::Samr::USER_INTERNAL1_INFORMATION,228member: RubySMB::Dcerpc::Samr::SamprUserInternal1Information.new(229encrypted_nt_owf_password: RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword.new(buffer: RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword.encrypt_hash(hash: new_nt, key: simple.client.application_key)),230encrypted_lm_owf_password: nil,231nt_password_present: 1,232lm_password_present: 0,233password_expired: 0234)235)236@samr.samr_set_information_user2(237user_handle: user_handle,238user_info: user_info239)240241print_good("Successfully reset password for #{datastore['TARGET_USER']}")242print_warning('AES Kerberos keys will not be available until user changes their password')243end244245def run_reset246fail_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?247fail_with(Module::Failure::BadConfig, 'Must set NEW_PASSWORD') if datastore['NEW_PASSWORD'].blank?248print_status('Resetting password')249authenticate(anonymous_on_expired: false)250251user_handle = get_user_handle(datastore['SMBDomain'], datastore['TARGET_USER'])252253user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(254tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW,255member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new(256i1: {257password_expired: 0,258which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED259},260user_password: {261buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password(262datastore['NEW_PASSWORD'],263simple.client.application_key264)265}266)267)268@samr.samr_set_information_user2(269user_handle: user_handle,270user_info: user_info271)272print_good("Successfully reset password for #{datastore['TARGET_USER']}")273end274275def run_change276fail_with(Module::Failure::BadConfig, 'Must set NEW_PASSWORD') if datastore['NEW_PASSWORD'].blank?277fail_with(Module::Failure::BadConfig, 'Must set SMBUser to change password') if datastore['SMBUser'].blank?278fail_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?279print_status('Changing password')280authenticate(anonymous_on_expired: true)281282if Net::NTLM.is_ntlm_hash?(Net::NTLM::EncodeUtil.encode_utf16le(datastore['SMBPass']))283old_lm, old_nt = datastore['SMBPass'].split(':')284old_lm = [old_lm].pack('H*')285old_nt = [old_nt].pack('H*')286@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)287else288@samr.samr_unicode_change_password_user2(target_username: datastore['SMBUser'], old_password: datastore['SMBPass'], new_password: datastore['NEW_PASSWORD'])289end290291print_good("Successfully changed password for #{datastore['SMBUser']}")292end293end294295296