Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/modules/auxiliary/admin/ldap/change_password.rb
Views: 15959
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary67include Msf::Auxiliary::Report8include Msf::Exploit::Remote::LDAP9include Msf::OptionalSession::LDAP1011ATTRIBUTE = 'unicodePwd'.freeze1213def initialize(info = {})14super(15update_info(16info,17'Name' => 'Change Password',18'Description' => %q{19This module allows Active Directory users to change their own passwords, or reset passwords for20accounts they have privileges over.21},22'Author' => [23'smashery' # module author24],25'References' => [26['URL', 'https://github.com/fortra/impacket/blob/master/examples/changepasswd.py'],27['URL', 'https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/6e803168-f140-4d23-b2d3-c3a8ab5917d2'],28],29'License' => MSF_LICENSE,30'Actions' => [31['RESET', { 'Description' => "Reset a target user's password, having permissions over their account" }],32['CHANGE', { 'Description' => "Change the user's password, knowing the existing password" }]33],34'DefaultAction' => 'RESET',35'Notes' => {36'Stability' => [],37'SideEffects' => [ IOC_IN_LOGS ],38'Reliability' => []39}40)41)4243register_options([44OptString.new('TARGET_USER', [false, 'The user to reset the password of.'], conditions: ['ACTION', 'in', %w[RESET]]),45OptString.new('NEW_PASSWORD', [ true, 'The new password to set for the user' ])46])47end4849def fail_with_ldap_error(message)50ldap_result = @ldap.get_operation_result.table51return if ldap_result[:code] == 05253print_error(message)54if ldap_result[:code] == 1955extra_error = ''56if action.name == 'CHANGE' && !datastore['SESSION'].blank?57# If you're already in a session, you could provide the wrong password, and you get this error58extra_error = ' or incorrect current password'59end6061error = "The password changed failed, likely due to a password policy violation (e.g. not sufficiently complex, matching previous password, or changing the password too often)#{extra_error}"62fail_with(Failure::NotFound, error)63else64validate_query_result!(ldap_result)65end66end6768def ldap_get(filter, attributes: [])69raw_obj = @ldap.search(base: @base_dn, filter: filter, attributes: attributes)&.first70return nil unless raw_obj7172obj = {}7374obj['dn'] = raw_obj['dn'].first.to_s75unless raw_obj['sAMAccountName'].empty?76obj['sAMAccountName'] = raw_obj['sAMAccountName'].first.to_s77end7879obj80end8182def run83if action.name == 'CHANGE'84fail_with(Failure::BadConfig, 'Must set USERNAME when changing password') if datastore['USERNAME'].blank?85fail_with(Failure::BadConfig, 'Must set PASSWORD when changing password') if datastore['PASSWORD'].blank?86elsif action.name == 'RESET'87fail_with(Failure::BadConfig, 'Must set TARGET_USER when resetting password') if datastore['TARGET_USER'].blank?88end89if session.blank? && datastore['USERNAME'].blank? && datastore['LDAP::Auth'] != Msf::Exploit::Remote::AuthOption::SCHANNEL90print_warning('Connecting with an anonymous bind')91end92ldap_connect do |ldap|93validate_bind_success!(ldap)9495if (@base_dn = datastore['BASE_DN'])96print_status("User-specified base DN: #{@base_dn}")97else98print_status('Discovering base DN automatically')99100if (@base_dn = ldap.base_dn)101print_status("#{ldap.peerinfo} Discovered base DN: #{@base_dn}")102else103fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!")104end105end106@ldap = ldap107108begin109send("action_#{action.name.downcase}")110rescue ::IOError => e111fail_with(Failure::UnexpectedReply, e.message)112end113end114rescue Errno::ECONNRESET115fail_with(Failure::Disconnected, 'The connection was reset.')116rescue Rex::ConnectionError => e117fail_with(Failure::Unreachable, e.message)118rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e119fail_with(Failure::NoAccess, e.message)120rescue Rex::Proto::LDAP::LdapException => e121fail_with(Failure::NoAccess, e.message)122rescue Net::LDAP::Error => e123fail_with(Failure::Unknown, "#{e.class}: #{e.message}")124end125126def get_user_obj(username)127obj = ldap_get("(sAMAccountName=#{ldap_escape_filter(username)})", attributes: ['sAMAccountName'])128fail_with(Failure::NotFound, "Failed to find sAMAccountName: #{username}") unless obj129130obj131end132133def action_reset134target_user = datastore['TARGET_USER']135obj = get_user_obj(target_user)136137new_pass = "\"#{datastore['NEW_PASSWORD']}\"".encode('utf-16le').bytes.pack('c*')138unless @ldap.replace_attribute(obj['dn'], ATTRIBUTE, new_pass)139fail_with_ldap_error("Failed to reset the password for #{datastore['TARGET_USER']}.")140end141print_good("Successfully reset password for #{datastore['TARGET_USER']}.")142end143144def action_change145obj = get_user_obj(datastore['USERNAME'])146147new_pass = "\"#{datastore['NEW_PASSWORD']}\"".encode('utf-16le').bytes.pack('c*')148old_pass = "\"#{datastore['PASSWORD']}\"".encode('utf-16le').bytes.pack('c*')149unless @ldap.modify(dn: obj['dn'], operations: [[:delete, ATTRIBUTE, old_pass], [:add, ATTRIBUTE, new_pass]])150fail_with_ldap_error("Failed to reset the password for #{datastore['USERNAME']}.")151end152print_good("Successfully changed password for #{datastore['USERNAME']}.")153end154end155156157